I have recently converted my existing django project to have a react front end. However , im facing the issue whereby the backend of my django-channels is not working. The issue im having is that the group_send method is not working in my case , as such the consumer does not receive the information generated by a signal in my models. Here is my code :
consumers.py
class NotificationConsumer (AsyncJsonWebsocketConsumer):
async def connect (self):
close_old_connections()
user = self.scope["user"]
if not user.is_anonymous:
await self.accept()
#connects the user to his/her websocket channel
group_name = "notifications_{}".format(str(user))
print(group_name)
await self.channel_layer.group_add(group_name, self.channel_name)
async def disconnect (self, code):
close_old_connections()
user = self.scope["user"]
if not user.is_anonymous:
#Notifications
notifications_group_name = "notifications_{}".format(str(user))
await self.channel_layer.group_discard(notifications_group_name, self.channel_name)
async def user_notification (self, event):
close_old_connections()
print('Notification recieved by consumer')
await self.send_json({
'event': 'notification',
'data': {
'event_type': 'notification',
'notification_pk': event['notification_pk'],
'link': event['link'],
'date_created': event['date_created'],
'object_preview': event['object_preview'],
}
})
print(event)
Models/signals
def ChannelNotification(sender, instance, created, **kwargs):
if created:
channel_layer = get_channel_layer()
print(channel_layer)
group_name = "notifications_{}".format(str(instance.target))
print('inside channel signal')
print(group_name)
async_to_sync(channel_layer.group_send)(
group_name, {
"type": "user_notification",
"notification_pk": instance.pk,
"link": instance.object_url,
"date_created": instance.time.strftime("%Y-%m-%d %H:%M:%S"),
"object_preview": instance.object_preview,
}
)
Whenever an notification object is created in the database , the signal will be sent to my ChannelNotification which receives this information and transmit it to the consumer via group send . It used to work perfectly but im not sure what happened after i converted my project to have react as a frontend.
Related
I've been using a websockets connection with django-channels for a chatroom app with the following routing:
re_path(r'ws/chat/(?P<room_name>\w+)/participant/(?P<user>\w+)/$', consumers.ChatConsumer.as_asgi())
So if the id of my chatroom is 1, to connect to WS I would use the following url with 2 being the id of the participant that wants to enter the room:
ws/chat/1/participant/1/
Now i changed the id of my room model to UUID so now to connect to a room I need to use the following url
ws/chat/84f48468-e966-46e9-a46c-67920026d669/participant/1/
where "84f48468-e966-46e9-a46c-67920026d669" is the id of my room now, but I'm getting the following error:
raise ValueError("No route found for path %r." % path)
ValueError: No route found for path 'ws/chat/84f48468-e966-46e9-a46c-
67920026d669/participant/1/'.
WebSocket DISCONNECT /ws/chat/84f48468-e966-46e9-a46c-67920026d669/participant/1/
[127.0.0.1:50532]
My consumers:
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.user = self.scope['url_route']['kwargs']['user']
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):
delete = delete_participant(self.room_name, self.user)
if delete == 'not_can_delete_user':
pass
else:
# Leave room group
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
f'chat_{self.room_name}',
{
'type': 'receive',
'message': "USER_DISCONNECT",
'body': {
'participant': delete,
'idParticipant': self.user
}
}
)
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
def receive(self, text_data=None, type='receive', **kwargs):
if isinstance(text_data, dict):
text_data_json = text_data
message = text_data_json['message']
body = text_data_json['body']
else:
text_data_json = json.loads(text_data)
message = text_data_json['message']
body = text_data_json['body']
self.send(text_data=json.dumps({
'message': message,
"body": body
}))
What is wrong?
try this regex:
re_path(r'ws/chat/(?P<room_name>[A-Za-z0-9_-]+)....
format the group id and remove the '-' pass it in the javascript and use groupId2 instead.
groupId2 = groupId.replaceAll("-","");
i think this is better than searching for a regex and the problem is that django uuid field is 32 bytes not 36 bytes as it would be with the '-'.
so reading the full log will show you that a value error was raised.
I'm new in Django Channels and I'm trying to build a simple chat app. But when I'm trying to test my async Websocket Consumer I run into the following exception: chat.models.RoomModel.DoesNotExist: RoomModel matching query does not exist.
It seems like the test room is not created.
test.py file is the following:
class ChatTest(TestCase):
#sync_to_async
def set_data(self):
room = RoomModel.objects.create_room('Room1', 'room_password_123')
room_slug = room.slug
user = User.objects.create_user(username='User1', password='user_password_123')
print(RoomModel.objects.all()) # querySet here contains the created room
return room_slug, user
async def test_my_consumer(self):
room_slug, user = await self.set_data()
application = URLRouter([
re_path(r'ws/chat/(?P<room_name>\w+)/$', ChatConsumer.as_asgi()),
])
communicator = WebsocketCommunicator(application, f'/ws/chat/{room_slug}/')
communicator.scope['user'] = user
connected, subprotocol = await communicator.connect()
self.assertTrue(connected)
await communicator.send_json_to({'message': 'hello'})
response = await communicator.receive_json_from()
self.assertEqual('hello', response)
await communicator.disconnect()
My consumer.py file is the following:
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f'chat_{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):
self.message = json.loads(text_data).get('message')
data = {'type': 'chat_message'}
data.update(await self.create_message())
await self.channel_layer.group_send(self.room_group_name, data)
async def chat_message(self, event):
await self.send(text_data=json.dumps({
'message': event['message'],
'user': event['user'],
'timestamp': event['timestamp']
}))
#database_sync_to_async
def create_message(self):
print(RoomModel.objects.all()) # qyerySet turns out to be empty here
room = RoomModel.objects.get(slug=self.room_name)
msg = room.messagemodel_set.create(text=self.message, user_name=self.scope['user'])
return {
'message': msg.text,
'user': self.scope['user'].username,
'timestamp': msg.timestamp.strftime("%d/%m/%Y, %H:%M")
}
I would be grateful for any help.
When you try to run room = RoomModel.objects.create_room('Room1', 'room_password_123') you are trying to commit a transaction.
And this cannot happen until the end of the test because TestCase wraps each test within a transaction. It waits until the end of the test to create this object.
And since you are awaiting set_data, the flow goes on to execute the rest of the test, i.e., it reaches the call to create_message where it tries to get the RoomModel object, which will be not present in the db yet since the transaction has not been committed.
I had a similar problem that I solved using this link https://github.com/django/channels/issues/1110. To summarize, you have to change TestCase to TransactionTestCase
Given a Django Channels consumer that looks like the following:
class NotificationConsumer(JsonWebsocketConsumer):
def connect(self):
user = self.scope["user"]
async_to_sync(self.channel_layer.group_add)("hello", "hello")
self.accept()
async_to_sync(self.channel_layer.group_send)(
"hello", {"type": "chat.message", "content": "hello"}
)
def receive_json(self, content, **kwargs):
print(content)
async_to_sync(self.channel_layer.group_send)(
"hello", {"type": "chat.message", "content": "hello"}
)
print("Here we are")
def chat_message(self, event):
self.send_json(content=event["content"])
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)("hello", "hello")
and a test that looks like the following:
#pytest.mark.asyncio
class TestWebsockets:
async def test_receives_data(self, settings):
communicator = WebsocketCommunicator(
application=application, path="/ws/notifications/"
)
connected, _ = await communicator.connect()
assert connected
await communicator.send_json_to({"type": "notify", "data": "who knows"})
response = await communicator.receive_json_from()
await communicator.disconnect()
I am always getting a TimeoutError when I run the test. What do I need to do differently?
If you'd like to see a full repo example, check out https://github.com/phildini/websockets-test
shouldn't async_to_sync(self.channel_layer.group_add)("hello", "hello") be async_to_sync(self.channel_layer.group_add)("hello", self.channel_name)?
In the first case you are adding "hello" to the group and the communicator.receive_json_from() in the test will fail as the group_send will not be received by the test client.
By refactoring the class as:
class NotificationConsumer(JsonWebsocketConsumer):
def connect(self):
user = self.scope["user"]
async_to_sync(self.channel_layer.group_add)("hello", self.channel_name)
self.accept()
async_to_sync(self.channel_layer.group_send)(
"hello", {"type": "chat.message", "content": "hello"}
)
def receive_json(self, content, **kwargs):
print(content)
async_to_sync(self.channel_layer.group_send)(
"hello", {"type": "chat.message", "content": "hello"}
)
print("Here we are")
def chat_message(self, event):
self.send_json(content=event["content"])
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)("hello", self.channel_name)
I can get the tests from the sample repo pass
For testing async channels code you are best of using purely functional async tests.
#pytest.mark.asyncio
async def test_receives_data(settings):
communicator = WebsocketCommunicator(
application=application, path="/ws/notifications/"
)
connected, _ = await communicator.connect()
assert connected
await communicator.send_json_to({"type": "notify", "data": "who knows"})
response = await communicator.receive_json_from()
await communicator.disconnect()
pytest will let you mix theses with class based regular Django tests.
Here you can find some examples for testing consumers.
https://github.com/hishnash/djangochannelsrestframework/tree/master/tests
I am trying to imitate a time consuming django response, by delaying group message in Django channels, but it does not work.
The consumer is very simple:
class ExportConsumer(JsonWebsocketConsumer):
...
def delayed_message(self, event):
print("In delayed")
time.sleep(5)
self.send(text_data=json.dumps({
'message': 'delayed!'
}))
def immediate_message(self, event):
print("In Immediate")
self.send(text_data=json.dumps({
'message': 'immediate_message!'
}))
and in views I just send to a group two messages - for delayed and immediate processing:
class DecisionListView(PaginatedListView):
...
def get(self, *args, **kwargs):
async_to_sync(get_channel_layer().group_send)(
'5e001793-18be-4a4b-8caf-c4a8144a11d2',
{
'type': 'immediate_message',
'message': 'message'
}
)
async_to_sync(get_channel_layer().group_send)(
'5e001793-18be-4a4b-8caf-c4a8144a11d2',
{
'type': 'delayed_message',
'message': 'message'
}
)
return super().get(*args, **kwargs)
The 'immediate message' is delivered. And I get In delayed on terminal (which means that it reaches the delayed_message but the message is never delivered (unless I comment out time.sleep.
What am I doing wrong?
UPDATE::
AsyncJsonWebsocketConsumer does not work as well:
consumers.py:
class ExportConsumer(AsyncJsonWebsocketConsumer):
...
async def delayed_message(self, event):
await asyncio.sleep(5)
await self.send(text_data=json.dumps({
'message': 'whatever'
}))
views.py:
class DecisionListView(PaginatedListView):
...
def get(self, *args, **kwargs):
_group_send = get_channel_layer().group_send
_sync_group_send = async_to_sync(_group_send)
_sync_group_send('5e001793-18be-4a4b-8caf-c4a8144a11d2',{"type":'delayed_message', 'message':'hello'})
return super().get(*args, **kwargs)
UPDATE2:
The only thing that works for me is asyncio.create_task:
class ExportConsumer(AsyncJsonWebsocketConsumer):
async def delayed_task(self):
await asyncio.sleep(5)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': 'LATER'
}
)
async def delayed_message(self, event):
asyncio.create_task(self.delayed_task())
await self.send_json('NOW')
async def chat_message(self, event):
message = event['message']
await self.send_json(message)
you should be using an AsyncJsonWebsocketConsumer and async methods then you can use the sleep method from asyncio.
time.sleep will block your entire server.
-- unrelated
you should be sending delayed.message not delayed_message (channels replaces . with _ when calling functions)
I'm using django, django rest framework and Rabbitmq,What I want is using Rabbitmq to send notifications to client after user create a comment(like long polling).
I follow the RabbitMQ Tutorials here.
Here is my view.py to create comments:
class CommentList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs):
#use pika and rabbitmq to notifity user
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
message = 'Hello, world'
channel.basic_publish(exchange='',
routing_key='task_queue',
body=message,
properties=pika.BasicProperties(
delivery_mode = 2, # make message persistent
))
connection.close()
return super().create(request, args, kwargs)
I create a queue call "task_queue", and sent message "Hello, world" every time I create I comment.
Here is my view.py to receive message:
def get_notifications(request):
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
def callback(ch, method, properties, body):
return HttpResponse("ok")
ch.basic_ack(delivery_tag = method.delivery_tag)
#channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
queue='task_queue')
channel.start_consuming()
In client side, I use a jquery ajax function to request data:
function poll() {
var poll_interval=0;
$.ajax({
url: "/sub", //sub calls get_notifications()
type: 'GET',
dataType: 'json',
success: function(data) {
append_circle();
poll_interval=0;
},
error: function () {
poll_interval=1000;
},
complete: function () {
setTimeout(poll, poll_interval);
},
});
}
I can send the message after I create the comment, but my client side doesn't receive any data, in my chrome console, I find this:
sub/ (pending) xhr
What should I do?
For this type of application, websockets are the better option. I suggest you to use websockets instead of long polling. Because long polling is resource hungry process. To use websockets, you can use channels