mirror of
				https://github.com/django/daphne.git
				synced 2025-10-31 07:47:25 +03:00 
			
		
		
		
	* Added accept at default behavior for websocket generic cbv and pass message instead of dict * Fix flake8 * Use HttpClient Instead of Client * Fix lsort
		
			
				
	
	
		
			183 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import json
 | |
| 
 | |
| from django.core import serializers
 | |
| from django.core.serializers.json import DjangoJSONEncoder
 | |
| 
 | |
| from ..generic.websockets import WebsocketMultiplexer
 | |
| from ..sessions import enforce_ordering
 | |
| from .base import Binding
 | |
| 
 | |
| 
 | |
| 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 WebsocketMultiplexer.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 is not None:
 | |
|             if self.fields == '__all__' or list(self.fields) == ['__all__']:
 | |
|                 fields = None
 | |
|             else:
 | |
|                 fields = self.fields
 | |
|         else:
 | |
|             fields = [f.name for f in instance._meta.get_fields() if f.name not in self.exclude]
 | |
|         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
 | |
| 
 | |
|     @classmethod
 | |
|     def trigger_inbound(cls, message, **kwargs):
 | |
|         """
 | |
|         Overrides base trigger_inbound to ignore connect/disconnect.
 | |
|         """
 | |
|         # Only allow received packets through further.
 | |
|         if message.channel.name != "websocket.receive":
 | |
|             return
 | |
|         super(WebsocketBinding, cls).trigger_inbound(message, **kwargs)
 | |
| 
 | |
|     def deserialize(self, message):
 | |
|         """
 | |
|         You must hook this up behind a Deserializer, so we expect the JSON
 | |
|         already dealt with.
 | |
|         """
 | |
|         body = json.loads(message['text'])
 | |
|         action = body['action']
 | |
|         pk = body.get('pk', None)
 | |
|         data = body.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)
 | |
| 
 | |
|         if self.fields is not None:
 | |
|             for name in data.keys():
 | |
|                 if name in self.fields or self.fields == ['__all__']:
 | |
|                     setattr(instance, name, getattr(hydrated.object, name))
 | |
|         else:
 | |
|             for name in data.keys():
 | |
|                 if name not in self.exclude:
 | |
|                     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, **kwargs):
 | |
|         data = super(WebsocketBindingWithMembers, self).serialize_data(instance, **kwargs)
 | |
|         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
 |