channels-api
channels-api copied to clipboard
RESTful Websocket APIs with Django Rest Framework and Channels
Channels API
.. image:: https://travis-ci.org/linuxlewis/channels-api.svg?branch=master :target: https://travis-ci.org/linuxlewis/channels-api
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 <https://vimeo.com/194110172#t=3033>
__ or PyBay 2016 <https://www.youtube.com/watch?v=HzC_pUhoW0I>
__
Table of Contents
-
Getting Started <#getting-started>
__ -
ResourceBinding <#resourcebinding>
__ -
Subscriptions <#subscriptions>
__ -
Custom Actions <#custom-actions>
__ -
Permissions <#permissions>
__
How does it work?
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
.. code:: javascript
var msg = {
stream: "users",
payload: {
action: "create",
data: {
email: "[email protected]",
password: "password"
}
}
}
ws.send(JSON.stringify(msg))
Why?
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.
Getting Started
This tutorial assumes you're familiar with channels and have completed
the Getting Started <https://channels.readthedocs.io/en/latest/getting-started.html>
__
- Add
channels_api
to requirements.txt
.. code:: bash
pip install channels_api
- Add
channels_api
toINSTALLED_APPS
.. code:: python
INSTALLED_APPS = (
'rest_framework',
'channels',
'channels_api'
)
- Add your first resource binding
.. code:: python
# 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
WebsocketDemultiplexer
to yourchannel_routing
.. code:: python
# 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.
.. code:: javascript
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
.
.. code:: python
# proj/urls.py
from django.conf.urls import url, include
urlpatterns = [
url(r'^channels-api/', include('channels_api.urls'))
]
ResourceBinding
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.
List Pagination
Pagination is handled by django.core.paginator.Paginator
You can configure the DEFAULT_PAGE_SIZE
by overriding the settings.
.. code:: python
settings.py
CHANNELS_API = { 'DEFAULT_PAGE_SIZE': 25 }
Subscriptions
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
.. code:: javascript
// 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" } } }
Custom Actions
To add your own custom actions, use the detail_action
or list_action
decorators.
.. code:: python
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, 200
Then pass the method name as "action" in your message
.. code:: javascript
// 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" } }
Permissions
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
.. code:: python
# settings.py
CHANNELS_API = {
'DEFAULT_PERMISSION_CLASSES': ('channels_api.permissions.AllowAny',)
}
You can also configure the permission classes on a ResourceBinding
itself like so
.. code:: python
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
.
.. code:: python
from channels_api.permissions import BasePermission
class MyPermission(BasePermission):
def has_permission(self, user, action, pk):
if action == "CREATE":
return True
return False