How to implement multichat in Django channels - django

I am implementing a real-time chat functionality using Django channels 3 and Django rest framework, where I have two types of users clients and admins , what I am trying to do is to create for every user a chat room in order to discuss with the admins, I tried to use multiple rooms and it doesn't work because the admin is unaware of the newly created user room , my second choice is to use room_group_name , but so far I don't really understand it, if someone can clarify or help I would be grateful .
Consumer.py
async def connect(self):
self.user = self.scope["user"]
print(self.user.is_superuser)
print(self.user.is_staff)
if self.user.is_superuser:
self.room_name = "chat"
self.room_group_name = 'admin_%s' % self.room_name
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
if self.user.is_client:
self.room_name = "chat"
self.room_group_name = 'client_%s'.format(self.user) % self.room_name
admin = 'admin_%s' % self.room_name
await self.channel_layer.group_add(
# self.room_group_name,
self.room_group_name,
self.channel_name
)
await self.accept()
So far I can separate the two groups, but the trick is how to add admin users to the client group room ??

Related

Send disconnect message to WebSocket in Django Channels

I am using Django==3.2.5 and channels==3.0.4. In the consumers.py I am using WebsocketConsumer class. My disconnect() method is executed After disconnecting the socket, which means the disconnect method is unable to send a response to the socket. I found a similar issue in this link but they are using AsyncWebsocketConsumer class. Is there a way to solve it using WebsocketConsumer class?
I have the following code:
class MeetingStatus_Consumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["id"]
self.room_group_name = 'Booking_id_%s' % self.room_name
# Adding room_name and group_name to the channel
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept() # accept the request from frontend
self.send(text_data=json.dumps({'status': "Websocket Connected"})) # send data to frontend
def disconnect(self, *args, **kwargs):
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
self.send(text_data=json.dumps({'status': "Websocket disconnected"}))

database_sync_to_async not working in django channels consumers

This is my consumers file and I am using AsyncWebSocketConsumer. And Get details function is not working.
class NotificationConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['username']
self.room_group_name = 'notification_%s' % self.room_name
notificationobjs=await self.get_details(self.room_name)
# unseencount=application_notifications.objects.filter(User=User.objects.get(username=self.room_name),is_seen=False).count()
data={'notifications':NotificationSerializer(notificationobjs,many=True).data,'count':0}
print(self.room_group_name)
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
await self.send(text_data=json.dumps({'status':'connected','user':self.room_group_name,'payload':data}))
#database_sync_to_async
def get_details(self,room_name):
return application_notifications.objects.filter(User=User.objects.get(username=room_name))
Its Showing the error "django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async."
If Anyone can help me solve this issue, It would be highly appreciated. Thanks

Django: Query database from Channels consumers

I had a Django app that had live chat working. Now I am trying to add a query to the database within the connect method. I am following the Channels documentation, and tried the solution in this other StackOverflow question but nothing is working.
Below is my code. The error I'm seeing in the Javascript console is `WebSocket connection to 'ws://localhost:9008/ws/chat/334/' failed: WebSocket is closed before the connection is established. I do have redis-server running on localhost and that was working before, so that's not a problem.
async def connect(self):
print('connect (got here!)')
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
print('self.room_name: ' + str(self.room_name))
valid_connection = await database_sync_to_async(self.verify_chat_room_key)()
print('valid_connection: ' + str(valid_connection))
# Join room group
# async_to_sync(self.channel_layer.group_add)(
# self.room_group_name,
# self.channel_name
# )
await self.accept()
def verify_chat_room_key(self):
print('~~~in verify method...~~~')
return True
Edit: Below is the stacktrace showing the python debugs:
HTTP GET /confirmed-trade/334/ 200 [1.28, 127.0.0.1:53877]
WebSocket HANDSHAKING /ws/chat/334/ [127.0.0.1:58882]
HTTP GET /static/favicon.png 200 [0.01, 127.0.0.1:53877]
WebSocket HANDSHAKING /ws/chat/334/ [127.0.0.1:59246]
WebSocket DISCONNECT /ws/chat/334/ [127.0.0.1:59246]
disconnect
WebSocket HANDSHAKING /ws/chat/334/ [127.0.0.1:65175]
WebSocket DISCONNECT /ws/chat/334/ [127.0.0.1:58882]
disconnect
WebSocket DISCONNECT /ws/chat/334/ [127.0.0.1:65175]
disconnect
self.accept() is asynchronous, so it should be:
await self.accept()
from channels.db import database_sync_to_async
class ChatConsumer(AsyncWebsocketConsumer):
""" handshake websocket front end """
room_name = None
room_group_name = None
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
"""Creating a new variable """
self.botname = await database_sync_to_async(self.get_name)()
print(self.botname)
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
""" Creating a Function """
def get_name(self):
return CustomUser.objects.filter(groups__id = self.room_name).filter(is_bot = True)[0].username
may be, helpful for you

Django channels live chat save sent messages

So I have a Django app in which I used channels to implement live chat. My consumer looks like this:
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
username = self.scope["user"]
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'user': username.username
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
user=event['user']
print(user)
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message,
'user':user
}))
So I am looking for a way to save the sent messages (because currently they are lost on refresh). I have created a Messages model that has a CharField for the message text. I think I can save new messages if I do this in my chat_message function:
new_message=Messages(text=message)
new_nessage.save()
My question is how do I preload the last 10 messages whenever a user gets connected to the chat?
So in order to do this, I ended up saving my message to the database in the "receive" function (because if you save it in the chat_message one it's saved 1 time for each active user). Then in order to preload messages I used an AJAX call every time a websocket is opened in order to get the 10 most recent messages using a python function and then I passed the messages back as a JsonResponse (https://simpleisbetterthancomplex.com/tutorial/2016/08/29/how-to-work-with-ajax-request-with-django.html scroll down to the AJAX requests part of this article and take a look at signup.html, urls.py and views.py files in this order for reference).

Send message on model save to anyone at WebSocket URL using Channels 2.0

I'm trying to send a message to all users with an open websocket connection at a specific URL each time a model is saved. I'm using the Channels community project knocker as a reference but in doing so I have to modify it to work with Channels 2.0.
Using signals fired on a model's post_save knocker sends a notification to the Group.
In Channels 2.0, Groups are handled differently so this line Group('myGroup').send({'text': json.dumps(knock)}) in the send_knock method isn't working. Is it possible to modify this line to work with the consumer below?
class WeightConsumer(WebsocketConsumer):
def connect(self):
self.group_name = 'weight'
# Join group
async_to_sync(self.channel_layer.group_add)(
self.group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave group
async_to_sync(self.channel_layer.group_discard)(
self.group_name,
self.channel_name
)
def receive(self, text_data):
pass
Just had to make use of get_channel_layer()
def send_knock(self, created=False):
"""
Send the knock in the associated channels Group
"""
channel_layer = get_channel_layer()
group_name = 'weight'
weight = '%s' % self.get_knocker_weight()
# Send message to group
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'weight_message',
'weight': weight
}
)
Then add another method to the consumer.
class WeightConsumer(WebsocketConsumer):
...
def receive(self, text_data):
pass
def weight_message(self, event):
weight = event['weight']
# Send message to websocket
self.send(text_data=json.dumps({
'weight': weight
}))