def save_handler(sender, instance, *args, **kwargs): model_label = sender._meta.label # noqa channel_layer = get_channel_layer() group_name = get_group_name(model_label) async_to_sync(channel_layer.group_send)( group_name, { "type": "model.saved", "model": model_label, "instance_pk": instance.pk, "channel_name": group_name, }, )
def receive_json(self, content: Dict[str, Any], **kwargs): """ Entrypoint for incoming messages from the connected client. """ request_id = content.get("id", None) if request_id is None: return # Can't send error message without request ID, so just return. message_type = content.get("type", None) if message_type == "subscribe": model_label = content.get("model") if model_label is None: self.send_error(request_id, 400, "No model specified.") return if model_label not in self.registry: self.send_error( request_id, 404, f"Model {model_label} not registered for realtime updates.", ) return view_action = content.get("action", None) if view_action is None or view_action not in ["list", "retrieve"]: self.send_error( request_id, 400, "`action` must be present and the value must be either `list` or `retrieve`.", ) lookup_value = content.get("lookup_by", None) view_kwargs = content.get("view_kwargs", dict()) query_params = content.get("query_params", dict()) view = self.registry[model_label].from_scope( view_action, self.scope, view_kwargs, query_params) model = view.get_model_class() # Check to make sure client has permissions to make this subscription. has_permission = True for permission in view.get_permissions(): has_permission = has_permission and permission.has_permission( view.request, view) # Retrieve actions must check has_object_permission as well. if view.action == "retrieve": try: instance = view.get_queryset().get( **{view.lookup_field: lookup_value}) except model.DoesNotExist: self.send_error(request_id, 404, "Instance not found.") return for permission in view.get_permissions(): has_permission = (has_permission and permission.has_object_permission( view.request, view, instance)) if not has_permission: self.send_error( request_id, 403, f"Unauthorized to subscribe to {model_label} for action {view_action}", ) return # If we've reached this point, then the client can subscribe. group_name = get_group_name(model_label) print(f"[REST-LIVE] got subscription to {group_name}") self.subscriptions.setdefault(group_name, []).append(request_id) self.kwargs[request_id] = view_kwargs self.params[request_id] = query_params self.actions[request_id] = view_action self.visible_instance_pks[request_id] = set([ instance1["pk"] for instance1 in view.get_queryset().all().values("pk") ]) # Add subscribe to updates from channel layer: this is the "actual" subscription action. async_to_sync(self.channel_layer.group_add)(group_name, self.channel_name) self.groups.append(group_name) elif message_type == "unsubscribe": # Get the group name given the request_id try: # List comprehension is empty if the provided request_id doesn't show up for this consumer group_name = [ k for k, v in self.subscriptions.items() if request_id in v ][0] except IndexError: self.send_error( request_id, 404, "Attempted to unsubscribe for request ID before subscribing.", ) return del self.actions[request_id] del self.kwargs[request_id] del self.visible_instance_pks[request_id] self.subscriptions[group_name].remove(request_id) self.groups.remove( group_name) # Removes the first occurance of this group name. if ( group_name not in self.groups ): # If there are no more occurances, unsubscribe to the channel layer. async_to_sync(self.channel_layer.group_discard)( group_name, self.channel_name) # Delete the key in the dictionary if no more subscriptions. if len(self.subscriptions[group_name]) == 0: del self.subscriptions[group_name] else: self.send_error(request_id, 400, f"unknown message type `{message_type}`.")
def receive_json(self, content: Dict[str, Any], **kwargs): """ Entrypoint for incoming messages from the connected client. """ request_id = content.get("id", None) if request_id is None: return # Can't send error message without request ID, so just return. message_type = content.get("type", None) if message_type == "subscribe": model_label = content.get("model") if model_label is None: self.send_error(request_id, 400, "No model specified.") return if model_label not in self.registry: self.send_error( request_id, 404, f"Model {model_label} not registered for realtime updates.", ) return view_action = content.get("action", None) if view_action is None or view_action not in ["list", "retrieve"]: self.send_error( request_id, 400, "`action` must be present and the value must be either `list` or `retrieve`.", ) lookup_value = content.get("lookup_by", None) view_kwargs = content.get("view_kwargs", dict()) query_params = content.get("query_params", dict()) view = self.registry[model_label].from_scope( view_action, self.scope, view_kwargs, query_params) # Check to make sure client has permissions to make this subscription. has_permission = True for permission in view.get_permissions(): has_permission = has_permission and permission.has_permission( view.request, view) # Retrieve actions use get_object() to check object permissions as well. if view.action == "retrieve": view.kwargs.setdefault(view.lookup_field, lookup_value) try: view.get_object() except Http404: self.send_error( request_id, 404, "Instance not found. Make sure 'lookup_by' is set to a valid ID", ) return except (NotAuthenticated, PermissionDenied): has_permission = False if not has_permission: self.send_error( request_id, 403, f"Unauthorized to subscribe to {model_label} for action {view_action}", ) return # If we've reached this point, then the client can subscribe. group_name = get_group_name(model_label) print(f"[REST-LIVE] got subscription to {group_name}") self.subscriptions.setdefault(group_name, []).append( Subscription( request_id, action=view_action, view_kwargs=view_kwargs, query_params=query_params, pks_to_lookup_in_queryset=dict({ inst["pk"]: inst[view.lookup_field] for inst in view.get_queryset().all().values( "pk", view.lookup_field) }), )) # Add subscribe to updates from channel layer: this is the "actual" subscription action. async_to_sync(self.channel_layer.group_add)(group_name, self.channel_name) self.groups.append(group_name) elif message_type == "unsubscribe": # Get the group name given the request_id try: # List comprehension is empty if the provided request_id doesn't show up for this consumer group_name = [ k for k, v in self.subscriptions.items() if request_id in [s.request_id for s in v] ][0] except IndexError: self.send_error( request_id, 404, "Attempted to unsubscribe for request ID before subscribing.", ) return self.subscriptions[group_name] = [ sub for sub in self.subscriptions[group_name] if sub.request_id != request_id ] self.groups.remove( group_name) # Removes the first occurrence of this group name. if ( group_name not in self.groups ): # If there are no more occurrences, unsubscribe to the channel layer. async_to_sync(self.channel_layer.group_discard)( group_name, self.channel_name) # Delete the key in the dictionary if no more subscriptions. if len(self.subscriptions[group_name]) == 0: del self.subscriptions[group_name] else: self.send_error(request_id, 400, f"unknown message type `{message_type}`.")