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:
- create
- retrieve
- update
- list
- delete
- subscribe
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