mirror of
				https://github.com/django/daphne.git
				synced 2025-10-31 07:47:25 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			169 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| 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 change 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, updates and deletion of instances.
 | |
| 
 | |
| * Inbound, where a standardised message format allows 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
 | |
|         stream = "intval"
 | |
|         fields = ["name", "value"]
 | |
| 
 | |
|         @classmethod
 | |
|         def group_names(cls, instance):
 | |
|             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 three things you must
 | |
| always provide:
 | |
| 
 | |
| * ``fields`` is a whitelist of fields to return in the serialized request.
 | |
|   Channels does not default to all fields for security concerns; if you want
 | |
|   this, set it to the value ``["__all__"]``. As an alternative, ``exclude``
 | |
|   acts as a blacklist of fields.
 | |
| 
 | |
| * ``group_names`` returns a list of groups to send outbound updates to based
 | |
|   on the instance. 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. Based on how ``group_names`` changes as the
 | |
|   instance changes, Channels will work out if clients need ``create``,
 | |
|   ``update`` or ``delete`` messages (or if the change is hidden from them).
 | |
| 
 | |
| * ``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"``. You also supply the :ref:`multiplexing`
 | |
| stream name to provide to the client - you must use multiplexing if you
 | |
| use WebSocket data binding.
 | |
| 
 | |
| 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. The WebSocket binding classes use the standard :ref:`multiplexing`,
 | |
| so you just need to use that::
 | |
| 
 | |
|     from channels.generic.websockets import WebsocketDemultiplexer
 | |
|     from .binding import IntegerValueBinding
 | |
| 
 | |
|     class Demultiplexer(WebsocketDemultiplexer):
 | |
| 
 | |
|         consumers = {
 | |
|             "intval": IntegerValueBinding.consumer,
 | |
|         }
 | |
| 
 | |
|         def connection_groups(self):
 | |
|             return ["intval-updates"]
 | |
| 
 | |
| As well as the standard stream-to-consumer mapping, you also need to set
 | |
| ``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. Notice that the binding has a ``.consumer`` attribute;
 | |
| this is a standard WebSocket-JSON consumer, that the demultiplexer can pass
 | |
| demultiplexed ``websocket.receive`` messages to.
 | |
| 
 | |
| Tie that into your routing, and you're ready to go::
 | |
| 
 | |
|     from channels import route_class, route
 | |
|     from .consumers import Demultiplexer
 | |
|     from .models import IntegerValueBinding
 | |
| 
 | |
|     channel_routing = [
 | |
|         route_class(Demultiplexer, path="^/binding/"),
 | |
|     ]
 | |
| 
 | |
| 
 | |
| Frontend Considerations
 | |
| -----------------------
 | |
| 
 | |
| You can use the standard Channels WebSocket wrapper **(not yet available)**
 | |
| to automatically run demultiplexing, and then tie the events you receive into
 | |
| your frontend framework of choice based on ``action``, ``pk`` and ``data``.
 | |
| 
 | |
| .. note::
 | |
| 
 | |
|     Common plugins for data binding against popular JavaScript frameworks are
 | |
|     wanted; if you're interested, please get in touch.
 | |
| 
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 
 | |
| Dealing with Disconnection
 | |
| --------------------------
 | |
| 
 | |
| Because the data binding Channels ships with has no history of events,
 | |
| it means that when a disconnection happens you may miss events that happen
 | |
| during your offline time. For this reason, it's recommended you reload
 | |
| data directly using an API call once connection has been re-established,
 | |
| don't rely on the live updates for critical functionality, or have UI designs
 | |
| that cope well with missing data (e.g. ones where it's all updates and no
 | |
| creates, so the next update will correct everything).
 |