Django Channels: WebSocket Consumer event handling method doesnt get trigerred

0

I’m working on a Django project using Django Channels with Redis as the channel layer. My setup allows WebSocket connections, and I am able to send and receive data from Redis without issues. However, when I try to send data to specific WebSocket channels using self.channel_layer.send, the send_online_users method in my AsyncWebsocketConsumer is not triggered.

I’ve verified that my Redis server is working and communicating with the Django server. The channels and users are being stored correctly in Redis. The self.channel_layer.send() function runs without throwing errors, but the send_online_users method is never triggered (the print statements inside send_online_users do not show in the logs).

Here is my Consumers.py code:

from channels.generic.websocket import AsyncWebsocketConsumer

import redis

from django.conf import settings

import redis.client

from asgiref.sync import sync_to_async

import json

from channels.layers import get_channel_layer



redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0)





class UserActivityConsumer(AsyncWebsocketConsumer):

    async def connect(self):

        self.user = self.scope['user']



        if self.user.is_anonymous or not self.user.is_authenticated:

            await self.close()

        else:

            self.channel_name = f'user_activity_{self.user.username}'

            await sync_to_async(redis_client.sadd)('online_users', self.user.id)

            await sync_to_async(redis_client.sadd)("online_channels",self.channel_name)

            await self.accept()



            await self.send_to_all()



    

    async def disconnect(self,close_code):

        user_id = self.scope['user'].id

        await sync_to_async(redis_client.srem)("online_users", user_id)



        await sync_to_async(redis_client.srem)("online_channels", self.channel_name)

        await self.send_to_all()







    async def send_to_all(self):

        try:

            

            online_users = await sync_to_async(redis_client.smembers)("online_users")

            online_users = [int(user_id) for user_id in online_users]



            online_channels = await sync_to_async(redis_client.smembers)("online_channels")





            channel_layer = get_channel_layer()



            for channel_name in online_channels:

                channel_name = channel_name.decode()

                await self.channel_layer.send(str(channel_name), {

                    "type": "send_online_users",

                    "online_users": online_users

                })

        except Exception as e:

            print(e)





    async def send_online_users(self, event):

        try:

            print('TRIGGERED - send_message')

            online_users = event.get('online_users', [])

            print(f"Online users: {online_users}")

            

            await self.send(text_data=json.dumps({

                "type": "online_users",

                "online_users": online_users

            }))

            print('Message sent to WebSocket client')

        except Exception as e:

            print(f"Error in send_message: {e}")

additionally the frontend code:

useEffect(() => {
        getOnlineUsers();
    
        return () => {
            if (socket) {
                socketRef.current.close();
            }
        };
    }, []);
    
    const getOnlineUsers = async () => {
        if (!socketRef.current || socketRef.current.readyState === WebSocket.CLOSED) {
            try {
                const token = localStorage.getItem('auth_token');
                socketRef.current = new WebSocket(`ws://192.168.1.101:8000/ws/activity/?token=${token}`);
                setSocket(socketRef.current);
    
                socketRef.current.onopen = () => {
                    console.log('WebSocket connection established DM');
                };
    
                socketRef.current.onclose = () => {
                    console.log('WebSocket connection closed DM');
                };
    
                socketRef.current.onmessage = (event) => {
                    const data = JSON.parse(event.data);
    
                    if (data.type === "online_users") {
                        console.log('received');
                        
                        setOnlineUsers(data.online_users);
                    }
                };
            } catch (error) {
                console.log(error);
            }
        }
    };

Additional Context: I have a real time chat system and it works perfectly well and in the chat consumer im assigning 2 users to a specific group and i’m using self.channel_layer.group_send(GROUP_NAME,{…}) method and it runs perfectly fine, the messages are being sent with no issues but when i try to send data with “CHANNEL_NAME” it does not work. All servers (frontend vite + Django + Redis server) run in a linux virtual machine with VM Box and bridge adaptor network settings

Django Channels Configuration:

CHANNEL_LAYERS = {

    'default': {

        'BACKEND': 'channels_redis.core.RedisChannelLayer',

        'CONFIG': {

            "hosts": [('192.168.1.101', 6379)],

        },

    },

}

What I’m expecting:

When I call self.channel_layer.send(channel_name, {…}), it should trigger the send_online_users method, which sends the list of online users to the WebSocket client.

What happens:

The send_online_users method is never triggered. Even the print statements doesn’t run and no data is sent to the client.

Welcome @a-Hunter89 !

Printed output from Daphne is buffered. If you want to ensure it’s in the logs at the appropriate time, you need to include flush=True in your print calls or run Python in unbuffered mode.

Also, have you visually inspected these keys in redis when you are expecting them to be sent? At a minimum, you should be able to inspect the online_users and online_channels keys to verify that they are populated properly.

I’d also suggest adding more print calls in send_to_all to show exactly what you’re passing to self.channel_layer.send before it’s being sent.

Finally, it’s helpful if you remove all the excess blank lines from code being posted here. Since the code blocks shown here are limited by height, whatever you can do to reduce the scrolling necessary is helpful.

Yes i can see the channel_names and online_users on redis-cli, and django server can retrieve them as well and i also used print before and after “self.channel_layer.send”, and the print statements worked. I printed the channel_names and they are the self.channel_name i assigned on connect method which is correct and as expected and type of channel_name and its str which is also correct and as expected i printed online_users too and its also fine. Sorry about the blanks.

Please show all those details here. Show the complete logs so that we can see what’s going on and what the data is that’s being passed around.

Here are all the django server logs:

System check identified 1 issue (0 silenced).
September 30, 2024 - 01:33:39
Django version 4.2.14, using settings 'neurocom.settings'
Starting ASGI/Daphne version 4.1.2 development server at http://192.168.1.101:8000/
Quit the server with CONTROL-C.
WebSocket HANDSHAKING /ws/activity/ [192.168.1.104:49989](user connects to websocket)
WebSocket CONNECT /ws/activity/ [192.168.1.104:49989]
HTTP OPTIONS /api/me/ 200 [0.03, 192.168.1.104:49990]
HTTP OPTIONS /api/me/ 200 [0.08, 192.168.1.104:49988]
ONLINE USERS: [30](online_users retrieved from redis)
ONLINE CHANNELS: [b'user_activity_artaka123'](online_channel data retrieved from redis)
CHANNEL NAME: user_activity_artaka123
<class 'str'> => print(type(channel_name))
SENT(this gets printed out after the for loop)

rest of the logs are not relevant but i included them in any case
HTTP GET /api/me/ 200 [0.04, 192.168.1.104:49990]
HTTP GET /api/me/ 200 [0.03, 192.168.1.104:49988]
WebSocket HANDSHAKING /ws/notifications/ [192.168.1.104:49991]
HTTP OPTIONS /api/settings/ 200 [0.01, 192.168.1.104:49990]
artaka123
WebSocket CONNECT /ws/notifications/ [192.168.1.104:49991]
HTTP OPTIONS /api/settings/ 200 [0.01, 192.168.1.104:49988]
HTTP GET /api/settings/ 200 [0.04, 192.168.1.104:49988]
HTTP GET /api/settings/ 200 [0.04, 192.168.1.104:49990]
HTTP OPTIONS /api/chatrooms/ 200 [0.01, 192.168.1.104:49988]
HTTP OPTIONS /api/chatrooms/ 200 [0.01, 192.168.1.104:49990]
HTTP GET /api/chatrooms/ 200 [0.06, 192.168.1.104:49990]
HTTP GET /api/chatrooms/ 200 [0.09, 192.168.1.104:49988]

Ahh - I see where you’re trying to assign the channel_name yourself.

I don’t think that’s going to work in the connect method. The channel_name for a consumer is defined in the __call__ method. (See the __call__ method in the base AsyncConsumer class to see what it’s doing.)

I’d remove the self.channel_name assignment and allow channels to manage that as it normally does.

1 Like

:person_facepalming: :person_facepalming: I was trying to solve it for hours… I removed the channel name assignments and let django deal with it and it worked… lol thank you so much <3<3 and sorry for taking your time for such a small misstake. :person_facepalming:

No worries, we’re here to help. (I actually wasn’t 100% sure this was the right answer, but figured it was worth a shot. I’ve never tried to assign my own channel names before…)

1 Like