django-actrack¶
© 2014-2020 Thomas Khyn
django-actrack
is an activity tracker for the Django framework. It enables
recording any activity by any actor, relative to any number of targets or
related objects, with or without additional data. The activity can then be
retrieved through feeds associated to any instance linked to any action(s).
It has been tested with Django 2.2.* and 3.0.* and their matching Python versions (3.5 to 3.8).
If you like django-actrack and find it useful, you may want to thank me and
encourage future development by sending a few mBTC / mBCH / mBSV at this address:
1EwENyR8RV6tMc1hsLTkPURtn5wJgaBfG9
.
Documentation contents:
Quick start¶
Installation¶
As straightforward as it can be, using pip:
pip install django-actrack
You then need to modify your INSTALLED_APPS
settings:
- make sure it contains
django.contrib.contenttypes
- add
'actrack'
andgm2m
First steps¶
All right, let’s start tracking.
Logging activity¶
To track actions, the first things we need are … actions. Let’s generate and
log some. We use the actrack.log
function:
import actrack
actrack.log(user, 'had_lunch')
user
can be a user model instance (for example an instance of
django.contrib.auth
’s User
model) but it could as well be any instance
of any model. It could be a train, for example (though trains usually don’t
have lunch).
You can also provide targets and related objects to add information to the action:
actrack.log(train, 'left_station', targets=[origin], related=[destination])
Or any relevant data as key-word arguments:
actrack.log(train, 'arrived', time=now())
OK, we’ve generated a few actions, let’s see how we can retrieve them.
Tracking activity¶
django-actrack
uses trackers to retrieve actions associated to instances.
If you want the user user
(here it needs to be an actual user, see below) to
track all actions related to a given train
, you can create a tracker using
actrack.track
:
actrack.track(user, train)
This creates a tracker entry in the database that will be used to retrieve
every activity related to train
. train
could have been any other
instance of any other model, or even a model class itself to follow any instance
of that model, but user
must be an instance of the USER_MODEL
specified
in the Settings (which defaults to AUTH_USER_MODEL_
).
Retrieving activity¶
To retrieve every action matching this tracker, django-actrack
can provide
convenient accessors, provided you have connected the model to it beforehand
using the @actrack.connect
decorator:
@actrack.connect
class Train(models.Models):
...
‘Connecting’ django-actrack
with a model will expose an actions
attribute on every instance of the model:
# all the actions where the train is involved
all_train_actions = train.actions.all()
# actions where the train is involved as an actor, target or related object
train_actions_as_actor = train.actions.as_actor()
train_actions_as_target = train.actions.as_target()
train_actions_as_related = train.actions.as_related()
All the above will work for a given user instance or any instance which model
has been connected to django-actrack
via the connect
decorator.
Additionally, for user instances, we can invoke:
user_feed = user.actions.feed()
And this will fetch all the actions related to all the objects the user is tracking (trains, airplanes, cars, anything …)
Note
It is not always possible to use the connect
decorator this way.
The most common example is django.contrib.auth.User
. We therefore use
connect
as a simple function, somewhere in our app (for example in an
AppConfig
subclass’ ready()
method) so that it is executed when
Django starts:
actrack.connect(UserModel)
Next steps¶
Want to track more trains? Head to the Advanced features page to discover all the
advanced stuff django-actrack
can offer, or check out the API and the
Settings.
Advanced features¶
The Quick start section showed you how to log, track and retrieve activity related to given instances.
This section provides more details on django-actrack
basic workflow and
presents some of its more advanced features.
Action creation parameters¶
Check the API documentation for actrack.log to learn more about the additional parameters that it can accept.
Action handlers¶
For each action you are using in your code, you can create a subclass of
actrack.ActionHandler
with a corresponding verb
class attribute that
will be related to this action. An instance of this handler class will be
attached to any Action
object that is created or retrieved, as the
handler
attribute:
from actrack import ActionHandler
class MyActionHandler(ActionHandler):
verb = 'my_action'
def render(self, context):
return 'I did that'
def do_something(self):
for t in self.action.targets.all():
do_something_with_this_target(t)
Handlers are used to process the action. The only special methods are:
- render
- Called when you call
render
on an Action instance. See Rendering- get_text
- Returns the text associated to the action
- get_timeinfo
- Returns the time info of the action
- get_context
- Returns a default rendering context for the action, should you need it for template rendering
- combine(kwargs) [classmethod]
- See Combination
- group(newer_kw, older_kw) [classmethod]
- See Grouping
See the actrack.handler module for default implementations.
You can of course override any of the above methods in the ActionHandler
subclasses if you need to customise how certain actions should be rendered or
combined.
Combination¶
Sometimes, actions should be combined. Either because 2 same actions with different arguments occurred at the same time, because two actions are redundant and should be merged, or for whatever app-dependant reason.
Only actions with the same actor and targets can be combined.
Action handlers can define custom combine_with_[verb]
methods that
determine what to do when a verb
action is already in the queue. The method
takes the keyword arguments that would be passed to the ‘Action’
constructor, and can make use of self.queue
, a registry of all the
previously added keyword arguments in this request. When this method returns
True
, the currently logged action is discarded. In this case, it is the
responsibility of combine_with_[verb]
to amend the action to which the
discarded action is combined.
Note that the combination occurs when the action is logged. If an action is combined / discarded, it is not placed into the queue. The queue is saved to the database when a request finishes, after Grouping takes place.
Grouping¶
When the same action is repeated over a number of objects or on the same object, it is useless to show very similar actions a number of times.
django-actrack
provides a way to check if an action that is being logged
is similar to recent actions and, if it finds one, it amends it instead of
creating a new one.
The definition of ‘recent’ can be changed by the GROUPING_DELAY
setting, in
seconds. Individually, it is also possible to change this delay or disable
action grouping when calling actrack.log
using the grouping_delay
argument.
By default, an action is considered ‘similar’ if it has the same actor, and at
least the same targets or related objects. This can be customized by
overriding the group
method in the ActionHandler
subclass relative to
the relevant action.
Grouping only occurs when the action queue is saved.
Deleted items¶
This is a great feature of django-actrack
. If an object to which an action
is related (the object can be the actor, a target or related object) is
deleted, the action itself can either be deleted (if passing
use_del_items=False
to actrack.connect
) or can remain. If it remains,
its reference to the deleted item is replaced by a reference to an instance of
a special model, that stores a verbose description of the deleted item.
For example, if the train
instance is deleted (retired from the railway
company’s network, for example), the actions that had been generated beforehand
refering to that train
will not be deleted, and one will still be able to
read when the train started and when it arrived.
To retrieve the verbose description, django-actrack
first looks for a
deleted_item_description
method, calls it with no arguments and takes the
returned string as the description. If that fails, it will simply evaluate
the instance as a string using str
.
The same thing exists for serialization. By default, the serialization
field of the deleted item instance is populated with {'pk': object.pk}
where object
is the object being deleted. The value stored in
serialization
can be customized on a per-instance basis using the
deleted_item_serialization
method.
Warning
If you are logging an action involving an instance while deleting it (typically within a pre_delete or post_delete signal handler), you need to turn it into a ‘deleted item’ first. This can be done using the function actrack.deletion.get_del_item which takes the instance as an argument and returns a deleted item instance. Be careful, get_del_item creates an entry for a deleted item in the database, so make sure you call it only when you are actually deleting an instance
Read / unread actions¶
When the TRACK_UNREAD
setting is set to True
,
django-actrack
can make the distinction between read and unread actions.
When a new action is created, it is simply considered ad unread by all users.
An action’s status can be retrieved using the Action.is_unread_for
method,
which takes a user as sole argument.
To update this status, you may use the Action.mark_read_for(user, force)
method. force
will override the AUTO_READ
setting.
Alternatively, if AUTO_READ
is True
, an action can be marked as read
when it is rendered, using its render
method.
There are also classmethods on Action
that implement the same functions on
a sequence of actions: bulk_is_unread_for
, bulk_mark_read_for
and
bulk_render
. All of them take an ordered sequence of actions as first
argument and return a list of booleans for the first two and strings for the
third.
Rendering¶
Speaking about rendering, any action can be rendered through its render
method. Action.render
calls the action handler’s render
method, that
can be overridden in subclasses of ActionHandler
.
The ActionHandler.get_context
method generates a useful default context
dictionary from the attached action data.
Settings¶
The settings must be stored in your Django project’s settings
module, as
a dictionary name ACTRACK
. This dictionary may contain the following items:
- USER_MODEL
- The user model that should be used for the owners of the tracker instances.
Defaults to Django’s
AUTH_USER_MODEL
- ACTIONS_ATTR
- The name of the accessor for actions, that can be changed in case it clashes
with one of your models’ fields. Defaults to
'actions'
- TRACKERS_ATTR
- The name of the accessor for trackers, that can be changed in case it clashes
with one of your models’ fields. Defaults to
'trackers'
- DEFAULT_HANDLER
- The path to the default action handler class (used when a matching action
handler is not found). Defaults to
'actrack.ActionHandler'
- TRACK_UNREAD
- Should unread actions be tracked? Defaults to
True
. - AUTO_READ
- Should actions be automatically marked as read when rendered? Defaults to
True
. - GROUPING_DELAY
- The time in seconds after which an action cannot be merged with a more
recent one. When set to
-1
, grouping is disabled. When set to0
, grouping occurs only on unsaved actions. Defaults to0
- PK_MAXLENGTH
- The maximum length of the primary keys of the objects that will be linked
to action (as targets or related). Defaults to
16
. - LEVELS
A dictionary of logging levels. Defaults to:
{ 'NULL': 0, 'DEBUG': 10, 'HIDDEN': 20, 'INFO': 30, 'WARNING': 40, 'ERROR': 50, }
Note
The logging levels should have upper case names and their values must be small positive integers from 0 to 32767
The defined logging levels can, after initialization, be accessed under the
actrack.level
module. E.g. actrack.level.INFO
.
- DEFAULT_LEVEL
- The default level to use for logging. Defaults to
LEVELS['INFO']
- READABLE_LEVEL
- Below that logging level (strictly), an action cannot appear as unread and
cannot be marked as read. Defaults to
LEVELS['INFO']
API¶
django-actrack
exposes several functions, models and managers.
Functions¶
This section lists all the functions exposed by django-actrack
and documents
their keyword arguments.
actrack.log(actor, verb, **kwargs)¶
Mandatory arguments:
- actor
- The instance that generates the activity. Can be any instance of any model, does not have to be a user.
- verb
- A string identifying the action. Tip: make it meaningful. The verb is used to retrieve a matching Action handlers subclass
Optional keyword arguments:
- targets
- A model instance or list of model instances being directly affected by the new action.
- related
- A model instance or list of model instances being related to the new action.
Note
Technically, the targets
and related
object lists are redundant and
they could be merged. However it can be meaningful or practical to split the
objects in two groups, hence the distinction.
- timestamp
- The timestamp that should be recorded for the action. If not provided, this default to now.
- level
- The logging level of the new action. Logging levels can especially be used to filter actions that can be marked as unread. See the LEVELS, READABLE_LEVEL and DEFAULT_LEVEL Settings.
- using
- The database to store the new action in.
- grouping_delay
- If an action with the same verb has occurred within the last
grouping_delay
(in seconds), it is merged with the current one. If it is set to0
, this prevents the action from being grouped. See Grouping. Defaults toGROUPING_DELAY
. - other keywords
- any other keyword will be included in the action’s data. They must only contain serializable data.
actrack.track(user, to_track, **kwargs)¶
actrack.track
can be used either to create a tracker or modify an existing
one. It can track model instances but also model classes.
- user
- The user who should track actions concerning
to_track
. Must be an instance of the model defined byAUTH_USER_MODEL
- to_track
- Actions relative to this model instance will appear in the
user
’s actions feed - log
- If set to
True
, the function will log an action with the verb ‘started tracking’. Defaults toFalse
- actor_only
- Will track actions only when the provided tracked object is the actor of
an action. Default to
True
. - using
- The database to store the new tracker in.
- verbs
- The verbs to track. Exclude any action that does not match the provide verbs. Defaults to any verb.
actrack.untrack(user, to_untrack, **kwargs)¶
Deletes a tracker object or deletes some verbs from its verbs set.
Mandatory arguments:
- user
- See actrack.track
- to_untrack
- The model instance to untrack
Optional keyword arguments:
- log
- See actrack.track
- verbs
- The verbs to stop tracking. If it is empty or equal to the current verbs set, no verb is to be tracked anymore and the tracker is deleted. Defaults to all verbs.
- using
- See actrack.track
@actrack.connect or actrack.connect(model)¶
The actrack.connect
decorator can be used with an optional argument:
- use_del_items
- Should the model that is to be connected use the
deleted items feature? Defaults to
True
.
Managers¶
The actions
manager¶
We’ve seen in the Quick start that connecting a django Model using the
actrack.connect
decorator exposed an actions
attribute on every instance
of that Model. This actions
attribute is actually a Django Manager that
queries Action instances:
@actrack.connect
class MyModel(models.Models):
...
instance = MyModel()
# this returns a Manager to fetch actions
instance.actions
An actions
manager has several useful methods:
instance.actions.as_actor(\*\*kw)
- All the actions where instance is the actor.
instance.actions.as_target(\*\*kw)
- All the actions where instance is among the targets.
instance.actions.as_related(\*\*kw)
- All the actions where instance is among the related objects.
instance.actions.all()
- Overrides the normal
all
method and returns all the actions where instance is either the actor or in the targets or related objects. It is a combination of the results of the 3 above methods. instance.actions.feed(\*\*kw)
- The most useful accessor. This will work only if instance is a user, and will return all the instances that match all the trackers the user is associated with.
All these manager methods take keyword arguments to further filter the result queryset and only fetch the actions you want (verbs, timestamp …).
The trackers
manager¶
In addition to the actions
attribute, actrack.connect
makes another
helpful manager available: the trackers
# this returns a Manager to fetch Tracker instances
instance.trackers
instance.tracker.tracking(\*\*kw)
- All the trackers that are tracking the instance.
instance.tracker.users(\*\*kw)
- All the users who are tracking the instance (= the owners of the trackers tracking the instance returned by the above method).
instance.tracker.owned(\*\*kw)
- Works only if instance is a user, returns all the trackers owned by the instance.
instance.tracker.tracked(\*models, \*\*kw)
- Works only if instance is a user, returns all the objects (various types) tracked by the user. Be aware that if there are model class trackers, there can be model classes in the returned set.
instance.tracker.all()
- Overrides the normal
all
method. If instance is a user, will return a combination ofinstance.tracker.owned()
andinstance.tracker.tracking
. If not, it returns the same asinstance.tracker.tracking
.
Similarly as above, these manager methods take keyword arguments to further
filter the result queryset and only fetch the trackers you want (except
tracker.tracked
that returns instances of different models).
Models¶
Action¶
The core model of django-actrack
.
-
class
actrack.models.
Action
(*args, **kwargs)¶ An action initiated by an actor and described by a verb. An action may have: - target objects (affected by the action) - related objects (related to the action)
-
actor
¶ The actor, can be anything
-
targets
¶ The target objects, can contain several objects of different types
The related objects, can also contain several objects of different types
-
verb
¶ The action’s verb or identifier
-
level
¶ The action’s level
-
data
¶ Data associated to the action (stored in a JSON field)
-
timestamp
¶ The timestamp of the action, from which actions are ordered
-
is_unread_for
(user)¶ Returns True if the action is unread for that user
-
mark_read_for
(user, force=False)¶ Attempts to mark the action as read using the tracker’s mark_read method. Returns True if the action was unread before To mark several actions as read, prefer the classmethod bulk_mark_read_for
-
render
(user=None, context=None)¶ Renders the action, attempting to mark it as read if user is not None Returns a rendered string
-
classmethod
bulk_is_unread_for
(user, actions)¶ Does not bring any performance gains over Action.is_read method, exists for the sake of consistency with bulk_mark_read_for and bulk_render
-
classmethod
bulk_mark_read_for
(user, actions, force=False)¶ Marks an iterable of actions as read for the given user It is more efficient than calling the mark_read method on each action, especially if many actions belong to only a few followers
Returns a list
l
of booleans. Ifactions[i]
was unread before the call to bulk_mark_read_for,l[i]
is True
-
classmethod
bulk_render
(actions=(), user=None, context=None)¶ Renders an iterable actions, returning a list of rendered strings in the same order as
actions
If
user
is provided, the class method will attempt to mark the actions as read for the user using Action.mark_read above
-
Trackers¶
django-actrack
features two types of trackers. A Tracker
model (which
instances are stored in the database) and a non-persistent TempTracker
class which is not actually a model but instead can be used to generate
read-only queries on-the-fly.
-
class
actrack.models.
Tracker
(*args, **kwargs)¶ Action tracking object, so that a user can track the actions on specific objects
-
user
¶ The user to which the tracker instance is attached
-
tracked
¶ The tracked object
-
verbs
¶ All the verbs that are tracked (when empty, that means ‘all verbs’)
-
actor_only
¶ Should the tracker only track actions where the tracked object is the actor?
-
update_unread
()¶ Retrieves the actions having occurred after the last time the tracker was updated and mark them as unread (bulk-add to unread_actions).
-
clean
()¶ Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.
-
clean_fields
(exclude=None)¶ Clean all fields and raise a ValidationError containing a dict of all validation errors if any occur.
-
full_clean
(exclude=None, validate_unique=True)¶ Call clean_fields(), clean(), and validate_unique() on the model. Raise a ValidationError for any errors that occur.
-
get_deferred_fields
()¶ Return a set containing names of deferred fields for this instance.
-
matches
(action)¶ Returns true if an action is to be tracked by the Tracker object
-
refresh_from_db
(using=None, fields=None)¶ Reload field values from the database.
By default, the reloading happens from the database this instance was loaded from, or by the read router if this instance wasn’t loaded from any database. The using parameter will override the default.
Fields can be used to specify which fields to reload. The fields should be an iterable of field attnames. If fields is None, then all non-deferred fields are reloaded.
When accessing deferred fields of an instance, the deferred loading of the field will call this method.
-
save
(force_insert=False, force_update=False, using=None, update_fields=None)¶ Save the current instance. Override this in a subclass if you want to control the saving process.
The ‘force_insert’ and ‘force_update’ parameters can be used to insist that the “save” must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.
-
save_base
(raw=False, force_insert=False, force_update=False, using=None, update_fields=None)¶ Handle the parts of saving which should be done only once per save, yet need to be done in raw saves, too. This includes some sanity checks and signal sending.
The ‘raw’ argument is telling save_base not to save any parent models and not to do any changes to the values before save. This is used by fixture loading.
-
serializable_value
(field_name)¶ Return the value of the field name for this instance. If the field is a foreign key, return the id value instead of the object. If there’s no Field object with this name on the model, return the model attribute’s value.
Used to serialize a field’s value (in the serializer, or form output, for example). Normally, you would just access the attribute directly and not use this method.
-
validate_unique
(exclude=None)¶ Check unique constraints on the model and raise ValidationError if any failed.
-
-
class
actrack.models.
TempTracker
(user, tracked, verbs=(), actor_only=True, last_updated=None)¶ A tracker that is designed to be used ‘on the fly’ and is not saved in the database Typically used to retrieve all actions regarding an object, without needing to specifically track this object
-
matches
(action)¶ Returns true if an action is to be tracked by the Tracker object
-
update_unread
(already_fetched=())¶ Retrieves the actions having occurred after the last time the tracker was updated and mark them as unread (bulk-add to unread_actions).
-
DeletedItem¶
See Deleted items.
-
class
actrack.models.
DeletedItem
(*args, **kwargs)¶ A model to keep track of objects that have been deleted but that still need to be linked by Action instances
-
ctype
¶ The deleted instance’s content type
-
description
¶ The deleted instance’s description when the instance was deleted
-
serialization
¶ The deleted instance’s serialization in JSON when the instance was deleted
-
Warning
This documentation is a work in progress. Some features may be undocumented, or only lightly documented. It may be necessary to have a look at the source code for more details on some features.