Skip to content

Flexible Python Django activity stream supporting an arbitrary number of associated objects

License

Notifications You must be signed in to change notification settings

michaelpb/actable

Repository files navigation

Django Actable

https://travis-ci.org/michaelpb/actable.svg?branch=master
  • NOTE: Presently only supports Python 3.5+ and Django 1.9+ (see issue #1)

Activity stream for Python Django. Unlike other activity streams, it is much more flexible, with every event designed to supporting an arbitrary number of associated objects. It also is designed to be unobtrusive: Any of your models can be registered as an activity generator, all you need to do is generate a data structure for context, or an HTML fragment.

Features

  • Very easily / magically integrated into an existing system, with signals being auto-generated based on principle objects
  • Arbitrary number of objects can be associated with every event
  • Fast look ups with denormalized events (no joins)
  • Looking up streams for particular actors or objects
  • Decent test coverage
  • Handy Paginator helper class to page through stream
  • Example project
  • Not yet implemented: Follow

Quick start

Overview:

  1. Install actable and put in requirements file
  2. Add to INSTALLED_APPS
  3. Pick several important models to implement the actable interface so that every save or update generates an event
  4. Add those models to ACTABLE_MODELS
  5. Use helper classes to add a streams to your views

1. Install

pip install actable

2. Add to INSTALLED_APPS

In your settings.py file, add something like:

INSTALLED_APPS = (
    ...
    'actable.apps.ActableConfig',
    ...
)

3. Implement Actable interface in one or more models

Pick one or more models to be your actable models. Whenever these models are updated or created, it will generate events. These events can involve any number of other objects.

To implement the required interface, you must implement at least 2 methods on your actable models. The first method is get_actable_relations which must return a dictionary where all the values are model instances that are related to this action. Instead of limiting yourself to "Actor, Verb, Object", this allows you to have any number of relations. Each one of these model instances will receive a copy of this event to its activity stream.

Example:

class ProjectBlogPost:
    def get_actable_relations(self, event):
        return {
            'subject': self.user,
            'object': self,
            'project': self.project,
        }

Now you must choose one of 2 other methods to implement. These constitute the data to cache for each event.

The most versatile of the two is one that returns a dictionary containing entirely simple (serializable) data types. This will be stored in serialized form in your database.

Example:

class ProjectBlogPost:
    def get_actable_json(self, event):
        verb = 'posted' if event.is_creation else 'updated'
        return {
            'subject': self.user.username,
            'subject_url': self.user.get_absolute_url(),
            'object': self.title,
            'object_url': self.get_absolute_url(),
            'project': self.project.title,
            'verb': verb,
        }

The other option is caching an HTML snippet (string) that can be generated any way you see fit.

Example:

class ProjectBlogPost:
    def get_actable_html(self, event):
        return '<a href="%s">%s</a> wrote %s' % (
            self.user.get_absolute_url(),
            self.user.username,
            self.title
        )

4. Add to ACTABLE_MODELS list

Finally, you should list your newly improved as an ACTABLE_MODEL, as such:

ACTABLE_MODELS = [
    'myapp.ProjectBlogPost',
]

5. Include stream in your views

In your views, you can use the EventDictPaginator to easily include streams. This will fetch streams relveant to any given model specified as an "actable relation" (that is, it works on more models than just the ACTABLE_MODELS)

Example:

from actable.helpers import EventDictPaginator
...

def view_user(request, username):
    user = User.objects.get(username=username)
    event_paginator = EventDictPaginator(user, 50)
    return render(request, 'userpage.html', {
        stream: event_paginator.page(request.get('page', 1)),
    })

EventDictPaginator will consist of de-serialized dicts, exactly as you generated them in get_actable_json, with one added property date, which will be a Python datetime for the event.

Other helpers

For more descriptive activity items in the style of 'Alice updated the blog post title from "2018 Plans" to "2018 goals"', there is a helper to detect changes between two versions of

Credits

Tools used in creating this package:

About

Flexible Python Django activity stream supporting an arbitrary number of associated objects

Resources

License

Stars

Watchers

Forks

Packages

No packages published