I'm going along with the channels documentation's tutorial and I have the exact same code as mentioned in the documentation's tutorial.
It's a simple chat application which echos the message to other connection in the same room.
here is my consumer class:
# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
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 group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
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']
# 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({
'message': message
}))
the problem is the consumer is sending all messages only to the last connection of the same group. for example if i open 2 browser tabs with /chat/room1/, only the second tab will be able to see the messages.
Related
i'm making a chat app with django,
everything is fine and working with the WebsocketConsumer and async_to_sync, but when i change to the AsyncWebSocket i get an error :
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
any idea or suggestion on why is that happening or what i can do here ? thanks for your help.
this is my consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.generic.websocket import WebsocketConsumer
#from asgiref.sync import async_to_sync
from django.utils import timezone
from .models import Message
from django.shortcuts import render, redirect, get_object_or_404
from courses.models import Course
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.user = self.scope['user']
self.id = self.scope['url_route']['kwargs']['course_id']
self.room_group_name = 'chat_%s' % self.id
# join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name)
# accept connection
await self.accept()
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']
now = timezone.now()
course = get_object_or_404(Course,id=self.id)
messages = Message.objects.create(author=self.scope['user'], content=message,course=course)
# send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'user': self.user.username,
'datetime': now.isoformat(),
}
)
# receive message from room group
async def chat_message(self, event):
# Send message to WebSocket
await self.send(text_data=json.dumps(event))
UPDATE
I found the Solution
those 2 lines are the rcause of the error:
course = get_object_or_404(Course,id=self.id)
messages = Message.objects.create(author=self.scope['user'],
content=message,course=course)
i'm using ththem to save messages in the database, aparently it's not possible in the async mode.
so according to the docs this is the modification i added :
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
then my save method:
#database_sync_to_async
def save_message(self,message):
course = get_object_or_404(Course,id=self.id)
messages = Message.objects.create(author=self.scope['user'],
content=message,course=course)
return messages
and finally replace the 2 line (the cause of problem) with this line in the receive() method :
await self.save_message(message)
Sorry for bad English :(
I want to create notification system per user. User has own group/room and every notification go to specific user's notification room. Every time when user connect to websocket, user creates the same id of user. self.scope["user"]. Therefore only user_notification_1 group name was created. How it is possible to create group name depending on user?
I use
application = ProtocolTypeRouter(
{
"websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
}
)
Code:
import json
from channels.generic.websocket import WebsocketConsumer
class NotificationConsumer(WebsocketConsumer):
async def connect(self):
self.user = self.scope["user"]
if self.user.is_authenticated:
self.room_group_name = f"user_notification_{self.user.id}"
else:
self.room_group_name = "anonymous"
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=None, bytes_data=None):
self.user = self.scope["user"]
if self.user.is_authenticated:
await self.send(
text_data=json.dumps({"message": "pong"})
)
else:
await self.send(
text_data=json.dumps({"type": "error", "code": "UserNotAuthenticated"})
)
async def new_chat_message(self, event):
await self.send(text_data=json.dumps(event.get("data")))
async def connect_successful(self, event):
await self.send(text_data=json.dumps(event.get("data")))
UPD My problem was solved when I noticed that I disabled REST_SESSION_LOGIN. I use dj-rest-auth
I want to send some message from Django view to django channels consumer. I have consumer like:
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class KafkaConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_group_name = 'kafka'
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
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']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'kafka_message',
'message': message
}
)
# Receive message from room group
async def kafka_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
And, my Django view is like:
from django.views.generic import TemplateView
from django.http import HttpResponse
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
class LogView(TemplateView):
template_name = "kafka/index.html"
def testview(request):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send(
'kafka',
{
'type': 'kafka.message',
'message': 'Test message'
}
))
return HttpResponse('<p>Done</p>')
URL url is like:
from django.urls import path
from .views import LogView, testview
urlpatterns = [
path(r'', LogView.as_view()),
path(r'test/', testview),
]
So, when I do http://mydevhost/test/, consumer do not receive message. However, I can send message from/within consumer i.e. KafkaConsumer.receive in channels consumer.
Pretty much silly mistake on async_to_sync. Actually async_to_sync should wrap only channel_layer.group_send instead of whole i.e. async_to_sync(channel_layer.group_send). So call looks like:
async_to_sync(channel_layer.group_send)(
'kafka',
{
'type': 'kafka.message',
'message': 'Test message'
}
)
All view code with corrected code:
from django.views.generic import TemplateView
from django.http import HttpResponse
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
class LogView(TemplateView):
template_name = "kafka/index.html"
def testview(request):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'kafka',
{
'type': 'kafka.message',
'message': 'Test message'
}
)
return HttpResponse('<p>Done</p>')
I have my WebSockets working with django channels. The last thing I'd like to do is close the socket if a user clicks a button, so in the frontend, I just send a disconnect message to the server:
socket.send({action: 'disconnect'});
Then, in my consumer, I simply disconnect:
async def receive(self, text_data):
text_data_json = json.loads(text_data)
if 'action' in text_data_json and text_data_json['action'] == 'disconnect':
await self.disconnect(0)
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
Why is my WebSocket not disconnecting properly? (I still receive messages)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
if 'action' in text_data_json and text_data_json['action'] == 'disconnect':
return await self.close()
you can also try this. You may also include a close code if required.
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
Try this
I want to integrate Django Channels.I have installed all required dependencies,and while starting the app server starts listening on TCP address 127.0.0.1:8000. My routing is specified as follows:
websocket_urlpatterns = [
url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumer.ChannelConsumer),
]
And the Consumer file:
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class ChannelConsumer(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 group
# await self.channel_layer.group_add(
# self.room_group_name,
# self.channel_name
# )
await self.accept()
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']
# 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({
'message': message
}))
However I cant open connection. Handshake fails
Delete ROUTING on CHANNEL_LAYERS settings
And change BACKEND to channels_redis.core.RedisChannelLayer
Example:
CHANNEL_LAYERS = {
"default": {
"CONFIG": {
"hosts": [('localhost','6379')],
},
"BACKEND": "channels_redis.core.RedisChannelLayer",
},
}
This happens just if you are upgrading channels 1.X to 2