5

I want to notify the client when my model is saved. I started by creating a django-signal on post_save.

@receiver(post_save, sender=Scooter)
async def scooter_post_update(sender, instance, created, **kwargs):
    # Notify client here

Next I created the AsyncConsumer class from django-channels and provided its routing.

// routing.py
    application = ProtocolTypeRouter({
        # Empty for now (http->django views is added by default)
        'websocket': AllowedHostsOriginValidator(
            AuthMiddlewareStack(
                URLRouter(
                    [
                        path('scooters/', ScootersUpdateConsumer)
                    ]
               )
        )
    )
})

// consumers.py
    class ScootersUpdateConsumer(AsyncConsumer):
        async def websocket_connect(self, event):
            print("Connected!", event)
            await self.send({
                "type": "websocket.accept"
            })
        async def send_message(self):
            await self.send({
                "type": "websocket.send",
                'text': 'Oy, mate!'
            })
        async def websocket_receive(self, event):
            print("Receive!", event)
        async def websocket_disconnect(self, event):
            print("Disconnected!", event)

Now my question is how can I call send_message() from the scooter_post_update() method.

harlusak
  • 51
  • 2

1 Answers1

3

The steps are really straightforward. You have to get the channel layer and just send a message with type key set as your listening method name:

    import channels
    from asgiref.sync import async_to_sync

    @receiver(post_save, sender=Scooter)
    def scooter_post_update(sender, instance, created, **kwargs):
        channel_layer = channels.layers.get_channel_layer()
        async_to_sync(channel_layer.send)(
            {"type": "send_message", "data": data}
        )

and any other stuff you want to send over channel.

Beware that all the data you pass must be serializable, so you have to take care that all your objects are serialized beforehand.

The mandatory part of the dictionary you pass to the send method is the type key (as mentioned before), which has to contain the method name which will be called on the consumer.

Moreover, you can use groups, so you can broadcast a message to a group of listeners:

    import channels
    from asgiref.sync import async_to_sync

    @receiver(post_save, sender=Scooter)
    def scooter_post_update(sender, instance, created, **kwargs):
        channel_layer = channels.layers.get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            "group_name", {"type": "send_message", "data": data}
        )

and on the consumer side:

    class ScootersUpdateConsumer(AsyncConsumer):
        async def websocket_connect(self, event):
            await self.channel_layer.group_add("group_name", self.channel_name)
            await self.send({
                "type": "websocket.accept"
            })

Note that in both cases, you use the async_to_sync wrapper which should be used when you call async code from sync scope.

Vlad Rusu
  • 1,414
  • 12
  • 17