I'm trying to get the profile firstname but it gives me this error
File "/home/marwan/Desktop/Gowd/venv/lib/python3.6/site-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
WebSocket DISCONNECT /public_chat/1/ [127.0.0.1:53742]
INFO 2021-03-12 01:43:30,570 runserver 119412 140507688003328 WebSocket DISCONNECT /public_chat/1/ [127.0.0.1:53742]
I think that this error has something to do with trying to get the user profile in line 56
"profile": self.scope["user"].profile.firstname,
if I remove it will work fine
this is my consumers.py file
from channels.generic.websocket import AsyncJsonWebsocketConsumer
import json
from django.contrib.auth import get_user_model
from gowd_project.users.models import User, Profile
User = get_user_model()
# Example taken from:
# https://github.com/andrewgodwin/channels-examples/blob/master/multichat/chat/consumers.py
class PublicChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
"""
Called when the websocket is handshaking as part of initial connection.
"""
print("PublicChatConsumer: connect: " + str(self.scope["user"]))
# let everyone connect. But limit read/write to authenticated users
await self.accept()
# Add them to the group so they get room messages
await self.channel_layer.group_add(
"public_chatroom_1",
self.channel_name,
)
async def disconnect(self, code):
"""
Called when the WebSocket closes for any reason.
"""
# leave the room
print("PublicChatConsumer: disconnect")
pass
async def receive_json(self, content):
"""
Called when we get a text frame. Channels will JSON-decode the payload
for us and pass it as the first argument.
"""
# Messages will have a "command" key we can switch on
command = content.get("command", None)
print("PublicChatConsumer: receive_json: " + str(command))
print("PublicChatConsumer: receive_json: message: " + str(content["message"]))
if command == "send":
if len(content["message"].lstrip()) == 0:
raise Exception("You can't send an empty message.")
await self.send_message(content["message"])
async def send_message(self, message):
await self.channel_layer.group_send(
"public_chatroom_1",
{
"type": "chat.message",
"user_id": self.scope["user"].id,
"user_name": self.scope["user"].name,
"profile": self.scope["user"].profile.firstname,
"message": message,
}
)
async def chat_message(self, event):
"""
Called when someone has messaged our chat.
"""
# Send a message down to the client
print("PublicChatConsumer: chat_message from user #" + str(event["user_id"]))
await self.send_json(
{
"user_id": event["user_id"],
"user_name": event["user_name"],
"message": event["message"],
},
)
sync_to_async takes a callable, not the result. so, I need to do this:
#sync_to_async
def get_user_profile(self, user):
return user.profile
async def send_message(self, message):
profile = await PublicChatConsumer.get_user_profile(self.scope["user"])
profile_name = profile.firstname
await self.channel_layer.group_send(
"public_chatroom_1",
{
"type": "chat.message",
"user_id": self.scope["user"].id,
"username": profile_name,
"message": message,
}
)
full documentation
Similar problem
Related
This is my class:
class MyClass(models.Model):
name = models.CharField(max_length=128, unique=True)
members = models.ManyToManyField("Employee")
def save(self, *args, **kwargs):
super().save(*args,**kwargs)
channel_layer = get_channel_layer()
data = {"current_obj":self.name}
async_to_sync(channel_layer.group_send)(
"test_consumer_group_1",{
'type':'send_notification',
'value':json.dumps(data)
}
)
This is my consumer file.
class MySyncGroupConsumer(SyncConsumer):
def websocket_connect(self,event):
print("Web socket connected", event)
self.send({
'type':'websocket.accept'
})
print("Channel layer ",self.channel_layer)
print("Channel name", self.channel_name)
async_to_sync(self.channel_layer.group_add)("test_consumer_group_1", self.channel_name)
def websocket_receive(self,event):
print("Web socket recieved",event)
print(event["text"])
sync_to_sync(self.channel_layer.group_send)("test_consumer_group_1", {
'type':'chat.message',
'message':event['text']
})
#event handler, which is sending data to client
def chat_message(self,event):
print('Event...', event)
print('Event...', event["message"])
self.send({
"type":"websocket.send",
"text":event['message']
})
def send_notification(self, event):
print("send_notification called")
print('Event...', event['value'])
# Get the current user from the channel layer
user = self.scope['user']
print(user)
# Check if the user is authenticated and is a member of the team
if user.is_authenticated and user.employee in self.team.members.all():
self.send({
"type":"websocket.send",
"text":event['value']
})
def websocket_disconnect(self,event):
print("Web socket disconnect", event)
async_to_sync(self.channel_layer.group_discard)("test_consumer_group_1", self.channel_name)
raise StopConsumer()
This is my asgi.py file:
import os
import app.routing
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
application = get_asgi_application()
application = ProtocolTypeRouter({
'http': get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
app.routing.websocket_urlpatterns
)
),
})
When an object of MyClass is created, send_notification is called.
Now I want to ask two things:
Inside send_notification, user = self.scope['user'] always returns anonymous user, even if user is authenticated.
I want to send notification to particular users onlyi.e. employees who are added in MyClass object and are logged in, instead of broadcasting the message
How to do that?
I want to send data from the firebase stream(pyrebase) via django channels websocket. Web scoket is working fine, But I can't imagine how I can use consumer.py file send message function in init_.py.
Here my django channel web socket Consumer.py file.
import json
from random import randint
from asyncio import sleep
from channels.generic.websocket import AsyncWebsocketConsumer
from django.conf import settings
class WSConsumer(AsyncWebsocketConsumer):
group_name = settings.STREAM_SOCKET_GROUP_NAME
async def connect(self):
# Joining group
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave group
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)
async def receive(self, text_data):
print(text_data)
# Send data to group
await self.channel_layer.group_send(
self.group_name,
{
'type': 'system_load',
'data': text_data
}
)
async def system_load(self, event):
# Receive data from group
print('sending message to client')
await self.send(text_data=json.dumps(event['data']))
This file works fine.
This is my inti.py file. In this file, I want to send data to the WebSocket in this stream_handler function.
import pyrebase
from configFiles.config import config
firebase = pyrebase.initialize_app(config)
authe = firebase.auth()
database = firebase.database()
safe_temperature = 20
safe_humidity = 40
def stream_handler(message):
if "Humidity" in message["path"]:
if message["data"] > safe_humidity:
database.child("Controller").child("Light").set(1)
else:
database.child("Controller").child("Light").set(0)
if "Temperature" in message["path"]:
if message["data"] > safe_humidity:
database.child("Controller").child("Fan").set(1)
else:
database.child("Controller").child("Fan").set(0)
# Here place I want to send data which is message["data"] to the WebSocket
mystream = database.child("Sensor").stream(stream_handler)
Thank you!
So I have a websockets consumer like this
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class GatewayEventsConsumer(AsyncWebsocketConsumer):
"""
An ASGI consumer for gateway event sending. Any authenticated
user can connect to this consumer. Users receive personalized events
based on the permissions they have.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
async def connect(self):
user = self.scope['user']
if user.is_anonymous:
# close the connection if the
# user isn't authenticated yet
await self.close()
for member in user.member_set.all():
await self.channel_layer.group_add(member.box.id, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
for member in self.scope['user'].member_set.all():
await self.channel_layer.group_discard(member.box.id, self.channel_name)
async def fire_event(self, event: dict):
# need to implement caching
formatted = {
'data': event['data'],
'event': event['event'],
}
required_permissions = event.get('listener_permissions', [])
overwrite_channel = event.get('overwrite_channel', None)
# need to know which group the event is being sent inside
# to get the box-id, and thus get the member to check perms
member_permissions = []
if not required_permissions or required_permissions in member_permissions:
await self.send(text_data=json.dumps(formatted))
on websocket connect, I put the user into groups according to the boxes they are a part of. On disconnect, I remove them all. Now, this is how I fire my events.
#receiver(post_save, sender=api_models.Upload)
def on_upload_save(instance=None, **kwargs):
if kwargs.pop('created', False):
return async_to_sync(channel_layer.group_send)(
instance.box.id,
{
'type': 'fire_event',
'event': 'UPLOAD_CREATE',
'listener_permissions': ['READ_UPLOADS'],
'overwrite_channel': instance.channel,
'data': api_serializers.PartialUploadSerializer(instance).data
}
)
So the event is being sent to a group with the box-id here.
However, I also need to check box-specific permissions for these events.
So inside the fire_event method, I need to do this.
async def fire_event(self, event: dict):
# need to implement caching
formatted = {
'data': event['data'],
'event': event['event'],
}
required_permissions = event.get('listener_permissions', [])
overwrite_channel = event.get('overwrite_channel', None)
# need to know which group the event is being sent inside
# to get the box-id, and thus get the member to check perms
member_permissions = []
if not required_permissions or required_permissions in member_permissions:
await self.send(text_data=json.dumps(formatted))
I need to know in which group this event is being sent under to determine permissions for the user. Is there any way to achieve this?
I'm developing WebSocket programs in Django. I want to receive the request from the client(ReactJS) and send signals to WebSocket consumer, and then WebSocket consumer sends messages to the client.
How to solve such a problem?
I tried as following.
urls.py
urlpatterns = [
url(r'^updatesignal/$',updatesignal),
url(r'^stopsignal/$',stopsignal),
]
views.py
#api_view(['POST'])
def updatesignal(request):
print("update")
consumers.isconnected = 1
return Response("update signal")
#api_view(['POST'])
def stopsignal(request):
print("stop signal")
consumers.isconnected = 0
return Response("stop signal")
consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
import json
import asyncio
isconnected = 0
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
print("connect")
await self.accept()
while isconnected == 1:
await asyncio.sleep(2)
print("true")
# obj = # do_something (Ex: constantly query DB...)
await self.send(text_data=json.dumps({
'message': "aaa"
}))
async def disconnect(self, close_code):
# Leave room group
pass
async def receive(self, text_data):
# Receive message from WebSocket
.....
But WebSocket consumer is fired after 10 seconds after receiving update signal request. How to solve this problem?
From this answer, which helps to send data from consumers for every n seconds.
Tried to handle the disconnection properly, using creat_task method, tried to stop while-loop(which is used to send data for every n seconds) by sending a flag=False(Assuming, this flag is not sent to the same instance which is created the task).
consumers.py:
class AutoUpdateConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("connected", event)
await self.send({
"type": "websocket.accept"
})
await self.create_task(True)
async def websocket_receive(self, event):
print("receive", event)
async def websocket_disconnect(self, event):
await self.create_task(False)
print("disconnected", event)
async def create_task(self, flag=True):
while flag:
await asyncio.sleep(2)
df= pd.DataFrame(data=[random.sample(range(100), 4) for _ in range(5)])
await self.send({
'type': 'websocket.send',
'text': df.to_html(),
})
Warning:
2019-09-11 14:40:06,400 - WARNING - server - Application instance
<Task pending coro=<SessionMiddlewareInstance.__call__() running at
D:\Django\Django channels\django_channels_env\lib\site-packages\channels\sessions.py:175>
wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at
0x000001870E06C618>()] for connection <WebSocketProtocol client=
['127.0.0.1', 63789] path=b'/ws/home'> took too long to shut down and was
killed.
How to stop_task safely instead of waiting for channels to kill task?
Or
How to stop infinite while loop running in a method, from another method in same class?
Versions:
Django == 2.0.7
channels == 2.1.2
I would suggest creating a group when connecting to the consumer. That way you can trigger a message from anywhere in your django project as long as you know the group name (auto_update).
from channels.generic.websocket import AsyncWebsocketConsumer
class AutoUpdateConsumer(AsyncWebsocketConsumer):
async def connect(self):
print('connect')
# join the group
self.group_name = 'auto_update'
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, event):
print('disconnect')
# leave the group
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)
async def receive(self, event):
print('receive')
async def auto_update(self, event):
print('sending df')
df = event['df']
await self.send({
'text': df
})
To send the message I would use a custom management command. To stop the command I would create a singleton model (a model with only one instance) that has a boolean field that can be periodically checked to see if the loop should be stopped.
First use get_channel_layer() to get the active layer that communicates with redis, then in the loop call group_send to invoke a consumer method specified by the type key.
# /project/app/management/commands/auto_update.py
from django.core.management.base import BaseCommand
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from config.models import AutoUpdateSettings
class Command(BaseCommand):
help = 'Command to start auto updating'
def handle(self, *args, **kwargs):
settings = AutoUpdateSettings.objects.first()
settings.keep_running = True
settings.save()
group_name = 'auto_update'
channel_layer = get_channel_layer()
while True:
settings.refresh_from_db()
if not settings.keep_running:
break
df= pd.DataFrame(data=[random.sample(range(100), 4) for _ in range(5)])
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'auto_update', # this is the name of your consumer method
'df': df.to_html()
}
)
To start the loop that sends the message to the group you would call the command python manage.py auto_update. To stop the command you would use the admin page and set keep_running to false.