Search code examples
pythondjangodjango-channels

How to send websocket deferred group messages with Django Channels?


I'm creating browser game on Django Channels. Player of which must answer the question during 1 minute. First of all I send event with question and then want to send message with minute delay.

I'm using RedisChannelLayer and JsonWebsocketConsumer.

class RoomConsumer(JsonWebsocketConsumer):
    def connect(self):
        room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'game_' + room_name
        async_to_sync(self.channel_layer.group_add)(self.room_group_name, self.channel_name)
        self.accept()
        self.send_json(connection_event())

    def send_message(self, event):
        self.send_json(event.get('data'))

    def receive_json(self, data, **kwargs):
        try:
            print(data)
            event_type = data.get('eventType')
            if event_type == 'start':
                tasks = []
                ...
                async_to_sync(self.channel_layer.group_send)(self.room_group_name, {
                    'type': 'send_message',
                    'data': start_event(tasks)
                })
                #  here want to send deferred message
        except Exception as e:
            print(e)

Was planing to do it with async sleep, but need to be able to pause and play this message or stop and resend with new delay.

async def send_delayed_message(self, event):
    await sleep(60)
    await self.send_json(event.get('data'))

Do you have cases to send such messages with delay with no stoping consumer?


Solution

  • I would use celery to accomplish this with a shared_task.

    Step 1:

    Write a function in a tasks.py that sends the delayed message:

    from celery import shared_task
    from channels.layers import get_channel_layer
    
    @shared_task
    def send_delayed_message(room_group_name, message):
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(room_group_name, {"type": "send_message", "room_group_name": room_group_name, "message": {"message": message})
    

    Step 2:

    Add the delay attributes to your data object:

    data["delay"] = 60 #in seconds
    

    Step 3:

    Call the function from your RoomConsumer with the delay attributes:

    async def send_delayed_message(self, event):
        data = event.get("data")
        message = data.get("message")
        room_id = self.room_group_name
        delay = data.get("delay")
        if delay:
            task = await send_delayed_message.apply_async(args=[room_id, message], countdown=delay)
            return task
    

    Step 4:

    Instead of "pausing" the task, just end it and resend the same message. Keep the logic the same for pause and stop.

    from proj.celery import app
    
    def end_celery_message(task_id):
        app.control.revoke(task_id, terminate=True)