From: Jessica Tallon Date: Fri, 29 Aug 2014 12:49:48 +0000 (+0100) Subject: Fix migrations and refactor object_type X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=0421fc5ee8e10606426a803b51bfe4333d3ab406;p=mediagoblin.git Fix migrations and refactor object_type - 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 --- diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index f467253f..aab01c12 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -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() diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index bc3a3bd2..6a733510 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -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: diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 6004e97d..5bd40c2a 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -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): diff --git a/mediagoblin/federation/routing.py b/mediagoblin/federation/routing.py index 0b0fbaf1..44a04bdb 100644 --- a/mediagoblin/federation/routing.py +++ b/mediagoblin/federation/routing.py @@ -51,12 +51,12 @@ add_route( # object endpoints add_route( "mediagoblin.federation.object", - "/api//", + "/api//", "mediagoblin.federation.views:object_endpoint" ) add_route( "mediagoblin.federation.object.comments", - "/api///comments", + "/api///comments", "mediagoblin.federation.views:object_comments" ) diff --git a/mediagoblin/federation/views.py b/mediagoblin/federation/views.py index 7d02d02e..60054b2e 100644 --- a/mediagoblin/federation/views.py +++ b/mediagoblin/federation/views.py @@ -71,14 +71,14 @@ def profile_endpoint(request): def user_endpoint(request): """ This is /api/user/ - 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): """ //activity/ - 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} ) - - + +