rohrpost – a protocol for Django Channels¶
Welcome to the rohrpost documentation!
rohrpost is a small library that aims to make protocol development for Django’s sub-project channels (which provides WebSockets capabilities) easy and fun. We also have a client library called rohrpost.js.
It features a light weight, JSON based protocol, including an exemplary handler implementing a ping/pong method. It also comes with a variety of helper functions, and Django model mixins that allow to automatically send updates when any user updates, deletes, or creates an object.
Protocol¶
The rohrpost protocol sits on top of channels. rohrpost expects the messages to be valid JSON with
- An
id
field that rohrpost sends back in the response. - A
type
field that contains a string defining the message type (and hence, which handler will process the message). - An optional
data
field containing whatever data rohrpost should pass to the handler. Please note that rohrpost will pass all request data to the handler, and the naming of thedata
field is convention, not a rule.
A typical message would look like this:
{
"id": 1234,
"type": "ping",
"data": [1, 2, 3, 4]
}
Both server and client messages look like this. When the server receives a
message, it will hand off the message to the appropriate handler named in the
type
field. If there is no such handler or the handler fails, the server
responds with a message containing at least an error
field, and optionally
other data in the data
field.
{
"id": 1234,
"error": "No registered handler called 'ping'."
}
Installation¶
From the command line:
pip install rohrpost
Or add this line to your requirements.txt:
rohrpost==3.*
Routing¶
Once you have installed rohrpost, you’ll need to add a rohrpost consumer to your routing.py. You can find details on this in Channels’ routing documentation.
from django.urls import path
from rohrpost.sync_consumer import SyncRohrpostConsumer
websocket_urlpatterns = [
path('ws/rohrpost/', SyncRohrpostConsumer),
]
If you use your own consumer implementation, make sure to a method
rohrpost_message
that handles messages sent over a channel layer to be
broadcast to all members of the group. Further information is available in
Channels documentation on Channel layers.
Adding Handlers¶
rohrpost provides a decorator rohrpost_handler
, that accepts both a string
and a list to register a method as the handler for incoming messages. This is
how the ping method works, that rohrpost provides out of the box:
from rohrpost.message import send_message
from rohrpost.registry import rohrpost_handler
@rohrpost_handler('ping')
def handle_ping(consumer, request):
send_message(
consumer=consumer,
message_id=request["id"],
handler="pong",
data=request.get("data"),
)
Using the mixins¶
We have four relevant Django model mixins in rohrpost.mixins
:
NotifyOnCreate
, NotifyOnUpdate
, NotifyOnDelete
and
NotifyOnChange
which inherits from the previous three classes.
When using these mixins, you’ll need to set some fields or fill in some methods:
get_group_name(self, message_type)
orgroup_name
, with the method having preference over the attribute. This method or attribute should return a string that denotes the group receiving the message. All users in that group will receive a message. This gives you the possibility to build per-object, per-class or global groups. If neither the message nor the attribute are present, the group name is the lower cased class name combined with the object’s ID: f’{object.__class__.__name__.lower()}-{object.pk}’get_push_notification_data(self, updated_fields, message_type)
returning a dictionary (or any data structure) containing the data you wish to send to the client. rohrpost will update the serialized object updated with anupdated_fields
attribute with a list if you do not setupdated_fields
inget_push_notification_data()
and if you set it when calling thesave()
method that lead to the notification. The fallback value if you do not fill in this method is{"id": obj.id}
.
The message will look like this:
.. code-block:: JSON
- {
“id”: <some id>, “type”: “subscription-update”, “data”: {
“type”: <create|update|delete>, “group”: <group-name>, “object”: <serialized object>,}
}
You will have to put users who should receive these notifications in the group
specified by get_group_name
or group_name
. Since authentication and
registration works differently in every use case, rohrpost does not include a
standard handler for this.
Utility functions¶
rohrpost provides a few helper functions for message sending in
rohrpost.message
:
rohrpost.message.send_message
consumer
: An instance ofchannels.generic.websocket.WebsocketConsumer
that represents the client the message is sent to (required).handler
: The string identifying your handler (required).message_id
: The message ID (any simple datatype allowed). If you do not provide any, an integer will be randomly chosen.error
: Include an error message or error contentdata
: A dict that will appear in the message asdata
(converted to a JSON object).
rohrpost.message.send_error
sends an error message explicitly, takes the same arguments assend_message
.rohrpost.messages.send_to_group
sends a message to a specific group.
Migrating from rohrpost v2¶
Make sure you are using Python 3.6+ and Channels 3+.
Migrating from rohrpost v1¶
Follow the migration guide from Channels 1 to 2.
Adjust the routing in your application. What was before
channel_routing = [ route('websocket.receive', handle_rohrpost_message, path=r'^/ws/rohrpost/$'), ]
should now be
websocket_urlpatterns = [ path('ws/rohrpost/', SyncRohrpostConsumer), ]
Your handlers will no longer receive
message
as a first argument, but an instance ofchannels.generic.websocket.WebsocketConsumer
. You can pass it directly to the utility functions as before withmessage
.To add clients to a group, change
Group(group_name).add(message.reply_channel)
toconsumer.add_to_group(group_name)
when usingrohrpost.sync_consumer.SyncRohrpostConsumer
. Otherwise we recommend to check out our consumer’s implementation and read about Groups in Channels 2.To send messages to a group you can use the new utility function
rohrpost.messages.send_to_group
.You can no longer pass
close
to the utility functions. Instead callconsumer.close()
after sending a message to close the connection.