Send message using Django Channels from outside Consumer class

11,386

Solution 1

Firstly you need your consumer instance to subscribe to a group.

from asgiref.sync import async_to_sync

class GameConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()
        self.render()
        async_to_sync(self.add_group)('render_updates_group') 
        controller.startTurn()
...

Then if you are outside of your consumer you will need to send a message to that group so that all the consumers that have registered onto the group get the message.

from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync

def sendDeployments(owner, armies):
    type = "renderDeployments"
    message = owner + " has " + str(armies) + " to deploy"
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        'render_updates_group',
        {'type': 'render', 'message': message}
    )

However, you also need to remember to remove your consumer from the group when it disconnects.

class GameConsumer(WebsocketConsumer):
    ....

    def disconnect(self, close_code):
         async_to_sync(self.group_name)('render_updates_group')

If you want to limit which open connections get this render message you need to build your group name accordingly.

eg if you are thinking about some online game then maybe you include a MATCH-ID in the group name that is shared by all connections for the same match.

A good reference for this is the channels Layers documentation but do remember when running this in production you will need a messaging layer (normally Redis) set up.

Solution 2

First make a little change for your Consumer like

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json

class EventConsumer(WebsocketConsumer):
    def connect(self):
        # self.room_name = self.scope['url_route']['kwargs']['room_name']
        # self.room_group_name = 'chat_%s' % self.room_name
        self.room_name = 'event'
        self.room_group_name = self.room_name+"_sharif"
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )
        print(self.room_group_name)
        self.accept()
        print("#######CONNECTED############")

    def disconnect(self, code):
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )
        print("DISCONNECED CODE: ",code)

    def receive(self, text_data=None, bytes_data=None):
        print(" MESSAGE RECEIVED")
        data = json.loads(text_data)
        message = data['message']
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,{
                "type": 'send_message_to_frontend',
                "message": message
            }
        )
    def send_message_to_frontend(self,event):
        print("EVENT TRIGERED")
        # Receive message from room group
        message = event['message']
        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))

then call the function outside/anywhere from your app like

def event_triger():
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        'event_sharif',
        {
            'type': 'send_message_to_frontend',
            'message': "event_trigered_from_views"
        }
    ) 
# here 'event_sharif' is your room_group_name as i defined before in consumer
# 'type' is like a command, for which method you wants to trigger in your consumer

Solution 3

The correct code for the disconnect function (using channels 2.3.0) would be:

class GameConsumer(WebsocketConsumer):
    ....

    def disconnect(self, close_code):
        self.channel_layer.group_discard(self.channel_name, 'render_updates_group')
Share:
11,386
Ahmed Hammad
Author by

Ahmed Hammad

I came here, not only to find answers to my questions when I struggle, but also to get that joyful moment when I successfully help other pals with their code.

Updated on June 28, 2022

Comments

  • Ahmed Hammad
    Ahmed Hammad almost 2 years

    I am building an online game, which uses Django channels 2.1.5 for websockets.

    I am able to build the connection between the client and the server, and also able to send data between them only inside the consumer class:

    from channels.generic.websocket import WebsocketConsumer
    import json
    from . import controller
    
    class GameConsumer(WebsocketConsumer):
        def connect(self):
            self.accept()
            print("Wohooo .. Connected to client!")
            self.render()
            controller.startTurn()
    
        def render(self, type="render", message=None):
            self.send(controller.renderMap(type, message))
    
        def disconnect(self, close_code):
            print("WebSocket connection is lost...")
    
        def receive(self, text_data):
            text_data_json = json.loads(text_data)
            controller.handleRecieved(text_data)
    ...
    

    Now, What I wish to do, is to call the function render, -which is inside the consumer class-, from another module

    I tried this:

    from .. import consumer
    
    def sendDeployments(owner, armies):
        type = "renderDeployments"
        message = owner + " has " + str(armies) + " to deploy"
        dummyConsumer = consumer.GameConsumer()
        consumer.GameConsumer.render(type, message)
    

    But failed because I can't use the "self" parameter from outside the class.

    Can anybody think of a way to achieve my goal?

    Ps: I don't care about synchronization at this occasion.