aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/db
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/db')
-rw-r--r--mediagoblin/db/migration_tools.py5
-rw-r--r--mediagoblin/db/migrations/alembic.ini56
-rw-r--r--mediagoblin/db/migrations/env.py3
-rw-r--r--mediagoblin/db/models.py61
-rw-r--r--mediagoblin/db/models_v0.py342
5 files changed, 108 insertions, 359 deletions
diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py
index f4273fa0..852f35ee 100644
--- a/mediagoblin/db/migration_tools.py
+++ b/mediagoblin/db/migration_tools.py
@@ -365,9 +365,8 @@ def build_alembic_config(global_config, cmd_options, session):
configuration. Initialize the database session appropriately
as well.
"""
- root_dir = os.path.abspath(os.path.dirname(os.path.dirname(
- os.path.dirname(__file__))))
- alembic_cfg_path = os.path.join(root_dir, 'alembic.ini')
+ alembic_dir = os.path.join(os.path.dirname(__file__), 'migrations')
+ alembic_cfg_path = os.path.join(alembic_dir, 'alembic.ini')
cfg = Config(alembic_cfg_path,
cmd_opts=cmd_options)
cfg.attributes["session"] = session
diff --git a/mediagoblin/db/migrations/alembic.ini b/mediagoblin/db/migrations/alembic.ini
new file mode 100644
index 00000000..4f7fc115
--- /dev/null
+++ b/mediagoblin/db/migrations/alembic.ini
@@ -0,0 +1,56 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = %(here)s
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# max length of characters to apply to the
+# "slug" field
+#truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/mediagoblin/db/migrations/env.py b/mediagoblin/db/migrations/env.py
index 43b7b247..a6d05cd1 100644
--- a/mediagoblin/db/migrations/env.py
+++ b/mediagoblin/db/migrations/env.py
@@ -48,7 +48,7 @@ def run_migrations_online():
and associate a connection with the context.
"""
- connection = config.attributes["session"].get_bind()
+ connection = config.attributes["session"].connection()
context.configure(
connection=connection,
target_metadata=target_metadata
@@ -61,4 +61,3 @@ if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
-
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index 9bbb252b..c19fe4da 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -43,6 +43,7 @@ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
from mediagoblin.tools.files import delete_media_files
from mediagoblin.tools.common import import_component
from mediagoblin.tools.routing import extract_url_arguments
+from mediagoblin.tools.text import convert_to_tag_list_of_dicts
import six
from six.moves.urllib.parse import urljoin
@@ -596,6 +597,16 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
# fail_error
@property
+ def get_uploader(self):
+ # for compatibility
+ return self.get_actor
+
+ @property
+ def uploader(self):
+ # for compatibility
+ return self.actor
+
+ @property
def collections(self):
""" Get any collections that this MediaEntry is in """
return list(Collection.query.join(Collection.collection_items).join(
@@ -617,9 +628,9 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
query = query.order_by(Comment.added.asc())
else:
query = query.order_by(Comment.added.desc())
-
+
return query
-
+
def url_to_prev(self, urlgen):
"""get the next 'newer' entry by this user"""
media = MediaEntry.query.filter(
@@ -769,7 +780,6 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
"self": {
"href": public_id,
},
-
}
}
@@ -785,6 +795,12 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
if self.location:
context["location"] = self.get_location.serialize(request)
+ # Always show tags, even if empty list
+ if self.tags:
+ context["tags"] = [tag['name'] for tag in self.tags]
+ else:
+ context["tags"] = []
+
if show_comments:
comments = [
l.comment().serialize(request) for l in self.get_comments()]
@@ -832,6 +848,9 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
if "location" in data:
License.create(data["location"], self)
+ if "tags" in data:
+ self.tags = convert_to_tag_list_of_dicts(', '.join(data["tags"]))
+
return True
class FileKeynames(Base):
@@ -966,7 +985,7 @@ class MediaTag(Base):
class Comment(Base):
"""
Link table between a response and another object that can have replies.
-
+
This acts as a link table between an object and the comments on it, it's
done like this so that you can look up all the comments without knowing
whhich comments are on an object before hand. Any object can be a comment
@@ -977,7 +996,7 @@ class Comment(Base):
__tablename__ = "core__comment_links"
id = Column(Integer, primary_key=True)
-
+
# The GMR to the object the comment is on.
target_id = Column(
Integer,
@@ -1006,7 +1025,25 @@ class Comment(Base):
# When it was added
added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
-
+
+ @property
+ def get_author(self):
+ # for compatibility
+ return self.comment().get_actor # noqa
+
+ def __getattr__(self, attr):
+ if attr.startswith('_'):
+ # if attr starts with '_', then it's probably some internal
+ # sqlalchemy variable. Since __getattr__ is called when
+ # non-existing attributes are being accessed, we should not try to
+ # fetch it from self.comment()
+ raise AttributeError
+ try:
+ _log.debug('Old attr is being accessed: {0}'.format(attr))
+ return getattr(self.comment(), attr) # noqa
+ except Exception as e:
+ _log.error(e)
+ raise
class TextComment(Base, TextCommentMixin, CommentingMixin):
"""
@@ -1040,7 +1077,7 @@ class TextComment(Base, TextCommentMixin, CommentingMixin):
if target is None:
target = {}
else:
- target = target.serialize(request, show_comments=False)
+ target = target.serialize(request, show_comments=False)
author = self.get_actor
@@ -1068,7 +1105,7 @@ class TextComment(Base, TextCommentMixin, CommentingMixin):
if "location" in data:
Location.create(data["location"], self)
-
+
# Handle changing the reply ID
if "inReplyTo" in data:
# Validate that the ID is correct
@@ -1099,7 +1136,7 @@ class TextComment(Base, TextCommentMixin, CommentingMixin):
link.target = media
link.comment = self
link.save()
-
+
return True
class Collection(Base, CollectionMixin, CommentingMixin):
@@ -1298,7 +1335,7 @@ class Notification(Base):
seen = Column(Boolean, default=lambda: False, index=True)
user = relationship(
User,
- backref=backref('notifications', cascade='all, delete-orphan'))
+ backref=backref('notifications', cascade='all, delete-orphan'))
def __repr__(self):
return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
@@ -1343,7 +1380,7 @@ class Report(Base):
which points to the reported object.
"""
__tablename__ = 'core__reports'
-
+
id = Column(Integer, primary_key=True)
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
reporter = relationship(
@@ -1371,7 +1408,7 @@ class Report(Base):
resolved = Column(DateTime)
result = Column(UnicodeText)
-
+
object_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=True)
object_helper = relationship(GenericModelReference)
obj = association_proxy("object_helper", "get_object",
diff --git a/mediagoblin/db/models_v0.py b/mediagoblin/db/models_v0.py
deleted file mode 100644
index bdedec2e..00000000
--- a/mediagoblin/db/models_v0.py
+++ /dev/null
@@ -1,342 +0,0 @@
-# GNU MediaGoblin -- federated, autonomous media hosting
-# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-TODO: indexes on foreignkeys, where useful.
-"""
-
-###########################################################################
-# WHAT IS THIS FILE?
-# ------------------
-#
-# Upon occasion, someone runs into this file and wonders why we have
-# both a models.py and a models_v0.py.
-#
-# The short of it is: you can ignore this file.
-#
-# The long version is, in two parts:
-#
-# - We used to use MongoDB, then we switched to SQL and SQLAlchemy.
-# We needed to convert peoples' databases; the script we had would
-# switch them to the first version right after Mongo, convert over
-# all their tables, then run any migrations that were added after.
-#
-# - That script is now removed, but there is some discussion of
-# writing a test that would set us at the first SQL migration and
-# run everything after. If we wrote that, this file would still be
-# useful. But for now, it's legacy!
-#
-###########################################################################
-
-
-import datetime
-import sys
-
-from sqlalchemy import (
- Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey,
- UniqueConstraint, PrimaryKeyConstraint, SmallInteger, Float)
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship, backref
-from sqlalchemy.orm.collections import attribute_mapped_collection
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.util import memoized_property
-
-from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
-from mediagoblin.db.base import GMGTableBase, Session
-
-
-Base_v0 = declarative_base(cls=GMGTableBase)
-
-
-class User(Base_v0):
- """
- TODO: We should consider moving some rarely used fields
- into some sort of "shadow" table.
- """
- __tablename__ = "core__users"
-
- id = Column(Integer, primary_key=True)
- username = Column(Unicode, nullable=False, unique=True)
- email = Column(Unicode, nullable=False)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
- pw_hash = Column(Unicode, nullable=False)
- email_verified = Column(Boolean, default=False)
- status = Column(Unicode, default=u"needs_email_verification", nullable=False)
- verification_key = Column(Unicode)
- is_admin = Column(Boolean, default=False, nullable=False)
- url = Column(Unicode)
- bio = Column(UnicodeText) # ??
- fp_verification_key = Column(Unicode)
- fp_token_expire = Column(DateTime)
-
- ## TODO
- # plugin data would be in a separate model
-
-
-class MediaEntry(Base_v0):
- """
- TODO: Consider fetching the media_files using join
- """
- __tablename__ = "core__media_entries"
-
- id = Column(Integer, primary_key=True)
- uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True)
- title = Column(Unicode, nullable=False)
- slug = Column(Unicode)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now,
- index=True)
- description = Column(UnicodeText) # ??
- media_type = Column(Unicode, nullable=False)
- state = Column(Unicode, default=u'unprocessed', nullable=False)
- # or use sqlalchemy.types.Enum?
- license = Column(Unicode)
-
- fail_error = Column(Unicode)
- fail_metadata = Column(JSONEncoded)
-
- queued_media_file = Column(PathTupleWithSlashes)
-
- queued_task_id = Column(Unicode)
-
- __table_args__ = (
- UniqueConstraint('uploader', 'slug'),
- {})
-
- get_uploader = relationship(User)
-
- media_files_helper = relationship("MediaFile",
- collection_class=attribute_mapped_collection("name"),
- cascade="all, delete-orphan"
- )
-
- attachment_files_helper = relationship("MediaAttachmentFile",
- cascade="all, delete-orphan",
- order_by="MediaAttachmentFile.created"
- )
-
- tags_helper = relationship("MediaTag",
- cascade="all, delete-orphan"
- )
-
- def media_data_init(self, **kwargs):
- """
- Initialize or update the contents of a media entry's media_data row
- """
- session = Session()
-
- media_data = session.query(self.media_data_table).filter_by(
- media_entry=self.id).first()
-
- # No media data, so actually add a new one
- if media_data is None:
- media_data = self.media_data_table(
- media_entry=self.id,
- **kwargs)
- session.add(media_data)
- # Update old media data
- else:
- for field, value in kwargs.iteritems():
- setattr(media_data, field, value)
-
- @memoized_property
- def media_data_table(self):
- # TODO: memoize this
- models_module = self.media_type + '.models'
- __import__(models_module)
- return sys.modules[models_module].DATA_MODEL
-
-
-class FileKeynames(Base_v0):
- """
- keywords for various places.
- currently the MediaFile keys
- """
- __tablename__ = "core__file_keynames"
- id = Column(Integer, primary_key=True)
- name = Column(Unicode, unique=True)
-
- def __repr__(self):
- return "<FileKeyname %r: %r>" % (self.id, self.name)
-
- @classmethod
- def find_or_new(cls, name):
- t = cls.query.filter_by(name=name).first()
- if t is not None:
- return t
- return cls(name=name)
-
-
-class MediaFile(Base_v0):
- """
- TODO: Highly consider moving "name" into a new table.
- TODO: Consider preloading said table in software
- """
- __tablename__ = "core__mediafiles"
-
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id),
- nullable=False)
- name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False)
- file_path = Column(PathTupleWithSlashes)
-
- __table_args__ = (
- PrimaryKeyConstraint('media_entry', 'name_id'),
- {})
-
- def __repr__(self):
- return "<MediaFile %s: %r>" % (self.name, self.file_path)
-
- name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True)
- name = association_proxy('name_helper', 'name',
- creator=FileKeynames.find_or_new
- )
-
-
-class MediaAttachmentFile(Base_v0):
- __tablename__ = "core__attachment_files"
-
- id = Column(Integer, primary_key=True)
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id),
- nullable=False)
- name = Column(Unicode, nullable=False)
- filepath = Column(PathTupleWithSlashes)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
-
-
-class Tag(Base_v0):
- __tablename__ = "core__tags"
-
- id = Column(Integer, primary_key=True)
- slug = Column(Unicode, nullable=False, unique=True)
-
- def __repr__(self):
- return "<Tag %r: %r>" % (self.id, self.slug)
-
- @classmethod
- def find_or_new(cls, slug):
- t = cls.query.filter_by(slug=slug).first()
- if t is not None:
- return t
- return cls(slug=slug)
-
-
-class MediaTag(Base_v0):
- __tablename__ = "core__media_tags"
-
- id = Column(Integer, primary_key=True)
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id),
- nullable=False, index=True)
- tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True)
- name = Column(Unicode)
- # created = Column(DateTime, nullable=False, default=datetime.datetime.now)
-
- __table_args__ = (
- UniqueConstraint('tag', 'media_entry'),
- {})
-
- tag_helper = relationship(Tag)
- slug = association_proxy('tag_helper', 'slug',
- creator=Tag.find_or_new
- )
-
- def __init__(self, name=None, slug=None):
- Base_v0.__init__(self)
- if name is not None:
- self.name = name
- if slug is not None:
- self.tag_helper = Tag.find_or_new(slug)
-
-
-class MediaComment(Base_v0):
- __tablename__ = "core__media_comments"
-
- id = Column(Integer, primary_key=True)
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
- author = Column(Integer, ForeignKey(User.id), nullable=False)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
- content = Column(UnicodeText, nullable=False)
-
- get_author = relationship(User)
-
-
-class ImageData(Base_v0):
- __tablename__ = "image__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("image__media_data", cascade="all, delete-orphan"))
-
- width = Column(Integer)
- height = Column(Integer)
- exif_all = Column(JSONEncoded)
- gps_longitude = Column(Float)
- gps_latitude = Column(Float)
- gps_altitude = Column(Float)
- gps_direction = Column(Float)
-
-
-class VideoData(Base_v0):
- __tablename__ = "video__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("video__media_data", cascade="all, delete-orphan"))
-
- width = Column(SmallInteger)
- height = Column(SmallInteger)
-
-
-class AsciiData(Base_v0):
- __tablename__ = "ascii__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("ascii__media_data", cascade="all, delete-orphan"))
-
-
-class AudioData(Base_v0):
- __tablename__ = "audio__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("audio__media_data", cascade="all, delete-orphan"))
-
-
-######################################################
-# Special, migrations-tracking table
-#
-# Not listed in MODELS because this is special and not
-# really migrated, but used for migrations (for now)
-######################################################
-
-class MigrationData(Base_v0):
- __tablename__ = "core__migrations"
-
- name = Column(Unicode, primary_key=True)
- version = Column(Integer, nullable=False, default=0)
-
-######################################################