A generic application to follow a resource with multiple backends (db, redis, etc.).
A timeline engine can be also found in sequere.contrib.timeline
.
This library is compatible with:
- python2.6, django1.5
- python2.6, django1.6
- python2.7, django1.5
- python2.7, django1.6
- python2.7, django1.7
- python3.3, django1.5
- python3.3, django1.6
- python3.3, django1.7
- python3.4, django1.5
- python3.4, django1.6
- python3.4, django1.7
If I'm a liar, you can ping me.
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 to Redis client, e.g.:
SEQUERE_REDIS_CONNECTION = {
'host': 'localhost',
'port': 6379,
'db': 0,
}
Alternatively you can use a URL to do the same:
SEQUERE_REDIS_CONNECTION = 'redis://username:password@localhost:6379/0'
An (optional) dotted import path to a connection to use, e.g.:
SEQUERE_REDIS_CONNECTION_CLASS = 'myproject.myapp.mockup.Connection'
The (optional) prefix to be used for the key when storing in the Redis database.
SEQUERE_REDIS_PREFIX = 'sequere:myproject:'
Defaults to sequere:
.
An (optional) dotted import path to a connection to use, e.g.:
SEQUERE_TIMELINE_CONNECTION_CLASS = 'myproject.myapp.mockup.Connection'
A dictionary of parameters to pass to the to Redis client, e.g.:
SEQUERE_TIMELINE_REDIS_CONNECTION = {
'host': 'localhost',
'port': 6379,
'db': 0,
}
Alternatively you can use a URL to do the same:
SEQUERE_TIMELINE_REDIS_CONNECTION = 'redis://username:password@localhost:6379/0'
The (optional) prefix to be used for the key when storing in the Redis database.
SEQUERE_TIMELINE_REDIS_PREFIX = 'sequere:myproject:timeline'
Defaults to sequere:timeline
.
nydus can be used to scale like a pro.
SEQUERE_TIMELINE_NYDUS_CONNECTION = {
'backend': 'nydus.db.backends.redis.Redis',
'router': 'nydus.db.routers.keyvalue.PartitionRouter',
'hosts': {
0: {'db': 0},
1: {'db': 1},
2: {'db': 2},
}
}
sequere.contrib.timeline
supports both redis-py and nydus.
If this settings is provided in your Django project then nydus will be needed as an additional dependency.
- 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