Метод 'save' модели Django ORM и состояние гонки / ACID Isolation
если мы рассмотрим следующий код:
class SomeModel(models.Model):
...
def save(self, *args, **kwargs):
self.validate_unique()
super().save()
def validate_unique(self, *args, **kwargs):
super().validate_unique(*args, **kwargs)
# Doing an ORM query on the model with *filter* on the model attributes & an associated model,
# and if exist - dont create a new instance, if not - create a new instance.
is_duplicated = SomeModel.objects.filter(name=given_name_from_user, attribute_from_associated_model=some_attr)
if is_duplicated:
raise Exception("go wrong")
# Else - continue to save
Теперь это будет работать с сервером разработки, но не с производственным сервером, таким как gunicorn или uvicorn, потому что метод сохранения гарантирует нам атомарность, но не изоляцию, верно?
чтобы гарантировать изоляцию, мы должны использовать 'get_or_create' на уровне представления?
Я где-то ошибаюсь? Любой комментарий поможет, спасибо!
Вы должны использовать UniqueConstraint
. Это предотвратит создание экземпляра с определенными полями с одинаковым значением.
Также get_or_create
не создаст экземпляр с теми же значениями, которые были предоставлены.
Но между этими двумя стратегиями есть разница. Если вы используете UniqureConstraint
, она предотвратит дублирование записей при любом способе создания экземпляра. Но в стратегии get_or_create
она будет предотвращать дублирование экземпляров только при ее вызове.
class SomeModel(models.Model):
class Meta:
constraints = [
models.UniqueConstraint(
fields=["name", "attribute_from_associated_model"], name="unique_constraint_name"
)
]
Если я правильно понимаю, вы должны использовать unique_together
ярлык Django:
class SomeModel(models.Model):
name = models.CharField(max_length=45)
foo = models.IntegerField()
bar = models.IntegerField()
class Meta:
unique_together = [('name', 'foo')]
Теперь при попытке создать дубликат записи будет выброшена ошибка IntregityError
:
SomeModel.objects.create(name='t1', foo=1, bar=2)
SomeModel.objects.create(name='t1', foo=1, bar=3)
# django.db.utils.IntregityError