New method for storing and formatting actions.

* Drop the `Activity` class and table, replace with `Action`.
* And associated renaming.
* Define the concept of an `ActionFormatter` and a base class.
* Add a README.
This commit is contained in:
James Socol 2011-03-10 15:38:51 -05:00
Родитель fc43a5ce3d
Коммит 6e4ff7d016
4 изменённых файлов: 135 добавлений и 8 удалений

68
apps/activity/README.rst Normal file
Просмотреть файл

@ -0,0 +1,68 @@
================
Logging Activity
================
The **activity** app provides a way to log arbitrary activity for interested
users (c.f. your Github dashboard). This activity can appear on a user's
profile, on their personal dashboard, or other places.
Logging What Now?
=================
Each bit of activity is represented in the database by an ``Action`` object.
It's linked to relevant users by a ``ManyToManyField``. To add a new action to
users' activity logs, create a new ``Action`` object and add the relevant users
to the ``action.users`` manager.
Formatting Actions
==================
``Action`` objects require a **formatter** class that determines how to render
the action in the log. For example, a formatter for a forum reply might decide
to render the title of the action like this::
_('{user} replied to {thread}').format(user=, thread=)
Formatters have access to the entire action object, so they can look at any
attached objects including, potentially, the creator (of the action), or the
relevant content object (a ``GenericForeignKey``).
Formatters should probably subclass ``activity.ActionFormatter``, though that's
not strictly required at the moment. They need to accept an ``Action`` object
to their constructors and implement the following properties:
``title``:
a title for the action
``content``:
text content, may be blank
``__unicode__()``:
probably the same as ``title``
An fuller example::
class ForumReplyFormatter(ActionFormatter):
def __init__(self, action):
self.action = action
self.post = action.content_object
title = _('{user} replied to {thread}')
self.title = title.format(user=action.creator,
thread=self.post.thread.title)
self.content = self.post.content[0:225]
def __unicode__(self):
return self.title
Saving the Formatter
--------------------
When creating an ``Action``, you need to save a Python route to a formatter
class. For example, assuming the formatter above was in ``forums.tasks``, you
might store::
action = Action()
action.formatter = 'forums.tasks.ForumReplyFormatter'
It should be a path you can import from the Django shell.

Просмотреть файл

@ -0,0 +1,13 @@
class ActionFormatter(object):
"""A base class for action formatters.
Subclasses must implement all properties, optionally with @property."""
title = 'Something Happened!'
content = ''
def __init__(self, action):
self.action = action
def __unicode__(self):
return self.title

Просмотреть файл

@ -8,29 +8,46 @@ from django.db import models
from sumo.models import ModelBase
class Activity(ModelBase):
class Action(ModelBase):
"""Represents a unit of activity in a user's 'inbox.'"""
user = models.ForeignKey(User, related_name='activity_inbox')
users = models.ManyToManyField(User, related_name='action_inbox')
creator = models.ForeignKey(User, null=True, blank=True,
related_name='activity')
related_name='actions')
created = models.DateTimeField(default=datetime.now, db_index=True)
title = models.CharField(max_length=120)
content = models.CharField(max_length=400, blank=True)
data = models.CharField(max_length=400, blank=True)
url = models.URLField(null=True, blank=True)
content_type = models.ForeignKey(ContentType, null=True, blank=True)
object_id = models.PositiveIntegerField(null=True, blank=True)
content_object = generic.GenericForeignKey()
formatter_cls = models.CharField(max_length=200,
default='activity.ActionFormatter')
class Meta(object):
ordering = ['-created']
@property
def formatter(self):
if not hasattr(self, 'fmt'):
mod, _, cls = self.formatter_cls.rpartition('.')
fmt_cls = getattr(__import__(mod, fromlist=[cls]), cls)
self.fmt = fmt_cls(self)
return self.fmt
def __unicode__(self):
return self.title
return unicode(self.formatter)
@property
def title(self):
return self.formatter.title
@property
def content(self):
return self.formatter.content
def get_absolute_url(self):
return self.url
class ActivityMixin(object):
class ActionMixin(object):
"""Add a GenericRelation to a model."""
activity = generic.GenericRelation(Activity)
actions = generic.GenericRelation(Action)

Просмотреть файл

@ -0,0 +1,29 @@
DROP TABLE `activity_activity`;
CREATE TABLE `activity_action_users` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`action_id` integer NOT NULL,
`user_id` integer NOT NULL,
UNIQUE (`action_id`, `user_id`)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `activity_action_users` ADD CONSTRAINT `user_id_refs_id_514426b8` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
CREATE TABLE `activity_action` (
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
`creator_id` integer,
`created` datetime NOT NULL,
`data` varchar(400) NOT NULL,
`url` varchar(200),
`content_type_id` integer,
`object_id` integer UNSIGNED,
`formatter_cls` varchar(200) NOT NULL
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `activity_action` ADD CONSTRAINT `creator_id_refs_id_4475b305` FOREIGN KEY (`creator_id`) REFERENCES `auth_user` (`id`);
ALTER TABLE `activity_action` ADD CONSTRAINT `content_type_id_refs_id_95f5c947` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
ALTER TABLE `activity_action_users` ADD CONSTRAINT `action_id_refs_id_52ad1f42` FOREIGN KEY (`action_id`) REFERENCES `activity_action` (`id`);
CREATE INDEX `activity_action_f97a5119` ON `activity_action` (`creator_id`);
CREATE INDEX `activity_action_3216ff68` ON `activity_action` (`created`);
CREATE INDEX `activity_action_e4470c6e` ON `activity_action` (`content_type_id`);