aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/db
diff options
context:
space:
mode:
authortilly-Q <nattilypigeonfowl@gmail.com>2013-07-29 18:40:19 -0400
committertilly-Q <nattilypigeonfowl@gmail.com>2013-07-29 18:40:19 -0400
commit52a355b27541597fc155dab5e4885207b12a0a7b (patch)
tree7d00edf4d9e39a6b7e4c838ca6f7cb07a4d9a51c /mediagoblin/db
parentf26c21cd5b7998c903fa67aaf164c07743fee651 (diff)
parent08cd10d84fd89df3f0cad3835ba8ab8b8000d4b2 (diff)
downloadmediagoblin-52a355b27541597fc155dab5e4885207b12a0a7b.tar.lz
mediagoblin-52a355b27541597fc155dab5e4885207b12a0a7b.tar.xz
mediagoblin-52a355b27541597fc155dab5e4885207b12a0a7b.zip
Merge branch 'ticket-679' into OPW-Moderation-Update
Conflicts: mediagoblin/auth/tools.py mediagoblin/auth/views.py mediagoblin/db/migration_tools.py mediagoblin/db/migrations.py mediagoblin/db/models.py mediagoblin/decorators.py mediagoblin/user_pages/views.py
Diffstat (limited to 'mediagoblin/db')
-rw-r--r--mediagoblin/db/base.py12
-rw-r--r--mediagoblin/db/migration_tools.py23
-rw-r--r--mediagoblin/db/migrations.py100
-rw-r--r--mediagoblin/db/mixin.py24
-rw-r--r--mediagoblin/db/models.py132
-rw-r--r--mediagoblin/db/models_v0.py23
-rw-r--r--mediagoblin/db/open.py4
-rw-r--r--mediagoblin/db/util.py2
8 files changed, 264 insertions, 56 deletions
diff --git a/mediagoblin/db/base.py b/mediagoblin/db/base.py
index 699a503a..c0cefdc2 100644
--- a/mediagoblin/db/base.py
+++ b/mediagoblin/db/base.py
@@ -24,18 +24,6 @@ Session = scoped_session(sessionmaker())
class GMGTableBase(object):
query = Session.query_property()
- @classmethod
- def find(cls, query_dict):
- return cls.query.filter_by(**query_dict)
-
- @classmethod
- def find_one(cls, query_dict):
- return cls.query.filter_by(**query_dict).first()
-
- @classmethod
- def one(cls, query_dict):
- return cls.find(query_dict).one()
-
def get(self, key):
return getattr(self, key)
diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py
index f100f47e..ad137683 100644
--- a/mediagoblin/db/migration_tools.py
+++ b/mediagoblin/db/migration_tools.py
@@ -29,7 +29,7 @@ class MigrationManager(object):
to the latest migrations, etc.
"""
- def __init__(self, name, models, migration_registry, session,
+ def __init__(self, name, models, foundations, migration_registry, session,
printer=simple_printer):
"""
Args:
@@ -40,6 +40,7 @@ class MigrationManager(object):
"""
self.name = unicode(name)
self.models = models
+ self.foundations = foundations
self.session = session
self.migration_registry = migration_registry
self._sorted_migrations = None
@@ -145,11 +146,11 @@ class MigrationManager(object):
Create the table foundations (default rows) as layed out in FOUNDATIONS
in mediagoblin.db.models
"""
- from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS
- for Model in MAIN_FOUNDATIONS.keys():
- for parameters in MAIN_FOUNDATIONS[Model]:
- row = Model(*parameters)
- row.save()
+ for Model, rows in self.foundations.items():
+ print u'\n + Laying foundations for %s table' % (Model.__name__)
+ for parameters in rows:
+ new_row = Model(**parameters)
+ new_row.save()
def create_new_migration_record(self):
"""
@@ -186,8 +187,7 @@ class MigrationManager(object):
if self.name == u'__main__':
return u"main mediagoblin tables"
else:
- # TODO: Use the friendlier media manager "human readable" name
- return u'media type "%s"' % self.name
+ return u'plugin "%s"' % self.name
def init_or_migrate(self):
"""
@@ -214,10 +214,9 @@ class MigrationManager(object):
self.init_tables()
# auto-set at latest migration number
- self.create_new_migration_record()
- if self.name==u'__main__':
- self.populate_table_foundations()
-
+ self.create_new_migration_record()
+ self.populate_table_foundations()
+
self.printer(u"done.\n")
self.set_current_migration()
return u'inited'
diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py
index 247298ac..53df7954 100644
--- a/mediagoblin/db/migrations.py
+++ b/mediagoblin/db/migrations.py
@@ -289,10 +289,99 @@ def unique_collections_slug(db):
db.commit()
-class ReportBase_v0(declarative_base()):
+@RegisterMigration(11, MIGRATIONS)
+def drop_token_related_User_columns(db):
"""
-
+ Drop unneeded columns from the User table after switching to using
+ itsdangerous tokens for email and forgot password verification.
"""
+ metadata = MetaData(bind=db.bind)
+ user_table = inspect_table(metadata, 'core__users')
+
+
+ verification_key = user_table.columns['verification_key']
+ fp_verification_key = user_table.columns['fp_verification_key']
+ fp_token_expire = user_table.columns['fp_token_expire']
+
+ verification_key.drop()
+ fp_verification_key.drop()
+ fp_token_expire.drop()
+
+ db.commit()
+
+
+class CommentSubscription_v0(declarative_base()):
+ __tablename__ = 'core__comment_subscriptions'
+ id = Column(Integer, primary_key=True)
+
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+ media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+
+
+ notify = Column(Boolean, nullable=False, default=True)
+ send_email = Column(Boolean, nullable=False, default=True)
+
+
+class Notification_v0(declarative_base()):
+ __tablename__ = 'core__notifications'
+ id = Column(Integer, primary_key=True)
+ type = Column(Unicode)
+
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False,
+ index=True)
+ seen = Column(Boolean, default=lambda: False, index=True)
+
+
+class CommentNotification_v0(Notification_v0):
+ __tablename__ = 'core__comment_notifications'
+ id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
+
+ subject_id = Column(Integer, ForeignKey(MediaComment.id))
+
+
+class ProcessingNotification_v0(Notification_v0):
+ __tablename__ = 'core__processing_notifications'
+
+ id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
+
+ subject_id = Column(Integer, ForeignKey(MediaEntry.id))
+
+
+@RegisterMigration(12, MIGRATIONS)
+def add_new_notification_tables(db):
+ metadata = MetaData(bind=db.bind)
+
+ user_table = inspect_table(metadata, 'core__users')
+ mediaentry_table = inspect_table(metadata, 'core__media_entries')
+ mediacomment_table = inspect_table(metadata, 'core__media_comments')
+
+ CommentSubscription_v0.__table__.create(db.bind)
+
+ Notification_v0.__table__.create(db.bind)
+ CommentNotification_v0.__table__.create(db.bind)
+ ProcessingNotification_v0.__table__.create(db.bind)
+
+
+@RegisterMigration(13, MIGRATIONS)
+def pw_hash_nullable(db):
+ """Make pw_hash column nullable"""
+ metadata = MetaData(bind=db.bind)
+ user_table = inspect_table(metadata, "core__users")
+
+ user_table.c.pw_hash.alter(nullable=True)
+
+ # sqlite+sqlalchemy seems to drop this constraint during the
+ # migration, so we add it back here for now a bit manually.
+ if db.bind.url.drivername == 'sqlite':
+ constraint = UniqueConstraint('username', table=user_table)
+ constraint.create()
+
+class ReportBase_v0(declarative_base()):
__tablename__ = 'core__reports'
id = Column(Integer, primary_key=True)
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
@@ -302,7 +391,6 @@ class ReportBase_v0(declarative_base()):
discriminator = Column('type', Unicode(50))
__mapper_args__ = {'polymorphic_on': discriminator}
-
class CommentReport_v0(ReportBase_v0):
__tablename__ = 'core__reports_on_comments'
__mapper_args__ = {'polymorphic_identity': 'comment_report'}
@@ -316,14 +404,13 @@ class MediaReport_v0(ReportBase_v0):
__mapper_args__ = {'polymorphic_identity': 'media_report'}
id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
- media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+ media_entry_id = Column(Integer, ForeignKey(MediaEntry.i
class ArchivedReport_v0(ReportBase_v0):
__tablename__ = 'core__reports_archived'
__mapper_args__ = {'polymorphic_identity': 'archived_report'}
id = Column('id',Integer, ForeignKey('core__reports.id'))
-
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
comment_id = Column(Integer, ForeignKey(MediaComment.id))
resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
@@ -344,7 +431,6 @@ class Privilege_v0(declarative_base()):
class PrivilegeUserAssociation_v0(declarative_base()):
__tablename__ = 'core__privileges_users'
-
group_id = Column(
'core__privilege_id',
Integer,
@@ -356,7 +442,7 @@ class PrivilegeUserAssociation_v0(declarative_base()):
ForeignKey(Privilege.id),
primary_key=True)
-@RegisterMigration(11, MIGRATIONS)
+@RegisterMigration(14, MIGRATIONS)
def create_moderation_tables(db):
ReportBase_v0.__table__.create(db.bind)
CommentReport_v0.__table__.create(db.bind)
diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py
index 70c9dd41..25ce6642 100644
--- a/mediagoblin/db/mixin.py
+++ b/mediagoblin/db/mixin.py
@@ -29,13 +29,14 @@ real objects.
import uuid
import re
-import datetime
+from datetime import datetime
from werkzeug.utils import cached_property
from mediagoblin import mg_globals
-from mediagoblin.media_types import get_media_managers, FileTypeNotSupported
+from mediagoblin.media_types import FileTypeNotSupported
from mediagoblin.tools import common, licenses
+from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin.tools.text import cleaned_markdown_conversion
from mediagoblin.tools.url import slugify
@@ -201,14 +202,14 @@ class MediaEntryMixin(GenerateSlugMixin):
Raises FileTypeNotSupported in case no such manager is enabled
"""
- # TODO, we should be able to make this a simple lookup rather
- # than iterating through all media managers.
- for media_type, manager in get_media_managers():
- if media_type == self.media_type:
- return manager(self)
+ manager = hook_handle(('media_manager', self.media_type))
+ if manager:
+ return manager(self)
+
# Not found? Then raise an error
raise FileTypeNotSupported(
- "MediaManager not in enabled types. Check media_types in config?")
+ "MediaManager not in enabled types. Check media_type plugins are"
+ " enabled in config?")
def get_fail_exception(self):
"""
@@ -287,6 +288,13 @@ class MediaCommentMixin(object):
"""
return cleaned_markdown_conversion(self.content)
+ def __repr__(self):
+ return '<{klass} #{id} {author} "{comment}">'.format(
+ klass=self.__class__.__name__,
+ id=self.id,
+ author=self.get_author,
+ comment=self.content)
+
class CollectionMixin(GenerateSlugMixin):
def check_slug_used(self, slug):
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index c85d546f..32d3135f 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -24,16 +24,16 @@ import datetime
from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \
Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \
SmallInteger
-from sqlalchemy.orm import relationship, backref
+from sqlalchemy.orm import relationship, backref, with_polymorphic
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql.expression import desc
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.util import memoized_property
-from sqlalchemy.schema import Table
from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
from mediagoblin.db.base import Base, DictReadAttrProxy
-from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin
+from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
+ MediaCommentMixin, CollectionMixin, CollectionItemMixin
from mediagoblin.tools.files import delete_media_files
from mediagoblin.tools.common import import_component
@@ -239,8 +239,8 @@ class MediaEntry(Base, MediaEntryMixin):
This will *not* automatically delete unused collections, which
can remain empty...
- :keyword del_orphan_tags: True/false if we delete unused Tags too
- :keyword commit: True/False if this should end the db transaction"""
+ :param del_orphan_tags: True/false if we delete unused Tags too
+ :param commit: True/False if this should end the db transaction"""
# User's CollectionItems are automatically deleted via "cascade".
# Comments on this Media are deleted by cascade, hopefully.
@@ -393,6 +393,10 @@ class MediaComment(Base, MediaCommentMixin):
backref=backref("posted_comments",
lazy="dynamic",
cascade="all, delete-orphan"))
+ get_entry = relationship(MediaEntry,
+ backref=backref("comments",
+ lazy="dynamic",
+ cascade="all, delete-orphan"))
# Cascade: Comments are somewhat owned by their MediaEntry.
# So do the full thing.
@@ -484,6 +488,92 @@ class ProcessingMetaData(Base):
"""A dict like view on this object"""
return DictReadAttrProxy(self)
+class CommentSubscription(Base):
+ __tablename__ = 'core__comment_subscriptions'
+ id = Column(Integer, primary_key=True)
+
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+ media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+ media_entry = relationship(MediaEntry,
+ backref=backref('comment_subscriptions',
+ cascade='all, delete-orphan'))
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+ user = relationship(User,
+ backref=backref('comment_subscriptions',
+ cascade='all, delete-orphan'))
+
+ notify = Column(Boolean, nullable=False, default=True)
+ send_email = Column(Boolean, nullable=False, default=True)
+
+ def __repr__(self):
+ return ('<{classname} #{id}: {user} {media} notify: '
+ '{notify} email: {email}>').format(
+ id=self.id,
+ classname=self.__class__.__name__,
+ user=self.user,
+ media=self.media_entry,
+ notify=self.notify,
+ email=self.send_email)
+
+
+class Notification(Base):
+ __tablename__ = 'core__notifications'
+ id = Column(Integer, primary_key=True)
+ type = Column(Unicode)
+
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+ user_id = Column(Integer, ForeignKey('core__users.id'), nullable=False,
+ index=True)
+ seen = Column(Boolean, default=lambda: False, index=True)
+ user = relationship(
+ User,
+ backref=backref('notifications', cascade='all, delete-orphan'))
+
+ __mapper_args__ = {
+ 'polymorphic_identity': 'notification',
+ 'polymorphic_on': type
+ }
+
+ def __repr__(self):
+ return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
+ id=self.id,
+ klass=self.__class__.__name__,
+ user=self.user,
+ subject=getattr(self, 'subject', None),
+ seen='unseen' if not self.seen else 'seen')
+
+
+class CommentNotification(Notification):
+ __tablename__ = 'core__comment_notifications'
+ id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
+
+ subject_id = Column(Integer, ForeignKey(MediaComment.id))
+ subject = relationship(
+ MediaComment,
+ backref=backref('comment_notifications', cascade='all, delete-orphan'))
+
+ __mapper_args__ = {
+ 'polymorphic_identity': 'comment_notification'
+ }
+
+
+class ProcessingNotification(Notification):
+ __tablename__ = 'core__processing_notifications'
+
+ id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
+
+ subject_id = Column(Integer, ForeignKey(MediaEntry.id))
+ subject = relationship(
+ MediaEntry,
+ backref=backref('processing_notifications',
+ cascade='all, delete-orphan'))
+
+ __mapper_args__ = {
+ 'polymorphic_identity': 'processing_notification'
+ }
class ReportBase(Base):
"""
@@ -672,20 +762,38 @@ class PrivilegeUserAssociation(Base):
ForeignKey(Privilege.id),
primary_key=True)
+with_polymorphic(
+ Notification,
+ [ProcessingNotification, CommentNotification])
-privilege_foundations = [[u'admin'], [u'moderator'], [u'uploader'],[u'reporter'], [u'commenter'] ,[u'active']]
MODELS = [
User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase,
CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation,
- ArchivedReport]
+ ArchivedReport, Notification, CommentNotification,
+ ProcessingNotification, CommentSubscription]
-# Foundations are the default rows that are created immediately after the tables are initialized. Each entry to
-# this dictionary should be in the format of
-# ModelObject:List of Rows
-# (Each Row must be a list of parameters that can create and instance of the ModelObject)
-#
+"""
+ Foundations are the default rows that are created immediately after the tables
+ are initialized. Each entry to this dictionary should be in the format of:
+ ModelConstructorObject:List of Dictionaries
+ (Each Dictionary represents a row on the Table to be created, containing each
+ of the columns' names as a key string, and each of the columns' values as a
+ value)
+
+ ex. [NOTE THIS IS NOT BASED OFF OF OUR USER TABLE]
+ user_foundations = [{'name':u'Joanna', 'age':24},
+ {'name':u'Andrea', 'age':41}]
+
+ FOUNDATIONS = {User:user_foundations}
+"""
+privilege_foundations = [{'privilege_name':u'admin'},
+ {'privilege_name':u'moderator'},
+ {'privilege_name':u'uploader'},
+ {'privilege_name':u'reporter'},
+ {'privilege_name':u'commenter'},
+ {'privilege_name':u'active'}]
FOUNDATIONS = {Privilege:privilege_foundations}
######################################################
diff --git a/mediagoblin/db/models_v0.py b/mediagoblin/db/models_v0.py
index ec51a1f5..bdedec2e 100644
--- a/mediagoblin/db/models_v0.py
+++ b/mediagoblin/db/models_v0.py
@@ -18,6 +18,29 @@
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
diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py
index 0b1679fb..4ff0945f 100644
--- a/mediagoblin/db/open.py
+++ b/mediagoblin/db/open.py
@@ -52,10 +52,6 @@ class DatabaseMaster(object):
def load_models(app_config):
import mediagoblin.db.models
- for media_type in app_config['media_types']:
- _log.debug("Loading %s.models", media_type)
- __import__(media_type + ".models")
-
for plugin in mg_globals.global_config.get('plugins', {}).keys():
_log.debug("Loading %s.models", plugin)
try:
diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py
index 1aa0a63c..2a99d467 100644
--- a/mediagoblin/db/util.py
+++ b/mediagoblin/db/util.py
@@ -25,7 +25,7 @@ from mediagoblin.db.models import (MediaEntry, Tag, MediaTag, Collection, \
def atomic_update(table, query_dict, update_values):
- table.find(query_dict).update(update_values,
+ table.query.filter_by(**query_dict).update(update_values,
synchronize_session=False)
Session.commit()