Как поставить запросы в очередь в 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
. Но логика будет такой же.