how to get data from database using django channels? - django

I'm trying to practice WebSocket implementation using django channels by querying the database and printing the data but I am unsuccessful.
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from .models import Chart
from chart.api.serializers import ChartSerializer
class ChartConsumer(AsyncWebsocketConsumer):
def get_chart(self):
return database_sync_to_async(Chart.objects.all)()
async def connect(self):
data = self.get_chart()
print(data) # <-------- I want to get this data
# for i in data:
# chart_data = ChartSerializer(i).data
# await self.send(json.dumps({'number': chart_data.number}))
# print(chart_data)
await self.accept()
async def disconnect(self, code):
pass
Output

You need the database_sync_to_async to be called in the connect method. Like so:
class ChartConsumer(AsyncWebsocketConsumer):
def get_chart(self):
return Chart.objects.all()
async def connect(self):
data = await database_sync_to_async(self.get_chart)()
print(data)
# for i in data:
# chart_data = ChartSerializer(i).data
# await self.send(json.dumps({'number': chart_data.number}))
# print(chart_data)
await self.accept()

Related

Send value from __init__.py to consumer.py websocket Django channels

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!

SynchronousOnlyOperation Error in with django 3 and django channels

I had a django 2 app and i used django channels for socket connection.
i just update django to version 3. and now daphne show this error when i try to make a socket connection. i had not any problem with django 2.
[Failure instance: Traceback: <class 'django.core.exceptions.SynchronousOnlyOperation'>: You cannot call this from an async context - use a thread or sync_to_async.
/home/ubuntu/pl_env/lib/python3.6/site-packages/autobahn/websocket/protocol.py:2844:processHandshake
/home/ubuntu/pl_env/lib/python3.6/site-packages/txaio/tx.py:429:as_future
/home/ubuntu/pl_env/lib/python3.6/site-packages/twisted/internet/defer.py:151:maybeDeferred
/home/ubuntu/pl_env/lib/python3.6/site-packages/daphne/ws_protocol.py:83:onConnect
--- <exception caught here> ---
/home/ubuntu/pl_env/lib/python3.6/site-packages/twisted/internet/defer.py:151:maybeDeferred
/home/ubuntu/pl_env/lib/python3.6/site-packages/daphne/server.py:201:create_application
/home/ubuntu/pl_env/lib/python3.6/site-packages/channels/routing.py:54:__call__
/home/ubuntu/pl_env/lib/python3.6/site-packages/channels/security/websocket.py:37:__call__
/home/ubuntu/petroline_django/orders/token_auth.py:25:__call__
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/manager.py:82:manager_method
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:411:get
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:258:__len__
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:1261:_fetch_all
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/query.py:57:__iter__
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/db/models/sql/compiler.py:1142:execute_sql
/home/ubuntu/pl_env/lib/python3.6/site-packages/django/utils/asyncio.py:24:inner
it says the problem is in token_auth.py, line 25. this line is token = Token.objects.get(key=token_key)
this is my token_auth.py that handles token authentication.
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework.authtoken.models import Token
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
see:
https://channels.readthedocs.io/en/latest/topics/authentication.html#custom-authentication
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
headers = dict(scope['headers'])
if b'authorization' in headers:
try:
token_name, token_key = headers[b'authorization'].decode().split()
if token_name == 'Token':
# Close old database connections to prevent usage of timed out connections
close_old_connections()
token = Token.objects.get(key=token_key)
scope['user'] = token.user
except Token.DoesNotExist:
scope['user'] = AnonymousUser()
return self.inner(scope)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
Thanks to #ivissani answers, i fixed my TokenAuthMiddleware with some of SessionMiddleware codes.
I have opened an issue for django channels about updating docs.
#database_sync_to_async
def get_user(token_key):
try:
return Token.objects.get(key=token_key).user
except Token.DoesNotExist:
return AnonymousUser()
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
see:
https://channels.readthedocs.io/en/latest/topics/authentication.html#custom-authentication
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
return TokenAuthMiddlewareInstance(scope, self)
class TokenAuthMiddlewareInstance:
def __init__(self, scope, middleware):
self.middleware = middleware
self.scope = dict(scope)
self.inner = self.middleware.inner
async def __call__(self, receive, send):
headers = dict(self.scope['headers'])
if b'authorization' in headers:
token_name, token_key = headers[b'authorization'].decode().split()
if token_name == 'Token':
self.scope['user'] = await get_user(token_key)
inner = self.inner(self.scope)
return await inner(receive, send)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
Fixed by using #database_sync_to_async decorator:
(see https://github.com/MathieuB1/KOREK-backend/commit/ff6a4b542cda583a1d5abbf200a5d57ef328cae0#diff-95e545fb374a9ed7e8af8c31087a3f29)
import jwt, re
import traceback
from channels.auth import AuthMiddlewareStack
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser
from django.conf import LazySettings
from jwt import InvalidSignatureError, ExpiredSignatureError, DecodeError
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
settings = LazySettings()
from django.db import close_old_connections
#database_sync_to_async
def close_connections():
close_old_connections()
#database_sync_to_async
def get_user(user_jwt):
try:
return User.objects.get(id=user_jwt)
except User.DoesNotExist:
return AnonymousUser()
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
# Close old database connections to prevent usage of timed out connections
close_connections()
# Login with JWT
try:
if scope['subprotocols'][0] != 'None':
token = scope['subprotocols'][0]
try:
user_jwt = jwt.decode(
token,
settings.SECRET_KEY,
)
scope['user'] = get_user(user_jwt['user_id'])
return self.inner(scope)
except (InvalidSignatureError, KeyError, ExpiredSignatureError, DecodeError):
traceback.print_exc()
pass
except Exception as e:
traceback.print_exc()
else:
raise
Refer to this part of the documentation. There it is explained that Django 3 will raise such exception if you try to use the ORM from within an async context (which seems to be the case).
As Django Channels documentation explains solution would be to use sync_to_async as follows:
from channels.db import database_sync_to_async
class TokenAuthMiddleware:
# more code here
async def __call__(self, scope):
# and some more code here
token = await database_sync_to_async(Token.objects.get(key=token_key))()
Although please bear in mind that I haven't used this in my life, so it may fail.
Note that in the Django channels documentation it says that you need to write your query in a separate method. So if this fails try doing that.

How to send signals to consumer in django?

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?

Connection error while connection has already been established

I use the Telethon == 1.4.3 in my code:
import telepot
import threading
from telethon import TelegramClient
from flask import Flask, request
from telepot.loop import OrderedWebhook
from telepot.delegate import (
per_chat_id, create_open, pave_event_space, include_callback_query_chat_id)
class Main_Class(telepot.helper.ChatHandler):
def __init__(self, *args, **kwargs):
super(Main_Class, self).__init__(*args, **kwargs)
def on_chat_message(self, msg):
content_type, chat_type, chat_id = telepot.glance(msg)
if content_type == 'text':
client = TelegramClient('session_name', api_id, api_hash)
client.connect()
client.send_message('me', 'Hello World from Telethon!')
app = Flask(__name__)
#app.route('/', methods=['GET', 'POST'])
def pass_update():
webhook.feed(request.data)
return 'OK'
TOKEN = my_token
bot = telepot.DelegatorBot(TOKEN, [
include_callback_query_chat_id(
pave_event_space())(
per_chat_id(types=['private']), create_open, Main_Class, timeout=100000),
])
webhook = OrderedWebhook(bot)
webhook.run_as_thread()
Since I also use the Flask, these two interfere with each other and I got the following error:
RuntimeError: There is no current event loop in thread 'Thread-1'
I imported asyncio and added the following lines to the code and the problem was solved
class Main_Class(telepot.helper.ChatHandler):
......
loop = asyncio.new_event_loop()
client = TelegramClient('session_name', api_id, api_hash,loop=loop)
loop.run_until_complete(goo(loop,client))
loop.close()
.....
async def goo(loop,client):
client.connect()
await client.send_message('me', 'Hello World from Telethon!')
The following error occurs despite the fact that I have already established a connection:
ConnectionError: Cannot send requests while disconnected
You should also wait for the connection to finish. Because it is done asynchronously.
async def goo(loop,client):
await client.connect()
await client.send_message('me', 'Hello World from Telethon!')
Read more, here.

django-channels webSocketBridge read data sent by consumers

I am having a simple setup with testing channels 2.0.
I have a routed consumer with three methods:
consumers.py
from channels.generic.websocket import JsonWebsocketConsumer
from datetime import datetime
#
class Feedback_001_Consumer(JsonWebsocketConsumer):
def connect(self,text_data=None, bytes_data=None):
self.accept()
self.send_json("Connection for Feedback 1 ready.")
#
def receive(self,text_data=None):
#self.accept()
self.send_json("{}".format(datetime.now()))
print(datetime.now())
#
def disconnect(self,close_code):
pass
and my js looks like this:
const webSocketBridge = new channels.WebSocketBridge();
#...
webSocketBridge.connect('my_route/etc...');
#...
console.log(webSocketBridge.send(''));
While the datetime is printing in the console, I cannot get the one sent by self.send_json in the receive method of the consumer. What would be the proper way to do this?
consumer.py
from channels.generic.websocket import JsonWebsocketConsumer
from datetime import datetime
class Feedback_001_Consumer(JsonWebsocketConsumer):
def connect(self):
self.accept()
def receive_json(self, content, **kwargs):
if content['command'] == 'time':
time = datetime.now()
self.send_json({'time': time})
print(time)
def disconnect(self,close_code):
pass
your.js
webSocketBridge.socket.onopen = function(){
console.log('connected to server');
// do something else. eg
webSocketBridge.send({'command': 'time'})
};
webSocketBridge.listen(function(message, stream){
var data = message.time
console.log(data)
});