A Django API app which supports WebSockets (long polling might be added). It aims to support the Django REST framework out of the box (with limitations).
I coded it for one of our projects and I thought why not make it available to others. However, I'm not very happy about the current implementation because it doesn't pay respect to all REST framework attributes/methods which might be quite buggy. Maybe we can make something nice out of it or it inspires somebody to make something better. Please be careful when you use it.
Warning
Please note that this package is in development and not suitable for production!
- Subscribe to changes
 - Creation
 - Updates
 - Deletion
 
- Listing
 - Gets
 - Options
 - Any format other than JSON
 
Normally the user object is set at the establishment of a scope and won't change if the user object changes or the user logs in/out. You can use the GroupUserConsumer in connection with the provided signals to disconnect a channel accordingly. To accomplish this the GroupUserConsumer tracks all channels which a user has.
I plan to separate the REST framework specific implementations from the general stuff in order to provide an API class with base functions like subscription.
Some features you might find helpful:
AuthWebsocketCommunicatorlogs in users automatically. Use it like this:communicator = await AuthWebsocketCommunicator(Consumer, 'path', user=user)
The class includes some helpful asynchronous methods, they don't change the scope, though:
login(**credentials)force_login(user, backend=None)logout()
create_user(username=None, password='pw', **kwargs)returns a user object. Usage:user = await create_user(first_name='Alex')
Fixtures
userandadmin. Usage:async def test_some_stuff(user, admin): result = do_something(user=user) assert result.owner == user.username assert result.supervisor == admin.username
- Python 3.5 and higher
 - Django 2.0 (Django 1.11 might also work but is not tested)
 - Channels 2.1
 - Django REST framework 3.7 (if you want to use it)
 
For production probably:
- channels_redis
 - Redis
 
Get real time API:
pip install git+https://github.com/dgilge/django-realtime-api.git#egg=django-realtime-api
The package is not available on PyPI yet. If there are several people who want to use it I will make it available. Just let me know.
Add
realtime_apito your INSTALLED_APPS setting like this:INSTALLED_APPS = [ ... 'channels', 'rest_framework', 'realtime_api', ]
Create a consumer for each Django REST framework view (or viewset) you want to have a WebSocket end point for.
streamis the first part of the URL. You may have them in aconsumers.pymodule in your app. For instance:from realtime_api.consumers import APIConsumer class MyRealTimeConsumer(APIConsumer): view = MyAPIView stream = 'my-api'
Register the consumers like this:
from realtime_api.consumers import APIDemultiplexer APIDemultiplexer.register(MyRealTimeConsumer, MyOtherConsumer)
Define a routing (for instance in
routing.pyin your project folder, whereurls.pylives, too):from channels.routing import ProtocolTypeRouter, URLRouter from channels.security.websocket import AllowedHostsOriginValidator from django.conf.urls import url from realtime_api.consumers import APIDemultiplexer application = ProtocolTypeRouter({ 'websocket': AllowedHostsOriginValidator( URLRouter([ url('^api/$', APIDemultiplexer), ]) ), })
You might also want to add the
AuthMiddlewareStack. More details are available in the Channels documentation.Update your
settings.pyto meet the Channels requirements:CHANNEL_LAYERS = { 'default': { # Not for production! 'BACKEND': 'channels.layers.InMemoryChannelLayer', }, } ASGI_APPLICATION = 'myproject.routing.application'
Start the development server with
python manage.py runserverand you are ready to communicate with the API endpoint. See the tutorial in the Channels documentation for a simple implementation how to do that. Read on for details.One thing you probably want to override is
get_group_name().
Alternatively to the path explained below you can send an equal stream value within your JSON object.
Note
One of these implementations (path/stream value) will probably be removed in the future.
Send a JSON string to /<stream>/subscribe/ with any field you have specified in your serializer you want to receive updates for:
{
  "id": 1
}Now you will receive any* changes made to the object in an almost equal (see limitations) JSON structure as you receive it in a GET response by the Django REST framework.
In order to cancel the subscription send the same JSON object to /<stream>/unsubscribe/.
You can also define other lookups by including a subscription_field_mapping in your consumer. For instance:
subscription_field_mapping = {
    'ids': 'pk__in',
    'name': 'name__istartswith',
}*= This is done inside the consumer or via Django's signals and has therefore following side effect.
Warning
You do not receive changes performed by update or bulk operations.
Send a JSON string to /<stream>/create in the same format as you use it in the Djang REST framework.
Send a JSON string to /<stream>/update/<pk>/.
Send an empty JSON string ({}) to /<stream>/delete/<pk>/.
Note
The APIConsumer is not a Channels consumer. The reason for this name is that I plan to convert it to a Channels consumer when demultiplexing is implemented.
Some things you might to override:
Required, a subclass of APIView. For instance ModelViewSet.
Required, the first part of the path.
Required if you don't include a queryset in your view.
Here you can specify the actions (as tuple or list) you want to allow if they differ from the allowed methods in the view. Possible values are create, update, delete (equivalent to the methods POST, PUT/PATCH, DELETE).
Defaults to pk.
If you don't want to use the view's serializer_class.
The default implementation is a group for each consumer's stream and object's pk.
Groups are used for broadcasting. When an object changes it will be serialized and sent to all users (channels) in a group.
Probably you desire wider groups. For instance you have a Comment model with a foreign key to the Topic model. In order to create one group for each Topic you could use:
def get_group_name(self, obj):
    return '{}-{}'.format(self.stream, obj.topic_id)If you need a special authentication.
A Channels consumer instance has a lifetime equal to the WebSocket connection time. I wanted to retain this design. Therefore your view is initialized on connection and remains for the whole scope. However, this makes the implementation not easier.
- Multiple view attributes and methods don't have any effect in the consumer. Check if you override them in your view and customize your consumer where needed! For details see below.
 - The view's request instance is a fake and has only a user attribute. (Permissions get the method additionally.)
 - URLs in the JSON objects are relative.
 
Your view might be suitable as it is.
However, if you overrode perform_create or perform_update your methods should return the saved instance. Alternatives are to override the methods of the same names in your APIConsumer subclass or to include the immediate_broadcast attribute and set it to False.
They are not used directly but via the view's methods.
parser_classespermission_classesquerysetrenderer_classesserializer_classsettings
- (
get_authenticate_header) - (
get_authenticators) get_exception_handlerget_exception_handler_contextget_parsersget_permissionsget_querysetget_renderersget_serializer-> Implement that correctly!get_serializer_classget_serializer_context-> Implement that correctly!handle_exceptionperform_createperform_destroyperform_updateraise_uncaught_exception
allowed_methodsauthentication_classescontent_negotiation_classdefault_response_headersfilter_backends(!)http_method_nameslookup_field-> Maybe use it in the consumer?lookup_url_kwarg-> Maybe use it in the consumer?metadata_classpagination_classpaginatorschemathrottle_classesversioning_class
Many of these are not used because of not having a proper request instance.
_allowed_methodsas_viewcheck_object_permissionscheck_permissionscheck_throttlescreatedestroydetermine_versiondispatchgetpostputpatchdeletefilter_queryset(!)finalize_responseget_content_negotiatorget_format_suffixget_object(!)get_paginated_responseget_parser_contextget_renderer_contextget_success_headersget_throttlesget_view_descriptionget_view_namehttp_method_not_allowedinitialinitialize_requestlistoptionspaginate_querysetpartial_updateperform_authenticationperform_content_negotiationpermission_deniedretrievethrottledupdate
You can have a look at cdrf.co on how they play together.
- JSON object design decisions
 - Separate the DRF specific implementations from the other API consumer code
 - Support nested routing (DRF extensions)
 - Support Django Guardian (e.g. AnonymousUser in the login signal)
 - Checking permissions (e.g. at subscription) allows you to get information whether it is in the database (you get a 403) or not (you get a 404). Is this a security leak (e.g. by cancelling subscription with 
{'email': '[email protected]'})?