Comment changes for federation
authorJessica Tallon <tsyesika@tsyesika.se>
Tue, 20 Oct 2015 12:24:54 +0000 (12:24 +0000)
committerJessica Tallon <tsyesika@tsyesika.se>
Tue, 20 Oct 2015 12:24:54 +0000 (12:24 +0000)
This adds a new Comment link table that is used to link between some
object and then the comment object, which can be more or less any
object in Mediagoblin. The MediaComment has been renamed to
TextComment as that more aptly describes what it is. There is
migrations for these changes.

There is also the conslidation of the Report tables into a single
Report table, the same with the Notification objects. This is because
both of them split out MediaEntry and Comment versions into their own
polymorphic versions from a base, this is no longer a meaningful
distinction as comments can be anything.

26 files changed:
mediagoblin/api/views.py
mediagoblin/db/base.py
mediagoblin/db/migrations.py
mediagoblin/db/mixin.py
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/moderation/tools.py
mediagoblin/moderation/views.py
mediagoblin/notifications/__init__.py
mediagoblin/notifications/task.py
mediagoblin/notifications/views.py
mediagoblin/submit/lib.py
mediagoblin/templates/mediagoblin/fragments/header_notifications.html
mediagoblin/templates/mediagoblin/moderation/report.html
mediagoblin/templates/mediagoblin/moderation/report_panel.html
mediagoblin/templates/mediagoblin/moderation/user.html
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/tests/test_api.py
mediagoblin/tests/test_misc.py
mediagoblin/tests/test_moderation.py
mediagoblin/tests/test_notifications.py
mediagoblin/tests/test_reporting.py
mediagoblin/tests/test_submission.py
mediagoblin/tests/tools.py
mediagoblin/user_pages/lib.py
mediagoblin/user_pages/views.py

index c515a8fab9fb31e72f9daf2da9d8bb82161a4f27..671c3b360ce40bd48d1e32914977d6606f9d5c4f 100644 (file)
@@ -22,7 +22,7 @@ from werkzeug.datastructures import FileStorage
 
 from mediagoblin.decorators import oauth_required, require_active_login
 from mediagoblin.api.decorators import user_has_privilege
-from mediagoblin.db.models import User, LocalUser, MediaEntry, MediaComment, Activity
+from mediagoblin.db.models import User, LocalUser, MediaEntry, Comment, TextComment, Activity
 from mediagoblin.tools.federation import create_activity, create_generator
 from mediagoblin.tools.routing import extract_url_arguments
 from mediagoblin.tools.response import redirect, json_response, json_error, \
@@ -268,7 +268,7 @@ def feed_endpoint(request, outbox=None):
                         status=403
                     )
 
-                comment = MediaComment(actor=request.user.id)
+                comment = TextComment(actor=request.user.id)
                 comment.unserialize(data["object"], request)
                 comment.save()
 
@@ -278,7 +278,7 @@ def feed_endpoint(request, outbox=None):
                     verb="post",
                     actor=request.user,
                     obj=comment,
-                    target=comment.get_entry,
+                    target=comment.get_reply_to(),
                     generator=generator
                 )
 
@@ -286,12 +286,22 @@ def feed_endpoint(request, outbox=None):
 
             elif obj.get("objectType", None) == "image":
                 # Posting an image to the feed
-                media_id = int(extract_url_arguments(
+                media_id = extract_url_arguments(
                     url=data["object"]["id"],
                     urlmap=request.app.url_map
-                )["id"])
+                )["id"]
 
-                media = MediaEntry.query.filter_by(id=media_id).first()
+                # Build public_id
+                public_id = request.urlgen(
+                    "mediagoblin.api.object",
+                    object_type=obj["objectType"],
+                    id=media_id,
+                    qualified=True
+                )
+
+                media = MediaEntry.query.filter_by(
+                    public_id=public_id
+                ).first()
 
                 if media is None:
                     return json_response(
@@ -345,10 +355,17 @@ def feed_endpoint(request, outbox=None):
             if "id" not in obj:
                 return json_error("Object ID has not been specified.")
 
-            obj_id = int(extract_url_arguments(
+            obj_id = extract_url_arguments(
                 url=obj["id"],
                 urlmap=request.app.url_map
-            )["id"])
+            )["id"]
+
+            public_id = request.urlgen(
+                "mediagoblin.api.object",
+                object_type=obj["objectType"],
+                id=obj_id,
+                qualified=True
+            )
 
             # Now try and find object
             if obj["objectType"] == "comment":
@@ -358,7 +375,9 @@ def feed_endpoint(request, outbox=None):
                         status=403
                     )
 
-                comment = MediaComment.query.filter_by(id=obj_id).first()
+                comment = TextComment.query.filter_by(
+                    public_id=public_id
+                ).first()
                 if comment is None:
                     return json_error(
                         "No such 'comment' with id '{0}'.".format(obj_id)
@@ -391,7 +410,9 @@ def feed_endpoint(request, outbox=None):
                 return json_response(activity.serialize(request))
 
             elif obj["objectType"] == "image":
-                image = MediaEntry.query.filter_by(id=obj_id).first()
+                image = MediaEntry.query.filter_by(
+                    public_id=public_id
+                ).first()
                 if image is None:
                     return json_error(
                         "No such 'image' with the id '{0}'.".format(obj["id"])
@@ -454,15 +475,22 @@ def feed_endpoint(request, outbox=None):
                 return json_error("Object ID has not been specified.")
 
             # Parse out the object ID
-            obj_id = int(extract_url_arguments(
+            obj_id = extract_url_arguments(
                 url=obj["id"],
                 urlmap=request.app.url_map
-            )["id"])
+            )["id"]
+
+            public_id = request.urlgen(
+                "mediagoblin.api.object",
+                object_type=obj["objectType"],
+                id=obj_id,
+                qualified=True
+            )
 
             if obj.get("objectType", None) == "comment":
                 # Find the comment asked for
-                comment = MediaComment.query.filter_by(
-                    id=obj_id,
+                comment = TextComment.query.filter_by(
+                    public_id=public_id,
                     actor=request.user.id
                 ).first()
 
@@ -491,7 +519,7 @@ def feed_endpoint(request, outbox=None):
             if obj.get("objectType", None) == "image":
                 # Find the image
                 entry = MediaEntry.query.filter_by(
-                    id=obj_id,
+                    public_id=public_id,
                     actor=request.user.id
                 ).first()
 
@@ -500,10 +528,6 @@ def feed_endpoint(request, outbox=None):
                         "No such 'image' with id '{0}'.".format(obj_id)
                     )
 
-                # Okay lets do our best to ensure there is a public_id for
-                # this image, there most likely is but it's important!
-                entry.get_public_id(request.urlgen)
-
                 # Make the delete activity
                 generator = create_generator(request)
                 activity = create_activity(
@@ -621,7 +645,14 @@ def object_endpoint(request):
             status=404
         )
 
-    media = MediaEntry.query.filter_by(id=object_id).first()
+    public_id = request.urlgen(
+        "mediagoblin.api.object",
+        object_type=object_type,
+        id=object_id,
+        qualified=True
+    )
+
+    media = MediaEntry.query.filter_by(public_id=public_id).first()
     if media is None:
         return json_error(
             "Can't find '{0}' with ID '{1}'".format(object_type, object_id),
@@ -633,7 +664,13 @@ def object_endpoint(request):
 @oauth_required
 def object_comments(request):
     """ Looks up for the comments on a object """
-    media = MediaEntry.query.filter_by(id=request.matchdict["id"]).first()
+    public_id = request.urlgen(
+        "mediagoblin.api.object",
+        object_type=request.matchdict["object_type"],
+        id=request.matchdict["id"],
+        qualified=True
+    )
+    media = MediaEntry.query.filter_by(public_id=public_id).first()
     if media is None:
         return json_error("Can't find '{0}' with ID '{1}'".format(
             request.matchdict["object_type"],
index a62cbebcebace95b20ddd35c91885a2ffc5e54bb..11afbcec5726210c5289e9ef59c41471d71ef819 100644 (file)
@@ -13,6 +13,9 @@
 #
 # 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/>.
+import six
+import copy
+
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy import inspect
 
@@ -22,6 +25,30 @@ if not DISABLE_GLOBALS:
     from sqlalchemy.orm import scoped_session, sessionmaker
     Session = scoped_session(sessionmaker())
 
+class FakeCursor(object):
+
+    def __init__ (self, cursor, mapper, filter=None):
+        self.cursor = cursor
+        self.mapper = mapper
+        self.filter = filter
+
+    def count(self):
+        return self.cursor.count()
+
+    def __copy__(self):
+        # Or whatever the function is named to make
+        # copy.copy happy?
+        return FakeCursor(copy.copy(self.cursor), self.mapper, self.filter)
+
+    def __iter__(self):
+        return six.moves.filter(self.filter, six.moves.map(self.mapper, self.cursor))
+
+    def __getitem__(self, key):
+        return self.mapper(self.cursor[key])
+
+    def slice(self, *args, **kwargs):
+        r = self.cursor.slice(*args, **kwargs)
+        return list(six.moves.filter(self.filter, six.moves.map(self.mapper, r)))
 
 class GMGTableBase(object):
     # Deletion types
@@ -93,7 +120,7 @@ class GMGTableBase(object):
                 id=self.actor
             ).first()
         tombstone.object_type = self.object_type
-        tombstone.save()
+        tombstone.save(commit=False)
 
         # There will be a lot of places where the GenericForeignKey will point
         # to the model, we want to remap those to our tombstone.
index 2df06fc05ff7f871dd3654ebce147c9392b3b83b..461b9c0a9232c55bc09f96c9b613d8e570c94997 100644 (file)
@@ -37,7 +37,7 @@ from mediagoblin.tools import crypto
 from mediagoblin.db.extratypes import JSONEncoded, MutationDict
 from mediagoblin.db.migration_tools import (
     RegisterMigration, inspect_table, replace_table_hack)
-from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User,
+from mediagoblin.db.models import (MediaEntry, Collection, Comment, User,
                                    Privilege, Generator, LocalUser, Location,
                                    Client, RequestToken, AccessToken)
 from mediagoblin.db.extratypes import JSONEncoded, MutationDict
@@ -353,7 +353,7 @@ 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))
+    subject_id = Column(Integer, ForeignKey(Comment.id))
 
 
 class ProcessingNotification_v0(Notification_v0):
@@ -542,7 +542,7 @@ class CommentReport_v0(ReportBase_v0):
 
     id = Column('id',Integer, ForeignKey('core__reports.id'),
                                                 primary_key=True)
-    comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
+    comment_id = Column(Integer, ForeignKey(Comment.id), nullable=True)
 
 
 class MediaReport_v0(ReportBase_v0):
@@ -917,7 +917,7 @@ class ActivityIntermediator_R0(declarative_base()):
     TYPES = {
         "user": User,
         "media": MediaEntry,
-        "comment": MediaComment,
+        "comment": Comment,
         "collection": Collection,
     }
 
@@ -1875,3 +1875,268 @@ def add_public_id(db):
 
     # Commit this.
     db.commit()
+
+class Comment_V0(declarative_base()):
+    __tablename__ = "core__comment_links"
+
+    id = Column(Integer, primary_key=True)
+    target_id = Column(
+        Integer,
+        ForeignKey(GenericModelReference_V0.id),
+        nullable=False
+    )
+    comment_id = Column(
+        Integer,
+        ForeignKey(GenericModelReference_V0.id),
+        nullable=False
+    )
+    added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+
+@RegisterMigration(41, MIGRATIONS)
+def federation_comments(db):
+    """
+    This reworks the MediaComent to be a more generic Comment model.
+    """
+    metadata = MetaData(bind=db.bind)
+    textcomment_table = inspect_table(metadata, "core__media_comments")
+    gmr_table = inspect_table(metadata, "core__generic_model_reference")
+
+    # First of all add the public_id field to the TextComment table
+    comment_public_id_column = Column(
+        "public_id",
+        Unicode,
+        unique=True
+    )
+    comment_public_id_column.create(
+        textcomment_table,
+        unique_name="public_id_unique"
+    )
+
+    comment_updated_column = Column(
+        "updated",
+        DateTime,
+    )
+    comment_updated_column.create(textcomment_table)
+
+
+    # First create the Comment link table.
+    Comment_V0.__table__.create(db.bind)
+    db.commit()
+
+    # now look up the comment table 
+    comment_table = inspect_table(metadata, "core__comment_links")
+
+    # Itierate over all the comments and add them to the link table.
+    for comment in db.execute(textcomment_table.select()):
+        # Check if there is a GMR to the comment.
+        comment_gmr = db.execute(gmr_table.select().where(and_(
+            gmr_table.c.obj_pk == comment.id,
+            gmr_table.c.model_type == "core__media_comments"
+        ))).first()
+
+        if comment_gmr:
+            comment_gmr = comment_gmr[0]
+        else:
+            comment_gmr = db.execute(gmr_table.insert().values(
+                obj_pk=comment.id,
+                model_type="core__media_comments"
+            )).inserted_primary_key[0]
+
+        # Get or create the GMR for the media entry
+        entry_gmr = db.execute(gmr_table.select().where(and_(
+            gmr_table.c.obj_pk == comment.media_entry,
+            gmr_table.c.model_type == "core__media_entries"
+        ))).first()
+
+        if entry_gmr:
+            entry_gmr = entry_gmr[0]
+        else:
+            entry_gmr = db.execute(gmr_table.insert().values(
+                obj_pk=comment.media_entry,
+                model_type="core__media_entries"
+            )).inserted_primary_key[0] 
+
+        # Add the comment link.
+        db.execute(comment_table.insert().values(
+            target_id=entry_gmr,
+            comment_id=comment_gmr,
+            added=datetime.datetime.utcnow()
+        ))
+
+        # Add the data to the updated field
+        db.execute(textcomment_table.update().where(
+            textcomment_table.c.id == comment.id
+        ).values(
+            updated=comment.created
+        ))
+    db.commit()
+    
+    # Add not null constraint
+    textcomment_update_column = textcomment_table.columns["updated"]
+    textcomment_update_column.alter(nullable=False)
+
+    # Remove the unused fields on the TextComment model
+    comment_media_entry_column = textcomment_table.columns["media_entry"]
+    comment_media_entry_column.drop()
+    db.commit()
+
+@RegisterMigration(42, MIGRATIONS)
+def consolidate_reports(db):
+    """ Consolidates the report tables into just one """
+    metadata = MetaData(bind=db.bind)
+
+    report_table = inspect_table(metadata, "core__reports")
+    comment_report_table = inspect_table(metadata, "core__reports_on_comments")
+    media_report_table = inspect_table(metadata, "core__reports_on_media")
+    gmr_table = inspect_table(metadata, "core__generic_model_reference")
+
+    # Add the GMR object field onto the base report table
+    report_object_id_column = Column(
+        "object_id",
+        Integer,
+        ForeignKey(GenericModelReference_V0.id),
+    )
+    report_object_id_column.create(report_table)
+    db.commit()
+
+    # Iterate through the reports in the comment table and merge them in.
+    for comment_report in db.execute(comment_report_table.select()):
+        # Find a GMR for this if one exists.
+        crgmr = db.execute(gmr_table.select().where(and_(
+            gmr_table.c.obj_pk == comment_report.comment_id,
+            gmr_table.c.model_type == "core__media_comments"
+        ))).first()
+
+        if crgmr:
+            crgmr = crgmr[0]
+        else:
+            crgmr = db.execute(gmr_table.insert().values(
+                gmr_table.c.obj_pk == comment_report.comment_id,
+                gmr_table.c.model_type == "core__media_comments"
+            )).inserted_primary_key[0]
+
+        # Great now we can save this back onto the (base) report.
+        db.execute(report_table.update().where(
+            report_table.c.id == comment_report.id
+        ).values(
+            object_id=crgmr
+        ))
+
+    # Iterate through the Media Reports and do the save as above.
+    for media_report in db.execute(media_report_table.select()):
+        # Find Mr. GMR :)
+        mrgmr = db.execute(gmr_table.select().where(and_(
+            gmr_table.c.obj_pk == media_report.media_entry_id,
+            gmr_table.c.model_type == "core__media_entries"
+        ))).first()
+
+        if mrgmr:
+            mrgmr = mrgmr[0]
+        else:
+            mrgmr = db.execute(gmr_table.insert().values(
+                obj_pk=media_report.media_entry_id,
+                model_type="core__media_entries"
+            )).inserted_primary_key[0]
+
+        # Save back on to the base.
+        db.execute(report_table.update().where(
+            report_table.c.id == media_report.id
+        ).values(
+            object_id=mrgmr
+        ))
+
+    db.commit()
+
+    # Add the not null constraint
+    report_object_id = report_table.columns["object_id"]
+    report_object_id.alter(nullable=False)
+
+    # Now we can remove the fields we don't need anymore
+    report_type = report_table.columns["type"]
+    report_type.drop()
+
+    # Drop both MediaReports and CommentTable.
+    comment_report_table.drop()
+    media_report_table.drop()
+
+    # Commit we're done.
+    db.commit()
+
+@RegisterMigration(43, MIGRATIONS)
+def consolidate_notification(db):
+    """ Consolidates the notification models into one """
+    metadata = MetaData(bind=db.bind)
+    notification_table = inspect_table(metadata, "core__notifications")
+    cn_table = inspect_table(metadata, "core__comment_notifications")
+    cp_table = inspect_table(metadata, "core__processing_notifications")
+    gmr_table = inspect_table(metadata, "core__generic_model_reference")
+
+    # Add fields needed
+    notification_object_id_column = Column(
+        "object_id",
+        Integer,
+        ForeignKey(GenericModelReference_V0.id)
+    )
+    notification_object_id_column.create(notification_table)
+    db.commit()
+
+    # Iterate over comments and move to notification base table.
+    for comment_notification in db.execute(cn_table.select()):
+        # Find the GMR.
+        cngmr = db.execute(gmr_table.select().where(and_(
+            gmr_table.c.obj_pk == comment_notification.subject_id,
+            gmr_table.c.model_type == "core__media_comments"
+        ))).first()
+
+        if cngmr:
+            cngmr = cngmr[0]
+        else:
+            cngmr = db.execute(gmr_table.insert().values(
+                obj_pk=comment_notification.subject_id,
+                model_type="core__media_comments"
+            )).inserted_primary_key[0]
+
+        # Save back on notification
+        db.execute(notification_table.update().where(
+            notification_table.c.id == comment_notification.id
+        ).values(
+            object_id=cngmr
+        ))
+    db.commit()
+
+    # Do the same for processing notifications
+    for processing_notification in db.execute(cp_table.select()):
+        cpgmr = db.execute(gmr_table.select().where(and_(
+            gmr_table.c.obj_pk == processing_notification.subject_id,
+            gmr_table.c.model_type == "core__processing_notifications"
+        ))).first()
+
+        if cpgmr:
+            cpgmr = cpgmr[0]
+        else:
+            cpgmr = db.execute(gmr_table.insert().values(
+                obj_pk=processing_notification.subject_id,
+                model_type="core__processing_notifications"
+            )).inserted_primary_key[0]
+
+        db.execute(notification_table.update().where(
+            notification_table.c.id == processing_notification.id
+        ).values(
+            object_id=cpgmr
+        ))
+    db.commit()
+
+    # Add the not null constraint
+    notification_object_id = notification_table.columns["object_id"]
+    notification_object_id.alter(nullable=False)
+
+    # Now drop the fields we don't need
+    notification_type_column = notification_table.columns["type"]
+    notification_type_column.drop()
+
+    # Drop the tables we no longer need
+    cp_table.drop()
+    cn_table.drop()
+
+    db.commit()
index e6a2dc357df021fae1a76275473da28036734fb9..ecd04874f39b4fee5341aa89ac072f51f05c4e8f 100644 (file)
@@ -41,6 +41,47 @@ from mediagoblin.tools.text import cleaned_markdown_conversion
 from mediagoblin.tools.url import slugify
 from mediagoblin.tools.translate import pass_to_ugettext as _
 
+class CommentingMixin(object):
+    """
+    Mixin that gives classes methods to get and add the comments on/to it
+
+    This assumes the model has a "comments" class which is a ForeignKey to the
+    Collection model. This will hold a Collection of comments which are
+    associated to this model. It also assumes the model has an "actor"
+    ForeignKey which points to the creator/publisher/etc. of the model.
+
+    NB: This is NOT the mixin for the Comment Model, this is for
+        other models which support commenting.
+    """
+
+    def get_comment_link(self):
+        # Import here to avoid cyclic imports
+        from mediagoblin.db.models import Comment, GenericModelReference
+
+        gmr = GenericModelReference.query.filter_by(
+            obj_pk=self.id,
+            model_type=self.__tablename__
+        ).first()
+
+        if gmr is None:
+            return None
+
+        link = Comment.query.filter_by(comment_id=gmr.id).first()
+        return link
+
+    def get_reply_to(self):
+        link = self.get_comment_link()
+        if link is None or link.target_id is None:
+            return None
+
+        return link.target()
+
+    def soft_delete(self, *args, **kwargs):
+        link = self.get_comment_link()
+        if link is not None:
+            link.delete()
+        super(CommentingMixin, self).soft_delete(*args, **kwargs)
+
 class GeneratePublicIDMixin(object):
     """
     Mixin that ensures that a the public_id field is populated.
@@ -71,9 +112,10 @@ class GeneratePublicIDMixin(object):
             self.public_id = urlgen(
                 "mediagoblin.api.object",
                 object_type=self.object_type,
-                id=self.id,
+                id=str(uuid.uuid4()),
                 qualified=True
             )
+            self.save()
         return self.public_id
 
 class UserMixin(object):
@@ -342,7 +384,7 @@ class MediaEntryMixin(GenerateSlugMixin, GeneratePublicIDMixin):
         return exif_short
 
 
-class MediaCommentMixin(object):
+class TextCommentMixin(GeneratePublicIDMixin):
     object_type = "comment"
 
     @property
@@ -367,7 +409,6 @@ class MediaCommentMixin(object):
             actor=self.get_actor,
             comment=self.content)
 
-
 class CollectionMixin(GenerateSlugMixin, GeneratePublicIDMixin):
     object_type = "collection"
 
@@ -404,6 +445,28 @@ class CollectionMixin(GenerateSlugMixin, GeneratePublicIDMixin):
             collection=self.slug_or_id,
             **extra_args)
 
+    def add_to_collection(self, obj, content=None, commit=True):
+        """ Adds an object to the collection """
+        # It's here to prevent cyclic imports
+        from mediagoblin.db.models import CollectionItem
+        
+        # Need the ID of this collection for this so check we've got one.
+        self.save(commit=False)
+
+        # Create the CollectionItem
+        item = CollectionItem()
+        item.collection = self.id
+        item.get_object = obj
+        
+        if content is not None:
+            item.note = content
+
+        self.num_items = self.num_items + 1
+        
+        # Save both!
+        self.save(commit=commit)
+        item.save(commit=commit)
+        return item 
 
 class CollectionItemMixin(object):
     @property
index e52cab824c8b21aaed565cada67506a82fe24dd2..67659552f9eeb90559f9cf59166c5ab2775c3f87 100644 (file)
@@ -36,10 +36,10 @@ from sqlalchemy.util import memoized_property
 
 from mediagoblin.db.extratypes import (PathTupleWithSlashes, JSONEncoded,
                                        MutationDict)
-from mediagoblin.db.base import Base, DictReadAttrProxy
+from mediagoblin.db.base import Base, DictReadAttrProxy, FakeCursor
 from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
-        MediaCommentMixin, CollectionMixin, CollectionItemMixin, \
-        ActivityMixin
+        CollectionMixin, CollectionItemMixin, ActivityMixin, TextCommentMixin, \
+        CommentingMixin
 from mediagoblin.tools.files import delete_media_files
 from mediagoblin.tools.common import import_component
 from mediagoblin.tools.routing import extract_url_arguments
@@ -262,7 +262,7 @@ class User(Base, UserMixin):
             collection.delete(**kwargs)
 
         # Find all the comments and delete those too
-        for comment in MediaComment.query.filter_by(actor=self.id):
+        for comment in TextComment.query.filter_by(actor=self.id):
             comment.delete(**kwargs)
 
         # Find all the activities and delete those too
@@ -509,7 +509,7 @@ class NonceTimestamp(Base):
     nonce = Column(Unicode, nullable=False, primary_key=True)
     timestamp = Column(DateTime, nullable=False, primary_key=True)
 
-class MediaEntry(Base, MediaEntryMixin):
+class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
     """
     TODO: Consider fetching the media_files using join
     """
@@ -595,11 +595,18 @@ class MediaEntry(Base, MediaEntryMixin):
         ))
 
     def get_comments(self, ascending=False):
-        order_col = MediaComment.created
-        if not ascending:
-            order_col = desc(order_col)
-        return self.all_comments.order_by(order_col)
+        query = Comment.query.join(Comment.target_helper).filter(and_(
+            GenericModelReference.obj_pk == self.id,
+            GenericModelReference.model_type == self.__tablename__
+        ))
 
+        if ascending:
+            query = query.order_by(Comment.added.asc())
+        else:
+            qury = query.order_by(Comment.added.desc())
+        
+        return FakeCursor(query, lambda c:c.comment())
     def url_to_prev(self, urlgen):
         """get the next 'newer' entry by this user"""
         media = MediaEntry.query.filter(
@@ -689,7 +696,7 @@ class MediaEntry(Base, MediaEntryMixin):
 
     def soft_delete(self, *args, **kwargs):
         # Find all of the media comments for this and delete them
-        for comment in MediaComment.query.filter_by(media_entry=self.id):
+        for comment in self.get_comments():
             comment.delete(*args, **kwargs)
 
         super(MediaEntry, self).soft_delete(*args, **kwargs)
@@ -927,15 +934,63 @@ class MediaTag(Base):
         """A dict like view on this object"""
         return DictReadAttrProxy(self)
 
+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
+    and more or less any object can accept comments too.
+
+    Important: This is NOT the old MediaComment table.
+    """
+    __tablename__ = "core__comment_links"
+
+    id = Column(Integer, primary_key=True)
+    
+    # The GMR to the object the comment is on.
+    target_id = Column(
+        Integer,
+        ForeignKey(GenericModelReference.id),
+        nullable=False
+    )
+    target_helper = relationship(
+        GenericModelReference,
+        foreign_keys=[target_id]
+    )
+    target = association_proxy("target_helper", "get_object",
+                                creator=GenericModelReference.find_or_new)
+
+    # The comment object
+    comment_id = Column(
+        Integer,
+        ForeignKey(GenericModelReference.id),
+        nullable=False
+    )
+    comment_helper = relationship(
+        GenericModelReference,
+        foreign_keys=[comment_id]
+    )
+    comment = association_proxy("comment_helper", "get_object",
+                                creator=GenericModelReference.find_or_new)
+
+    # When it was added
+    added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+    
 
-class MediaComment(Base, MediaCommentMixin):
+class TextComment(Base, TextCommentMixin, CommentingMixin):
+    """
+    A basic text comment, this is a usually short amount of text and nothing else
+    """
+    # This is a legacy from when Comments where just on MediaEntry objects.
     __tablename__ = "core__media_comments"
 
     id = Column(Integer, primary_key=True)
-    media_entry = Column(
-        Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
+    public_id = Column(Unicode, unique=True)
     actor = Column(Integer, ForeignKey(User.id), nullable=False)
     created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+    updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
     content = Column(UnicodeText, nullable=False)
     location = Column(Integer, ForeignKey("core__locations.id"))
     get_location = relationship("Location", lazy="joined")
@@ -947,38 +1002,25 @@ 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.
-    # lazy=dynamic: MediaEntries might have many comments,
-    #     so make the "all_comments" a query-like thing.
-    get_media_entry = relationship(MediaEntry,
-                                   backref=backref("all_comments",
-                                                   lazy="dynamic",
-                                                   cascade="all, delete-orphan"))
-
     deletion_mode = Base.SOFT_DELETE
 
     def serialize(self, request):
         """ Unserialize to python dictionary for API """
-        href = request.urlgen(
-            "mediagoblin.api.object",
-            object_type=self.object_type,
-            id=self.id,
-            qualified=True
-        )
-        media = MediaEntry.query.filter_by(id=self.media_entry).first()
+        target = self.get_reply_to()
+        # If this is target just.. give them nothing?
+        if target is None:
+            target = {}
+        else:
+            target = target.serialize(request, show_comments=False)        
+
+
         author = self.get_actor
         published = UTC.localize(self.created)
         context = {
-            "id": href,
+            "id": self.get_public_id(request.urlgen),
             "objectType": self.object_type,
             "content": self.content,
-            "inReplyTo": media.serialize(request, show_comments=False),
+            "inReplyTo": target,
             "author": author.serialize(request),
             "published": published.isoformat(),
             "updated": published.isoformat(),
@@ -991,34 +1033,47 @@ class MediaComment(Base, MediaCommentMixin):
 
     def unserialize(self, data, request):
         """ Takes API objects and unserializes on existing comment """
+        if "content" in data:
+            self.content = data["content"]
+
+        if "location" in data:
+            Location.create(data["location"], self)
+
+    
         # Handle changing the reply ID
         if "inReplyTo" in data:
             # Validate that the ID is correct
             try:
-                media_id = int(extract_url_arguments(
+                id = extract_url_arguments(
                     url=data["inReplyTo"]["id"],
                     urlmap=request.app.url_map
-                )["id"])
+                )["id"]
             except ValueError:
-                return False
+                raise False
+
+            public_id = request.urlgen(
+                "mediagoblin.api.object",
+                id=id,
+                object_type=data["inReplyTo"]["objectType"],
+                qualified=True
+            )
 
-            media = MediaEntry.query.filter_by(id=media_id).first()
+            media = MediaEntry.query.filter_by(public_id=public_id).first()
             if media is None:
                 return False
 
-            self.media_entry = media.id
-
-        if "content" in data:
-            self.content = data["content"]
-
-        if "location" in data:
-            Location.create(data["location"], self)
+            # We need an ID for this model.
+            self.save(commit=False)
 
+            # Create the link
+            link = Comment()
+            link.target = media
+            link.comment = self
+            link.save()
+        
         return True
 
-
-
-class Collection(Base, CollectionMixin):
+class Collection(Base, CollectionMixin, CommentingMixin):
     """A representation of a collection of objects.
 
     This holds a group/collection of objects that could be a user defined album
@@ -1070,6 +1125,7 @@ class Collection(Base, CollectionMixin):
     OUTBOX_TYPE = "core-outbox"
     FOLLOWER_TYPE = "core-followers"
     FOLLOWING_TYPE = "core-following"
+    COMMENT_TYPE = "core-comments"
     USER_DEFINED_TYPE = "core-user-defined"
 
     def get_collection_items(self, ascending=False):
@@ -1201,21 +1257,19 @@ class CommentSubscription(Base):
 class Notification(Base):
     __tablename__ = 'core__notifications'
     id = Column(Integer, primary_key=True)
-    type = Column(Unicode)
 
-    created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+    object_id = Column(Integer, ForeignKey(GenericModelReference.id))
+    object_helper = relationship(GenericModelReference)
+    obj = association_proxy("object_helper", "get_object",
+                            creator=GenericModelReference.find_or_new)
 
+    created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
     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
-    }
+        backref=backref('notifications', cascade='all, delete-orphan')) 
 
     def __repr__(self):
         return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
@@ -1233,42 +1287,9 @@ class Notification(Base):
             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'
-    }
-
-# the with_polymorphic call has been moved to the bottom above MODELS
-# this is because it causes conflicts with relationship calls.
-
-class ReportBase(Base):
+class Report(Base):
     """
-    This is the basic report object which the other reports are based off of.
+    Represents a report that someone might file against Media, Comments, etc.
 
         :keyword    reporter_id         Holds the id of the user who created
                                             the report, as an Integer column.
@@ -1281,8 +1302,6 @@ class ReportBase(Base):
                                             an Integer column.
         :keyword    created             Holds a datetime column of when the re-
                                             -port was filed.
-        :keyword    discriminator       This column distinguishes between the
-                                            different types of reports.
         :keyword    resolver_id         Holds the id of the moderator/admin who
                                             resolved the report.
         :keyword    resolved            Holds the DateTime object which descri-
@@ -1291,8 +1310,11 @@ class ReportBase(Base):
                                             resolver's reasons for resolving
                                             the report this way. Some of this
                                             is auto-generated
+        :keyword    object_id           Holds the ID of the GenericModelReference
+                                            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(
@@ -1300,7 +1322,7 @@ class ReportBase(Base):
         backref=backref("reports_filed_by",
             lazy="dynamic",
             cascade="all, delete-orphan"),
-        primaryjoin="User.id==ReportBase.reporter_id")
+        primaryjoin="User.id==Report.reporter_id")
     report_content = Column(UnicodeText)
     reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
     reported_user = relationship(
@@ -1308,69 +1330,42 @@ class ReportBase(Base):
         backref=backref("reports_filed_on",
             lazy="dynamic",
             cascade="all, delete-orphan"),
-        primaryjoin="User.id==ReportBase.reported_user_id")
+        primaryjoin="User.id==Report.reported_user_id")
     created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
-    discriminator = Column('type', Unicode(50))
     resolver_id = Column(Integer, ForeignKey(User.id))
     resolver = relationship(
         User,
         backref=backref("reports_resolved_by",
             lazy="dynamic",
             cascade="all, delete-orphan"),
-        primaryjoin="User.id==ReportBase.resolver_id")
+        primaryjoin="User.id==Report.resolver_id")
 
     resolved = Column(DateTime)
     result = Column(UnicodeText)
-    __mapper_args__ = {'polymorphic_on': discriminator}
+    
+    object_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=False)
+    object_helper = relationship(GenericModelReference)
+    obj = association_proxy("object_helper", "get_object",
+                            creator=GenericModelReference.find_or_new)
+
+    def is_archived_report(self):
+        return self.resolved is not None
 
     def is_comment_report(self):
-        return self.discriminator=='comment_report'
+        if self.object_id is None:
+            return False
+        return isinstance(self.obj(), TextComment)
 
     def is_media_entry_report(self):
-        return self.discriminator=='media_report'
-
-    def is_archived_report(self):
-        return self.resolved is not None
+        if self.object_id is None:
+            return False
+        return isinstance(self.obj(), MediaEntry)
 
     def archive(self,resolver_id, resolved, result):
         self.resolver_id   = resolver_id
         self.resolved   = resolved
         self.result     = result
 
-
-class CommentReport(ReportBase):
-    """
-    Reports that have been filed on comments.
-        :keyword    comment_id          Holds the integer value of the reported
-                                            comment's ID
-    """
-    __tablename__ = 'core__reports_on_comments'
-    __mapper_args__ = {'polymorphic_identity': 'comment_report'}
-
-    id = Column('id',Integer, ForeignKey('core__reports.id'),
-                                                primary_key=True)
-    comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
-    comment = relationship(
-        MediaComment, backref=backref("reports_filed_on",
-            lazy="dynamic"))
-
-class MediaReport(ReportBase):
-    """
-    Reports that have been filed on media entries
-        :keyword    media_entry_id      Holds the integer value of the reported
-                                            media entry's ID
-    """
-    __tablename__ = 'core__reports_on_media'
-    __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=True)
-    media_entry = relationship(
-        MediaEntry,
-        backref=backref("reports_filed_on",
-            lazy="dynamic"))
-
 class UserBan(Base):
     """
     Holds the information on a specific user's ban-state. As long as one of
@@ -1576,18 +1571,12 @@ class Graveyard(Base):
             "deleted": self.deleted
         }
 
-with_polymorphic(
-    Notification,
-    [ProcessingNotification, CommentNotification])
-
 MODELS = [
-    LocalUser, RemoteUser, User, MediaEntry, Tag, MediaTag, MediaComment,
+    LocalUser, RemoteUser, User, MediaEntry, Tag, MediaTag, Comment, TextComment,
     Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile,
-    ProcessingMetaData, Notification, CommentNotification,
-    ProcessingNotification, Client, CommentSubscription, ReportBase,
-    CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation,
-    RequestToken, AccessToken, NonceTimestamp, Activity, Generator, Location,
-    GenericModelReference, Graveyard]
+    ProcessingMetaData, Notification, Client, CommentSubscription, Report,
+    UserBan, Privilege, PrivilegeUserAssociation, RequestToken, AccessToken,
+    NonceTimestamp, Activity, Generator, Location, GenericModelReference, Graveyard]
 
 """
  Foundations are the default rows that are created immediately after the tables
index 8874d2a0f9b9436cea58b584a8ff0d0fee98a9a2..a2c49bccd071f79db651ac6f493a526506bf9143 100644 (file)
@@ -23,7 +23,8 @@ from six.moves.urllib.parse import urljoin
 
 from mediagoblin import mg_globals as mgg
 from mediagoblin import messages
-from mediagoblin.db.models import MediaEntry, LocalUser, MediaComment, AccessToken
+from mediagoblin.db.models import MediaEntry, LocalUser, TextComment, \
+                                  AccessToken, Comment
 from mediagoblin.tools.response import (
     redirect, render_404,
     render_user_banned, json_response)
@@ -325,11 +326,11 @@ def allow_reporting(controller):
 
 def get_optional_media_comment_by_id(controller):
     """
-    Pass in a MediaComment based off of a url component. Because of this decor-
-    -ator's use in filing Media or Comment Reports, it has two valid outcomes.
+    Pass in a Comment based off of a url component. Because of this decor-
+    -ator's use in filing Reports, it has two valid outcomes.
 
     :returns        The view function being wrapped with kwarg `comment` set to
-                        the MediaComment who's id is in the URL. If there is a
+                        the Comment who's id is in the URL. If there is a
                         comment id in the URL and if it is valid.
     :returns        The view function being wrapped with kwarg `comment` set to
                         None. If there is no comment id in the URL.
@@ -339,8 +340,9 @@ def get_optional_media_comment_by_id(controller):
     @wraps(controller)
     def wrapper(request, *args, **kwargs):
         if 'comment' in request.matchdict:
-            comment = MediaComment.query.filter_by(
-                    id=request.matchdict['comment']).first()
+            comment = Comment.query.filter_by(
+                    id=request.matchdict['comment']
+            ).first()
 
             if comment is None:
                 return render_404(request)
index d1fedb0e138f3f099082aa16045264d75529c67a..73afd05159c5b617626f6b5216ffaa2f6be77122 100644 (file)
@@ -68,14 +68,14 @@ def take_punitive_actions(request, form, report, user):
 
     if u'delete' in form.action_to_resolve.data and \
         report.is_comment_report():
-            deleted_comment = report.comment
+            deleted_comment = report.obj()
             Session.delete(deleted_comment)
             form.resolution_content.data += \
                 _(u"\n{mod} deleted the comment.").format(
                     mod=request.user.username)
     elif u'delete' in form.action_to_resolve.data and \
         report.is_media_entry_report():
-            deleted_media = report.media_entry
+            deleted_media = report.obj()
             deleted_media.delete()
             form.resolution_content.data += \
                 _(u"\n{mod} deleted the media entry.").format(
index a73537d6a1861c5775aeee16d3d14d961dab7e22..fdcbf051428fd17bfdf929959beea07c3e066175 100644 (file)
@@ -15,7 +15,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-from mediagoblin.db.models import (MediaEntry, User, ReportBase, Privilege,
+from mediagoblin.db.models import (MediaEntry, User, Report, Privilege,
                                    UserBan, LocalUser)
 from mediagoblin.decorators import (require_admin_or_moderator_login,
                                     active_user_from_url, user_has_privilege,
@@ -83,9 +83,9 @@ def moderation_users_detail(request):
         LocalUser.username==request.matchdict['user']
     ).first()
     active_reports = user.reports_filed_on.filter(
-        ReportBase.resolved==None).limit(5)
+        Report.resolved==None).limit(5)
     closed_reports = user.reports_filed_on.filter(
-        ReportBase.resolved!=None).all()
+        Report.resolved!=None).all()
     privileges = Privilege.query
     user_banned = UserBan.query.get(user.id)
     ban_form = moderation_forms.BanForm()
@@ -116,23 +116,23 @@ def moderation_reports_panel(request):
             active_settings['current_page'] = form.active_p.data or 1
             closed_settings['current_page'] = form.closed_p.data or 1
             filters = [
-               getattr(ReportBase,key)==val
+               getattr(Report,key)==val
                for key,val in filters.viewitems()]
 
-    all_active = ReportBase.query.filter(
-        ReportBase.resolved==None).filter(
+    all_active = Report.query.filter(
+        Report.resolved==None).filter(
         *filters)
-    all_closed = ReportBase.query.filter(
-        ReportBase.resolved!=None).filter(
+    all_closed = Report.query.filter(
+        Report.resolved!=None).filter(
         *filters)
 
     # report_list and closed_report_list are the two lists of up to 10
     # items which are actually passed to the user in this request
     report_list = all_active.order_by(
-        ReportBase.created.desc()).offset(
+        Report.created.desc()).offset(
             (active_settings['current_page']-1)*10).limit(10)
     closed_report_list = all_closed.order_by(
-        ReportBase.created.desc()).offset(
+        Report.created.desc()).offset(
             (closed_settings['current_page']-1)*10).limit(10)
 
     active_settings['last_page'] = int(ceil(all_active.count()/10.))
@@ -155,7 +155,7 @@ def moderation_reports_detail(request):
     erator would go to to take an action to resolve a report.
     """
     form = moderation_forms.ReportResolutionForm(request.form)
-    report = ReportBase.query.get(request.matchdict['report_id'])
+    report = Report.query.get(request.matchdict['report_id'])
 
     form.take_away_privileges.choices = [
         (s.privilege_name,s.privilege_name.title()) \
index ea468f8e583109ebf80e89e65a3066ede24ac922..8690aae5a71134671829a16f121ca30d065d1e83 100644 (file)
@@ -16,8 +16,8 @@
 
 import logging
 
-from mediagoblin.db.models import Notification, \
-        CommentNotification, CommentSubscription, User
+from mediagoblin.db.models import Notification, CommentSubscription, User, \
+                                  Comment, GenericModelReference
 from mediagoblin.notifications.task import email_notification_task
 from mediagoblin.notifications.tools import generate_comment_message
 
@@ -37,10 +37,10 @@ def trigger_notification(comment, media_entry, request):
         if comment.get_actor == subscription.user:
             continue
 
-        cn = CommentNotification(
+        cn = Notification(
             user_id=subscription.user_id,
-            subject_id=comment.id)
-
+        )
+        cn.obj = comment
         cn.save()
 
         if subscription.send_email:
@@ -61,9 +61,15 @@ def mark_notification_seen(notification):
 
 
 def mark_comment_notification_seen(comment_id, user):
-    notification = CommentNotification.query.filter_by(
+    comment = Comment.query.get(comment_id).comment()
+    comment_gmr = GenericModelReference.query.filter_by(
+        obj_pk=comment.id,
+        model_type=comment.__tablename__
+    ).first()
+    notification = Notification.query.filter_by(
         user_id=user.id,
-        subject_id=comment_id).first()
+        object_id=comment_gmr.id
+    ).first()
 
     _log.debug(u'Marking {0} as seen.'.format(notification))
 
index d915212afacc1a732fcd183fbb4ae7f1c25ff14a..652b78e2c590491522640cdfea3b6ef39bca84c9 100644 (file)
@@ -20,7 +20,7 @@ from celery import registry
 from celery.task import Task
 
 from mediagoblin.tools.mail import send_email
-from mediagoblin.db.models import CommentNotification
+from mediagoblin.db.models import Notification
 
 
 _log = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ class EmailNotificationTask(Task):
     the web server.
     '''
     def run(self, notification_id, message):
-        cn = CommentNotification.query.filter_by(id=notification_id).first()
+        cn = Notification.query.filter_by(id=notification_id).first()
         _log.info(u'Sending notification email about {0}'.format(cn))
 
         return send_email(
index cfe66b2e7d5c88527859dee90c92849f23e02492..984b9c9bf547118ce7e3893431f460cc70333ef2 100644 (file)
@@ -57,7 +57,10 @@ def mark_all_comment_notifications_seen(request):
     Marks all comment notifications seen.
     """
     for comment in get_notifications(request.user.id):
-        mark_comment_notification_seen(comment.subject_id, request.user)
+        mark_comment_notification_seen(
+            comment.obj().get_comment_link().id,
+            request.user
+        )
 
     if request.GET.get('next'):
         return redirect(request, location=request.GET.get('next'))
index eee5653fb1c6132c940d64fa6425c171c96ebc1d..2edea70f193242a2d36f86cd56a02e6984355bcf 100644 (file)
@@ -281,6 +281,9 @@ def api_upload_request(request, file_data, entry):
     # This will be set later but currently we just don't have enough information
     entry.slug = None
 
+    # This is a MUST.
+    entry.get_public_id(request.urlgen)
+
     queue_file = prepare_queue_task(request.app, entry, file_data.filename)
     with queue_file:
         queue_file.write(request.data)
index 68c42bf4bdac4e272b1b34c47d902f11e7718bab..99c5abbab657dabeb637a08772fdc73c5d72536f 100644 (file)
@@ -4,9 +4,9 @@
     <h3>{% trans %}New comments{% endtrans %}</h3>
     <ul>
         {% for notification in  notifications %}
-        {% set comment = notification.subject %}
+        {% set comment = notification.obj() %}
         {% set comment_author = comment.get_actor %}
-        {% set media = comment.get_entry %}
+        {% set media = comment.get_reply_to() %}
         <li class="comment_wrapper">
             <div class="comment_author">
                 <img src="{{ request.staticdirect('/images/icon_comment.png') }}" />
index 584dca998691e0af7eb62a8bda0c5171c7396ccb..abbd4a0cf4b4a9b96a0eb421657d2d8f52fa9060 100644 (file)
       {% trans %}Return to Reports Panel{% endtrans %}</a>
   </div>
   <h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2>
-  {% if report.is_comment_report() and report.comment %}
+  {% if report.is_comment_report() and report.object_id %}
 
     {% trans %}Reported comment{% endtrans %}:
-    {% set comment = report.comment %}
+    {% set comment = report.obj() %}
+    {% set target = report.obj().get_reply_to() %}
     {% set reported_user = comment.get_actor %}
     <div id="comment-{{ comment.id }}"
         class="comment_wrapper">
@@ -50,8 +51,8 @@
         <a href="{{ request.urlgen(
                         'mediagoblin.user_pages.media_home.view_comment',
                         comment=comment.id,
-                        user=comment.get_media_entry.get_actor.username,
-                        media=comment.get_media_entry.slug_or_id) }}#comment"
+                        user=target.get_actor.username,
+                        media=target.slug_or_id) }}#comment"
            class="comment_whenlink">
           <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
             {%- trans formatted_time=timesince(comment.created) -%}
@@ -65,9 +66,9 @@
         {% endautoescape %}
       </div>
     </div>
-  {% elif report.is_media_entry_report() and report.media_entry %}
+  {% elif report.is_media_entry_report() and report.object_id %}
 
-    {% set media_entry = report.media_entry %}
+    {% set media_entry = report.obj() %}
     <div class="three columns media_thumbnail">
       <a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
                             user=media_entry.get_actor.username,
index 39ca90f560b441348665a1dc86f7cff25ab2ee18..c82cd41260749e0b04c6741ebbc9d03261dc85e6 100644 (file)
@@ -81,7 +81,7 @@ curr_page !=p %}
     </tr>
     {% for report in report_list %}
       <tr>
-        {% if report.discriminator == "comment_report" %}
+        {% if report.is_comment_report %}
           <td>
             <img
               src="{{ request.staticdirect(
@@ -97,7 +97,7 @@ curr_page !=p %}
               {% endtrans %}
             </a>
           </td>
-        {% elif report.discriminator == "media_report" %}
+        {% elif report.is_media_entry_report %}
           <td>
             <img
               src="{{ request.staticdirect(
index 594f845d1b9a218073100eec01359cab1cc04665..1e48bf8439582e48c4fd4b2ac95c5200f30f1937 100644 (file)
                 </a>
               </td>
               <td>
-                {% if report.discriminator == "comment_report" %}
+                {% if report.is_comment_report() %}
                   <a>{%- trans %}Reported Comment{% endtrans -%}</a>
-                {% elif report.discriminator == "media_report" %}
+                {% elif report.is_media_entry_report() %}
                   <a>{%- trans %}Reported Media Entry{% endtrans -%}</a>
                 {% endif %}
               </td>
index b74ca13e7e6e6bfc623131c5b8e4648f3fb99381..1a35414fd289a119bdbe58165115c44a53baaf2f 100644 (file)
@@ -93,7 +93,7 @@
       <p>{{ media.description_html }}</p>
     {% endautoescape %}
     </div>
-    {% if comments and request.user and request.user.has_privilege('commenter') %}
+    {% if request.user and request.user.has_privilege('commenter') %}
     <div class="media_comments">
       {% if app_config['allow_comments'] %}
         <a
index b58e112f5857d5c7ac2289a11117de63f9fa468e..205b8a696101e5973257a1721b2a5e430269f9ac 100644 (file)
@@ -25,7 +25,7 @@ from webtest import AppError
 
 from .resources import GOOD_JPG
 from mediagoblin import mg_globals
-from mediagoblin.db.models import User, MediaEntry, MediaComment
+from mediagoblin.db.models import User, MediaEntry, TextComment
 from mediagoblin.tools.routing import extract_url_arguments
 from mediagoblin.tests.tools import fixture_add_user
 from mediagoblin.moderation.tools import take_away_privileges
@@ -188,8 +188,7 @@ class TestAPI(object):
         # Lets change the image uploader to be self.other_user, this is easier
         # than uploading the image as someone else as the way self.mocked_oauth_required
         # and self._upload_image.
-        id = int(data["object"]["id"].split("/")[-2])
-        media = MediaEntry.query.filter_by(id=id).first()
+        media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
         media.actor = self.other_user.id
         media.save()
 
@@ -232,14 +231,13 @@ class TestAPI(object):
         image = json.loads(response.body.decode())["object"]
 
         # Check everything has been set on the media correctly
-        id = int(image["id"].split("/")[-2])
-        media = MediaEntry.query.filter_by(id=id).first()
+        media = MediaEntry.query.filter_by(public_id=image["id"]).first()
         assert media.title == title
         assert media.description == description
         assert media.license == license
 
         # Check we're being given back everything we should on an update
-        assert int(image["id"].split("/")[-2]) == media.id
+        assert image["id"] == media.public_id
         assert image["displayName"] == title
         assert image["content"] == description
         assert image["license"] == license
@@ -288,8 +286,7 @@ class TestAPI(object):
             request = test_app.get(object_uri)
 
         image = json.loads(request.body.decode())
-        entry_id = int(image["id"].split("/")[-2])
-        entry = MediaEntry.query.filter_by(id=entry_id).first()
+        entry = MediaEntry.query.filter_by(public_id=image["id"]).first()
 
         assert request.status_code == 200
 
@@ -319,8 +316,7 @@ class TestAPI(object):
         assert response.status_code == 200
 
         # Find the objects in the database
-        media_id = int(data["object"]["id"].split("/")[-2])
-        media = MediaEntry.query.filter_by(id=media_id).first()
+        media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
         comment = media.get_comments()[0]
 
         # Tests that it matches in the database
@@ -382,8 +378,7 @@ class TestAPI(object):
         response, comment_data = self._activity_to_feed(test_app, activity)
 
         # change who uploaded the comment as it's easier than changing
-        comment_id = int(comment_data["object"]["id"].split("/")[-2])
-        comment = MediaComment.query.filter_by(id=comment_id).first()
+        comment = TextComment.query.filter_by(public_id=comment_data["object"]["id"]).first()
         comment.actor = self.other_user.id
         comment.save()
 
@@ -510,8 +505,7 @@ class TestAPI(object):
         response = self._activity_to_feed(test_app, activity)[1]
 
         # Check the media is no longer in the database
-        media_id = int(object_id.split("/")[-2])
-        media = MediaEntry.query.filter_by(id=media_id).first()
+        media = MediaEntry.query.filter_by(public_id=object_id).first()
 
         assert media is None
 
@@ -552,8 +546,8 @@ class TestAPI(object):
         delete = self._activity_to_feed(test_app, activity)[1]
 
         # Verify the comment no longer exists
-        comment_id = int(comment["object"]["id"].split("/")[-2])
-        assert MediaComment.query.filter_by(id=comment_id).first() is None
+        assert TextComment.query.filter_by(public_id=comment["object"]["id"]).first() is None
+        comment_id = comment["object"]["id"]
 
         # Check we've got a delete activity back
         assert "id" in delete
@@ -593,7 +587,6 @@ class TestAPI(object):
         comment = self._activity_to_feed(test_app, activity)[1]
 
         # Verify the comment reflects the changes
-        comment_id = int(comment["object"]["id"].split("/")[-2])
-        model = MediaComment.query.filter_by(id=comment_id).first()
+        model = TextComment.query.filter_by(public_id=comment["object"]["id"]).first()
 
         assert model.content == activity["object"]["content"]
index 2bff00578dd7c703e1f1ddc81caee3764cfa3f6f..558a9bd76f75f949a6c03bc0f6aa90a69a6b4c36 100644 (file)
@@ -24,7 +24,7 @@ from mediagoblin.db.base import Session
 from mediagoblin.media_types import sniff_media
 from mediagoblin.submit.lib import new_upload_entry
 from mediagoblin.submit.task import collect_garbage
-from mediagoblin.db.models import User, MediaEntry, MediaComment
+from mediagoblin.db.models import User, MediaEntry, TextComment, Comment
 from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
 
 
@@ -46,25 +46,31 @@ def test_user_deletes_other_comments(test_app):
     Session.flush()
 
     # Create all 4 possible comments:
-    for u_id in (user_a.id, user_b.id):
-        for m_id in (media_a.id, media_b.id):
-            cmt = MediaComment()
-            cmt.media_entry = m_id
-            cmt.actor = u_id
+    for u in (user_a, user_b):
+        for m in (media_a, media_b):
+            cmt = TextComment()
+            cmt.actor = u.id
             cmt.content = u"Some Comment"
             Session.add(cmt)
+            # think i need this to get the command ID
+            Session.flush()
+
+            link = Comment()
+            link.target = m
+            link.comment = cmt
+            Session.add(link)
 
     Session.flush()
 
     usr_cnt1 = User.query.count()
     med_cnt1 = MediaEntry.query.count()
-    cmt_cnt1 = MediaComment.query.count()
+    cmt_cnt1 = TextComment.query.count()
 
     User.query.get(user_a.id).delete(commit=False)
 
     usr_cnt2 = User.query.count()
     med_cnt2 = MediaEntry.query.count()
-    cmt_cnt2 = MediaComment.query.count()
+    cmt_cnt2 = TextComment.query.count()
 
     # One user deleted
     assert usr_cnt2 == usr_cnt1 - 1
@@ -77,7 +83,7 @@ def test_user_deletes_other_comments(test_app):
 
     usr_cnt2 = User.query.count()
     med_cnt2 = MediaEntry.query.count()
-    cmt_cnt2 = MediaComment.query.count()
+    cmt_cnt2 = TextComment.query.count()
 
     # All users gone
     assert usr_cnt2 == usr_cnt1 - 2
index 85c130cac6082277572400fcb3f1455265824b85..55bb4c4bbcfec0267f9b83d20cfd36526324c56a 100644 (file)
@@ -18,7 +18,8 @@ import pytest
 
 from mediagoblin.tests.tools import (fixture_add_user,
             fixture_add_comment_report, fixture_add_comment)
-from mediagoblin.db.models import User, LocalUser, CommentReport, MediaComment, UserBan
+from mediagoblin.db.models import User, LocalUser, Report, TextComment, \
+                                  UserBan, GenericModelReference
 from mediagoblin.tools import template, mail
 from webtest import AppError
 
@@ -102,15 +103,15 @@ class TestModerationViews:
         # to a reported comment
         #----------------------------------------------------------------------
         fixture_add_comment_report(reported_user=self.user)
-        comment_report = CommentReport.query.filter(
-            CommentReport.reported_user==self.user).first()
+        comment_report = Report.query.filter(
+            Report.reported_user==self.user).first()
 
         response = self.test_app.get('/mod/reports/{0}/'.format(
             comment_report.id))
         assert response.status == '200 OK'
         self.query_for_users()
-        comment_report = CommentReport.query.filter(
-            CommentReport.reported_user==self.user).first()
+        comment_report = Report.query.filter(
+            Report.reported_user==self.user).first()
 
         response, context = self.do_post({'action_to_resolve':[u'takeaway'],
             'take_away_privileges':[u'commenter'],
@@ -118,15 +119,15 @@ class TestModerationViews:
             url='/mod/reports/{0}/'.format(comment_report.id))
 
         self.query_for_users()
-        comment_report = CommentReport.query.filter(
-            CommentReport.reported_user==self.user).first()
+        comment_report = Report.query.filter(
+            Report.reported_user==self.user).first()
         assert response.status == '302 FOUND'
         assert not self.user.has_privilege(u'commenter')
         assert comment_report.is_archived_report() is True
 
         fixture_add_comment_report(reported_user=self.user)
-        comment_report = CommentReport.query.filter(
-            CommentReport.reported_user==self.user).first()
+        comment_report = Report.query.filter(
+            Report.reported_user==self.user).first()
 
         # Then, test a moderator sending an email to a user in response to a
         # reported comment
@@ -139,8 +140,8 @@ class TestModerationViews:
             url='/mod/reports/{0}/'.format(comment_report.id))
 
         self.query_for_users()
-        comment_report = CommentReport.query.filter(
-            CommentReport.reported_user==self.user).first()
+        comment_report = Report.query.filter(
+            Report.reported_user==self.user).first()
         assert response.status == '302 FOUND'
         assert mail.EMAIL_TEST_MBOX_INBOX ==  [{'to': [u'regular@example.com'],
             'message': 'Content-Type: text/plain; charset="utf-8"\n\
@@ -157,13 +158,17 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
         self.query_for_users()
         fixture_add_comment(author=self.user.id,
             comment=u'Comment will be removed')
-        test_comment = MediaComment.query.filter(
-            MediaComment.actor==self.user.id).first()
+        test_comment = TextComment.query.filter(
+            TextComment.actor==self.user.id).first()
         fixture_add_comment_report(comment=test_comment,
             reported_user=self.user)
-        comment_report = CommentReport.query.filter(
-            CommentReport.comment==test_comment).filter(
-            CommentReport.resolved==None).first()
+        comment_gmr = GenericModelReference.query.filter_by(
+            obj_pk=test_comment.id,
+            model_type=test_comment.__tablename__
+        ).first()
+        comment_report = Report.query.filter(
+            Report.object_id==comment_gmr.id).filter(
+            Report.resolved==None).first()
 
         response, context = self.do_post(
             {'action_to_resolve':[u'userban', u'delete'],
@@ -176,17 +181,17 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
         test_user_ban = UserBan.query.filter(
             UserBan.user_id == self.user.id).first()
         assert test_user_ban is not None
-        test_comment = MediaComment.query.filter(
-            MediaComment.actor==self.user.id).first()
+        test_comment = TextComment.query.filter(
+            TextComment.actor==self.user.id).first()
         assert test_comment is None
 
         # Then, test what happens when a moderator attempts to punish an admin
         # from a reported comment on an admin.
         #----------------------------------------------------------------------
         fixture_add_comment_report(reported_user=self.admin_user)
-        comment_report = CommentReport.query.filter(
-            CommentReport.reported_user==self.admin_user).filter(
-            CommentReport.resolved==None).first()
+        comment_report = Report.query.filter(
+            Report.reported_user==self.admin_user).filter(
+            Report.resolved==None).first()
 
         response, context = self.do_post({'action_to_resolve':[u'takeaway'],
             'take_away_privileges':[u'active'],
index f7e67d80a4da337045120a0027836dc17adea0f7..a1dc60edc80297a8e0b685c3f3678f68ebdc1baa 100644 (file)
@@ -20,8 +20,7 @@ import six.moves.urllib.parse as urlparse
 
 from mediagoblin.tools import template, mail
 
-from mediagoblin.db.models import Notification, CommentNotification, \
-        CommentSubscription
+from mediagoblin.db.models import Notification, CommentSubscription
 from mediagoblin.db.base import Session
 
 from mediagoblin.notifications import mark_comment_notification_seen
@@ -109,11 +108,10 @@ class TestNotifications:
 
         notification = notifications[0]
 
-        assert type(notification) == CommentNotification
         assert notification.seen == False
         assert notification.user_id == user.id
-        assert notification.subject.get_actor.id == self.test_user.id
-        assert notification.subject.content == u'Test comment #42'
+        assert notification.obj().get_actor.id == self.test_user.id
+        assert notification.obj().content == u'Test comment #42'
 
         if wants_email == True:
             assert mail.EMAIL_TEST_MBOX_INBOX == [
@@ -130,7 +128,7 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
 
         # Save the ids temporarily because of DetachedInstanceError
         notification_id = notification.id
-        comment_id = notification.subject.id
+        comment_id = notification.obj().get_comment_link().id
 
         self.logout()
         self.login('otherperson', 'nosreprehto')
index f036303a3afbb97ece7a92cbb031ad4425c39983..803fc84914f63203f09db57d71d5d76c57a5d363 100644 (file)
@@ -20,8 +20,7 @@ import six
 from mediagoblin.tools import template
 from mediagoblin.tests.tools import (fixture_add_user, fixture_media_entry,
         fixture_add_comment, fixture_add_comment_report)
-from mediagoblin.db.models import (MediaReport, CommentReport, User, LocalUser,
-    MediaComment)
+from mediagoblin.db.models import Report, User, LocalUser, TextComment
 
 
 class TestReportFiling:
@@ -80,7 +79,7 @@ class TestReportFiling:
 
         assert response.status == "302 FOUND"
 
-        media_report = MediaReport.query.first()
+        media_report = Report.query.first()
 
         allie_user, natalie_user = self.query_for_users()
         assert media_report is not None
@@ -88,7 +87,6 @@ class TestReportFiling:
         assert media_report.reporter_id == allie_id
         assert media_report.reported_user_id == natalie_user.id
         assert media_report.created is not None
-        assert media_report.discriminator == 'media_report'
 
     def testCommentReports(self):
         self.login(u'allie')
@@ -98,9 +96,11 @@ class TestReportFiling:
         media_entry = fixture_media_entry(uploader=natalie_user.id,
             state=u'processed')
         mid = media_entry.id
-        fixture_add_comment(media_entry=mid,
-            author=natalie_user.id)
-        comment = MediaComment.query.first()
+        fixture_add_comment(
+            media_entry=media_entry,
+            author=natalie_user.id
+        )
+        comment = TextComment.query.first()
 
         comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username,
                                                 media_entry.slug,
@@ -115,7 +115,7 @@ class TestReportFiling:
 
         assert response.status == "302 FOUND"
 
-        comment_report = CommentReport.query.first()
+        comment_report = Report.query.first()
 
         allie_user, natalie_user = self.query_for_users()
         assert comment_report is not None
@@ -123,7 +123,6 @@ class TestReportFiling:
         assert comment_report.reporter_id == allie_id
         assert comment_report.reported_user_id == natalie_user.id
         assert comment_report.created is not None
-        assert comment_report.discriminator == 'comment_report'
 
     def testArchivingReports(self):
         self.login(u'natalie')
@@ -132,14 +131,14 @@ class TestReportFiling:
 
         fixture_add_comment(author=allie_user.id,
             comment=u'Comment will be removed')
-        test_comment = MediaComment.query.filter(
-            MediaComment.actor==allie_user.id).first()
+        test_comment = TextComment.query.filter(
+            TextComment.actor==allie_user.id).first()
         fixture_add_comment_report(comment=test_comment,
             reported_user=allie_user,
             report_content=u'Testing Archived Reports #1',
             reporter=natalie_user)
-        comment_report = CommentReport.query.filter(
-            CommentReport.reported_user==allie_user).first()
+        comment_report = Report.query.filter(
+            Report.reported_user==allie_user).first()
 
         assert comment_report.report_content == u'Testing Archived Reports #1'
         response, context = self.do_post(
@@ -151,10 +150,10 @@ class TestReportFiling:
         assert response.status == "302 FOUND"
         allie_user, natalie_user = self.query_for_users()
 
-        archived_report = CommentReport.query.filter(
-            CommentReport.reported_user==allie_user).first()
+        archived_report = Report.query.filter(
+            Report.reported_user==allie_user).first()
 
-        assert CommentReport.query.count() != 0
+        assert Report.query.count() != 0
         assert archived_report is not None
         assert archived_report.report_content == u'Testing Archived Reports #1'
         assert archived_report.reporter_id == natalie_id
@@ -164,4 +163,3 @@ class TestReportFiling:
         assert archived_report.result == u'''This is a test of archiving reports.
 natalie banned user allie indefinitely.
 natalie deleted the comment.'''
-        assert archived_report.discriminator == 'comment_report'
index c0c3d3cfb66e8e4bad72faf8bbc85c28cd8bc045..f9031d379ef8b4b2bb20495a589dc094d689cd54 100644 (file)
@@ -99,8 +99,14 @@ class TestSubmission:
         return {'upload_files': [('file', filename)]}
 
     def check_comments(self, request, media_id, count):
-        comments = request.db.MediaComment.query.filter_by(media_entry=media_id)
-        assert count == len(list(comments))
+        gmr = request.db.GenericModelReference.query.filter_by(
+            obj_pk=media_id,
+            model_type=request.db.MediaEntry.__tablename__
+        ).first()
+        if gmr is None and count <= 0:
+            return # Yerp it's fine.
+        comments = request.db.Comment.query.filter_by(target_id=gmr.id)
+        assert count == comments.count()
 
     def test_missing_fields(self):
         # Test blank form
index ba143acdccb9a822b3f0a10af0f55dc30d944f97..77a9a86c07dbaca155c5832741dd76e93ed962ec 100644 (file)
@@ -25,9 +25,9 @@ from paste.deploy import loadapp
 from webtest import TestApp
 
 from mediagoblin import mg_globals
-from mediagoblin.db.models import User, LocalUser, MediaEntry, Collection, MediaComment, \
-    CommentSubscription, CommentNotification, Privilege, CommentReport, Client, \
-    RequestToken, AccessToken, Activity, Generator
+from mediagoblin.db.models import User, LocalUser, MediaEntry, Collection, TextComment, \
+    CommentSubscription, Notification, Privilege, Report, Client, \
+    RequestToken, AccessToken, Activity, Generator, Comment
 from mediagoblin.tools import testing
 from mediagoblin.init.config import read_mediagoblin_config
 from mediagoblin.db.base import Session
@@ -222,14 +222,16 @@ def fixture_comment_subscription(entry, notify=True, send_email=None):
     return cs
 
 
-def fixture_add_comment_notification(entry_id, subject_id, user_id,
+def fixture_add_comment_notification(entry, subject, user,
                                      seen=False):
-    cn = CommentNotification(user_id=user_id,
-                             seen=seen,
-                             subject_id=subject_id)
+    cn = Notification(
+        user_id=user,
+        seen=seen,
+    )
+    cn.obj = subject
     cn.save()
 
-    cn = CommentNotification.query.filter_by(id=cn.id).first()
+    cn = Notification.query.filter_by(id=cn.id).first()
 
     Session.expunge(cn)
 
@@ -309,22 +311,27 @@ def fixture_add_comment(author=None, media_entry=None, comment=None):
         author = fixture_add_user().id
 
     if media_entry is None:
-        media_entry = fixture_media_entry().id
+        media_entry = fixture_media_entry()
 
     if comment is None:
         comment = \
             'Auto-generated test comment by user #{0} on media #{0}'.format(
                 author, media_entry)
 
-    comment = MediaComment(actor=author,
-                      media_entry=media_entry,
-                      content=comment)
+    text_comment = TextComment(
+        actor=author,
+        content=comment
+    )
+    text_comment.save()
 
-    comment.save()
+    comment_link = Comment()
+    comment_link.target = media_entry
+    comment_link.comment = text_comment
+    comment_link.save()
 
-    Session.expunge(comment)
+    Session.expunge(comment_link)
 
-    return comment
+    return text_comment
 
 def fixture_add_comment_report(comment=None, reported_user=None,
         reporter=None, created=None, report_content=None):
@@ -344,12 +351,13 @@ def fixture_add_comment_report(comment=None, reported_user=None,
         report_content = \
             'Auto-generated test report'
 
-    comment_report = CommentReport(comment=comment,
-        reported_user = reported_user,
-        reporter = reporter,
-        created = created,
-        report_content=report_content)
-
+    comment_report = Report()
+    comment_report.obj = comment
+    comment_report.reported_user = reported_user
+    comment_report.reporter = reporter
+    comment_report.created = created
+    comment_report.report_content = report_content
+    comment_report.obj = comment
     comment_report.save()
 
     Session.expunge(comment_report)
index fc1b6a7e03e44042b1911b4e98273b5861848c13..b6741001bd9847f57b2ad5356d4b6dfc4b82369c 100644 (file)
@@ -16,8 +16,8 @@
 
 from mediagoblin import mg_globals
 from mediagoblin.db.base import Session
-from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport,
-                                   MediaComment, MediaEntry)
+from mediagoblin.db.models import CollectionItem, Report, TextComment, \
+                                  MediaEntry
 from mediagoblin.tools.mail import send_email
 from mediagoblin.tools.pluginapi import hook_runall
 from mediagoblin.tools.template import render_template
@@ -82,34 +82,27 @@ def add_media_to_collection(collection, media, note=None, commit=True):
 def build_report_object(report_form, media_entry=None, comment=None):
     """
     This function is used to convert a form object (from a User filing a
-        report) into either a MediaReport or CommentReport object.
+        report) into a Report.
 
     :param report_form          A MediaReportForm or a CommentReportForm object
                                   with valid information from a POST request.
     :param media_entry          A MediaEntry object. The MediaEntry being repo-
-                                  -rted by a MediaReport. In a CommentReport,
-                                  this will be None.
-    :param comment              A MediaComment object. The MediaComment being
-                                  reported by a CommentReport. In a MediaReport
-                                  this will be None.
-
-    :returns                A MediaReport object if a valid MediaReportForm is
-                              passed as kwarg media_entry. This MediaReport has
+                                  -rted by a Report.
+    :param comment              A Comment object. The Comment being
+                                  reported by a Report.
+
+    :returns                A Report object if a valid MediaReportForm is
+                              passed as kwarg media_entry. This Report has
                               not been saved.
-    :returns                A CommentReport object if a valid CommentReportForm
-                              is passed as kwarg comment. This CommentReport
-                              has not been saved.
     :returns                None if the form_dict is invalid.
     """
-
+    report_object = Report()
     if report_form.validate() and comment is not None:
-        report_object = CommentReport()
-        report_object.comment_id = comment.id
-        report_object.reported_user_id = MediaComment.query.get(
+        report_object.obj = comment.comment()
+        report_object.reported_user_id = TextComment.query.get(
             comment.id).get_actor.id
     elif report_form.validate() and media_entry is not None:
-        report_object = MediaReport()
-        report_object.media_entry_id = media_entry.id
+        report_object.obj = media_entry
         report_object.reported_user_id = MediaEntry.query.get(
             media_entry.id).get_actor.id
     else:
index f1c8a622d4b23d554f935f0268edd387aa730edd..ba94ec1626ee57e2e2ed2efad4c536d63610240c 100644 (file)
@@ -21,8 +21,9 @@ import json
 import six
 
 from mediagoblin import messages, mg_globals
-from mediagoblin.db.models import (MediaEntry, MediaTag, Collection,
-                                   CollectionItem, LocalUser, Activity)
+from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, Comment,
+                                   CollectionItem, LocalUser, Activity, \
+                                   GenericModelReference)
 from mediagoblin.tools.response import render_to_response, render_404, \
     redirect, redirect_obj
 from mediagoblin.tools.text import cleaned_markdown_conversion
@@ -178,8 +179,7 @@ def media_post_comment(request, media):
     if not request.method == 'POST':
         raise MethodNotAllowed()
 
-    comment = request.db.MediaComment()
-    comment.media_entry = media.id
+    comment = request.db.TextComment()
     comment.actor = request.user.id
     comment.content = six.text_type(request.form['comment_content'])
 
@@ -199,6 +199,11 @@ def media_post_comment(request, media):
         add_comment_subscription(request.user, media)
         comment.save()
 
+        link = request.db.Comment()
+        link.target = media
+        link.comment = comment
+        link.save()
+
         messages.add_message(
             request, messages.SUCCESS,
             _('Your comment has been posted!'))
@@ -682,15 +687,15 @@ def processing_panel(request):
 @get_optional_media_comment_by_id
 def file_a_report(request, media, comment):
     """
-    This view handles the filing of a MediaReport or a CommentReport.
+    This view handles the filing of a Report.
     """
     if comment is not None:
-        if not comment.get_media_entry.id == media.id:
+        if not comment.target().id == media.id:
             return render_404(request)
 
         form = user_forms.CommentReportForm(request.form)
-        context = {'media': media,
-                   'comment':comment,
+        context = {'media': comment.target(),
+                   'comment':comment.comment(),
                    'form':form}
     else:
         form = user_forms.MediaReportForm(request.form)
@@ -700,9 +705,11 @@ def file_a_report(request, media, comment):
 
 
     if request.method == "POST":
-        report_object = build_report_object(form,
+        report_object = build_report_object(
+            form,
             media_entry=media,
-            comment=comment)
+            comment=comment
+        )
 
         # if the object was built successfully, report_table will not be None
         if report_object: