Channels API exposes a RESTful Streaming API over WebSockets using channels. It provides a ResourceBinding which is comparable to Django Rest Framework's ModelViewSet. It is based on DRF serializer classes.
It requires Python 2.7 or 3.x, Channels <=1.1.8.1, Django <=1.11, and Django Rest Framework 3.x
You can learn more about channels-api from my talk at the SF Django Meetup or PyBay 2016
The API builds on top of channels' WebsocketBinding class. It works by having the client send a stream and payload parameters. This allows us to route messages to different streams (or resources) for a particular action. So POST /user would have a message that looks like the following
var msg = { stream: "users", payload: { action: "create", data: { email: "test@example.com", password: "password" } } } ws.send(JSON.stringify(msg))You're already using Django Rest Framework and want to expose similar logic over WebSockets.
WebSockets can publish updates to clients without a request. This is helpful when a resource can be edited by multiple users across many platforms.
This tutorial assumes you're familiar with channels and have completed the Getting Started
- Add
channels_apito requirements.txt
pip install channels_api- Add
channels_apitoINSTALLED_APPS
INSTALLED_APPS = ( 'rest_framework', 'channels', 'channels_api' )- Add your first resource binding
# polls/bindings.py from channels_api.bindings import ResourceBinding from .models import Question from .serializers import QuestionSerializer class QuestionBinding(ResourceBinding): model = Question stream = "questions" serializer_class = QuestionSerializer queryset = Question.objects.all()- Add a
WebsocketDemultiplexerto yourchannel_routing
# proj/routing.py from channels.generic.websockets import WebsocketDemultiplexer from channels.routing import route_class from polls.bindings import QuestionBinding class APIDemultiplexer(WebsocketDemultiplexer): consumers = { 'questions': QuestionBinding.consumer } channel_routing = [ route_class(APIDemultiplexer) ]That's it. You can now make REST WebSocket requests to the server.
var ws = new WebSocket("ws://" + window.location.host + "/") ws.onmessage = function(e){ console.log(e.data) } var msg = { stream: "questions", payload: { action: "create", data: { question_text: "What is your favorite python package?" }, request_id: "some-guid" } } ws.send(JSON.stringify(msg)) // response { stream: "questions", payload: { action: "create", data: { id: "1", question_text: "What is your favorite python package" } errors: [], response_status: 200 request_id: "some-guid" } }- Add the channels debugger page (Optional)
This page is helpful to debug API requests from the browser and see the response. It is only designed to be used when DEBUG=TRUE.
# proj/urls.py from django.conf.urls import url, include urlpatterns = [ url(r'^channels-api/', include('channels_api.urls')) ]By default the ResourceBinding implements the following REST methods:
createretrieveupdatelistdeletesubscribe
See the test suite for usage examples for each method.
Pagination is handled by django.core.paginator.Paginator
You can configure the DEFAULT_PAGE_SIZE by overriding the settings.
# settings.py CHANNELS_API = { 'DEFAULT_PAGE_SIZE': 25 }Subscriptions are a way to programmatically receive updates from the server whenever a resource is created, updated, or deleted
By default channels-api has implemented the following subscriptions
- create a Resource
- update any Resource
- update this Resource
- delete any Resource
- delete this Resource
To subscribe to a particular event just use the subscribe action with the parameters to filter
// get an event when any question is updated var msg = { stream: "questions", payload: { action: "subscribe", data: { action: "update" } } } // get an event when question(1) is updated var msg = { stream: "questions", payload: { action: "subscribe", pk: "1", data: { action: "update" } } }To add your own custom actions, use the detail_action or list_action decorators.
from channels_api.bindings import ResourceBinding from channels_api.decorators import detail_action, list_action from .models import Question from .serializers import QuestionSerializer class QuestionBinding(ResourceBinding): model = Question stream = "questions" serializer_class = QuestionSerializer queryset = Question.objects.all() @detail_action() def publish(self, pk, data=None, **kwargs): instance = self.get_object(pk) result = instance.publish() return result, 200 @list_action() def report(self, data=None, **kwargs): report = self.get_queryset().build_report() return report, 200Then pass the method name as "action" in your message
// run the publish() custom action on Question 1 var msg = { stream: "questions", payload: { action: "publish", data: { pk: "1" } } } // run the report() custom action on all Questions var msg = { stream: "questions", payload: { action: "report" } }Channels API offers a simple permission class system inspired by rest_framework. There are two provided permission classes: AllowAny and IsAuthenticated.
To configure permissions globally use the setting DEFAULT_PERMISSION_CLASSES like so
# settings.py CHANNELS_API = { 'DEFAULT_PERMISSION_CLASSES': ('channels_api.permissions.AllowAny',) }You can also configure the permission classes on a ResourceBinding itself like so
from channels_api.permissions import IsAuthenticated class MyBinding(ResourceBinding): permission_classes = (IsAuthenticated,)Lastly, to implement your own permission class, override the has_permission of BasePermission.
from channels_api.permissions import BasePermission class MyPermission(BasePermission): def has_permission(self, user, action, pk): if action == "CREATE": return True return False