Add ActivityIntermediator table and refactor some of Activity model
authorJessica Tallon <jessica@megworld.co.uk>
Wed, 27 Aug 2014 13:34:07 +0000 (14:34 +0100)
committerJessica Tallon <jessica@megworld.co.uk>
Wed, 27 Aug 2014 14:20:27 +0000 (15:20 +0100)
- This has introduced a intermediatory table between object/target and
  the activity. This allows for multiple activities to be associated
  with one object/target.
- This moves some of the methods off Activity model into a mixin which
  didn't need to interact with database things.
- This also cleaned up the migrations as well as adding retroactive
  creation of activities for collection creation.

mediagoblin/db/migrations.py
mediagoblin/db/mixin.py
mediagoblin/db/models.py

index 72f853697f13762493cfae86c72e10bcbd56b944..f467253f030fc835d29d9835a1b9b3afd0229388 100644 (file)
@@ -29,7 +29,7 @@ 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,
-    Privilege)
+    Privilege, Generator)
 from mediagoblin.db.extratypes import JSONEncoded, MutationDict
 
 
@@ -578,30 +578,6 @@ PRIVILEGE_FOUNDATIONS_v0 = [{'privilege_name':u'admin'},
                             {'privilege_name':u'commenter'},
                             {'privilege_name':u'active'}]
 
-
-class Activity_R0(declarative_base()):
-    __tablename__ = "core__activities"
-    id = Column(Integer, primary_key=True)
-    actor = Column(Integer, ForeignKey(User.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=False)
-    title = Column(Unicode, nullable=True)
-    target = Column(Integer, ForeignKey(User.id), nullable=True)
-    object_comment = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
-    object_collection = Column(Integer, ForeignKey(Collection.id), nullable=True)
-    object_media = Column(Integer, ForeignKey(MediaEntry.id), nullable=True)
-    object_user = Column(Integer, ForeignKey(User.id), nullable=True)
-
-class Generator(declarative_base()):
-    __tablename__ = "core__generators"
-    id = Column(Integer, primary_key=True)
-    name = Column(Unicode, nullable=False)
-    published = Column(DateTime, nullable=False, default=datetime.datetime.now)
-    updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
-    object_type = Column(Unicode, nullable=False)
-
 # vR1 stands for "version Rename 1".  This only exists because we need
 # to deal with dropping some booleans and it's otherwise impossible
 # with sqlite.
@@ -914,17 +890,91 @@ def revert_username_index(db):
 
     db.commit()
 
+class Generator_R0(declarative_base()):
+    __tablename__ = "core__generators"
+    id = Column(Integer, primary_key=True)
+    name = Column(Unicode, nullable=False)
+    published = Column(DateTime, nullable=False, default=datetime.datetime.now)
+    updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+    object_type = Column(Unicode, nullable=False)
+
+class Activity_R0(declarative_base()):
+    __tablename__ = "core__activities"
+    id = Column(Integer, primary_key=True)
+    actor = Column(Integer, ForeignKey(User.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=False)
+    title = Column(Unicode, nullable=True)
+    target = Column(Integer, ForeignKey(User.id), nullable=True)
+    generator = Column(Integer, ForeignKey(Generator.id), nullable=True)
+
+class ActivityIntermediator_R0(declarative_base()):
+    __tablename__ = "core__acitivity_intermediators"
+    id = Column(Integer, primary_key=True)
+    type = Column(Integer, nullable=False)
+
 @RegisterMigration(24, MIGRATIONS)
-def create_activity_table(db):
-    """ This will create the activity table """
+def activity_migration(db):
+    """
+    Creates everything to create activities in GMG
+    - Adds Activity, ActivityIntermediator and Generator table
+    - Creates GMG service generator for activities produced by the server
+    - Adds the activity_as_object and activity_as_target to objects/targets
+    - Retroactively adds activities for what we can acurately work out
+    """
+    # Set constants we'll use later
+    FOREIGN_KEY = "core__acitivity_intermediators.id"
+
+
+    # Create the new tables.
     Activity_R0.__table__.create(db.bind)
     Generator_R0.__table__.create(db.bind)
+    ActivityIntermediator_R0.__table__.create(db.bind)
     db.commit()
-    
-    # Create the GNU MediaGoblin generator
-    gmg_generator = Generator(name="GNU MediaGoblin", object_type="service")
-    gmg_generator.save()
-    
+
+
+    # Initiate the tables we want to use later
+    metadata = MetaData(bind=db.bind)
+    user_table = inspect_table(metadata, "core__users")
+    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")
+
+
+    # Create the foundations for Generator
+    db.execute(generator_table.insert().values(
+        name="GNU Mediagoblin",
+        object_type="service"
+    ))
+    db.commit()
+
+
+    # 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.create(media_entry_table)
+    as_target = Column("activity_as_target", Integer, ForeignKey(FOREIGN_KEY))
+    as_target.create(media_entry_table)
+
+    as_object = Column("activity_as_object", Integer, ForeignKey(FOREIGN_KEY))
+    as_object.create(user_table)
+    as_target = Column("activity_as_target", Integer, ForeignKey(FOREIGN_KEY))
+    as_target.create(user_table)
+
+    as_object = Column("activity_as_object", Integer, ForeignKey(FOREIGN_KEY))
+    as_object.create(media_comments_table)
+    as_target = Column("activity_as_target", Integer, ForeignKey(FOREIGN_KEY))
+    as_target.create(media_comments_table)
+
+    as_object = Column("activity_as_object", Integer, ForeignKey(FOREIGN_KEY))
+    as_object.create(collection_table)
+    as_target = Column("activity_as_target", Integer, ForeignKey(FOREIGN_KEY))
+    as_target.create(collection_table)
+    db.commit()
+
+
     # 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():
@@ -932,19 +982,40 @@ def create_activity_table(db):
             verb="create",
             actor=media.uploader,
             published=media.created,
-            object_media=media.id,
+            updated=media.created,
+            generator=gmg_generator.id
         )
         activity.generate_content()
-        activity.save()
-    
+        activity.save(set_updated=False)
+        activity.set_object(media)
+        media.save()
+
     # 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()
+
+    # 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()
-    
+        activity.save(set_updated=False)
+        activity.set_object(collection)
+        collection.save()
+
     db.commit()
index 1f2e7ec30262d762deab08704059cc15b02bd6f2..bc3a3bd2eb65bed0181b1ea709daef63ef09b91d 100644 (file)
@@ -39,6 +39,7 @@ from mediagoblin.tools import common, licenses
 from mediagoblin.tools.pluginapi import hook_handle
 from mediagoblin.tools.text import cleaned_markdown_conversion
 from mediagoblin.tools.url import slugify
+from mediagoblin.tools.translate import pass_to_ugettext as _
 
 
 class UserMixin(object):
@@ -208,7 +209,7 @@ class MediaEntryMixin(GenerateSlugMixin):
         will return self.thumb_url if original url doesn't exist"""
         if u"original" not in self.media_files:
             return self.thumb_url
-        
+
         return mg_globals.app.public_store.file_url(
             self.media_files[u"original"]
             )
@@ -363,3 +364,109 @@ class CollectionItemMixin(object):
         Run through Markdown and the HTML cleaner.
         """
         return cleaned_markdown_conversion(self.note)
+
+class ActivityMixin(object):
+
+    VALID_VERBS = ["add", "author", "create", "delete", "dislike", "favorite",
+                   "follow", "like", "post", "share", "unfavorite", "unfollow",
+                   "unlike", "unshare", "update", "tag"]
+
+    def get_url(self, request):
+        return request.urlgen(
+            "mediagoblin.federation.activity_view",
+            username=self.get_actor.username,
+            id=self.id,
+            qualified=True
+        )
+
+    def generate_content(self):
+        """ Produces a HTML content for object """
+        # some of these have simple and targetted. If self.target it set
+        # it will pick the targetted. If they DON'T have a targetted version
+        # the information in targetted won't be added to the content.
+        verb_to_content = {
+            "add": {
+                "simple" : _("{username} added {object}"),
+                "targetted":  _("{username} added {object} to {target}"),
+            },
+            "author": {"simple": _("{username} authored {object}")},
+            "create": {"simple": _("{username} created {object}")},
+            "delete": {"simple": _("{username} deleted {object}")},
+            "dislike": {"simple": _("{username} disliked {object}")},
+            "favorite": {"simple": _("{username} favorited {object}")},
+            "follow": {"simple": _("{username} followed {object}")},
+            "like": {"simple": _("{username} liked {object}")},
+            "post": {
+                "simple": _("{username} posted {object}"),
+                "targetted": _("{username} posted {object} to {targetted}"),
+            },
+            "share": {"simple": _("{username} shared {object}")},
+            "unfavorite": {"simple": _("{username} unfavorited {object}")},
+            "unfollow": {"simple": _("{username} stopped following {object}")},
+            "unlike": {"simple": _("{username} unliked {object}")},
+            "unshare": {"simple": _("{username} unshared {object}")},
+            "update": {"simple": _("{username} updated {object}")},
+            "tag": {"simple": _("{username} tagged {object}")},
+        }
+
+        obj = self.get_object()
+        target = self.get_target()
+        actor = self.get_actor
+        content = verb_to_content.get(self.verb, None)
+
+        if content is None or obj is None:
+            return
+
+        if target is None or "targetted" not in content:
+            self.content = content["simple"].format(
+                username=actor.username,
+                object=obj.objectType
+            )
+        else:
+            self.content = content["targetted"].format(
+                username=actor.username,
+                object=obj.objectType,
+                target=target.objectType,
+            )
+
+        return self.content
+
+    def serialize(self, request):
+        obj = {
+            "id": self.id,
+            "actor": self.get_actor.serialize(request),
+            "verb": self.verb,
+            "published": self.published.isoformat(),
+            "updated": self.updated.isoformat(),
+            "content": self.content,
+            "url": self.get_url(request),
+            "object": self.get_object().serialize(request)
+        }
+
+        if self.generator:
+            obj["generator"] = self.get_generator.seralize(request)
+
+        if self.title:
+            obj["title"] = self.title
+
+        target = self.get_target()
+        if target is not None:
+            obj["target"] = target.seralize(request)
+
+        return obj
+
+    def unseralize(self, data):
+        """
+        Takes data given and set it on this activity.
+
+        Several pieces of data are not written on because of security
+        reasons. For example changing the author or id of an activity.
+        """
+        if "verb" in data:
+            self.verb = data["verb"]
+
+        if "title" in data:
+            self.title = data["title"]
+
+        if "content" in data:
+            self.content = data["content"]
index aadd3fea8c65729e246b78b37b33f1c512629af6..6004e97d383d45029a6860a26c304cb3f0ad777e 100644 (file)
@@ -35,9 +35,9 @@ from mediagoblin.db.extratypes import (PathTupleWithSlashes, JSONEncoded,
                                        MutationDict)
 from mediagoblin.db.base import Base, DictReadAttrProxy
 from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
-        MediaCommentMixin, CollectionMixin, CollectionItemMixin
+        MediaCommentMixin, CollectionMixin, CollectionItemMixin, \
+        ActivityMixin
 from mediagoblin.tools.files import delete_media_files
-from mediagoblin.tools.translate import pass_to_ugettext as _
 from mediagoblin.tools.common import import_component
 
 # It's actually kind of annoying how sqlalchemy-migrate does this, if
@@ -77,6 +77,11 @@ class User(Base, UserMixin):
     uploaded = Column(Integer, default=0)
     upload_limit = Column(Integer)
 
+    activity_as_object = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+    activity_as_target = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+
     ## TODO
     # plugin data would be in a separate model
 
@@ -313,6 +318,11 @@ class MediaEntry(Base, MediaEntryMixin):
     media_metadata = Column(MutationDict.as_mutable(JSONEncoded),
         default=MutationDict())
 
+    activity_as_object = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+    activity_as_target = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+
     ## TODO
     # fail_error
 
@@ -656,8 +666,14 @@ class MediaComment(Base, MediaCommentMixin):
                                                    lazy="dynamic",
                                                    cascade="all, delete-orphan"))
 
+
+    activity_as_object = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+    activity_as_target = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+
     objectType = "comment"
-    
+
     def serialize(self, request):
         """ Unserialize to python dictionary for API """
         media = MediaEntry.query.filter_by(id=self.media_entry).first()
@@ -722,6 +738,11 @@ class Collection(Base, CollectionMixin):
                                backref=backref("collections",
                                                cascade="all, delete-orphan"))
 
+    activity_as_object = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+    activity_as_target = Column(Integer,
+                                ForeignKey("core__acitivity_intermediators.id"))
+
     __table_args__ = (
         UniqueConstraint('creator', 'slug'),
         {})
@@ -1069,13 +1090,13 @@ class Generator(Base):
     objects for the pump.io APIs.
     """
     __tablename__ = "core__generators"
-    
+
     id = Column(Integer, primary_key=True)
     name = Column(Unicode, nullable=False)
-    published = Column(DateTime, nullable=False, default=datetime.datetime.now)
-    updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+    published = Column(DateTime, default=datetime.datetime.now)
+    updated = Column(DateTime, default=datetime.datetime.now)
     object_type = Column(Unicode, nullable=False)
-    
+
     def serialize(self, request):
         return {
             "id": self.id,
@@ -1084,199 +1105,144 @@ class Generator(Base):
             "updated": self.updated.isoformat(),
             "objectType": self.object_type,
         }
-    
+
     def unserialize(self, data):
         if "displayName" in data:
             self.name = data["displayName"]
-        
-    
 
-class Activity(Base):
+
+class ActivityIntermediator(Base):
+    """
+    This is used so that objects/targets can have a foreign key back to this
+    object and activities can a foreign key to this object. This objects to be
+    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"
+
+    id = Column(Integer, primary_key=True)
+    type = Column(Integer, nullable=False)
+
+    TYPES = {
+        0: User,
+        1: MediaEntry,
+        2: MediaComment,
+        3: Collection,
+    }
+
+    def _find_model(self, obj):
+        """ Finds the model for a given object """
+        for key, model in self.TYPES.items():
+            if isinstance(obj, model):
+                return key, model
+
+        return None, None
+
+    def set_object(self, obj):
+        """ This sets itself as the object for an activity """
+        key, model = self._find_model(obj)
+        if key is None:
+            raise ValueError("Invalid type of object given")
+
+        # First set self as activity
+        obj.activity_as_object = self.id
+        self.type = key
+
+    @property
+    def get_object(self):
+        """ Finds the object for an activity """
+        if self.type is None:
+            return None
+
+        model = self.TYPES[self.type]
+        return model.query.filter_by(activity_as_object=self.id).first()
+
+    def set_target(self, obj):
+        """ This sets itself as the target for an activity """
+        key, model = self._find_model(obj)
+        if key is None:
+            raise ValueError("Invalid type of object given")
+
+        obj.activity_as_target = self.id
+        self.type = key
+
+    @property
+    def get_target(self):
+        """ Gets the target for an activity """
+        if self.type is None:
+            return None
+
+        model = self.TYPES[self.type]
+        return model.query.filter_by(activity_as_target=self.id).first()
+
+    def save(self, *args, **kwargs):
+        if self.type not in self.TYPES.keys():
+            raise ValueError("Invalid type set")
+        Base.save(self, *args, **kwargs)
+
+class Activity(Base, ActivityMixin):
     """
     This holds all the metadata about an activity such as uploading an image,
-    posting a comment, etc. 
+    posting a comment, etc.
     """
     __tablename__ = "core__activities"
-    
+
     id = Column(Integer, primary_key=True)
-    actor = Column(Integer, ForeignKey(User.id), nullable=False)
+    actor = Column(Integer,
+                   ForeignKey(User.id, use_alter=True, name="actor"),
+                   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)
-    target = Column(Integer, ForeignKey(User.id), nullable=True)
     generator = Column(Integer, ForeignKey(Generator.id), nullable=True)
-    
-    
-    # Links to other models (only one of these should have a value).
-    object_comment = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
-    object_collection = Column(Integer, ForeignKey(Collection.id), nullable=True)
-    object_media = Column(Integer, ForeignKey(MediaEntry.id), nullable=True)
-    object_user = Column(Integer, ForeignKey(User.id), nullable=True)
-    
-    # The target could also be several things
-    target_comment = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
-    target_collection = Column(Integer, ForeignKey(Collection.id), nullable=True)
-    target_media = Column(Integer, ForeignKey(MediaEntry.id), nullable=True)
-    target_user = Column(Integer, ForeignKey(User.id), nullable=True)
-    
-    get_actor = relationship(User, foreign_keys="Activity.actor")
+    object = Column(Integer,
+                    ForeignKey(ActivityIntermediator.id), nullable=False)
+    target = Column(Integer,
+                    ForeignKey(ActivityIntermediator.id), nullable=True)
+
+    get_actor = relationship(User,
+        foreign_keys="Activity.actor", post_update=True)
     get_generator = relationship(Generator)
-    
-    VALID_VERBS = ["add", "author", "create", "delete", "dislike", "favorite", 
-                   "follow", "like", "post", "share", "unfavorite", "unfollow",
-                   "unlike", "unshare", "update", "tag"]
-    
+
+    def set_object(self, *args, **kwargs):
+        if self.object is None:
+            ai = ActivityIntermediator()
+            ai.set_object(*args, **kwargs)
+            ai.save()
+            self.object = ai.id
+            return
+
+        self.object.set_object(*args, **kwargs)
+        self.object.save()
+
+    @property
     def get_object(self):
-        """ This represents the object that is given to the activity """
-        # Do we have a cached version
-        if getattr(self, "_cached_object", None) is not None:
-            return self._cached_object
-        
-        if self.object_comment is not None:
-            obj = MediaComment.query.filter_by(id=self.object_comment).first()
-        elif self.object_collection is not None:
-            obj = Collection.query.filter_by(id=self.object_collection).first()
-        elif self.object_media is not None:
-            obj = MediaEntry.query.filter_by(id=self.object_media).first()
-        elif self.object_user is not None:
-            obj = User.query.filter_by(id=self.object_user).first()
-        else:
-            # Shouldn't happen but in case it does
-            return None
-        
-        self._cached_object = obj
-        return obj
-    
+        return self.object.get_object
+
+    def set_target(self, *args, **kwargs):
+        if self.target is None:
+            ai = ActivityIntermediator()
+            ai.set_target(*args, **kwargs)
+            ai.save()
+            self.object = ai.id
+            return
+
+        self.target.set_object(*args, **kwargs)
+        self.targt.save()
+
+    @property
     def get_target(self):
-        """ This represents the target given on the activity (if any) """
-        if getattr(self, "_cached_target", None) is not None:
-            return self._cached_target
-        
-        if self.target_comment is not None:
-            target = MediaComment.query.filter_by(id=self.target_comment).first()
-        elif self.target_collection is not None:
-            target = Collection.query.filter_by(id=self.target_collection).first()
-        elif self.target_media is not None:
-            target = MediaEntry.query.filter_by(id=self.target_media).first()
-        elif self.target_user is not None:
-            target = User.query.filter_by(id=self.target_user).first()
-        else:
-            # Shouldn't happen but in case it does
+        if self.target is None:
             return None
-        
-        self._cached_target = target
-        return self._cached_target
-        
-    
-    def url(self, request):
-        actor = User.query.filter_by(id=self.actor).first() 
-        return request.urlgen(
-            "mediagoblin.federation.activity_view",
-            username=actor.username,
-            id=self.id,
-            qualified=True
-        )
-    
-    def generate_content(self):
-        """
-        Produces a HTML content for object
-        TODO: Can this be moved to a mixin?
-        """
-        # some of these have simple and targetted. If self.target it set
-        # it will pick the targetted. If they DON'T have a targetted version
-        # the information in targetted won't be added to the content.
-        verb_to_content = {
-            "add": {
-                "simple" : _("{username} added {object}"),
-                "targetted":  _("{username} added {object} to {target}"),
-            },
-            "author": {"simple": _("{username} authored {object}")},
-            "create": {"simple": _("{username} created {object}")},
-            "delete": {"simple": _("{username} deleted {object}")},
-            "dislike": {"simple": _("{username} disliked {object}")},
-            "favorite": {"simple": _("{username} favorited {object}")},
-            "follow": {"simple": _("{username} followed {object}")},
-            "like": {"simple": _("{username} liked {object}")},
-            "post": {
-                "simple": _("{username} posted {object}"),
-                "targetted": _("{username} posted {object} to {targetted}"),
-            },
-            "share": {"simple": _("{username} shared {object}")},
-            "unfavorite": {"simple": _("{username} unfavorited {object}")},
-            "unfollow": {"simple": _("{username} stopped following {object}")},
-            "unlike": {"simple": _("{username} unliked {object}")},
-            "unshare": {"simple": _("{username} unshared {object}")},
-            "update": {"simple": _("{username} updated {object}")},
-            "tag": {"simple": _("{username} tagged {object}")},
-        }
-                
-        obj = self.get_object()
-        target = self.get_target()
-        actor = self.get_actor
-        content = verb_to_content.get(self.verb, None)
-        
-        if content is None or obj is None:
-            return
-        
-        if target is None or "targetted" not in content:
-            self.content = content["simple"].format(
-                username=actor.username,
-                object=obj.objectType
-            )
-        else:
-            self.content = content["targetted"].format(
-                username=actor.username,
-                object=obj.objectType,
-                target=target.objectType,
-            )
-        
-        return self.content
-    
-    def serialize(self, request):
-        obj = {
-            "id": self.id,
-            "actor": self.get_actor.serialize(request),
-            "verb": self.verb,
-            "published": self.published.isoformat(),
-            "updated": self.updated.isoformat(),
-            "content": self.content,
-            "url": self.url(request),
-            "object": self.get_object().serialize(request)
-        }
-        
-        if self.generator:
-            obj["generator"] = generator.seralize(request)
-        
-        if self.title:
-            obj["title"] = self.title
-        
-        target = self.get_target()
-        if target is not None:
-            obj["target"] = target.seralize(request)
-        
-        return obj
-    
-    def unseralize(self, data):
-        """
-        Takes data given and set it on this activity.
-        
-        Several pieces of data are not written on because of security
-        reasons. For example changing the author or id of an activity.
-        """
-        if "verb" in data:
-            self.verb = data["verb"]
-        
-        if "title" in data:
-            self.title = data["title"]
-        
-        if "content" in data:
-            self.content = data["content"]
-    
-    def save(self, *args, **kwargs):
-        self.updated = datetime.datetime.now()
-        super(Activity, self).save(*args, **kwargs)    
+
+        return self.target.get_target
+
+    def save(self, set_updated=True, *args, **kwargs):
+        if set_updated:
+            self.updated = datetime.datetime.now()
+        super(Activity, self).save(*args, **kwargs)
 
 MODELS = [
     User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
@@ -1285,7 +1251,7 @@ MODELS = [
     CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan,
        Privilege, PrivilegeUserAssociation,
     RequestToken, AccessToken, NonceTimestamp,
-    Activity, Generator]
+    Activity, ActivityIntermediator, Generator]
 
 """
  Foundations are the default rows that are created immediately after the tables