Fix migrations and refactor object_type
authorJessica Tallon <jessica@megworld.co.uk>
Fri, 29 Aug 2014 12:49:48 +0000 (13:49 +0100)
committerJessica Tallon <jessica@megworld.co.uk>
Tue, 2 Sep 2014 15:38:52 +0000 (16:38 +0100)
- Make changes to objectType to be more pythonic "object_type"
- Move object_type to mixins rather than be on the models
- Convert migrations to sqlalchemy core rather than ORM (fix)
- Change TYPES to use descriptive strings rather than numbers

mediagoblin/db/migrations.py
mediagoblin/db/mixin.py
mediagoblin/db/models.py
mediagoblin/federation/routing.py
mediagoblin/federation/views.py

index f467253f030fc835d29d9835a1b9b3afd0229388..aab01c12bdf9c174ec3f989ba0daa676365cc582 100644 (file)
@@ -905,15 +905,21 @@ class Activity_R0(declarative_base()):
     published = Column(DateTime, nullable=False, default=datetime.datetime.now)
     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
     verb = Column(Unicode, nullable=False)
-    content = Column(Unicode, nullable=False)
+    content = Column(Unicode, nullable=True)
     title = Column(Unicode, nullable=True)
     target = Column(Integer, ForeignKey(User.id), nullable=True)
     generator = Column(Integer, ForeignKey(Generator.id), nullable=True)
+    object = Column(Integer,
+                    ForeignKey("core__activity_intermediators.id"),
+                    nullable=False)
+    target = Column(Integer,
+                    ForeignKey("core__activity_intermediators.id"),
+                    nullable=True)
 
 class ActivityIntermediator_R0(declarative_base()):
-    __tablename__ = "core__acitivity_intermediators"
+    __tablename__ = "core__activity_intermediators"
     id = Column(Integer, primary_key=True)
-    type = Column(Integer, nullable=False)
+    type = Column(Unicode, nullable=False)
 
 @RegisterMigration(24, MIGRATIONS)
 def activity_migration(db):
@@ -925,32 +931,40 @@ def activity_migration(db):
     - Retroactively adds activities for what we can acurately work out
     """
     # Set constants we'll use later
-    FOREIGN_KEY = "core__acitivity_intermediators.id"
+    FOREIGN_KEY = "core__activity_intermediators.id"
 
 
     # Create the new tables.
-    Activity_R0.__table__.create(db.bind)
-    Generator_R0.__table__.create(db.bind)
     ActivityIntermediator_R0.__table__.create(db.bind)
+    Generator_R0.__table__.create(db.bind)
+    Activity_R0.__table__.create(db.bind)
     db.commit()
 
 
     # Initiate the tables we want to use later
     metadata = MetaData(bind=db.bind)
     user_table = inspect_table(metadata, "core__users")
+    activity_table = inspect_table(metadata, "core__activities")
     generator_table = inspect_table(metadata, "core__generators")
     collection_table = inspect_table(metadata, "core__collections")
     media_entry_table = inspect_table(metadata, "core__media_entries")
     media_comments_table = inspect_table(metadata, "core__media_comments")
+    ai_table = inspect_table(metadata, "core__activity_intermediators")
 
 
     # Create the foundations for Generator
     db.execute(generator_table.insert().values(
         name="GNU Mediagoblin",
-        object_type="service"
+        object_type="service",
+        published=datetime.datetime.now(),
+        updated=datetime.datetime.now()
     ))
     db.commit()
 
+    # Get the ID of that generator
+    gmg_generator = db.execute(generator_table.select(
+        generator_table.c.name==u"GNU Mediagoblin")).first()
+
 
     # Now we want to modify the tables which MAY have an activity at some point
     as_object = Column("activity_as_object", Integer, ForeignKey(FOREIGN_KEY))
@@ -977,45 +991,93 @@ def activity_migration(db):
 
     # Now we want to retroactively add what activities we can
     # first we'll add activities when people uploaded media.
-    for media in MediaEntry.query.all():
-        activity = Activity_R0(
-            verb="create",
-            actor=media.uploader,
-            published=media.created,
-            updated=media.created,
-            generator=gmg_generator.id
-        )
-        activity.generate_content()
-        activity.save(set_updated=False)
-        activity.set_object(media)
-        media.save()
+    # these can't have content as it's not fesible to get the
+    # correct content strings.
+    for media in db.execute(media_entry_table.select()):
+        # Now we want to create the intermedaitory
+        db_ai = db.execute(ai_table.insert().values(
+            type="media",
+        ))
+        db_ai = db.execute(ai_table.select(
+            ai_table.c.id==db_ai.inserted_primary_key[0]
+        )).first()
+
+        # Add the activity
+        activity = {
+            "verb": "create",
+            "actor": media.uploader,
+            "published": media.created,
+            "updated": media.created,
+            "generator": gmg_generator.id,
+            "object": db_ai.id
+        }
+        db.execute(activity_table.insert().values(**activity))
+
+        # Add the AI to the media.
+        db.execute(media_entry_table.update().values(
+            activity_as_object=db_ai.id
+        ).where(id=media.id))
 
     # Now we want to add all the comments people made
-    for comment in MediaComment.query.all():
-        activity = Activity_R0(
-            verb="comment",
-            actor=comment.author,
-            published=comment.created,
-            updated=comment.created,
-            generator=gmg_generator.id
-        )
-        activity.generate_content()
-        activity.save(set_updated=False)
-        activity.set_object(comment)
-        comment.save()
+    for comment in db.execute(media_comments_table.select()):
+        # Get the MediaEntry for the comment
+        media_entry = db.execute(
+            media_entry_table.select(id=comment.media_entry_id))
+
+        # Create an AI for target
+        db_ai_media = db.execute(ai_table.insert().values(
+            type="media"
+        ))
+        db_ai_media = db.execute(ai_table.select(
+            ai_table.c.id==db_ai_media.inserted_primary_key[0]
+        ))
+
+        db.execute(
+            media_entry_table.update().values(
+                activity_as_target=db_ai_media.id
+        ).where(id=media_entry.id))
+
+        # Now create the AI for the comment
+        db_ai_comment = db.execute(ai_table.insert().values(
+            type="comment"
+        ))
+
+        activity = {
+            "verb": "comment",
+            "actor": comment.author,
+            "published": comment.created,
+            "updated": comment.created,
+            "generator": gmg_generator.id,
+            "object": db_ai_comment.id,
+            "target": db_ai_media.id,
+        }
+
+        # Now add the comment object
+        db.execute(media_comments_table.insert().values(**activity))
 
     # Create 'create' activities for all collections
-    for collection in Collection.query.all():
-        activity = Activity_R0(
-            verb="create",
-            actor=collection.creator,
-            published=collection.created,
-            updated=collection.created,
-            generator=gmg_generator.id
-        )
-        activity.generate_content()
-        activity.save(set_updated=False)
-        activity.set_object(collection)
-        collection.save()
+    for collection in db.execute(collection_table.select()):
+        # create AI
+        db_ai = db.execute(ai_table.insert().values(
+            type="collection"
+        ))
+
+        # Now add link the collection to the AI
+        db.execute(collection_table.update().values(
+            activity_as_object=db_ai.id
+        ).where(id=collection.id))
+
+        activity = {
+            "verb": "create",
+            "actor": collection.creator,
+            "published": collection.created,
+            "updated": collection.created,
+            "generator": gmg_generator.id,
+            "object": db_ai.id,
+        }
+
+        db.execute(activity_table.insert().values(**activity))
+
+
 
     db.commit()
index bc3a3bd2eb65bed0181b1ea709daef63ef09b91d..6a733510bffd42d6907be85a350213e588a1fb23 100644 (file)
@@ -43,6 +43,8 @@ from mediagoblin.tools.translate import pass_to_ugettext as _
 
 
 class UserMixin(object):
+    object_type = "person"
+
     @property
     def bio_html(self):
         return cleaned_markdown_conversion(self.bio)
@@ -131,6 +133,11 @@ class MediaEntryMixin(GenerateSlugMixin):
 
         return check_media_slug_used(self.uploader, slug, self.id)
 
+    @property
+    def object_type(self):
+        """ Converts media_type to pump-like type - don't use internally """
+        return self.media_type.split(".")[-1]
+
     @property
     def description_html(self):
         """
@@ -298,6 +305,8 @@ class MediaEntryMixin(GenerateSlugMixin):
 
 
 class MediaCommentMixin(object):
+    object_type = "comment"
+
     @property
     def content_html(self):
         """
@@ -322,6 +331,8 @@ class MediaCommentMixin(object):
 
 
 class CollectionMixin(GenerateSlugMixin):
+    object_type = "collection"
+
     def check_slug_used(self, slug):
         # import this here due to a cyclic import issue
         # (db.models -> db.mixin -> db.util -> db.models)
@@ -366,6 +377,7 @@ class CollectionItemMixin(object):
         return cleaned_markdown_conversion(self.note)
 
 class ActivityMixin(object):
+    object_type = "activity"
 
     VALID_VERBS = ["add", "author", "create", "delete", "dislike", "favorite",
                    "follow", "like", "post", "share", "unfavorite", "unfollow",
@@ -440,7 +452,8 @@ class ActivityMixin(object):
             "updated": self.updated.isoformat(),
             "content": self.content,
             "url": self.get_url(request),
-            "object": self.get_object().serialize(request)
+            "object": self.get_object().serialize(request),
+            "objectType": self.object_type,
         }
 
         if self.generator:
index 6004e97d383d45029a6860a26c304cb3f0ad777e..5bd40c2a6e75b6272fb9ddf021d71466324ca3cb 100644 (file)
@@ -78,15 +78,13 @@ class User(Base, UserMixin):
     upload_limit = Column(Integer)
 
     activity_as_object = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
+                                ForeignKey("core__activity_intermediators.id"))
     activity_as_target = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
+                                ForeignKey("core__activity_intermediators.id"))
 
     ## TODO
     # plugin data would be in a separate model
 
-    objectType = "person"
-
     def __repr__(self):
         return '<{0} #{1} {2} {3} "{4}">'.format(
                 self.__class__.__name__,
@@ -151,7 +149,7 @@ class User(Base, UserMixin):
             "id": "acct:{0}@{1}".format(self.username, request.host),
             "preferredUsername": self.username,
             "displayName": "{0}@{1}".format(self.username, request.host),
-            "objectType": self.objectType,
+            "objectType": self.object_type,
             "pump_io": {
                 "shared": False,
                 "followed": False,
@@ -319,9 +317,9 @@ class MediaEntry(Base, MediaEntryMixin):
         default=MutationDict())
 
     activity_as_object = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
+                                ForeignKey("core__activity_intermediators.id"))
     activity_as_target = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
+                                ForeignKey("core__activity_intermediators.id"))
 
     ## TODO
     # fail_error
@@ -444,18 +442,13 @@ class MediaEntry(Base, MediaEntryMixin):
         # pass through commit=False/True in kwargs
         super(MediaEntry, self).delete(**kwargs)
 
-    @property
-    def objectType(self):
-        """ Converts media_type to pump-like type - don't use internally """
-        return self.media_type.split(".")[-1]
-
     def serialize(self, request, show_comments=True):
         """ Unserialize MediaEntry to object """
         author = self.get_uploader
         context = {
             "id": self.id,
             "author": author.serialize(request),
-            "objectType": self.objectType,
+            "objectType": self.object_type,
             "url": self.url_for_self(request.urlgen),
             "image": {
                 "url": request.host_url + self.thumb_url[1:],
@@ -472,7 +465,7 @@ class MediaEntry(Base, MediaEntryMixin):
                 "self": {
                     "href": request.urlgen(
                         "mediagoblin.federation.object",
-                        objectType=self.objectType,
+                        object_type=self.objectType,
                         id=self.id,
                         qualified=True
                     ),
@@ -491,14 +484,15 @@ class MediaEntry(Base, MediaEntryMixin):
             context["license"] = self.license
 
         if show_comments:
-            comments = [comment.serialize(request) for comment in self.get_comments()]
+            comments = [
+                comment.serialize(request) for comment in self.get_comments()]
             total = len(comments)
             context["replies"] = {
                 "totalItems": total,
                 "items": comments,
                 "url": request.urlgen(
                         "mediagoblin.federation.object.comments",
-                        objectType=self.objectType,
+                        object_type=self.object_type,
                         id=self.id,
                         qualified=True
                         ),
@@ -620,8 +614,6 @@ class MediaTag(Base):
         creator=Tag.find_or_new
         )
 
-    objectType = "tag"
-
     def __init__(self, name=None, slug=None):
         Base.__init__(self)
         if name is not None:
@@ -668,11 +660,9 @@ class MediaComment(Base, MediaCommentMixin):
 
 
     activity_as_object = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
+                                ForeignKey("core__activity_intermediators.id"))
     activity_as_target = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
-
-    objectType = "comment"
+                                ForeignKey("core__activity_intermediators.id"))
 
     def serialize(self, request):
         """ Unserialize to python dictionary for API """
@@ -680,7 +670,7 @@ class MediaComment(Base, MediaCommentMixin):
         author = self.get_author
         context = {
             "id": self.id,
-            "objectType": self.objectType,
+            "objectType": self.object_type,
             "content": self.content,
             "inReplyTo": media.serialize(request, show_comments=False),
             "author": author.serialize(request)
@@ -739,16 +729,14 @@ class Collection(Base, CollectionMixin):
                                                cascade="all, delete-orphan"))
 
     activity_as_object = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
+                                ForeignKey("core__activity_intermediators.id"))
     activity_as_target = Column(Integer,
-                                ForeignKey("core__acitivity_intermediators.id"))
+                                ForeignKey("core__activity_intermediators.id"))
 
     __table_args__ = (
         UniqueConstraint('creator', 'slug'),
         {})
 
-    objectType = "collection"
-
     def get_collection_items(self, ascending=False):
         #TODO, is this still needed with self.collection_items being available?
         order_col = CollectionItem.position
@@ -1085,10 +1073,7 @@ class PrivilegeUserAssociation(Base):
         primary_key=True)
 
 class Generator(Base):
-    """
-    This holds the information about the software used to create
-    objects for the pump.io APIs.
-    """
+    """ Information about what created an activity """
     __tablename__ = "core__generators"
 
     id = Column(Integer, primary_key=True)
@@ -1118,16 +1103,16 @@ class ActivityIntermediator(Base):
     used multiple times for the activity object or target and also allows for
     different types of objects to be used as an Activity.
     """
-    __tablename__ = "core__acitivity_intermediators"
+    __tablename__ = "core__activity_intermediators"
 
     id = Column(Integer, primary_key=True)
-    type = Column(Integer, nullable=False)
+    type = Column(Unicode, nullable=False)
 
     TYPES = {
-        0: User,
-        1: MediaEntry,
-        2: MediaComment,
-        3: Collection,
+        "user": User,
+        "media": MediaEntry,
+        "comment": MediaComment,
+        "collection": Collection,
     }
 
     def _find_model(self, obj):
@@ -1189,18 +1174,22 @@ class Activity(Base, ActivityMixin):
 
     id = Column(Integer, primary_key=True)
     actor = Column(Integer,
-                   ForeignKey(User.id, use_alter=True, name="actor"),
+                   ForeignKey("core__users.id"),
                    nullable=False)
     published = Column(DateTime, nullable=False, default=datetime.datetime.now)
     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
     verb = Column(Unicode, nullable=False)
     content = Column(Unicode, nullable=True)
     title = Column(Unicode, nullable=True)
-    generator = Column(Integer, ForeignKey(Generator.id), nullable=True)
+    generator = Column(Integer,
+                       ForeignKey("core__generators.id"),
+                       nullable=True)
     object = Column(Integer,
-                    ForeignKey(ActivityIntermediator.id), nullable=False)
+                    ForeignKey("core__activity_intermediators.id"),
+                    nullable=False)
     target = Column(Integer,
-                    ForeignKey(ActivityIntermediator.id), nullable=True)
+                    ForeignKey("core__activity_intermediators.id"),
+                    nullable=True)
 
     get_actor = relationship(User,
         foreign_keys="Activity.actor", post_update=True)
@@ -1214,8 +1203,9 @@ class Activity(Base, ActivityMixin):
             self.object = ai.id
             return
 
-        self.object.set_object(*args, **kwargs)
-        self.object.save()
+        ai = ActivityIntermediator.query.filter_by(id=self.object).first()
+        ai.set_object(*args, **kwargs)
+        ai.save()
 
     @property
     def get_object(self):
@@ -1229,8 +1219,9 @@ class Activity(Base, ActivityMixin):
             self.object = ai.id
             return
 
-        self.target.set_object(*args, **kwargs)
-        self.targt.save()
+        ai = ActivityIntermediator.query.filter_by(id=self.target).first()
+        ai.set_object(*args, **kwargs)
+        ai.save()
 
     @property
     def get_target(self):
index 0b0fbaf17a5702dcdc7f6457ed6d68c29f2f8b39..44a04bdb30d2171404af253fc6c64119eb522779 100644 (file)
@@ -51,12 +51,12 @@ add_route(
 # object endpoints
 add_route(
     "mediagoblin.federation.object",
-    "/api/<string:objectType>/<string:id>",
+    "/api/<string:object_type>/<string:id>",
     "mediagoblin.federation.views:object_endpoint"
     )
 add_route(
     "mediagoblin.federation.object.comments",
-    "/api/<string:objectType>/<string:id>/comments",
+    "/api/<string:object_type>/<string:id>/comments",
     "mediagoblin.federation.views:object_comments"
 )
 
index 7d02d02ec800d322026d3724ef1895876e051ef7..60054b2e75bb1f625218ec196064d80ea4eeab23 100644 (file)
@@ -71,14 +71,14 @@ def profile_endpoint(request):
 def user_endpoint(request):
     """ This is /api/user/<username> - This will get the user """
     user, user_profile = get_profile(request)
-    
+
     if user is None:
         username = request.matchdict["username"]
         return json_error(
             "No such 'user' with username '{0}'".format(username),
             status=404
         )
-    
+
     return json_response({
         "nickname": user.username,
         "updated": user.created.isoformat(),
@@ -350,7 +350,7 @@ def feed_endpoint(request):
 @oauth_required
 def object_endpoint(request):
     """ Lookup for a object type """
-    object_type = request.matchdict["objectType"]
+    object_type = request.matchdict["object_type"]
     try:
         object_id = int(request.matchdict["id"])
     except ValueError:
@@ -382,17 +382,17 @@ def object_comments(request):
     media = MediaEntry.query.filter_by(id=request.matchdict["id"]).first()
     if media is None:
         return json_error("Can't find '{0}' with ID '{1}'".format(
-            request.matchdict["objectType"],
+            request.matchdict["object_type"],
             request.matchdict["id"]
         ), 404)
 
-    comments = response.serialize(request)
+    comments = media.serialize(request)
     comments = comments.get("replies", {
         "totalItems": 0,
         "items": [],
         "url": request.urlgen(
             "mediagoblin.federation.object.comments",
-            objectType=media.objectType,
+            object_type=media.object_type,
             id=media.id,
             qualified=True
         )
@@ -459,27 +459,30 @@ def whoami(request):
 @require_active_login
 def activity_view(request):
     """ /<username>/activity/<id> - Display activity
-    
+
     This should display a HTML presentation of the activity
     this is NOT an API endpoint.
     """
     # Get the user object.
     username = request.matchdict["username"]
     user = User.query.filter_by(username=username).first()
-    
+
     activity_id = request.matchdict["id"]
-    
+
     if request.user is None:
         return render_404(request)
-    
-    activity = Activity.query.filter_by(id=activity_id).first()
+
+    activity = Activity.query.filter_by(
+        id=activity_id,
+        author=user.id
+    ).first()
     if activity is None:
         return render_404(request)
-    
+
     return render_to_response(
         request,
         "mediagoblin/federation/activity.html",
         {"activity": activity}
     )
-    
-    
+
+