Django Channels showing current users in a room - python

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.

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!

Related

How to bind the data entered by the user in the mongodb database to the same user in python

For example: This is how I do it. That when I try to add new values to the database, it adds them to the same user, and does not change for a specific one. I would like to do so. The user has a specific ID, and when he changes / adds data, this would be attributed to him in the data table. How to do it?
client = MongoClient("mongodb")
db = client ["Data_1"]
collection = db["Coll"]
bot = commands.Bot()
#bot.slash_command(description="Change message", name="start")
async def menu(ctx: discord.ApplicationContext):
embed = discord.Embed(colour=discord.Colour.dark_teal())
embed.add_field(name="Your ID", value=f"{ctx.author.id}")
embed.add_field(name="Balance", value="0")
embed.add_field(name="Subscription ends", value="<t:1675116353:R>")
view = MyView()
await ctx.send(view=view, embed=embed)
await ctx.delete()
user = collection.find_one({"_id": ctx.author.id})
if not user:
new_user = {
"_id": ctx.author.id,
"Balance": "0",
"Subscription ends": "<t:1675116353:R>"}
collection.insert_one(new_user)
print("User added successfully.")
else:
collection.update_one({"_id": ctx.author.id}, {"$set": {"Баланс":0, "Subscription ends": "<t:1675116353:R>"}})
class MyModal1(discord.ui.Modal):
def __init__(self, *args, **kwargs, ) -> None:
super().__init__(*args, **kwargs)
self.add_item(ui.InputText(label="Category name", style=discord.InputTextStyle.short, placeholder="Name"))
self.add_item(ui.InputText(label="Token", style=discord.InputTextStyle.long, placeholder="Paste token", max_length=4000))
self.add_item(ui.InputText(label="Channel ID", style=discord.InputTextStyle.short, placeholder="Paste channel ID"))
self.add_item(ui.InputText(label="Image links", style=discord.InputTextStyle.long, placeholder="Paste links", max_length=4000, required=False))
self.add_item(ui.InputText(label="Text", style=discord.InputTextStyle.long, placeholder="Paste text", max_length=4000))
async def callback(self, interaction: discord.Interaction, userID):
self.userID = userID
result1_1 = self.children[0].value
result2_1 = self.children[1].value
result3_1 = self.children[2].value
result4_1 = self.children[3].value
result5_1 = self.children[4].value
# Save data to MongoDB
data1 = {
"Category_name1": result1_1,
"Token1": result2_1,
"Channel_ID1": result3_1,
"Image_links1": result4_1,
"Text1": result5_1
}
await interaction.user.id()
collection.update_one({"_id": self.userID}, {"$set": data1})
await interaction.response.defer()

Django Channels Consumer won't execute method

I'm trying to develop a chat application that saves messages in the database via a WebSocket with Django Channels.
settings.py:
ASGI_APPLICATION = "app.asgi.application"
CHANNEL_LAYERS = {
'default': {
'BACKEND': "channels.layers.InMemoryChannelLayer",
},
}
asgi.py:
from api.routing import websocket_urlpatterns
application = ProtocolTypeRouter({
"http" : get_asgi_application(),
"websocket": JWTAuthMiddleware(
URLRouter(
websocket_urlpatterns
)
),
})
routing.py:
websocket_urlpatterns = [
path('ws/chat/<chat_uuid>/', consumers.ChatConsumer.as_asgi())
]
consumers.py
class ChatConsumer(AsyncJsonWebsocketConsumer):
#database_sync_to_async
def create_message(self, sender, message, chat):
if sender:
if content_validation(message):
chat = Chat.objects.filter(uuid=chat)
if chat.exists():
msg = Message.objects.create(sent_by=sender, content=message)
chat.first().messages.add(msg)
return msg
return None
#database_sync_to_async
def check_security(self, user, uuid):
# Chat has to exist and user has to be part of it
chat = Chat.objects.filter(Q(Q(user1=user) | Q(user2=user)) & Q(uuid=uuid))
if chat.exists():
return True
return False
async def connect(self):
self.chat_uuid = self.scope['url_route']['kwargs']['chat_uuid']
self.room_group_name = 'chat_%s' % self.chat_uuid
user = self.scope["user"]
if user:
check = await self.check_security(user, self.chat_uuid)
if check == True:
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
await self.disconnect(401)
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
name = text_data_json['name']
print(text_data_json)
user = self.scope["user"]
# Is this smart? User not receiving a message for whatever reason won't trigger the create event
new_msg = await self.create_message(user, message, self.chat_uuid)
if new_msg != None:
# Send message to room group
print("Receiving")
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'name': name
}
)
else:
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'content_guideline_violation',
'message': "",
'name': name
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
name = event['name']
print(event)
# Send message to WebSocket
print("Sending")
await self.send(text_data=json.dumps({
'message': message,
'name': name
}))
I won't post the JWTAuthMiddleware for now since everything worked fine after first implementing it.
The problem is that even though connect and receive are being executed just fine, it just won't execute chat_message anymore for whatever reason.
My Postman requests are looking like the following:
ws://127.0.0.1:8000/ws/chat/<chat_uuid>/?Bearer=<jwt_token>
Postman is able to connect to the websocket and it sends messages as well as saves them in a database but it doesn't show these messages for the other user in the chat.

Django Channels consumers.py scope['user'] returns anonymousUser

I'm trying to create one to one chat but when I'm trying to get
self.scope['user']
it returns AnonymousUser
consumers.py
class ChatConsumer(SyncConsumer):
def websocket_connect(self,event):
#error
me = self.scope['user']
other_username = self.scope['url_route']['kwargs']['username']
other_user = User.objects.get(username=other_username)
self.thread_obj = Thread.objects.get_or_create_personal_thread(me,other_user)
self.room_name = f'{self.thread_obj.id}_service'
print(f"{self.channel_name} connected")
async_to_sync(self.channel_layer.group_add)(self.room_name,self.channel_name)
self.send({
'type':'websocket.accept'
})
def websocket_receive(self,event):
print(f"{self.channel_name} message received {event['text']}")
msg = json.dumps({
'text':event.get('text'),
'username':self.scope['user'].username
})
async_to_sync(self.channel_layer.group_send)(
self.room_name,
{
'type':'websocket.message',
'text':msg
}
)
def websocket_message(self,event):
print(f"{self.channel_name} message sent {event['text']}")
self.send({
'type':'websocket.send',
'text':event.get('text')
})
def websocket_disconnect(self,event):
print(f"{self.channel_name} disconnected")
async_to_sync(self.channel_layer.group_discard)(self.room_name,self.channel_name)
print(event)
routing.py
from channels.routing import ProtocolTypeRouter,URLRouter
from django.urls import path
from .consumers import ChatConsumer,EchoConsumer
from channels.auth import AuthMiddlewareStack
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
'websocket': AuthMiddlewareStack(
URLRouter([
path('ws/chat/',EchoConsumer()),
path('ws/chat/<str:username>/',ChatConsumer()),
])
)
})
I think the problem is here in auth.py:
#database_sync_to_async
def get_user(scope):
"""
Return the user model instance associated with the given scope.
If no user is retrieved, return an instance of `AnonymousUser`.
"""
# postpone model import to avoid ImproperlyConfigured error before Django
# setup is complete.
from django.contrib.auth.models import AnonymousUser
if "session" not in scope:
raise ValueError(
"Cannot find session in scope. You should wrap your consumer in "
"SessionMiddleware."
)
session = scope["session"]
user = None
try:
user_id = _get_user_session_key(session)
backend_path = session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if hasattr(user, "get_session_auth_hash"):
session_hash = session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash, user.get_session_auth_hash()
)
if not session_hash_verified:
session.flush()
user = None
return user or AnonymousUser()
because codes under else not running
I've made some changes to the code that I took from the documentation and the result is like this
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
me = self.scope['user']
other_username = self.scope['url_route']['kwargs']['username']
other_user =await sync_to_async(User.objects.get)(username=other_username)
thread_obj =await sync_to_async(Thread.objects.get_or_create_personal_thread)(me,other_user)
self.room_name = f'{thread_obj.id}_service'
self.room_group_name = self.room_name
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, 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)
message = text_data_json['message']
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
await self.send(text_data=json.dumps({
'message': message
}))

Group name not taking spaces django-channels

I am working on a chat app with django channels, everthing is working but messages don't get send when room name has spaces or some other special carachters, ideally I'd like to be possible to the user to be free and name their room whatever they want and include spaces as well.
/*
consumers.py:
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from web socket
async def receive(self, text_data):
data = json.loads(text_data)
message = data['message']
username = data['username']
room = data['room']
await self.save_message(username, room, message)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'username': username
}
)
async def chat_message(self, event):
message = event['message']
username = event['username']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message,
'username': username,
'timestamp': timezone.now().isoformat()
}))
#sync_to_async
def save_message(self, username, room, message):
if len(message) > 0:
Message.objects.create(username=username, room=room, content=message)
routing.py:
websocket_urlpatterns = [
path('ws/<str:room_name>/', consumers.ChatConsumer.as_asgi()),
]
urls.py:
urlpatterns = [
path('', views.index, name='index'),
path('<str:room_name>/', views.room, name='room'),
]
index.html:
<script>
document.querySelector('#room-name-input').focus();
document.querySelector("#room-name-input, #username-input").onkeyup = function(e) {
if (e.keyCode === 13) {
document.querySelector('#room-name-submit').click();
}
};
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
var userName = document.querySelector('#username-input').value;
window.location.replace(roomName + '/?username=' + userName);
};
</script>
*/

Signals and django channels chat room

Hi i'm struggling with getting two things working simultaneously...
The channels2 chat room example was okay to get going, but i wanted to add a feature of knowing how many people where in the room. I did this by updating a model of the rooms.
Then I wanted to have a dashboard which would show the most popular current rooms, which again i wanted to update with using channels.
I used the django signals method and this method worked for updating the model whilst nobody was using the chat.
However, when seeing if the dashboard updated when somebody joined the chat there was an error.
2018-05-11 19:19:09,634 - ERROR - server - Exception inside application: You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly.
File "/dev/channels_sk/channels-master/channels/consumer.py", line 54, in __call__
await await_many_dispatch([receive, self.channel_receive], self.dispatch)
File "/dev/channels_sk/channels-master/channels/utils.py", line 50, in await_many_dispatch
await dispatch(result)
File "/dev/channels_sk/channels-master/channels/consumer.py", line 67, in dispatch
await handler(message)
File "/dev/channels_sk/channels-master/channels/generic/websocket.py", line 173, in websocket_connect
await self.connect()
File "/dev/channels_sk/tables/consumers.py", line 19, in connect
room.save()
File "/dev/channels_sk/.env/lib/python3.6/site-packages/django/db/models/base.py", line 729, in save
force_update=force_update, update_fields=update_fields)
File "/dev/channels_sk/.env/lib/python3.6/site-packages/django/db/models/base.py", line 769, in save_base
update_fields=update_fields, raw=raw, using=using,
File "/dev/channels_sk/.env/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 178, in send
for receiver in self._live_receivers(sender)
File "/dev/channels_sk/.env/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 178, in <listcomp>
for receiver in self._live_receivers(sender)
File "/dev/channels_sk/tables/signals.py", line 20, in room_save_handler
'update': instance.population,
File "/dev/channels_sk/.env/lib/python3.6/site-packages/asgiref/sync.py", line 34, in __call__
"You cannot use AsyncToSync in the same thread as an async event loop - "
You cannot use AsyncToSync in the same thread as an async event loop - just await the async function directly.
consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
import json
import time
from .models import Room
from django.db.models import F
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# create room or increment
room, created = Room.objects.get_or_create(title=self.room_name)
pop = room.population + 1
room.population = F('population') + 1
room.save()
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
# send new population to group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'pop_message',
'population': pop,
}
)
async def disconnect(self, close_code):
room = Room.objects.get(title=self.room_name)
pop = room.population - 1
if room.population == 1:
if room.permanent == False:
room.delete()
else:
room.population = F('population') - 1
room.save()
# send new population to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'pop_message',
'population': pop,
}
)
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
# 'type': 'chat_message',
'message': message,
}))
# change in room group population
async def pop_message(self, event):
content = event['type']
population = event['population']
# Send message to WebSocket
await self.send(text_data=json.dumps({
# 'type': 'pop_message',
'content': content,
'population': population,
}))
class RoomConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.group_name = 'chat_room_dash'
print("joined dash room")
# Join room group
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
print("left dash room")
pass
async def send_message(self, text_data=None):
print(text_data)
labels = []
data = []
for room in Room.objects.all()[:3]:
labels.append(room.title)
data.append(room.population)
await self.send(text_data=json.dumps(
{
'labels': labels,
'data': data,
}))
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from .models import Room
#receiver(post_save, sender=Room)
def room_save_handler(sender, instance, **kwargs):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'chat_room_dash',
{
'type': 'send_message',
'update': instance.population,
}
)
so far
In reading the error i have attempted the suggested solution by changing the the signals.py room_save_handler() to be async and await the message to the group.
This however didn't ever send the message when i updated the model manually or when a user went into the chat room.
My guest is that when the first consumer calls room.save() that the room_save_handler() is also called which means there is an async call within an async call.
Any help would be great!
I have the same problem and I have solved it in the following way:
import asyncio
from django.db.models.signals import post_save
from django.dispatch import receiver
from channels.layers import get_channel_layer
from .models import Room
#receiver(post_save, sender=Room)
def room_save_handler(sender, instance, **kwargs):
channel_layer = get_channel_layer()
loop = asyncio.get_event_loop()
coroutine = async_to_sync(channel_layer.group_send)(
'chat_room_dash',
{
'type': 'send_message',
'update': instance.population,
}
)
loop.run_until_complete(coroutine)

Categories

Resources