A Django application to implement a follow system and a timeline using multiple backends (db, redis, etc.).
The timeline engine can be found in sequere.contrib.timeline
.
Either check out the package from GitHub or it pull from a release via PyPI
pip install django-sequere
Add
sequere
to yourINSTALLED_APPS
INSTALLED_APPS = (
'sequere',
)
In Sequere any resources can follow any resources and vice versa.
Let's say you have two models:
# models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=150)
class Project(models.Model):
name = models.CharField(max_length=150)
Now you to register them in sequere to identify them when a resource is following another one.
# sequere_registry.py
from .models import Project, User
import sequere
sequere.register(User)
sequere.register(Project)
Sequere uses the same concepts as Django Admin, so if you have already used it, you will not be lost.
You can now use Sequere like any other application, let's play with it:
>>> from sequere.models import (follow, unfollow, get_followings_count, is_following,
get_followers_count, get_followers, get_followings)
>>> from myapp.models import User, Project
>>> user = User.objects.create(username='thoas')
>>> project = Project.objects.create(name='La classe americaine')
>>> follow(user, project) # thoas will now follow "La classe americaine"
>>> is_following(user, project)
True
>>> get_followers_count(project)
1
>>> get_followings_count(user)
1
>>> get_followers(user)
[]
>>> get_followers(project)
[(<User: thoas>, datetime.datetime(2013, 10, 25, 4, 41, 31, 612067))]
>>> get_followings(user)
[(<Project: La classe americaine, datetime.datetime(2013, 10, 25, 4, 41, 31, 612067))]
If you are as lazy as me to provide the original instance in each sequere calls, use SequereMixin
# models.py
from django.db import models
from sequere.mixin import SequereMixin
class User(SequereMixin, models.Model):
username = models.Charfield(max_length=150)
class Project(SequereMixin, models.Model):
name = models.Charfield(max_length=150)
Now you can use calls directly from the instance:
>>> from myapp.models import User, Project
>>> user = User.objects.create(username='thoas')
>>> project = Project.objects.create(name'La classe americaine')
>>> user.follow(project) # thoas will now follow "La classe americaine"
>>> user.is_following(project)
True
>>> project.get_followers_count()
1
>>> user.get_followings_count()
1
>>> user.get_followers()
[]
>>> project.get_followers()
[(<User: thoas>, datetime.datetime(2013, 10, 25, 4, 41, 31, 612067))]
>>> user.get_followings()
[(<Project: La classe americaine, datetime.datetime(2013, 10, 25, 4, 41, 31, 612067))]
So much fun!
A database backend to store your follows in you favorite database using the Django's ORM.
To use this backend you will have to add sequere.backends.database
to your INSTALLED_APPS
INSTALLED_APPS = (
'sequere',
'sequere.backends.database',
)
The follower will be identified by the couple (from_identifier, from_object_id) and the following by (to_identifier, to_object_id).
Each identifiers are taken from the registry. For example, if you want to create a custom identifier key from a model you can customized it like so:
# sequere_registry.py
from myapp.models import Project
from sequere.base import ModelBase
import sequere
class ProjectSequere(ModelBase):
identifier = 'projet' # the french way ;)
sequere.registry(Project, ProjectSequere)
We are using exclusively Sorted Sets in this Redis implementation.
Create a uid for a new resource
INCR sequere:global:uid => 1 SET sequere:uid:{identifier}:{id} 1 HMSET sequere:uid::{id} identifier {identifier} object_id {id}
Store followers count
INCR sequere:uid:{to_uid}:followers:count => 1 INCR sequere:uid:{to_uid}:followers:{from_identifier}:count => 1
Store followings count
INCR sequere:uid:{from_uid}:followings:count => 1 INCR sequere:uid:{from_uid}:followings:{to_identifier}:count => 1
Add a new follower
ZADD sequere:uid:{to_uid}:followers {from_uid} {timestamp} ZADD sequere:uid:{to_uid}:followers:{from_identifier} {from_uid} {timestamp}
Add a new following
ZADD sequere:uid:{from_uid}:followings {to_uid} {timestamp} ZADD sequere:uid:{from_uid}:followings{to_identifier} {to_uid} {timestamp}
Retrieve the followers uids
ZRANGEBYSCORE sequere:uid:{uid}:followers -inf +inf
Retrieve the followings uids
ZRANGEBYSCORE sequere:uid:{uid}:followings =inf +inf
With this implementation you can retrieve your followers ordered
ZREVRANGEBYSCORE sequere:uid:{uid}:followers +inf -inf
The timeline engine is directly based on sequere
resources system.
A Timeline
is basically a list of Action
.
An Action
is represented by:
actor
which is the actor of the actionverb
which is the action nametarget
which is the target of the action (not required)date
which is the date when the action has been done
You have to follow installation instructions of sequere
first before installing
sequere.contrib.timeline
.
Add sequere.contrib.timeline
to your INSTALLED_APPS
INSTALLED_APPS = (
'sequere.contrib.timeline',
)
sequere.contrib.timeline
requires celery to work properly,
so you will have to install it.
You have to register your actions based on your resources, for example
# sequere_registry.py
from .models import Project, User
from sequere.contrib.timeline import Action
from sequere import register
from sequere.base import ModelBase
# actions
class JoinAction(Action):
verb = 'join'
class LikeAction(Action):
verb = 'like'
# resources
class ProjectSequere(ModelBase):
identifier = 'project'
class UserSequere(ModelBase):
identifier = 'user'
actions = (JoinAction, LikeAction, )
# register resources
register(User, UserSequere)
register(Project, ProjectSequere)
Now we have registered our actions we can play with the timeline API
>>> from sequere.models import (follow, unfollow)
>>> from sequere.contrib.timeline import Timeline
>>> from myapp.models import User, Project
>>> from myapp.sequere_registry import JoinAction, LikeAction
>>> thoas = User.objects.create(username='thoas')
>>> project = Project.objects.create(name='La classe americaine')
>>> timeline = Timeline(thoas) # create a timeline
>>> timeline.save(JoinAction(actor=thoas)) # save the action in the timeline
>>> timeline.get_private()
[<JoinAction: thoas join>]
>>>: timeline.get_public()
[<JoinAction: thoas join>]
When the resource is the actor of its own action then we push the action both in private and public timelines.
Now we have to test the system with the follow process
>>> newbie = User.objects.create(username='newbie')
>>> follow(newbie, thoas) # newbie is now following thoas
>>> Timeline(newbie).get_private() # thoas actions now appear in the private timeline of newbie
[<JoinAction: thoas join>]
>>> Timeline(newbie).get_public()
[]
When A is following B we copy actions of B in the private timeline of A, celery is needed to handle these asynchronous tasks.
>>> unfollow(newbie, thoas)
>>> Timeline(newbie).get_private()
[]
When A is unfollowing B we delete the actions of B in the private timeline of A.
As you may have noticed the JoinAction
is an action which does not need a target,
some actions will need target, sequere.contrib.timeline
provides a quick way
to query actions for a specific target.
>>> timeline = Timeline(thoas)
>>> timeline.save(LikeAction(actor=thoas, target=project))
>>> timeline.get_private()
[<JoinAction: thoas join>, <LikeAction: thoas like La classe americaine>]
>>> timeline.get_private(target=Project) # only retrieve actions with Project resource as target
[<LikeAction: thoas like La classe americaine>]
>>> timeline.get_private(target='project') # only retrieve actions with 'project' identifier as target
[<LikeAction: thoas like La classe americaine>]
The backend used to store follows
Defaults to sequere.backends.database.DatabaseBackend
.
A dictionary of parameters to pass to the backend, for example with redis:
SEQUERE_BACKEND = 'sequere.backends.redis.RedisBackend'
SEQUERE_BACKEND_OPTIONS = {
'client_class': 'myproject.myapp.mockup.Connection',
'options': {
'host': 'localhost',
'port': 6379,
'db': 0,
},
'prefix': 'prefix-used:'
}
The (optional) prefix to be used for the key when storing in the Redis database.
Defaults to sequere:
.
The backend used to store follows
Defaults to sequere.contrib.timeline.redis.RedisBackend
.
A dictionary of parameters to pass to the backend, for example with redis:
SEQUERE_TIMELINE_BACKEND = 'sequere.contrib.timeline.redis.RedisBackend'
SEQUERE_TIMELINE_BACKEND_OPTIONS = {
'client_class': 'myproject.myapp.mockup.Connection',
'options': {
'host': 'localhost',
'port': 6379,
'db': 0,
},
'prefix': 'prefix-used:'
}
The (optional) prefix to be used for the key when storing in the Redis database.
Defaults to sequere:timeline:
.
- haplocheirus: a Redis backed storage engine for timelines written in Scala
- Case study from Redis documentation: write a twitter clone
- Amico: relationships backed by Redis
- django-constance: a multi-backends settings management application