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
Related
I am trying to get use Django channels to send data over a websocket to my react native application from django. I have read all the available documentation on this subject on Django and have went through numerous stackoverflow posts, but I don't think they are applicable to me because they use redis and I decided not to use redis.
Whenever I try to send data right now, nothing sends.
These are my files.
models.py
from django.db import models
import json
from .consumers import DBUpdateConsumer
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
channel_layer = get_channel_layer()
class Connect(models.Model):
id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
neighborhood = models.CharField(max_length=50, choices=neighborhood_choices, default='all')
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.CharField(max_length=100)
phone = models.CharField(max_length=50)
def save(self, *args, **kwargs):
super().save(self, *args, **kwargs)
print("def save")
async_to_sync(channel_layer.send)("hello", {"type": "something", "text": "hellooo"})
class Meta:
managed = False
db_table = 'connect'
settings.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
consumers.py
import json
from channels.generic.websocket import AsyncJsonWebsocketConsumer
#used https://blog.logrocket.com/django-channels-and-websockets/
#https://channels.readthedocs.io/en/latest/topics/consumers.html
class DBUpdateConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.send_message(self, "UPDATE")
await self.accept()
await self.send(text_data=json.dumps({
"payload": "UPDATE",
}))
print("connect!")
async def disconnect(self, close_code):
print("Disconnected")
async def receive(self, text_data):
"""
Receive message from WebSocket.
Get the event and send the appropriate event
"""
response = json.loads(text_data)
#event = response.get("event", None)
#message = response.get("message", None)
print(response)
#classmethod
async def send_message(cls, self, res):
# Send message to WebSocket
print("send msg")
await self.send(text_data=json.dumps({
"payload": res,
}))
print("send msg")
What I am trying to do is whenever a new value is stored in my database, I am trying to send a message through a websocket that connects my react native app and my django backend. The websocket currently connects fine, but I am having trouble using the send_message function contained within my consumers.py file from outside consumers.py. So what I am trying to do is in my models.py file, send a message to all the channels that are open to eventually update my database. Currently, I am just trying to send test messages through, but no matter what I do, nothing goes through, and being a newbie to Django, I have no idea why.
Thank you!
Solved, with some help from a friend!
consumers.py
import json
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from .models import Client
from asgiref.sync import sync_to_async
#used https://blog.logrocket.com/django-channels-and-websockets/
#https://channels.readthedocs.io/en/latest/topics/consumers.html
class DBUpdateConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
print("channel name is " + self.channel_name)
await sync_to_async(Client.objects.create)(channel_name=self.channel_name)
await self.accept()
await self.send(text_data=json.dumps({
"payload": "UPDATE",
}))
print("connect!")
async def disconnect(self, close_code):
print("Disconnected")
# Leave room group
"""await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)"""
async def update(self, message):
print("Sent message " + message["text"])
await self.send(text_data=json.dumps({
"payload": "UPDATE",
}))
async def receive(self, text_data):
"""
Receive message from WebSocket.
Get the event and send the appropriate event
"""
response = json.loads(text_data)
#event = response.get("event", None)
#message = response.get("message", None)
print(response)
"""if event == 'MOVE':
# Send message to room group
await self.channel_layer.group_send(self.room_group_name, {
'type': 'send_message',
'message': message,
"event": "MOVE"
})
if event == 'START':
# Send message to room group
await self.channel_layer.group_send(self.room_group_name, {
'type': 'send_message',
'message': message,
'event': "START"
})
if event == 'END':
# Send message to room group
await self.channel_layer.group_send(self.room_group_name, {
'type': 'send_message',
'message': message,
'event': "END"
})"""
# #classmethod
# async def send_message(cls, self, res):
# # Send message to WebSocket
# print("send msg")
# await self.send(text_data=json.dumps({
# "payload": res,
# }))
# print("send msg")
models.py
class Connect(models.Model):
id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
neighborhood = models.CharField(max_length=50, choices=neighborhood_choices, default='all')
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.CharField(max_length=100)
phone = models.CharField(max_length=50)
def save(self, *args, **kwargs):
super().save(self, *args, **kwargs)
clients = Client.objects.all()
for client in clients:
async_to_sync(channel_layer.send)(client.channel_name, {"type": "update", "text": "hellooo"})
class Meta:
managed = False
db_table = 'connect'
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)
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.
I have a model called deposit and I am trying to send real time notification when a new row is added in table. Is it possible to do so using django-channels ?
You can use the save method of your Django model, like so:
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
class Info(models.Model):
# your model fields
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
super(Info, self).save(force_insert, force_update, using, update_fields)
# send info to channel
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'infochannel',
{
'type': 'infochannel.message',
'device_id': str(self.device_id)
}
)
And in the consumer:
from channels.generic.websocket import AsyncWebsocketConsumer
class DataConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.group_name = ''
async def connect(self):
# we are using one fixed group
self.group_name = 'infochannel'
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard('infochannel', self.channel_name)
async def infochannel_message(self, event):
# Send message to websocket group
await self.send(text_data=event['device_id'])
Where device_id is a field on my model, and of course you also have to set up routing, redis_channels, and so on.
You can simply add a signal for a post_save
#receiver(post_save, sender=Deposit)
def signal_deposit_save(sender, instance, created, **kwargs):
if created: # This means it is a new row
# Send notification using django-channels
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