Как поставить запросы в очередь в Django?

Я управляю физическим шкафчиком с помощью Django (DRF). Пользователи заполняют форму, аутентифицируются по ссылке, отправленной на их e-mail, авторизуются с помощью пин-кода, отображаемого на шкафчике.

Мое представление должно обрабатывать три случая:

  • Если пользователь успешно прошел аутентификацию и авторизацию, пин-код, отображаемый на на шкафчике заменяется общим сообщением, и шкафчик открывается. (Уже реализовано)

  • Если пользователь не авторизуется в течение 3 минут, пин-код шкафчика заменяется общим сообщением.

  • Если новый запрос на авторизацию сделан пользователем Foo, в то время как авторизация пользователя Bar еще не завершена, поместите запрос в очередь и подождите, пока завершится случай 1. или случай 2.

  • Как я могу:

    • Реализуйте очередь запросов, чтобы пин, отображаемый на шкафчике, не переопределялся/заменялся при поступлении нового запроса?
    • Как я могу ждать 3 минуты для завершения авторизации перед обработкой следующего запроса?

    Просмотр как есть, на случай, если это будет полезно:

       if request.method == 'POST':
            form = ConfirmationForm(request.POST)
            if form.is_valid():
                if pin == form.cleaned_data['pin']:
                    open_bay(jwt_token=jwt[1], pin=pin)
                    display_generic_message(jwt_token=jwt[1])
                    lock_bay(jwt_token=jwt[1], pin=pin)
                    return render(request, 'static/pages/request-success.html')
                else:
                    pass
        else:
            form = ConfirmationForm()
        return render(request, 'static/pages/confirmation.html', {'form': form})
    

    В вашей модели, в которой вы сохраняете авторизацию, вам нужно добавить поле date_created или date_requested, и это должно быть поле datetime. Затем при каждом запросе вы можете проверить, прошло ли 3 минуты от даты_requested, которую вы сохранили. Вам также нужно поле is_authorized, чтобы проверить, авторизован ли ваш пользователь или нет. Мы предполагаем, что вы получаете пользователя из электронной почты, которую вы отправили .

    user = get_object_or_404(User,email=kwargs['email'])
    if user.date_request + timedelta(minutes=3) > datetime.datetime.now():
       "do your authorzing stuff ..."
    else:
         return HttpResponse("you need to wait 3 minutes to request again")
    

    Вам нужно хранить время начала последней авторизации, очищать эту временную метку, если пользователь вставляет пин.

    # models.py
    class LockerUserQueue(models.Model):
    
        user = models.ForeignKey(get_user_model())
        locker = models.ForeignKey("yourapp.Locker")
        created_at = models.DateTimeField(index=True, auto_now_add=True)
    
    
    class Locker(models.Model):
        last_authorization = models.DateTimeField(null=True, blank=True)
        user_queue = models.ManyToManyField(through=LockerUserQueue)
    
        class BadPin(Exception):
            pass
    
        def enqueue_user(self, user):
            self.user_queue.add(user)
    
        def process_authorization(self, user):
            # do authorization for user
    
        def process_pin(self, user, pin):
            self.last_authorization = None
            if validate_pin(user, pin):
                # pin OK logic
            else:
                raise self.BadPin
    
    # views.py
    def authorize_user(request, locker_id):
        locker = get_object_or_404(pk=locker_id)
        locker.enqueue_user(request.user)
        locker.save()
        return render(request, "authorization_started.html")
    
    def open_bay_with_pin(request, locker_id):
        locker = get_object_or_404(pk=locker_id)
        pin = get_pin_from_request(request)
        try:
            # No matter if the pin is correct, the locker.last_authorization is cleared
            locker.process_pin(user, pin)
        except locker.BadPin:
            return render(request, "bad_pin.html")
        finally:
            locker.save()
        return render(request, "good_pin.html")
    
    
    
    # management/commands/process_queue.py
    # you would run this by
    # $ python manage.py process_queue
    class Command(BaseCommand):
    
        @transaction.atomic
        def process_queue(self):
            # you probably want to put the 3 min delay in your settings.py
            # so you don't end up with a magic value here
            for locker in LockerUserQueue.objects.filter(
                Q(locker__last_authorization__isnull=True)|Q(locker__last_authorization__gt=now() - timedelta(minutes=3))
            ).order_by("created_at", "locker").values_list("locker", flat=True).distinct():
                locker.process_authorization()
    
        def handle(self):
            while True:
                self.process_queue()
                # you don't want to keep querying the DB when there's nothing in the queue
                sleep(5)
    

    В приведенном выше коде показано, как это можно сделать, используя базу данных в качестве очереди, в случае небольшого объема использования это должно быть просто отлично. Если объем большой, я бы хранил шкафчик last_authorization в более быстром хранилище, например redis, очередь можно поддерживать и в redis. Но логика будет такой же.

    Вернуться на верх