call django consumer using signals - django-channels

Is there a way to call a consumer by post_save signal?
that's my consumer and i don't know how to call it, what should i put in the parameters of it?
in models:
#receiver(post_save, sender=Message)
def SendMessage(sender, instance, **kwargs):
consumer = ChatConsumer(room_id=instance.receiver.pk)
ChatConsumer.receive(self=consumer, pk=instance.pk)
my consumer:
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_id = self.scope['url_route']['kwargs']['pk']
self.room_group_name = 'chat_%s' % self.room_id
# Join room
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from web socket
async def receive(self, pk):
pk = pk
print(pk)
self.room_id = pk
self.room_group_name = 'chat_%s' % self.room_id
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'pk': pk,
}
)
# Receive message from room group
async def chat_message(self, event):
pk = event['pk']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'pk': pk
}))
when i try this, i get this error:
object.init() takes exactly one argument (the instance to initialize)

if anyone still looking, I solved it here's my consumers full code
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import sync_to_async, async_to_sync
from django.db.models.signals import *
from django.dispatch import receiver
from .models import Message
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_id = self.scope['url_route']['kwargs']['pk']
self.room_group_name = 'chat_%s' % self.room_id
# Join room
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from room group
async def chat_message(self, event):
message_pk = event["message_pk"]
print("received" + str(message_pk))
# Send message to WebSocket
await self.send(text_data=json.dumps({'pk': message_pk}))
#receiver(post_save, sender=Message)
def SendMessage(sender, instance, **kwargs):
pk = instance.receiver.pk
message_pk = instance.pk
room_group_name = 'chat_%s' % pk
async_to_sync(channel_layer.group_send)(
room_group_name,
{'type': 'chat_message','message_pk': message_pk,})

Related

Using channels outside consumers.py but function in consumers not firing

I am trying to send a message to django consumers.py from one user to another after a user logs in. However, the chat_message function doesn't seem to be firing with the code I wrote in the log in function in views.py as "ccc" in not printing on the server side. New to Django Channels here. Would appreciate any input to this problem of mine.
consumers.py
class OnlineFriends(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope["user"]
print(self.room_name)
print(self.channel_layer)
self.room_group_name = 'chat_' + str(self.room_name)
print(self.room_group_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):
print("aa")
message = self.channel_name.split("-")
await self.send(text_data=json.dumps({
'message': message
}))
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
}
)
async def chat_message(self, event):
print("ccc")
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
users.views.py *Updated
class Login(View):
form_class = LoginForm
template = 'login.html'
def get(self, request):
form=self.form_class(None)
return render(request, self.template, {'form':form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
username = form.cleaned_data.get("username")
password = form.cleaned_data.get("password")
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
user.onlinestatus = True
user.save()
for obj in user.friends.all():
print( user.friends.all())
if obj.onlinestatus == True:
print("obj: "+ str(obj.username))
channel_layer = get_channel_layer()
print("channel_layer: "+ str(channel_layer))
room_name = "chat_" + str(obj.username)
print("room_name: "+ str(room_name))
print(obj.channel_name)
async_to_sync(channel_layer.group_send(
room_name, {
"type": "chat.message",
"message": room_name,
}))
return redirect('home')
else:
return HttpResponse("No such user.")
else:
return render(request, 'login.html', {'form':form})
traceback updated
WebSocket HANDSHAKING /friendslist/ [127.0.0.1:49751] ****loading home page on browser****
foonghanyao ****output from consumers.OnlineFriends.connect****
RedisChannelLayer(hosts=[{'address': ('127.0.0.1', 6379)}])
chat_foonghanyao
WebSocket CONNECT /friendslist/ [127.0.0.1:49751]
HTTP GET /static/songs/images/favicon.png 200 [0.02, 127.0.0.1:61225]
<QuerySet [<User: foonghanyao>]> ****output from Login class in views.py****
obj: foonghanyao
channel_layer: RedisChannelLayer(hosts=[{'address': ('127.0.0.1', 6379)}])
room_name: chat_foonghanyao
specific.45d45ec67e0a4282b2066d67847e4bab!8ab6617c8be94d0dabefadf636ba886e
HTTP POST /accounts/login/ 302 [0.88, 127.0.0.1:60752]
<QuerySet [<Tag: apple>, <Tag: bee>]>
HTTP GET /home/ 200 [0.16, 127.0.0.1:60752]
WebSocket DISCONNECT /friendslist/ [127.0.0.1:55762] ****Disconnecting from websockets in login page****
aa
WebSocket HANDSHAKING /friendslist/ [127.0.0.1:58978] ****new page loading after being redirected following log in****
brandonfoong
RedisChannelLayer(hosts=[{'address': ('127.0.0.1', 6379)}])
chat_brandonfoong
WebSocket CONNECT /friendslist/ [127.0.0.1:58978]
sync.routing.py updated
websocket_urlpatterns = [
re_path(r"^friendslist/$", consumers.OnlineFriends.as_asgi(), name='friendslist'),
]
mainapp.asgi.py updated
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "music.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
sync.routing.websocket_urlpatterns,
)
),
})

Django channels: messages are duplicated in a one channel

I'm going through official Django Channels tutorial and get stacked with the same problem as this guy:
Django Channels group send only sends the message to last channel
So, I have a bunch of standard files:
# asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chat.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
# routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
# 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
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):
print('def receive from websocket')
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
}))
And a part of settings.py
ASGI_APPLICATION = 'mysite.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
This is a simple chat room. I open two tabs in the same browser, text "Hello" in the first one and get two "Hello" inside of the second tab with zero "Hello" in the first tab.
UPDATE!!!
I made some experiments and I think that consumers functions work correctly (I logged channel_name parameter during the messages sending and inside def receive() I can see exactly that channel_name, from which I sent a message, and at the same time inside def chat_message() I can see all channels). So, the problem should be in js?
<textarea id="chat-log" cols="100" rows="20"></textarea><br>
<input id="chat-message-input" type="text" size="100"><br>
<input id="chat-message-submit" type="button" value="Send">
{{ room_name|json_script:"room-name" }}
<script>
const roomName = JSON.parse(document.getElementById('room-name').textContent);
const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/chat/'
+ roomName
+ '/'
);
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.message + '\n');
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
document.querySelector('#chat-message-input').focus();
document.querySelector('#chat-message-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#chat-message-submit').click();
}
};
document.querySelector('#chat-message-submit').onclick = function(e) {
const messageInputDom = document.querySelector('#chat-message-input');
const message = messageInputDom.value;
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
};
</script>
The problem is solved. In my case it was with django environment. I used the common one, which I also use for other projects. Maybe the reason of issue was in conflict between some installs. I've created a new environment, installed django and channels and everything is working now.
I faced this issue too. I think this is a bug in channels=3.0.0
I upgraded to channels=3.0.4 and it is working now!

async endpoints for swagger generated server using tornado

I have generated a server using swagger editor. Then I move to using Tornado as http-server like:
def main():
app = App(system_manager=system_manager, import_name=__name__,
specification_dir='./swagger/', server='tornado')
app.app.json_encoder = encoder.JSONEncoder
app.add_api('swagger.yaml', arguments={
'title': 'API'}, pythonic_params=True)
app.run(port=8085)
Where App is:
class App(connexion.App):
def __init__(self, system_manager, import_name, server='tornado', **kwargs):
super(App, self).__init__(import_name, server=server, **kwargs)
if not issubclass(type(system_manager), SystemManager):
raise ValueError(
"App.init: 'system_manager' is not a subclass of 'SystemManager'")
self.__system_manager = system_manager
def run(self, port=None, server=None, debug=None, host=None, **options):
server_type = server or self.server
if server_type != 'tornado':
super(App, self).run(port=port, server=server,
debug=debug, host=host, **options)
return None
if port is not None:
self.port = port
elif self.port is None:
self.port = 5000
self.host = host or self.host or '0.0.0.0'
if server is not None:
self.server = server
if debug is not None:
self.debug = debug
wsgi_container = tornado.wsgi.WSGIContainer(self.app)
http_server = tornado.web.Application([
(r'/websocket/.*', WebSocket, dict(system_manager=self.__system_manager)),
(r'^/v1/wifi(/all)*$', AsyncFallbackHandler,
dict(fallback=wsgi_container)),
(r'.*', tornado.web.FallbackHandler, dict(fallback=wsgi_container))
], websocket_ping_interval=5)
http_server.listen(self.port, address=self.host)
tornado.ioloop.IOLoop.instance().start()
For some reasons I have some endpoints that are taking ~30secs to respond and because I am using WSGIContainer all the requests are synchronous. Which means that every request comming after those ones will be dealyed until they are done. Cite from documentation:
WSGI is a synchronous interface, while Tornado's concurrency model is
based on single-threaded asynchronous execution. This means that
running a WSGI app with Tornado's WSGIContainer is less scalable than
running the same app in a multi-threaded WSGI server like gunicorn or
uwsgi.
I have tried to:
Keep using WSGIContainer but in a handler that will make calls async. Didn't work out. I'm getting: RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-0_0'
class AsyncFallbackHandler(tornado.web.RequestHandler):
def initialize(
self, fallback: Callable[[httputil.HTTPServerRequest], None]
) -> None:
self.fallback = fallback
async def prepare(self, *args, **kwargs):
await self.run_in_executor()
self._finished = True
self.on_finish()
async def run_in_executor(self):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, self.fallback, self.request)],
return_when=asyncio.ALL_COMPLETED
)
Create another RequestHandler which is not using WSGIContainer. But here when the request is 404 it fails to json encode ConnexionResponse. Neither I can write a ConnexionResponse to pipe as it has to be string/bytes/dict.
class WifiRequestHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
# await tornado.gen.sleep(20)
ids = self.get_arguments('ids')
method = get_wifi
if self.request.path.startswith('/v1/wifi/all'):
method = get_wifi_all
self.write(await self.run_in_executor(method, ids))
self.set_header('Content-Type', 'application/json')
async def post(self, *args, **kwargs):
logger.info(kwargs)
body = tornado.escape.json_decode(self.request.body)
self.write(await self.run_in_executor(update_wifi, body))
self.set_header('Content-Type', 'application/json')
async def run_in_executor(self, method, *args):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, method, args)],
return_when=asyncio.ALL_COMPLETED
)
result = done.pop().result()
if type(result) is ConnexionResponse:
return result
enc = JSONEncoder()
return enc.encode(result)
Please help me find a way to make some of my endpoints asynchronous
I made my second solution to work by unpacking the ConnexionResponse and setting the status_code. This is how it looks after fix:
async def run_in_executor(self, method, *args):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, method, args)],
return_when=asyncio.ALL_COMPLETED
)
result = done.pop().result()
if type(result) is ConnexionResponse:
self.set_status(result.status_code)
return result.body
enc = JSONEncoder()
return enc.encode(result)

Messages in Django Channels sent outside consumer not being received

I'm trying to send messages to specific websocket instances, but neither channel_layer.send, nor using channel_layer.group_send with unique groups for each instance seems to be working, no errors are being raised, they just aren't received by the instances.
The function that sends the message is:
def listRequest(auth_user, city):
request_country = city["country_name"]
request_city = city["city"]
request_location = request_city +", "+request_country
concatenate_service_email = auth_user.service + "-" + auth_user.email
this_request = LoginRequest(service_and_email=concatenate_service_email, location=request_location)
this_request.generate_challenge()
this_request.set_expiry(timezone.now() + timezone.timedelta(minutes=5))
this_request.save()
channel_layer = get_channel_layer()
print(auth_user.current_socket)
async_to_sync(channel_layer.group_send)(
auth_user.current_socket,{
"type": "new.request",
"service_and_email" : concatenate_service_email
},
)
My current working consumers.py (receive and scanrequest don't have anything that's likely to be relevant to the issue):
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .helpers import verify_identity, unique_group
from django.utils import timezone
from .models import Authentication, LoginRequest
import json
import time
class AuthConsumer(WebsocketConsumer):
account_list =[]
def connect(self):
print("Connect attempted")
print(self.channel_name)
print(unique_group(self.channel_name))
async_to_sync(self.channel_layer.group_add)(unique_group(self.channel_name), self.channel_name)
self.accept()
def disconnect(self, close_code):
print("Disconnect attempted")
async_to_sync(self.channel_layer.group_discard)(unique_group(self.channel_name), self.channel_name)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket("NONE")
auth_user.save()
except:
print("Error user %s does not exist" %i)
pass
def receive(self, text_data):
print("Receiving data")
if text_data[0:7] == "APPROVE":
data_as_list = text_data.split(",")
serviceEmailSplit = data_as_list[1].split("-")
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
this_request = LoginRequest.objects.get(service_and_email=data_as_list[1],approved=False, expiry__gt=timezone.now())
if verify_identity(auth_user.public_key, data_as_list[2], this_request.challenge):
this_request.set_approved()
self.send("Request Approved!")
else:
self.send("ERROR: User verification failed")
else:
self.account_list = text_data.split(",")
self.account_list.pop(-1)
print(self.account_list)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket(unique_group(self.channel_name))
auth_user.save()
except:
self.send("Error user %s does not exist" %i)
self.scanRequest()
def scanRequest(self):
requestSet = LoginRequest.objects.filter(service_and_email__in = self.account_list, approved = False, request_expiry__gt = timezone.now())
if requestSet.count() > 0:
for request in requestSet:
self.send(request.service_and_email+","+request.location+","+str(request.challenge))
else:
self.send("NOREQUESTS")
def new_request(self,event):
print("NEW REQUEST!")
this_request = LoginRequest.objects.filter(service_and_email = event["service_and_email"]).latest('request_expiry')
self.send(this_request.service_and_email+","+this_request.location+","+str(this_request.challenge))
And my routing.py:
from django.urls import re_path
from . import consumers
from django.conf.urls import url
websocket_urlpatterns = [
url(r"^ws/$", consumers.AuthConsumer.as_asgi()),
]
"NEW REQUEST!" is never printed, having tried to call it both by sending a message directly, and neither does using groups like I have written above.
My redis server appears to be working from testing like the documentation for the channels tutorial suggests:
https://channels.readthedocs.io/en/stable/tutorial/part_2.html
I'm pretty stumped after attempts to fix it, and I've looked at the other posts on stackoverflow with the same/similar issues and I'm already following whatever solutions they have in my code.

Python-socketio: How to emit message regulary from server to client?

I want to send a message from server to client at 1 second intervals,
writing the string 'send clock' to console before the message is sent.
However, client does not receive "clock" message.
I have got the echo message is working fine, so it's not a connection issue.
How can I send a message at 1 second intervals?
Server code(python):
import threading
import time
import socketio
import eventlet
socket = socketio.Server(async_mode='eventlet')
app = socketio.WSGIApp(socket)
#socket.on('echo')
def echo(sid, message):
socket.emit('echo', message)
def worker1():
eventlet.wsgi.server(eventlet.listen(('', 5030)), app)
def worker2():
while(1):
print("send clock")
socket.emit('clock', '1 sec')
time.sleep(1)
def main():
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
t1.start()
t2.start()
if __name__ == '__main__':
main()
Client code(nodejs):
const io = require("socket.io-client");
socket = io("http://xxx.xxx.xxx.xxx:5030", {
transports: ["websocket"],
});
socket.emit("echo", "test");
socket.on("echo", (data) => {
console.log(data);
});
socket.on("clock", (data) => {
console.log("receive clock");
});
My server side environment:
python: 3.7.3
python-socketio: 4.5.1
eventlet: 0.25.1
Thank you for reading it until the very end. <3
You can't combine threads or the sleep() function with eventlet, you should use greenlet based equivalents instead. The easiest way to do this is to start your background tasks with the socket.start_background_task() helper function, and sleep with socket.sleep(). I'm writing this by memory so there might be small mistakes, but your application could do something like the following:
import socketio
import eventlet
socket = socketio.Server(async_mode='eventlet')
app = socketio.WSGIApp(socket)
#socket.on('echo')
def echo(sid, message):
socket.emit('echo', message)
def worker1():
eventlet.wsgi.server(eventlet.listen(('', 5030)), app)
def worker2():
while(1):
print("send clock")
socket.emit('clock', '1 sec')
socket.sleep(1)
def main():
socket.start_background_task(worker2)
worker1()
if __name__ == '__main__':
main()

Resources