Запрос 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()
Есть и другие способы сделать это, если вы не хотите использовать такой подход. Я видел решения, использующие модуль транзакций.