Django 条件创建

Django conditional create

Django ORM 是否提供有条件地创建对象的方法?

例如,假设您想使用某种乐观并发控制来插入新对象。
在某个时间点,您知道要插入到那个 table 中的最新对象,并且您只想在从那时起没有插入新对象的情况下才创建一个新对象。

如果是更新,您可以根据修订号进行过滤:

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

但是,我找不到任何记录的方式来为 create()save() 调用提供条件。

我正在寻找可以在 SQL 查询级别应用这些条件的东西,以避免 "read-modify-write" 问题。

除了 QuerySet.update 返回受影响的行数 Django 不提供任何原语来处理乐观锁定。

然而,有一些第三方应用程序提供了这样的功能。

  1. django-concurrency 这是最受欢迎的选项,它同时提供数据库级别的约束和应用程序级别的约束
  2. django-optimistic-lock 不太受欢迎,但我在过去的项目中尝试过,效果很好。
  3. django-locking 未维护。

编辑:看来 OP 毕竟不是乐观锁定解决方案。

编辑: 这不是 Optimistic Lock 尝试。这是对 OP 提供的代码的直接回答。


Django 提供了一种实现 conditional queries. It also offers the update_or_create(defaults=None, **kwargs) 快捷方式的方法,其中:

The update_or_create method tries to fetch an object from the database based on the given kwargs. If a match is found, it updates the fields passed in the defaults dictionary.

The values in defaults can be callables.

所以我们可以尝试混合和匹配这两者以重新创建提供的查询:

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.idversion=self.version 的对象。

  • Found: 对象的 balanceversion 字段将相应地更新为 Case 条件表达式中的值(参见答案的下一部分)。
  • 未找到: 将创建具有 id=self.idversion=self.version 的对象,然后它将获得其 balanceversion 字段已更新。

条件查询的细分:

  1. balance 查询:

    • 如果对象存在,When 表达式的条件将为真,因此 balance 字段将更新为以下值:

      # Existing balance       # Added amount
         F('balance')      +        amount
      
    • 如果对象被创建,它将收到 balance amount 值作为初始值。

  2. version 查询:

    • 如果对象存在,When 表达式的条件将为真,因此 version 字段将更新为以下值:

      # Existing version        # Next Version
         F('version')      +           1
      
    • 如果对象被创建,它将收到 version 作为初始 self.version 值(它也可以是默认的初始版本,如 1.0.0) .


备注:

  • 您可能需要为 Case 表达式提供一个 output_field 参数,看看 here.
  • 如果(双关语肯定是故意的)对 F() 表达式是什么以及它是如何使用感到好奇,我在这里有一个问答式示例: