Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using django signals in channels consumer classes

Tags:

I am trying to develop an auction type system, where a customer makes an order, and then different stores can offer a price for that order.

An interesting part of this system is that when the order is initially created, the available stores will have 60 seconds to make their respective offer. When a first store makes their offer, the "auction" will now only have the next 20 seconds for other stores to make their own offer. If they do make another offer, in this smaller allocated time, then this 20 second is refreshed. Offers can keep on being received as long as there is enough time, which cannot surpass the initial 60 seconds given.

class Order(models.Model):     customer = models.ForeignKey(Customer)     create_time = models.DateTimeField(auto_now_add=True)     update_time = models.DateTimeField(auto_now_add=True)     total = models.FloatField(default=0)     status = models.IntegerField(default=0)     delivery_address = models.ForeignKey(DeliveryAddress)     store = models.ForeignKey(Store, null=True, blank=True, related_name='orders', on_delete=models.CASCADE)     credit_card = models.ForeignKey(CreditCard, null=True, blank=True, related_name='orders')  class OrderOffer(models.Model):     store = models.ForeignKey(Store, related_name="offers", on_delete=models.CASCADE)     order = models.ForeignKey(Order, related_name="offers", on_delete=models.CASCADE)     create_time = models.DateTimeField(auto_now_add=True) 

Besides these requirements, I also want to update the client when new offers arrive in real-time. For this, I'm using django-channels implementation of WebSockets.

I have the following consumers.pyfile:

from channels.generic.websockets import WebsocketConsumer from threading import Timer from api.models import Order, OrderOffer from django.db.models.signals import post_save from django.dispatch import receiver  class OrderConsumer(WebsocketConsumer):      def connect(self, message, **kwargs):         """         Initialize objects here.         """         order_id = int(kwargs['order_id'])         self.order = Order.objects.get(id=order_id)         self.timer = Timer(60, self.sendDone)         self.timer.start()         self.message.reply_channel.send({"accept": True})      def sendDone(self):         self.send(text="Done")      # How do I bind self to onOffer?     @receiver(post_save, sender=OrderOffer)     def onOffer(self, sender, **kwargs):         self.send(text="Offer received!")         if (len(self.offers) == 0):             self.offerTimer = Timer(20, self.sendDone)             self.offers = [kwargs['instance'],]         else:             self.offerTimer = Timer(20, self.sendDone)          self.offers.append(kwargs['instance'])       def receive(self, text=None, bytes=None, **kwargs):         # Echo         self.send(text=text, bytes=bytes)      def disconnect(self, message, **kwargs):         """         Perform necessary disconnect operations.         """         pass 

I have successfully been able to establish a WebSocket communication channel between my client and the server. I've tested sending messages, and everything seems ok. Now I want to detect the creation of new OrderOffer's, and send a notification to the client. For this, I need access to the self variable, to use self.send, which is impossible, as the signals decorator does not send this parameter. I've tried forcing it by declaring onOffer with self, but I get the following error:

TypeError: onOffer() missing 1 required positional argument: 'self'

If I could somehow access the keyword arguments, that signals sets, I could maybe do something like: context = self.

I would appreciate any help, or even alternative solutions to my original problem.

like image 573
jhc Avatar asked Oct 06 '17 22:10

jhc


People also ask

Should I use signals Django?

Only use signals to avoid introducing circular dependencies. If you have two apps, and one app wants to trigger behaviour in an app it already knows about, don't use signals. The app should just import the function it needs and call it directly.

How do I use Django signals?

There are 3 types of signal. pre_save/post_save: This signal works before/after the method save(). pre_delete/post_delete: This signal works before after delete a model's instance (method delete()) this signal is thrown.

Is signal asynchronous Django?

To answer directly: No. It's sync.

What is the use of the Post_delete signal in Django?

Django Signals - post_delete()To notify another part of the application after the delete event of an object happens, you can use the post_delete signal.


1 Answers

If someone stumbles upon on that, this is the way I solved it in the signals.py. I have a Job and need to send its status to the client every time it changes. This is my signals.py:

import channels.layers from asgiref.sync import async_to_sync  from django.db.models.signals import post_save from django.dispatch import receiver  from .models import Job   def send_message(event):     '''     Call back function to send message to the browser     '''     message = event['text']     channel_layer = channels.layers.get_channel_layer()     # Send message to WebSocket     async_to_sync(channel_layer.send)(text_data=json.dumps(         message     ))   @receiver(post_save, sender=Job, dispatch_uid='update_job_status_listeners') def update_job_status_listeners(sender, instance, **kwargs):     '''     Sends job status to the browser when a Job is modified     '''      user = instance.owner     group_name = 'job-user-{}'.format(user.username)      message = {         'job_id': instance.id,         'title': instance.title,         'status': instance.status,         'modified': instance.modified.isoformat(),     }      channel_layer = channels.layers.get_channel_layer()      async_to_sync(channel_layer.group_send)(         group_name,         {             'type': 'send_message',             'text': message         }     ) 

By the way, I have a Consumer class JobUserConsumer(AsyncWebsocketConsumer) where I define the groups:

async def connect(self):      user = self.scope["user"]     self.group_name = 'job-user-{}'.format(user.username)      await self.channel_layer.group_add(         self.group_name,         self.channel_name     )      await self.accept() 

The project I used this is here: https://github.com/ornl-ndav/django-remote-submission/tree/master/django_remote_submission

like image 102
RicLeal Avatar answered Oct 21 '22 23:10

RicLeal