Django условное создание

Является ли Django ORM способом условного создания объекта?

Например, допустим, вы хотите использовать какой-то оптимистический контроль параллелизма для вставки новых объектов.
В определенный момент вы знаете последний объект, который нужно вставить в эту таблицу, и вы хотите создать только новый объект, только если с этого момента не были вставлены новые объекты.

Если это обновление, вы можете фильтровать на основе номера версии:

updated = Account.objects.filter(
    id=self.id,
    version=self.version,
).update(
    balance=balance + amount,
    version=self.version + 1,
)

Тем не менее, я не могу найти документально подтвержденный способ создания условий для вызова create() или save().

Я ищу что-то, что будет применять эти условия на уровне запросов SQL, чтобы избежать проблем с чтением-изменением-записью.

Ответ 1

EDIT: Это не попытка Optimistic Lock. Это прямой ответ на предоставленный OP код.


Django предлагает способ выполнения условных запросов. Он также предлагает update_or_create(defaults=None, **kwargs) который:

Метод update_or_create пытается извлечь объект из базы данных на основе данных kwargs. Если совпадение найдено, оно обновляет поля, переданные в словаре по умолчанию.

Значения в значениях по умолчанию могут быть вызываемыми.

Поэтому мы можем попытаться смешать и сопоставить эти два, чтобы воссоздать поставленный запрос:

obj, created = Account.objects.update_or_create(
    id=self.id,
    version=self.version,
    defaults={
        balance: Case(
            When(version=self.version, then=F('balance')+amount),
            default=amount
        ),
        version: Case(
            When(version=self.version, then=F('version')+1),
            default=self.version
        )
    }
)

Разбивка запроса:

update_or_create попытается получить объект с id=self.id и version=self.version в базе данных.

  • Найдено: Поля balance объекта и version будут обновляться со значениями внутри условных выражений Case (см. Следующий раздел ответа).
  • Не найдено. Объект с id=self.id и version=self.version будет создан, а затем version=self.version его balance и поля version.

Распределение условных запросов:

  1. balance Запрос:

    • Если объект существует, When выражение условие будет истинным, поэтому balance поле будет обновляться со значением:

      # Existing balance       # Added amount
         F('balance')      +        amount
      
    • Если объект создается, он будет получать в качестве начального balance значение amount.

  2. version Запрос:

    • Если объект существует, условие When expression будет истинным, поэтому поле version будет обновляться со значением:

      # Existing version        # Next Version
         F('version')      +           1
      
    • Если объект будет создан, он получит в качестве начальной version значение self.version (он также может быть исходной версией по умолчанию, такой как 1.0.0).


Заметки:

Ответ 2

За исключением QuerySet.update возвращающего количество затронутых строк, Django не предоставляет никаких примитивов для работы с оптимистичной блокировкой.

Однако там есть несколько сторонних приложений, которые предоставляют такую функцию.

  1. django-concurrency который является самым популярным вариантом, который обеспечивает как ограничения уровня базы данных, так и приложения
  2. django-optimistic-lock который немного популярен, но я пробовал в прошлом проекте, и он работал нормально.
  3. django-locking поддерживается.

Edit: Похоже, что OP не был после оптимистических решений блокировки.