From d9e8fb703241e200d3709e4445c3a0039a462571 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Mon, 18 Jul 2016 22:23:33 -0400 Subject: [PATCH] Docs updates --- docs/binding.rst | 142 +++++++++++++++++++++++++++++++++++++++ docs/getting-started.rst | 2 +- docs/index.rst | 1 + 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 docs/binding.rst diff --git a/docs/binding.rst b/docs/binding.rst new file mode 100644 index 0000000..e795d6a --- /dev/null +++ b/docs/binding.rst @@ -0,0 +1,142 @@ +Data Binding +============ + +The Channels data binding framework automates the process of tying Django +models into frontend views, such as javascript-powered website UIs. It provides +a quick and flexible way to generate messages on Groups for model changes +and to accept messages that chanes models themselves. + +The main target for the moment is WebSockets, but the framework is flexible +enough to be used over any protocol. + +What does data binding allow? +----------------------------- + +Data binding in Channels works two ways: + +* Outbound, where model changes made through Django are sent out to listening + clients. This includes creation, update and deletion of instances. + +* Inbound, where a standardised message format allow creation, update and + deletion of instances to be made by clients sending messages. + +Combined, these allow a UI to be designed that automatically updates to +reflect new values and reflects across clients. A live blog is easily done +using data binding against the post object, for example, or an edit interface +can show data live as it's edited by other users. + +It has some limitations: + +* Signals are used to power outbound binding, so if you change the values of + a model outside of Django (or use the ``.update()`` method on a QuerySet), + the signals are not triggered and the change will not be sent out. You + can trigger changes yourself, but you'll need to source the events from the + right place for your system. + +* The built-in serializers are based on the built-in Django ones and can only + handle certain field types; for more flexibility, you can plug in something + like the Django REST Framework serializers. + +Getting Started +--------------- + +A single Binding subclass will handle outbound and inbound binding for a model, +and you can have multiple bindings per model (if you want different formats +or permission checks, for example). + +You can inherit from the base Binding and provide all the methods needed, but +we'll focus on the WebSocket JSON variant here, as it's the easiest thing to +get started and likely close to what you want. Start off like this:: + + from django.db import models + from channels.binding.websockets import WebsocketBinding + + class IntegerValue(models.Model): + + name = models.CharField(max_length=100, unique=True) + value = models.IntegerField(default=0) + + class IntegerValueBinding(WebsocketBinding): + + model = IntegerValue + + def group_names(self, instance, action): + return ["intval-updates"] + + def has_permission(self, user, action, pk): + return True + +This defines a WebSocket binding - so it knows to send outgoing messages +formatted as JSON WebSocket frames - and provides the two methods you must +always provide: + +* ``group_names`` returns a list of groups to send outbound updates to based + on the model and action. For example, you could dispatch posts on different + liveblogs to groups that included the parent blog ID in the name; here, we + just use a fixed group name. + +* ``has_permission`` returns if an inbound binding update is allowed to actually + be carried out on the model. We've been very unsafe and made it always return + ``True``, but here is where you would check against either Django's or your + own permission system to see if the user is allowed that action. + +For reference, ``action`` is always one of the unicode strings ``"create"``, +``"update"`` or ``"delete"``. + +Just adding the binding like this in a place where it will be imported will +get outbound messages sending, but you still need a Consumer that will both +accept incoming binding updates and add people to the right Groups when they +connect. For that, you need the other part of the WebSocket binding module, +the demultiplexer:: + + from channels.binding.websockets import WebsocketBindingDemultiplexer + from .models import IntegerValueBinding + + class BindingConsumer(WebsocketBindingDemultiplexer): + + bindings = [ + IntegerValueBinding, + ] + + def connection_groups(self): + return ["intval-updates"] + +This class needs two things set: + +* ``bindings``, a list of Binding subclasses (the ones from before) of the + models you want this to receive messages for. The socket will take care of + looking for what model the incoming message is and giving it to the correct + Binding. + +* ``connection_groups``, a list of groups to put people in when they connect. + This should match the logic of ``group_names`` on your binding - we've used + our fixed group name again. + +Tie that into your routing and you're ready to go:: + + from channels import route_class + from .consumers import BindingConsumer + + channel_routing = [ + route_class(BindingConsumer, path="^binding/"), + ] + + +Frontend Considerations +----------------------- + +Channels is a Python library, and so does not provide any JavaScript to tie +the binding into your JavaScript (though hopefully some will appear over time). +It's not very hard to write your own; messages are all in JSON format, and +have a key of ``action`` to tell you what's happening and ``model`` with the +Django label of the model they're on. + + +Custom Serialization/Protocols +------------------------------ + +Rather than inheriting from the ``WebsocketBinding``, you can inherit directly +from the base ``Binding`` class and implement serialization and deserialization +yourself. Until proper reference documentation for this is written, we +recommend looking at the source code in ``channels/bindings/base.py``; it's +reasonably well-commented. diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 4e2838b..b52f723 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -60,7 +60,7 @@ Here's what that looks like:: "ROUTING": "myproject.routing.channel_routing", }, } -.. + :: # In routing.py diff --git a/docs/index.rst b/docs/index.rst index 6b87e62..e34db68 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -31,6 +31,7 @@ Contents: deploying generics routing + binding backends testing cross-compat