I'm using django-channels to organize my websockets on backend.
Right now everything is working alright except of message sending to the front-end when info in db is changed. There is http endpoint to change model.
Here is my websocket consumer
import asyncio
from asgiref.sync import async_to_sync, sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from rest_framework.response import Response
from rest_framework import status
from channels.db import database_sync_to_async
# models and serializers
from billing.models import Billing
from billing.serializers import BillingSerializer
from customers.models import Customer
from customers.serializers import CustomersSerializer
from shipping.models import Shipping
from shipping.serializers import ShippingSerializer
from .models import Order
from .serializers import OrdersSerializer
from .views import OrdersViewSet
from .exceptions import ClientError
from .utils import get_orders_or_error, get_orders_or_warning
class OrdersConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
orders = await get_orders_or_warning()
if 'user' in self.scope:
await self.close()
else:
await self.accept()
self.orders = set(orders)
# await self.create_order(content)
async def receive_json(self, content):
command = content.get('command', None)
orders = await get_orders_or_warning()
# print(list(self.orders))
# print(set(orders))
try:
if command == "join":
await self.join_room(JWT_Token=content['token'])
elif command == "leave":
await self.leave_room()
elif command == "send":
await self.send_room(content['message'])
except ClientError as e:
await self.send_json({"error": e.code})
async def disconnect(self, code):
for room_id in list(self.rooms):
try:
self.send_json({
'type': 'CLOSE',
'message': "Socket closed"
})
await self.leave_room(content['token'])
except ClientError:
pass
async def join_room(self, JWT_Token):
orders = await get_orders_or_warning()
serializer = OrdersSerializer(orders, many=True)
# print('serializer', serializer)
if self.orders != set(orders):
await self.send_json(
{
'type': 'orders',
'data': json.dumps(serializer.data),
},
)
await self.send_json(
{
'type': 'orders',
'data': json.dumps(serializer.data),
},
)
async def leave_room(self, JWT_Token):
await self.channel_layer.group_send(
orders.group_name,
{
'type': 'orders.leave',
'JWT_Token': JWT_Token
}
)
self.rooms.discard()
await self.channel_layer.group_discard(
orders.group_name,
self.channel_name
)
await self.send_json({
"leave": str(room_id),
})
async def send_room(self, message):
if room_id not in self.rooms:
raise ClientError("ROOM_ACCESS_DENIED")
orders = await get_orders_or_warning()
serializer = OrdersSerializer(orders, many=True)
await self.send_json(
{
'type': 'orders.give',
'data': json.dumps(serializer.data)
}
)
await self.send(text_data=json.dumps({
'message': message
}))
async def orders_leave(self, event):
await self.send_json(
{
'type': 'LEAVE',
'room': event["room_id"],
'username': event["username"]
}
)
Here is my routing file
application = ProtocolTypeRouter({
'websocket': (
URLRouter([
url('orders/', OrdersConsumer),
])
)
})
I want to get all data with front-end when some changes happened. Can this be done with current consumer? And if yes, how? I've looked too many info sources i think and now I'm kinda confused how i can actually do this.
I rly don't want to rewrite the structure, if it's possible. If you can explain how and why I need to write it the way you say I will be very grateful
You can use signals to monitor the change and send a message to the consumer using the approach defined in the docs on how to interact with the consumer from the outside
Related
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.
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!
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
}))
I know how to broadcast but i can not target clients.
Here is my script:
import json
import trio
from quart import render_template, websocket, render_template_string
from quart_trio import QuartTrio
from quart_auth import current_user,login_required
from quart_auth import AuthUser, login_user, logout_user, AuthManager
import random
connections = set()
app = QuartTrio(__name__)
AuthManager(app)
app.secret_key = "secret key"
#app.route("/")
async def index():
clean_guy = await current_user.is_authenticated
if not clean_guy:
fake_ID = random.randrange(0, 9999) #quick dirty to test
login_user(AuthUser(fake_ID))
return await render_template_string("{{ current_user.__dict__ }}")
return await render_template_string("{{ current_user.__dict__ }}")
#app.websocket("/ws")
async def chat():
try:
connections.add(websocket._get_current_object())
async with trio.open_nursery() as nursery:
nursery.start_soon(heartbeat)
while True:
message = await websocket.receive()
await broadcast(message)
finally:
connections.remove(websocket._get_current_object())
async def broadcast(message):
for connection in connections:
await connection.send(json.dumps({"type": "message", "value": message}))
async def heartbeat():
while True:
await trio.sleep(1)
await websocket.send(json.dumps({"type": "heartbeat"}))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Here is my template:
<div>
<div>
<ul>
</ul>
</div>
<form>
<input type="text">
<button type="submit">Send</button>
</form>
</div>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
const ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === "message") {
const ulDOM = document.querySelectorAll("ul")[0];
const liDOM = document.createElement("li");
liDOM.innerText = data.value;
ulDOM.appendChild(liDOM);
}
}
document.querySelectorAll("form")[0].onsubmit = function(event) {
event.preventDefault();
const inputDOM = document.querySelectorAll("input")[0];
ws.send(inputDOM.value);
inputDOM.value = "";
return false;
};
});
</script>
Also one problem:
if i use this in my script:
return await render_template("{{ current_user.__dict__ }}")
i am not able to display it with my jinja template even if i add {{ current_user.dict }} in my template.
I also noticed that:
with mozilla: i get something stable like
{'_auth_id': 9635, 'action': <Action.PASS: 2>}
with chrome: it changes on each refresh, it looks like
{'_auth_id': 529, 'action': <Action.WRITE: 3>}
I need to display the author, and the destination , and an input with a send button, how to fix the template ?
Is it also possible to send messages to targeted users with post via curl or websocat ? how to do that ?
Quart-Auth uses cookies to identify the user on each request/websocket-request so you can always get the identity of the user from the current_user if request is authenticated. Then for your need you will need to map websocket connections to each user (so you can target messages), hence the connections mapping should be a dictionary of connections, e.g.
import random
from collections import defaultdict
from quart import request, websocket
from quart_trio import QuartTrio
from quart_auth import (
AuthUser, current_user, login_required, login_user, logout_user, AuthManager
)
connections = defaultdict(set)
app = QuartTrio(__name__)
AuthManager(app)
app.secret_key = "secret key"
#app.route("/login", methods=["POST"])
async def login():
# Figure out who the user is,
user_id = random.randrange(0, 9999)
login_user(AuthUser(fake_ID))
return {}
#app.websocket("/ws")
#login_required
async def chat():
user_id = await current_user.auth_id
try:
connections[user_id].add(websocket._get_current_object())
while True:
data = await websocket.receive_json()
await broadcast(data["message"])
finally:
connections[user_id].remove(websocket._get_current_object())
#app.route('/broadcast', methods=['POST'])
#login_required
async def send_broadcast():
data = await request.get_json()
await broadcast(data["message"], data.get("target_id"))
return {}
async def broadcast(message, target = None):
if target is None:
for user_connections in connections.values():
for connection in user_connections:
await connection.send_json({"type": "message", "value": message})
else:
for connection in connections[target]:
await connection.send_json({"type": "message", "value": message})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
You can then send to the /broadcast either JSON that is just a message {"message": "something"} or a message with an id to target someone specifically {"message": "something for user 2", "target_id": 2}. Note as well the #login_required decorator ensures the route handler is only called for logged in users.
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)