I am trying to save the data which i am receiving from my client using the Django Channels.
I have read the documentation but its not very clear.
Here is my code of consumer.py
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
# Send message to WebSocket
message2 = message[1]
self.save_data(message2)
self.send(text_data=json.dumps({
'message': message2
}))
#database_sync_to_async
def save_data (self, message):
return DeviceLogs.objects.create(voltage=message)
As you may have noticed that i just want to save message2 in database.
Not sure what's the problem in your code but this should work for you.
async def chat_message(self, event):
...
message2 = message[1]
await self.save_message(message2)
...
#database_sync_to_async
def save_message(self, message):
... save message here
Looks like your consumer extends WebsocketConsumer (sync consumer). If that is the case, remove the #database_sync_to_async decorator and it should be fine. You only need to do that if your consumer is async.
From the docs:
The Django ORM is a synchronous piece of code, and so if you want to access it from asynchronous code you need to do special handling to make sure its connections are closed properly.
If you’re using SyncConsumer, or anything based on it - like JsonWebsocketConsumer - you don’t need to do anything special, as all your code is already run in a synchronous mode and Channels will do the cleanup for you as part of the SyncConsumer code.
If you are writing asynchronous code, however, you will need to call database methods in a safe, synchronous context, using database_sync_to_async.
Related
How would a continuous stream be implemented with websockets (preferably with django-channels)?
My code continuously sends a message. I would like to be able to start/stop it but at the moment doesn't respond on the serverside to the start/stop event (the print statement doesnt show up). It's like the whole consumer is blocking the thread even though it is async. I can't seem to figure out how to implement this.
import asyncio
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
pause = False
async def connect(self):
await self.accept()
await self._main_loop()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
print(text_data_json)
pause = text_data_json["pause"]
self.pause = bool(pause)
async def _main_loop(self):
while True:
if not self.pause:
await self.send(text_data=json.dumps({"message": "play"}))
else:
await self.send(text_data=json.dumps({"message": "pause"}))
await asyncio.sleep(1)
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?
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).
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.