Запрос Django не возвращает экземпляр, который я только что сохранил в базе данных

Я работаю над проектом, в котором необходимо отслеживать события, связанные с процессом. В моем случае у меня есть модели Registration и RegistrationEvent, причем последняя связана через ForeignKey с Registration.

Я также написал метод RegistrationEvent под названием _ensure_correct_flow_of_events, который предотвращает добавление событий в порядке, не имеющем смысла, и вызывается при вызове model.save. Фактически поток событий должен быть SIGNED -> STARTED -> SUCCESS -> CERTIFICATE_ISSUED. В любой момент может произойти событие CANCELED. Для оценки последовательности событий этот метод вызывает другой метод _get_previous_event, который возвращает последнее событие, зарегистрированное в Registration.

После создания события SUCCESS метод save вызывает Registration.threaded_issue_certificate, который должен создать certifcate и после этого создать событие CERTIFICATE_ISSUED в новом потоке, чтобы быстро обработать ответ. Проблема заключается в том, что когда CERTIFICATE_ISSUED вот-вот будет создан _ensure_correct_flow_of_events и _get_previous_event вызываются, и в этот момент _get_previous_event возвращает не только что созданное SUCCESS событие, а предыдущее STARTED событие.

Мой журнал

Checking correct flow, previous event: Course started - admin registration id: 1 current event_type: 3
Checking correct flow, previous event: Course started - admin registration id: 1 current event_type: 4
Exception in thread Thread-2 (threaded_issue_certificate):
Traceback (most recent call last):
  File "/Users/zenodallavalle/miniconda3/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/Users/zenodallavalle/miniconda3/lib/python3.10/threading.py", line 953, in run
[21/Mar/2024 15:16:41] "POST /admin/main/registrationevent/add/ HTTP/1.1" 302 0
    self._target(*self._args, **self._kwargs)
  File "/Users/zenodallavalle/Downloads/test/main/models.py", line 79, in threaded_issue_certificate
    return self.issue_certificate()
  File "/Users/zenodallavalle/Downloads/test/main/models.py", line 84, in issue_certificate
    RegistrationEvent.objects.create(
  File "/Users/zenodallavalle/Downloads/test/env/lib/python3.10/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/zenodallavalle/Downloads/test/env/lib/python3.10/site-packages/django/db/models/query.py", line 679, in create
    obj.save(force_insert=True, using=self.db)
  File "/Users/zenodallavalle/Downloads/test/main/models.py", line 175, in save
    self._ensure_correct_flow_of_events(is_new=is_new)
  File "/Users/zenodallavalle/Downloads/test/main/models.py", line 155, in _ensure_correct_flow_of_events
    raise ValueError(
ValueError: After started next event must be 'success' or 'canceled'

Почему?

Я оставляю здесь свою models.py, чтобы повторить поведение.

Эта проблема, похоже, является общей проблемой Django и того, как он обрабатывает многопоточные операции и их тайминги. Конкретно в данном случае проблема возникает, когда новый поток запускается для события CERTIFICATE_ISSUED, прежде чем событие SUCCESS полностью обновит базу данных. Есть несколько решений, которые предлагает Django, и то, которое я нашел лучшим, это использовать post_save и receiver для автоматической выдачи сертификатов в случае, когда обрабатываемое событие является событием SUCCESS.

Необходимый импорт:

from django.db.models.signals import post_save
from django.dispatch import receiver

Реализуемый метод будет выглядеть примерно так:

@receiver(post_save, sender=RegistrationEvent)
def issue_certificate_if_success(sender, instance, created, **kwargs):
     if created and instance.event_type == RegistrationEvent.EventType.SUCCESS:
         instance.course_registration.threaded_issue_certificate()

Есть и другие способы сделать это, если вы не хотите использовать такой подход. Я видел решения, использующие модуль транзакций.

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