web-dev-qa-db-ja.com

チャネルコンシューマークラスでDjangoシグナルを使用する

顧客が注文すると、さまざまな店舗がその注文の価格を提示できるオークションタイプのシステムを開発しようとしています。

このシステムの興味深い部分は、注文が最初に作成されたときに、利用可能なストアがそれぞれのオファーを行うのに60秒かかることです。最初のストアがオファーを行うと、「オークション」は他のストアが独自のオファーを行うために次の20秒しかありません。彼らが別の申し出をした場合、この短い割り当て時間内に、この20秒が更新されます。オファーは、最初の60秒を超えることができない十分な時間がある限り、受信し続けることができます。

_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)
_

これらの要件に加えて、新しいオファーがリアルタイムで到着したときにクライアントを更新したいと思います。このために、私はWebSocketの_Django-channels_実装を使用しています。

私は次の_consumers.py_ファイルを持っています:

_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
_

クライアントとサーバーの間にWebSocket通信チャネルを確立することに成功しました。メッセージの送信をテストしましたが、すべて問題ないようです。ここで、新しいOrderOfferの作成を検出し、クライアントに通知を送信します。このために、self変数にアクセスして、_self.send_を使用する必要があります。これは、シグナルデコレータがこのパラメータを送信しないため不可能です。 onOfferをselfで宣言して強制しようとしましたが、次のエラーが発生します。

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

セットを通知するキーワード引数に何らかの方法でアクセスできれば、_context = self_のようなことができるかもしれません。

私の元の問題に対する助け、あるいは代替の解決策さえもいただければ幸いです。

12
jhc

誰かがそれに遭遇した場合、これは私が_signals.py_でそれを解決した方法です。 Jobがあり、変更されるたびにそのstatusをクライアントに送信する必要があります。これは私の_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
        }
    )
_

ちなみに、私はコンシューマーclass JobUserConsumer(AsyncWebsocketConsumer)を持っています。ここでグループを定義します。

_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()
_

私がこれを使用したプロジェクトはここにあります: https://github.com/ornl-ndav/Django-remote-submission/tree/master/Django_remote_submission

5
RicLeal

「外部」から(この場合はモデルの保存メソッドから)コンシューマーと通信する場合は、チャネルレイヤーを使用して通信する必要があります。 http://channels.readthedocs.io /en/latest/topics/channel_layers.html

基本的に、次のことを行う必要があります。

  • 起動時にコンシューマーをグループに追加します(おそらく注文IDに基づいて)
  • カスタムOrderOfferを使用した新しいtypeがある場合は常に、グループにメッセージを送信します。例: _{"type": "order.new_offer", "order_offer_id": 45}_
  • これを処理するハンドラーをコンシューマーで定義します。これは型名と一致するため、この場合はdef order_new_offer(self, event):になります。
  • そのハンドラーで、_self.send_を使用してソケットについて話すことができます(イベントメッセージに入らなかったクライアントに送信するために追加情報が必要な場合は、データベースにクエリを実行します)。

MultiChatサンプルプロジェクトでこれの変形を見ることができます: https://github.com/andrewgodwin/channels-examples/tree/master/multichat

2
Andrew Godwin

それでもWebソケットに問題がある場合は、これが役立つ可能性があります。

from api.models import Order, OrderOffer
from asgiref.sync import async_to_sync
import channels.layers
from channels.generic.websocket import JsonWebsocketConsumer
from Django.db.models import signals
from Django.dispatch import receiver


class OrderOfferConsumer(JsonWebsocketConsumer):
    def connect(self):
        async_to_sync(self.channel_layer.group_add)(
            'order_offer_group',
            self.channel_name
        )
        self.accept()

    def disconnect(self, close_code):
        async_to_sync(self.channel_layer.group_discard)(
            'order_offer_group',
            self.channel_name
        )
        self.close()

    def receive_json(self, content, **kwargs):
        print(f"Received event: {content}")

    def events_alarm(self, event):
        self.send_json(event['data'])

    @staticmethod
    @receiver(signals.post_save, sender=OrderOffer)
    def order_offer_observer(sender, instance, **kwargs):
        layer = channels.layers.get_channel_layer()
        async_to_sync(layer.group_send)('order_offer_group', {
            'type': 'events.alarm',
            'data': {
                'text': 'Offer received',
                'id': instance.pk
            }
        })

Urls.pyで、新しいwebscoketルートを登録する必要があります。

websocket_urlpatterns = [url(r'^order_offer$', OrderOfferConsumer)]
2
Tavy