在 Django 中以原子方式比较交换模型字段

Atomically Compare-Exchange a Model Field in Django

如何以原子方式比较-交换-保存 Django Model 实例 Field 的值? (使用 PostgreSQL 作为数据库后端)。

一个示例用例是确保具有相似内容的多个帖子(例如相同表单的提交)仅生效一次,而不依赖于不安全且仅有时工作的客户端 javascript 或服务器-对表单 UUID 的侧面跟踪,这对于恶意的多发帖子是不安全的。

例如:

def compare_exchange_save(model_object, field_name, comp, exch):
    # How to implement?
    ....


from django.views.generic.edit import FormView    
from django.db import transaction
from my_app.models import LicenseCode

class LicenseCodeFormView(FormView):
    def post(self, request, ...):

        # Get object matching code entered in form
        license_code = LicenseCode.objects.get(...)

        # Safely redeem the code exactly once
        # No change is made in case of error
        try:
            with transaction.atomic()
                if compare_exchange_save(license_code, 'was_redeemed', False, True):
                    # Deposit a license for the user with a 3rd party service. Raises an exception if it fails.
                    ...
                else:
                    # License code already redeemed, don't deposit another license.
                    pass
        except:
            # Handle exception
            ...

您要查找的是 QuerySet 对象上的 update 函数。

根据值,您可以与 Case、When 对象进行比较 - 查看 conditional updates 上的文档 NOTE link 适用于1.10 - Case/When 出现在 1.8 中。

您可能还会发现使用 F 的实用性,它用于引用字段中的值。

例如:

我需要更新模型模型中的一个值:

(Model.objects
 .filter(id=my_id)
 .update(field_to_be_updated=Case(
      When(my_field=True, then=Value(get_new_license_string()),
      default=Value(''),
      output_field=models.CharField())))

如果您需要使用 F 对象,只需在更新表达式中等号的右侧引用它即可。

更新不需要使用 transaction.atomic() 上下文管理器,但如果您需要执行任何其他数据库操作,您应该继续使用 transaction.atomic()

包装该代码

编辑:

您可能还喜欢使用queryset select_for_update方法,在执行queryset时实现行锁docs