django-activity-stream / actstream /

from datetime import datetime
from operator import or_
from django.db import models
from django.db.models.query import QuerySet
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.utils.timesince import timesince as timesince_
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User

from actstream.signals import action

class FollowManager(models.Manager):
    def stream_for_user(self, user):
        Produces a QuerySet of most recent activities from actors the user follows
        follows = self.filter(user=user)
        qs = (Action.objects.stream_for_actor( for follow in follows)
        if follows.count():
            return reduce(or_, qs).order_by('-timestamp')

        return Action.objects.none()

class Follow(models.Model):
    Lets a user follow the activities of any specific actor
    user = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField() 
    actor = generic.GenericForeignKey()
    objects = FollowManager()

    class Meta:
        unique_together = ("user", "content_type", "object_id")

    def __unicode__(self):
        return u'%s -> %s' % (self.user,

class ActionManager(models.Manager):
    def stream_for_actor(self, actor):
        Produces a QuerySet of most recent activities for any actor
        return self.filter(
            actor_content_type = ContentType.objects.get_for_model(actor),
            actor_object_id =,
    def stream_for_model(self, model):
        Produces a QuerySet of most recent activities for any model
        return self.filter(
            target_content_type = ContentType.objects.get_for_model(model)
class Action(models.Model):
    Action model describing the actor acting out a verb (on an optional target). 
    Nomenclature based on
    Generalized Format::
        <actor> <verb> <time>
        <actor> <verb> <target> <time>
        <actor> <verb> <action_object> <target> <time>
        <justquick> <reached level 60> <1 minute ago>
        <brosner> <commented on> <pinax/pinax> <2 hours ago>
        <washingtontimes> <started follow> <justquick> <8 minutes ago>
        <mitsuhiko> <closed> <issue 70> on <mitsuhiko/flask> <about 3 hours ago>
    Unicode Representation::
        justquick reached level 60 1 minute ago
        mitsuhiko closed issue 70 on mitsuhiko/flask 3 hours ago
    HTML Representation::
        <a href="">brosner</a> commented on <a href="">pinax/pinax</a> 2 hours ago

    actor_content_type = models.ForeignKey(ContentType,related_name='actor')
    actor_object_id = models.PositiveIntegerField() 
    actor = generic.GenericForeignKey('actor_content_type','actor_object_id')
    verb = models.CharField(max_length=255)
    description = models.TextField(blank=True,null=True)
    target_content_type = models.ForeignKey(ContentType,related_name='target',blank=True,null=True)
    target_object_id = models.PositiveIntegerField(blank=True,null=True) 
    target = generic.GenericForeignKey('target_content_type','target_object_id')
    action_object_content_type = models.ForeignKey(ContentType,related_name='action_object',blank=True,null=True)
    action_object_object_id = models.PositiveIntegerField(blank=True,null=True) 
    action_object = generic.GenericForeignKey('action_object_content_type','action_object_object_id')
    timestamp = models.DateTimeField(auto_now_add=True)
    public = models.BooleanField(default=True)
    objects = ActionManager()
    def __unicode__(self):
            if self.action_object:
                return u'%s %s %s on %s %s ago' % (, self.verb, self.action_object,, self.timesince())
                return u'%s %s %s %s ago' % (, self.verb,, self.timesince())
        return u'%s %s %s ago' % (, self.verb, self.timesince())
    def actor_url(self):
        Returns the URL to the ``actstream_actor`` view for the current actor
        return reverse('actstream_actor', None,
                       (, self.actor_object_id))
    def target_url(self):
        Returns the URL to the ``actstream_actor`` view for the current target
        return reverse('actstream_actor', None,
                       (, self.target_object_id))
    def timesince(self, now=None):
        Shortcut for the ``django.utils.timesince.timesince`` function of the current timestamp
        return timesince_(self.timestamp, now)

    def get_absolute_url(self):
        return ('actstream.views.detail', [])

def follow(user, actor, send_action=True):
    Creates a ``User`` -> ``Actor`` follow relationship such that the actor's activities appear in the user's stream.
    Also sends the ``<user> started following <actor>`` action signal.
    Returns the created ``Follow`` instance.
    If ``send_action`` is false, no "started following" signal will be created
        follow(<user>, <actor>)
        follow(request.user, group)
    follow,created = Follow.objects.get_or_create(user=user,, 
    if send_action:
        action.send(user, verb=_('started following'), target=actor)
    return follow

def unfollow(user, actor, send_action=False):
    Removes ``User`` -> ``Actor`` follow relationship. 
    Optionally sends the ``<user> stopped following <actor>`` action signal.
        unfollow(<user>, <actor>)
        unfollow(request.user, other_user)
    Follow.objects.filter(user = user, object_id =, 
        content_type = ContentType.objects.get_for_model(actor)).delete()
    if send_action:
        action.send(user, verb=_('stopped following'), target=actor)
def actor_stream(actor):
    return Action.objects.stream_for_actor(actor)
actor_stream.__doc__ = Action.objects.stream_for_actor.__doc__
def user_stream(user):
    return Follow.objects.stream_for_user(user)
user_stream.__doc__ = Follow.objects.stream_for_user.__doc__
def model_stream(model):
    return Action.objects.stream_for_model(model)
model_stream.__doc__ = Action.objects.stream_for_model.__doc__
def action_handler(verb, **kwargs):
    kwargs.pop('signal', None)
    actor = kwargs.pop('sender')
    newaction = Action(actor_content_type = ContentType.objects.get_for_model(actor),
                    actor_object_id =,
                    verb = unicode(verb),
                    public = bool(kwargs.pop('public', True)),
                    description = kwargs.pop('description', None),
                    timestamp = kwargs.pop('timestamp',

    target = kwargs.pop('target', None)
    if target:
        newaction.target_object_id =
        newaction.target_content_type = ContentType.objects.get_for_model(target)
    action_object = kwargs.pop('action_object', None)
    if action_object:
        newaction.action_object_object_id =
        newaction.action_object_content_type = ContentType.objects.get_for_model(action_object)
action.connect(action_handler, dispatch_uid="actstream.models")