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)
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()

View 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:

View 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):

View 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"
)

View 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}
)