banan
banan

Reputation: 33

Django Channels showing current users in a room

I'm trying to show the current users that are connected to same websocket (in my case chat room) but I have a little problem. I want to create variable that stores the users and send it trough websockets to my frontend, I've achieved that, but here is the problem.

Consumers.py - approach 1

class ChatRoomConsumer(AsyncWebsocketConsumer):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.users: list = []

    @database_sync_to_async
    def create_msg(self, user_id=None, message=None, room=None):
        if user_id is not None:
            sender = User.objects.get(id=user_id)
            msg = Message.objects.create(
                author=sender, message=message, room_name=room)
            msg.save()
            return msg
        else:
            get_msgs = Message.objects.filter(room_name__in=[room])
            serializer = MessageSerializer(get_msgs, many=True)

            return serializer.data

    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f'chat_{self.room_name}'

        self.messages = await self.create_msg(room=self.room_name)
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

        await self.send(text_data=json.dumps({
            'db_messages': self.messages,

        }))

    async def disconnect(self, close_code):

        print(close_code)

        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        type = text_data_json['type']
        message = text_data_json['message']
        username = text_data_json['username']
        user_id = text_data_json['user_id']
        self.user_id = text_data_json['user_id']

        if type == 'chatroom_message':
            self.msg = await self.create_msg(user_id, message, self.room_name)

        await self.channel_layer.group_send(
            self.room_group_name, {
                'type': type,
                'message': message,
                'username': username,
                'user_id': user_id
            }
        )

    async def chatroom_message(self, event):
        message = event['message']
        username = event['username']

        await self.send(text_data=json.dumps({
            'message': message,
            'username': username,
        }))

    # nefunkcni ukazatel momentalnich uzivatelu v mistnosti
    async def get_user(self, event):
        print('get_user called')

        if event['message'] == 'disconnect':
            print('remove', event['username'])
            try:
                self.users.remove(event['username'])
            except ValueError:
                print('user already removed')
        else:
            if event['username'] not in self.users:
                self.users.append(event['username'])
            print(self.users)
        await self.send(text_data=json.dumps({
            'users': self.users
        }))

In this approach, it corretly shows the current logged in users in the view only from the 1st user that has entered the chat, other users dont see, that the user that has joined before them is there. In this approach I define the variable users in the constructor.

Consumers.py - approach 2

from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from .models import Message
from .serializers import MessageSerializer
from django.contrib.auth.models import User
from django.db.models import Prefetch
from django.core.serializers.json import DjangoJSONEncoder
from asgiref.sync import sync_to_async, async_to_sync
import channels
import json

users: list = []
class ChatRoomConsumer(AsyncWebsocketConsumer):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #self.users: list = []

    @database_sync_to_async
    def create_msg(self, user_id=None, message=None, room=None):
        if user_id is not None:
            sender = User.objects.get(id=user_id)
            msg = Message.objects.create(
                author=sender, message=message, room_name=room)
            msg.save()
            return msg
        else:
            get_msgs = Message.objects.filter(room_name__in=[room])
            serializer = MessageSerializer(get_msgs, many=True)

            return serializer.data

    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f'chat_{self.room_name}'

        self.messages = await self.create_msg(room=self.room_name)
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()

        await self.send(text_data=json.dumps({
            'db_messages': self.messages,

        }))

    async def disconnect(self, close_code):

        print(close_code)

        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        type = text_data_json['type']
        message = text_data_json['message']
        username = text_data_json['username']
        user_id = text_data_json['user_id']
        self.user_id = text_data_json['user_id']

        if type == 'chatroom_message':
            self.msg = await self.create_msg(user_id, message, self.room_name)

        await self.channel_layer.group_send(
            self.room_group_name, {
                'type': type,
                'message': message,
                'username': username,
                'user_id': user_id
            }
        )

    async def chatroom_message(self, event):
        message = event['message']
        username = event['username']

        await self.send(text_data=json.dumps({
            'message': message,
            'username': username,
        }))

    # nefunkcni ukazatel momentalnich uzivatelu v mistnosti
    async def get_user(self, event):
        print('get_user called')

        if event['message'] == 'disconnect':
            print('remove', event['username'])
            try:
                users.remove(event['username'])
            except ValueError:
                print('user already removed')
        else:
            if event['username'] not in users:
                users.append(event['username'])
            print(users)
        await self.send(text_data=json.dumps({
            'users': users
        }))

In this approach, the users show correctly, but not from the current room (it shows users from all the other rooms). It's kinda logical because the declaration of the variable is at the top level.

But my question is where should I declare it then? When it's in the constructor, it always overwrites the users that were in the room before the current user and when it's in the top level, it takes all the users from all the rooms.

Upvotes: 2

Views: 1893

Answers (1)

Yandiro
Yandiro

Reputation: 157

In case you haven't found an answer yet, here's my 2 cents:

Consumers are singletons, meaning that there is one instance for every channel (websocket connection). So probably that is the problem of having users inside the consumer class.

An alternative would be to make user a dictionary, not an array. So you can have a key for every room, like so: users = { "room_1": [], "room_2": [] }

I know there is no code in my answer, but I hope it serves as a guide for your solution!

Upvotes: 2

Related Questions