mirror of
				https://github.com/django/daphne.git
				synced 2025-11-04 09:37:32 +03:00 
			
		
		
		
	* Added groups as Binding attr for easy groups_names definition * Binding: inbound - updating fields that only in fields attribute * Added tests for inbound part of binding #343 * Fix for flake8 checker * Revert "Added groups as Binding attr for easy groups_names definition" This reverts commit 009b4adbee534d4ccbea191ce2523a0edb09d407. * Using group_names at inbound tests
		
			
				
	
	
		
			165 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import json
 | 
						|
 | 
						|
from django.core import serializers
 | 
						|
from django.core.serializers.json import DjangoJSONEncoder
 | 
						|
 | 
						|
from .base import Binding
 | 
						|
from ..generic.websockets import WebsocketDemultiplexer
 | 
						|
from ..sessions import enforce_ordering
 | 
						|
 | 
						|
 | 
						|
class WebsocketBinding(Binding):
 | 
						|
    """
 | 
						|
    Websocket-specific outgoing binding subclass that uses JSON encoding
 | 
						|
    and the built-in JSON/WebSocket multiplexer.
 | 
						|
 | 
						|
    To implement outbound, implement:
 | 
						|
     - group_names, which returns a list of group names to send to
 | 
						|
 | 
						|
    To implement inbound, implement:
 | 
						|
     - has_permission, which says if the user can do the action on an instance
 | 
						|
 | 
						|
    Optionally also implement:
 | 
						|
     - serialize_data, which returns JSON-safe data from a model instance
 | 
						|
     - create, which takes incoming data and makes a model instance
 | 
						|
     - update, which takes incoming data and a model instance and applies one to the other
 | 
						|
    """
 | 
						|
 | 
						|
    # Mark as abstract
 | 
						|
 | 
						|
    model = None
 | 
						|
 | 
						|
    # Stream multiplexing name
 | 
						|
 | 
						|
    stream = None
 | 
						|
 | 
						|
    # Decorators
 | 
						|
    strict_ordering = False
 | 
						|
    slight_ordering = False
 | 
						|
 | 
						|
    # Outbound
 | 
						|
    @classmethod
 | 
						|
    def encode(cls, stream, payload):
 | 
						|
        return WebsocketDemultiplexer.encode(stream, payload)
 | 
						|
 | 
						|
    def serialize(self, instance, action):
 | 
						|
        payload = {
 | 
						|
            "action": action,
 | 
						|
            "pk": instance.pk,
 | 
						|
            "data": self.serialize_data(instance),
 | 
						|
            "model": self.model_label,
 | 
						|
        }
 | 
						|
        return payload
 | 
						|
 | 
						|
    def serialize_data(self, instance):
 | 
						|
        """
 | 
						|
        Serializes model data into JSON-compatible types.
 | 
						|
        """
 | 
						|
        if self.fields == ['__all__']:
 | 
						|
            fields = None
 | 
						|
        else:
 | 
						|
            fields = self.fields
 | 
						|
        data = serializers.serialize('json', [instance], fields=fields)
 | 
						|
        return json.loads(data)[0]['fields']
 | 
						|
 | 
						|
    # Inbound
 | 
						|
    @classmethod
 | 
						|
    def get_handler(cls):
 | 
						|
        """
 | 
						|
        Adds decorators to trigger_inbound.
 | 
						|
        """
 | 
						|
        # Get super-handler
 | 
						|
        handler = super(WebsocketBinding, cls).get_handler()
 | 
						|
        # Ordering decorators
 | 
						|
        if cls.strict_ordering:
 | 
						|
            return enforce_ordering(handler, slight=False)
 | 
						|
        elif cls.slight_ordering:
 | 
						|
            return enforce_ordering(handler, slight=True)
 | 
						|
        else:
 | 
						|
            return handler
 | 
						|
 | 
						|
    def deserialize(self, message):
 | 
						|
        """
 | 
						|
        You must hook this up behind a Deserializer, so we expect the JSON
 | 
						|
        already dealt with.
 | 
						|
        """
 | 
						|
        action = message['action']
 | 
						|
        pk = message.get('pk', None)
 | 
						|
        data = message.get('data', None)
 | 
						|
        return action, pk, data
 | 
						|
 | 
						|
    def _hydrate(self, pk, data):
 | 
						|
        """
 | 
						|
        Given a raw "data" section of an incoming message, returns a
 | 
						|
        DeserializedObject.
 | 
						|
        """
 | 
						|
        s_data = [
 | 
						|
            {
 | 
						|
                "pk": pk,
 | 
						|
                "model": self.model_label,
 | 
						|
                "fields": data,
 | 
						|
            }
 | 
						|
        ]
 | 
						|
        # TODO: Avoid the JSON roundtrip by using encoder directly?
 | 
						|
        return list(serializers.deserialize("json", json.dumps(s_data)))[0]
 | 
						|
 | 
						|
    def create(self, data):
 | 
						|
        self._hydrate(None, data).save()
 | 
						|
 | 
						|
    def update(self, pk, data):
 | 
						|
        instance = self.model.objects.get(pk=pk)
 | 
						|
        hydrated = self._hydrate(pk, data)
 | 
						|
        for name in data.keys():
 | 
						|
            if name in self.fields or self.fields == ['__all__']:
 | 
						|
                setattr(instance, name, getattr(hydrated.object, name))
 | 
						|
        instance.save()
 | 
						|
 | 
						|
 | 
						|
class WebsocketBindingWithMembers(WebsocketBinding):
 | 
						|
    """
 | 
						|
    Outgoing binding binding subclass based on WebsocketBinding.
 | 
						|
    Additionally enables sending of member variables, properties and methods.
 | 
						|
    Member methods can only have self as a required argument.
 | 
						|
    Just add the name of the member to the send_members-list.
 | 
						|
    Example:
 | 
						|
 | 
						|
    class MyModel(models.Model):
 | 
						|
        my_field = models.IntegerField(default=0)
 | 
						|
        my_var = 3
 | 
						|
 | 
						|
        @property
 | 
						|
        def my_property(self):
 | 
						|
            return self.my_var + self.my_field
 | 
						|
 | 
						|
        def my_function(self):
 | 
						|
            return self.my_var - self.my_vield
 | 
						|
 | 
						|
    class MyBinding(BindingWithMembersMixin, WebsocketBinding):
 | 
						|
        model = MyModel
 | 
						|
        stream = 'mystream'
 | 
						|
 | 
						|
        send_members = ['my_var', 'my_property', 'my_function']
 | 
						|
    """
 | 
						|
 | 
						|
    model = None
 | 
						|
    send_members = []
 | 
						|
 | 
						|
    encoder = DjangoJSONEncoder()
 | 
						|
 | 
						|
    def serialize_data(self, instance):
 | 
						|
        data = super(WebsocketBindingWithMembers, self).serialize_data(instance)
 | 
						|
        member_data = {}
 | 
						|
        for m in self.send_members:
 | 
						|
            member = instance
 | 
						|
            for s in m.split('.'):
 | 
						|
                member = getattr(member, s)
 | 
						|
            if callable(member):
 | 
						|
                member_data[m.replace('.', '__')] = member()
 | 
						|
            else:
 | 
						|
                member_data[m.replace('.', '__')] = member
 | 
						|
        member_data = json.loads(self.encoder.encode(member_data))
 | 
						|
        # the update never overwrites any value from data,
 | 
						|
        # because an object can't have two attributes with the same name
 | 
						|
        data.update(member_data)
 | 
						|
        return data
 |