Usage¶
Prerequisites¶
Install and set up django-channels and channel layers. A
CHANNEL_LAYERS
configuration is necessary to enable the use of consumer
channel_name
properties, to allow storing groups of channels by name.
Managing presences¶
In django-channels-presence
, two main models track the presence of channels in a room:
Room
: represents a collection of channels that are in the same “room”. It has a single property,channel_name
, which is the “group name” for the channel layer group to which its members are added.Presence
: represents an association of a single consumer channel name with aRoom
, as well as the associated authUser
if any.
To keep track of presence, the following steps need to be taken:
- Add channels to a
Room
when users successfully join. - Remove channels from the
Room
when users leave or disconnect. - Periodically update the
last_seen
timestamp for clients’Presence
. - Prune stale
Presence
records that have old timestamps by running periodic tasks. - Listen for changes in presence to update application state or notify other users
1. Adding channels to rooms¶
Add a user to a Room
using the manager add
method. For example, this
consumer adds the connecting user to a room on connection. This will trigger the channels_presence.signals.presence_changed signal:
from channels.generic.websocket import WebsocketConsumer
from channels_presence.models import Room
class MyConsumer(WebsocketConsumer):
def connect(self):
super().connect()
Room.objects.add("some_room", self.channel_name, self.scope["user"])
Channel names could be added to a room at any stage – for example, in the
connect
handler, the receive
handler, or
wherever else makes sense. In addition to handling Room
and Presence
models, Room.objects.add
takes care of adding the channel name to the named
channel layer group.
2. Removing channels from rooms¶
Remove a consumer’s channel from a Room using the manager remove
method.
For example, this handler for disconnect
removes the user from the room on
disconnect. This will trigger the presence_changed
signal:
from channels.generic.websocket import WebsocketConsumer
from channels_presence.models import Room
class MyConsumer(WebsocketConsumer):
def disconnect(self, close_code):
Room.objects.remove("some_room", self.channel_name)
Room.objects.remove
takes care of removing the specified channel name from
the channels group.
For convenience, channels_presence.decorators.remove_presence
is a decorator to accomplish the same thing:
from channels.generic.websocket import WebsocketConsumer
from channels_presence.decorators import remove_presence
class MyConsumer(WebsocketConsumer):
@remove_presence
def disconnect(self, close_code):
pass
3. Updating the “last_seen” timestamp¶
In order to keep track of which sockets are actually still connected, we must
regularly update the last_seen
timestamp for all present connections, and
periodically remove connections from rooms if they haven’t been seen in a
while.
from channels.generic.websocket import WebsocketConsumer
from channels_presence.models import Presence
class MyConsumer(WebsocketConsumer):
def receive(self, close_code):
Presence.objects.touch(self.channel_name)
For convenience, the channels_presence.decorators.touch_presence
decorator accomplishes the same thing:
from channels.generic.websocket import WebsocketConsumer
from channels_presence.decorators import touch_presence
# handler for "websocket.receive"
class MyConsumer(WebsocketConsumer):
@touch_presence
def receive(self, text_data=None, bytes_data=None):
...
This will update the last_seen
timestamp any time any message is received
from the client. To ensure that the timestamp remains current, clients should
send a periodic “heartbeat” message if they aren’t otherwise sending data but
should be considered to still be present.
3a. Heartbeats¶
To allow efficient updates, if a client sends a message which is just the JSON
encoded string "heartbeat"
, the touch_presence
decorator will stop
processing of the message after updating the timestamp. The decorator can be
placed first in a decorator chain in order to stop processing of heartbeat
messages prior to other costly steps.
If updating last_seen
on every message is too costly, an alternative to
using the touch_presence
decorator is to manually call
Presence.objects.touch
whenever desired. For example, this updates
last_seen
only when the literal message "heartbeat"
is received:
from channels.generic.websocket import WebsocketConsumer
from channels_presence.models import Presence
class MyConsumer(WebsocketConsumer):
def receive(self, text_data=None, bytes_data=None):
...
if text_data == '"heartbeat"':
Presence.objects.touch(self.channel_name)
To ensure that an active connection is not marked as stale, clients should
occasionally send "heartbeat"
messages:
// client.js
setInterval(function() {
socket.send(JSON.stringify("heartbeat"));
}, 30000);
The frequency should be adjusted to occur before the maximum age for
last-seen presence, set with settings.CHANNELS_PRESENCE_MAX_AGE
(default 60
seconds).
4. Pruning stale connections¶
In order to remove connections whose timestamps have expired, we need to
periodically launch a cleaning task. This can be accomplished with
Room.objects.prune_presences()
. For convenience, this is implemented as a
celery task which can be called with celery beat:
channels_presence.tasks.prune_presences
. The management command
./manage.py prune_presences
is also available for calling from cron.
A second maintenance command, Room.objects.prune_rooms()
, removes any Room
models that have no connections. This is also available as the celery task
channels_presence.tasks.prune_rooms
and management command
./manage.py prune_rooms
.
See the documentation for periodic tasks in celery details on configuring celery beat with Django. Here is one example:
# settings.py
CELERYBEAT_SCHEDULE = {
'prune-presence': {
'task': 'channels_presence.tasks.prune_presences',
'schedule': timedelta(seconds=60)
},
'prune-rooms': {
'task': 'channels_presence.tasks.prune_rooms',
'schedule': timedelta(seconds=600)
}
}
5. Listening for changes in presence¶
Use the channels_presence.signals.presence_changed
signal to be notified when
a user is added or removed from a Room. This is a useful place to define logic
to update other connected clients with the list of present users. See the
API reference for presence_changed for an example.