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
This commit is contained in:
Jessica Tallon 2014-08-29 13:49:48 +01:00
parent 23bf7f3b02
commit 0421fc5ee8
5 changed files with 174 additions and 105 deletions

View File

@ -905,15 +905,21 @@ class Activity_R0(declarative_base()):
published = Column(DateTime, nullable=False, default=datetime.datetime.now) published = Column(DateTime, nullable=False, default=datetime.datetime.now)
updated = Column(DateTime, nullable=False, default=datetime.datetime.now) updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
verb = Column(Unicode, nullable=False) verb = Column(Unicode, nullable=False)
content = Column(Unicode, nullable=False) content = Column(Unicode, nullable=True)
title = Column(Unicode, nullable=True) title = Column(Unicode, nullable=True)
target = Column(Integer, ForeignKey(User.id), nullable=True) target = Column(Integer, ForeignKey(User.id), nullable=True)
generator = Column(Integer, ForeignKey(Generator.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()): class ActivityIntermediator_R0(declarative_base()):
__tablename__ = "core__acitivity_intermediators" __tablename__ = "core__activity_intermediators"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
type = Column(Integer, nullable=False) type = Column(Unicode, nullable=False)
@RegisterMigration(24, MIGRATIONS) @RegisterMigration(24, MIGRATIONS)
def activity_migration(db): def activity_migration(db):
@ -925,32 +931,40 @@ def activity_migration(db):
- Retroactively adds activities for what we can acurately work out - Retroactively adds activities for what we can acurately work out
""" """
# Set constants we'll use later # Set constants we'll use later
FOREIGN_KEY = "core__acitivity_intermediators.id" FOREIGN_KEY = "core__activity_intermediators.id"
# Create the new tables. # Create the new tables.
Activity_R0.__table__.create(db.bind)
Generator_R0.__table__.create(db.bind)
ActivityIntermediator_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() db.commit()
# Initiate the tables we want to use later # Initiate the tables we want to use later
metadata = MetaData(bind=db.bind) metadata = MetaData(bind=db.bind)
user_table = inspect_table(metadata, "core__users") user_table = inspect_table(metadata, "core__users")
activity_table = inspect_table(metadata, "core__activities")
generator_table = inspect_table(metadata, "core__generators") generator_table = inspect_table(metadata, "core__generators")
collection_table = inspect_table(metadata, "core__collections") collection_table = inspect_table(metadata, "core__collections")
media_entry_table = inspect_table(metadata, "core__media_entries") media_entry_table = inspect_table(metadata, "core__media_entries")
media_comments_table = inspect_table(metadata, "core__media_comments") media_comments_table = inspect_table(metadata, "core__media_comments")
ai_table = inspect_table(metadata, "core__activity_intermediators")
# Create the foundations for Generator # Create the foundations for Generator
db.execute(generator_table.insert().values( db.execute(generator_table.insert().values(
name="GNU Mediagoblin", name="GNU Mediagoblin",
object_type="service" object_type="service",
published=datetime.datetime.now(),
updated=datetime.datetime.now()
)) ))
db.commit() 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 # 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)) 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 # Now we want to retroactively add what activities we can
# first we'll add activities when people uploaded media. # first we'll add activities when people uploaded media.
for media in MediaEntry.query.all(): # these can't have content as it's not fesible to get the
activity = Activity_R0( # correct content strings.
verb="create", for media in db.execute(media_entry_table.select()):
actor=media.uploader, # Now we want to create the intermedaitory
published=media.created, db_ai = db.execute(ai_table.insert().values(
updated=media.created, type="media",
generator=gmg_generator.id ))
) db_ai = db.execute(ai_table.select(
activity.generate_content() ai_table.c.id==db_ai.inserted_primary_key[0]
activity.save(set_updated=False) )).first()
activity.set_object(media)
media.save() # 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 # Now we want to add all the comments people made
for comment in MediaComment.query.all(): for comment in db.execute(media_comments_table.select()):
activity = Activity_R0( # Get the MediaEntry for the comment
verb="comment", media_entry = db.execute(
actor=comment.author, media_entry_table.select(id=comment.media_entry_id))
published=comment.created,
updated=comment.created, # Create an AI for target
generator=gmg_generator.id db_ai_media = db.execute(ai_table.insert().values(
) type="media"
activity.generate_content() ))
activity.save(set_updated=False) db_ai_media = db.execute(ai_table.select(
activity.set_object(comment) ai_table.c.id==db_ai_media.inserted_primary_key[0]
comment.save() ))
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 # Create 'create' activities for all collections
for collection in Collection.query.all(): for collection in db.execute(collection_table.select()):
activity = Activity_R0( # create AI
verb="create", db_ai = db.execute(ai_table.insert().values(
actor=collection.creator, type="collection"
published=collection.created, ))
updated=collection.created,
generator=gmg_generator.id # Now add link the collection to the AI
) db.execute(collection_table.update().values(
activity.generate_content() activity_as_object=db_ai.id
activity.save(set_updated=False) ).where(id=collection.id))
activity.set_object(collection)
collection.save() 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() db.commit()

View File

@ -43,6 +43,8 @@ from mediagoblin.tools.translate import pass_to_ugettext as _
class UserMixin(object): class UserMixin(object):
object_type = "person"
@property @property
def bio_html(self): def bio_html(self):
return cleaned_markdown_conversion(self.bio) return cleaned_markdown_conversion(self.bio)
@ -131,6 +133,11 @@ class MediaEntryMixin(GenerateSlugMixin):
return check_media_slug_used(self.uploader, slug, self.id) 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 @property
def description_html(self): def description_html(self):
""" """
@ -298,6 +305,8 @@ class MediaEntryMixin(GenerateSlugMixin):
class MediaCommentMixin(object): class MediaCommentMixin(object):
object_type = "comment"
@property @property
def content_html(self): def content_html(self):
""" """
@ -322,6 +331,8 @@ class MediaCommentMixin(object):
class CollectionMixin(GenerateSlugMixin): class CollectionMixin(GenerateSlugMixin):
object_type = "collection"
def check_slug_used(self, slug): def check_slug_used(self, slug):
# import this here due to a cyclic import issue # import this here due to a cyclic import issue
# (db.models -> db.mixin -> db.util -> db.models) # (db.models -> db.mixin -> db.util -> db.models)
@ -366,6 +377,7 @@ class CollectionItemMixin(object):
return cleaned_markdown_conversion(self.note) return cleaned_markdown_conversion(self.note)
class ActivityMixin(object): class ActivityMixin(object):
object_type = "activity"
VALID_VERBS = ["add", "author", "create", "delete", "dislike", "favorite", VALID_VERBS = ["add", "author", "create", "delete", "dislike", "favorite",
"follow", "like", "post", "share", "unfavorite", "unfollow", "follow", "like", "post", "share", "unfavorite", "unfollow",
@ -440,7 +452,8 @@ class ActivityMixin(object):
"updated": self.updated.isoformat(), "updated": self.updated.isoformat(),
"content": self.content, "content": self.content,
"url": self.get_url(request), "url": self.get_url(request),
"object": self.get_object().serialize(request) "object": self.get_object().serialize(request),
"objectType": self.object_type,
} }
if self.generator: if self.generator:

View File

@ -78,15 +78,13 @@ class User(Base, UserMixin):
upload_limit = Column(Integer) upload_limit = Column(Integer)
activity_as_object = Column(Integer, activity_as_object = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
activity_as_target = Column(Integer, activity_as_target = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
## TODO ## TODO
# plugin data would be in a separate model # plugin data would be in a separate model
objectType = "person"
def __repr__(self): def __repr__(self):
return '<{0} #{1} {2} {3} "{4}">'.format( return '<{0} #{1} {2} {3} "{4}">'.format(
self.__class__.__name__, self.__class__.__name__,
@ -151,7 +149,7 @@ class User(Base, UserMixin):
"id": "acct:{0}@{1}".format(self.username, request.host), "id": "acct:{0}@{1}".format(self.username, request.host),
"preferredUsername": self.username, "preferredUsername": self.username,
"displayName": "{0}@{1}".format(self.username, request.host), "displayName": "{0}@{1}".format(self.username, request.host),
"objectType": self.objectType, "objectType": self.object_type,
"pump_io": { "pump_io": {
"shared": False, "shared": False,
"followed": False, "followed": False,
@ -319,9 +317,9 @@ class MediaEntry(Base, MediaEntryMixin):
default=MutationDict()) default=MutationDict())
activity_as_object = Column(Integer, activity_as_object = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
activity_as_target = Column(Integer, activity_as_target = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
## TODO ## TODO
# fail_error # fail_error
@ -444,18 +442,13 @@ class MediaEntry(Base, MediaEntryMixin):
# pass through commit=False/True in kwargs # pass through commit=False/True in kwargs
super(MediaEntry, self).delete(**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): def serialize(self, request, show_comments=True):
""" Unserialize MediaEntry to object """ """ Unserialize MediaEntry to object """
author = self.get_uploader author = self.get_uploader
context = { context = {
"id": self.id, "id": self.id,
"author": author.serialize(request), "author": author.serialize(request),
"objectType": self.objectType, "objectType": self.object_type,
"url": self.url_for_self(request.urlgen), "url": self.url_for_self(request.urlgen),
"image": { "image": {
"url": request.host_url + self.thumb_url[1:], "url": request.host_url + self.thumb_url[1:],
@ -472,7 +465,7 @@ class MediaEntry(Base, MediaEntryMixin):
"self": { "self": {
"href": request.urlgen( "href": request.urlgen(
"mediagoblin.federation.object", "mediagoblin.federation.object",
objectType=self.objectType, object_type=self.objectType,
id=self.id, id=self.id,
qualified=True qualified=True
), ),
@ -491,14 +484,15 @@ class MediaEntry(Base, MediaEntryMixin):
context["license"] = self.license context["license"] = self.license
if show_comments: 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) total = len(comments)
context["replies"] = { context["replies"] = {
"totalItems": total, "totalItems": total,
"items": comments, "items": comments,
"url": request.urlgen( "url": request.urlgen(
"mediagoblin.federation.object.comments", "mediagoblin.federation.object.comments",
objectType=self.objectType, object_type=self.object_type,
id=self.id, id=self.id,
qualified=True qualified=True
), ),
@ -620,8 +614,6 @@ class MediaTag(Base):
creator=Tag.find_or_new creator=Tag.find_or_new
) )
objectType = "tag"
def __init__(self, name=None, slug=None): def __init__(self, name=None, slug=None):
Base.__init__(self) Base.__init__(self)
if name is not None: if name is not None:
@ -668,11 +660,9 @@ class MediaComment(Base, MediaCommentMixin):
activity_as_object = Column(Integer, activity_as_object = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
activity_as_target = Column(Integer, activity_as_target = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
objectType = "comment"
def serialize(self, request): def serialize(self, request):
""" Unserialize to python dictionary for API """ """ Unserialize to python dictionary for API """
@ -680,7 +670,7 @@ class MediaComment(Base, MediaCommentMixin):
author = self.get_author author = self.get_author
context = { context = {
"id": self.id, "id": self.id,
"objectType": self.objectType, "objectType": self.object_type,
"content": self.content, "content": self.content,
"inReplyTo": media.serialize(request, show_comments=False), "inReplyTo": media.serialize(request, show_comments=False),
"author": author.serialize(request) "author": author.serialize(request)
@ -739,16 +729,14 @@ class Collection(Base, CollectionMixin):
cascade="all, delete-orphan")) cascade="all, delete-orphan"))
activity_as_object = Column(Integer, activity_as_object = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
activity_as_target = Column(Integer, activity_as_target = Column(Integer,
ForeignKey("core__acitivity_intermediators.id")) ForeignKey("core__activity_intermediators.id"))
__table_args__ = ( __table_args__ = (
UniqueConstraint('creator', 'slug'), UniqueConstraint('creator', 'slug'),
{}) {})
objectType = "collection"
def get_collection_items(self, ascending=False): def get_collection_items(self, ascending=False):
#TODO, is this still needed with self.collection_items being available? #TODO, is this still needed with self.collection_items being available?
order_col = CollectionItem.position order_col = CollectionItem.position
@ -1085,10 +1073,7 @@ class PrivilegeUserAssociation(Base):
primary_key=True) primary_key=True)
class Generator(Base): class Generator(Base):
""" """ Information about what created an activity """
This holds the information about the software used to create
objects for the pump.io APIs.
"""
__tablename__ = "core__generators" __tablename__ = "core__generators"
id = Column(Integer, primary_key=True) 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 used multiple times for the activity object or target and also allows for
different types of objects to be used as an Activity. different types of objects to be used as an Activity.
""" """
__tablename__ = "core__acitivity_intermediators" __tablename__ = "core__activity_intermediators"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
type = Column(Integer, nullable=False) type = Column(Unicode, nullable=False)
TYPES = { TYPES = {
0: User, "user": User,
1: MediaEntry, "media": MediaEntry,
2: MediaComment, "comment": MediaComment,
3: Collection, "collection": Collection,
} }
def _find_model(self, obj): def _find_model(self, obj):
@ -1189,18 +1174,22 @@ class Activity(Base, ActivityMixin):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
actor = Column(Integer, actor = Column(Integer,
ForeignKey(User.id, use_alter=True, name="actor"), ForeignKey("core__users.id"),
nullable=False) nullable=False)
published = Column(DateTime, nullable=False, default=datetime.datetime.now) published = Column(DateTime, nullable=False, default=datetime.datetime.now)
updated = Column(DateTime, nullable=False, default=datetime.datetime.now) updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
verb = Column(Unicode, nullable=False) verb = Column(Unicode, nullable=False)
content = Column(Unicode, nullable=True) content = Column(Unicode, nullable=True)
title = 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, object = Column(Integer,
ForeignKey(ActivityIntermediator.id), nullable=False) ForeignKey("core__activity_intermediators.id"),
nullable=False)
target = Column(Integer, target = Column(Integer,
ForeignKey(ActivityIntermediator.id), nullable=True) ForeignKey("core__activity_intermediators.id"),
nullable=True)
get_actor = relationship(User, get_actor = relationship(User,
foreign_keys="Activity.actor", post_update=True) foreign_keys="Activity.actor", post_update=True)
@ -1214,8 +1203,9 @@ class Activity(Base, ActivityMixin):
self.object = ai.id self.object = ai.id
return return
self.object.set_object(*args, **kwargs) ai = ActivityIntermediator.query.filter_by(id=self.object).first()
self.object.save() ai.set_object(*args, **kwargs)
ai.save()
@property @property
def get_object(self): def get_object(self):
@ -1229,8 +1219,9 @@ class Activity(Base, ActivityMixin):
self.object = ai.id self.object = ai.id
return return
self.target.set_object(*args, **kwargs) ai = ActivityIntermediator.query.filter_by(id=self.target).first()
self.targt.save() ai.set_object(*args, **kwargs)
ai.save()
@property @property
def get_target(self): def get_target(self):

View File

@ -51,12 +51,12 @@ add_route(
# object endpoints # object endpoints
add_route( add_route(
"mediagoblin.federation.object", "mediagoblin.federation.object",
"/api/<string:objectType>/<string:id>", "/api/<string:object_type>/<string:id>",
"mediagoblin.federation.views:object_endpoint" "mediagoblin.federation.views:object_endpoint"
) )
add_route( add_route(
"mediagoblin.federation.object.comments", "mediagoblin.federation.object.comments",
"/api/<string:objectType>/<string:id>/comments", "/api/<string:object_type>/<string:id>/comments",
"mediagoblin.federation.views:object_comments" "mediagoblin.federation.views:object_comments"
) )

View File

@ -71,14 +71,14 @@ def profile_endpoint(request):
def user_endpoint(request): def user_endpoint(request):
""" This is /api/user/<username> - This will get the user """ """ This is /api/user/<username> - This will get the user """
user, user_profile = get_profile(request) user, user_profile = get_profile(request)
if user is None: if user is None:
username = request.matchdict["username"] username = request.matchdict["username"]
return json_error( return json_error(
"No such 'user' with username '{0}'".format(username), "No such 'user' with username '{0}'".format(username),
status=404 status=404
) )
return json_response({ return json_response({
"nickname": user.username, "nickname": user.username,
"updated": user.created.isoformat(), "updated": user.created.isoformat(),
@ -350,7 +350,7 @@ def feed_endpoint(request):
@oauth_required @oauth_required
def object_endpoint(request): def object_endpoint(request):
""" Lookup for a object type """ """ Lookup for a object type """
object_type = request.matchdict["objectType"] object_type = request.matchdict["object_type"]
try: try:
object_id = int(request.matchdict["id"]) object_id = int(request.matchdict["id"])
except ValueError: except ValueError:
@ -382,17 +382,17 @@ def object_comments(request):
media = MediaEntry.query.filter_by(id=request.matchdict["id"]).first() media = MediaEntry.query.filter_by(id=request.matchdict["id"]).first()
if media is None: if media is None:
return json_error("Can't find '{0}' with ID '{1}'".format( return json_error("Can't find '{0}' with ID '{1}'".format(
request.matchdict["objectType"], request.matchdict["object_type"],
request.matchdict["id"] request.matchdict["id"]
), 404) ), 404)
comments = response.serialize(request) comments = media.serialize(request)
comments = comments.get("replies", { comments = comments.get("replies", {
"totalItems": 0, "totalItems": 0,
"items": [], "items": [],
"url": request.urlgen( "url": request.urlgen(
"mediagoblin.federation.object.comments", "mediagoblin.federation.object.comments",
objectType=media.objectType, object_type=media.object_type,
id=media.id, id=media.id,
qualified=True qualified=True
) )
@ -459,27 +459,30 @@ def whoami(request):
@require_active_login @require_active_login
def activity_view(request): def activity_view(request):
""" /<username>/activity/<id> - Display activity """ /<username>/activity/<id> - Display activity
This should display a HTML presentation of the activity This should display a HTML presentation of the activity
this is NOT an API endpoint. this is NOT an API endpoint.
""" """
# Get the user object. # Get the user object.
username = request.matchdict["username"] username = request.matchdict["username"]
user = User.query.filter_by(username=username).first() user = User.query.filter_by(username=username).first()
activity_id = request.matchdict["id"] activity_id = request.matchdict["id"]
if request.user is None: if request.user is None:
return render_404(request) 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: if activity is None:
return render_404(request) return render_404(request)
return render_to_response( return render_to_response(
request, request,
"mediagoblin/federation/activity.html", "mediagoblin/federation/activity.html",
{"activity": activity} {"activity": activity}
) )