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

Posted

技术标签:

【中文标题】以原子方式比较-交换 Django 中的模型字段【英文标题】:Atomically Compare-Exchange a Model Field in Django 【发布时间】:2016-10-31 17:03:16 【问题描述】:

如何以原子方式比较-交换-保存 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
            ...

【问题讨论】:

【参考方案1】:

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

根据值,您可以与 Case、When 对象进行比较 - 查看 conditional updates 上的文档 注意 该链接适用于 1.10 - Case/When 进入 1.8。

您可能还会发现使用F 来引用字段中的值的实用程序。

例如:

我需要更新我的模型 Model 中的值:

(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() 包装该代码

编辑:

您可能还想使用查询集select_for_update 方法,该方法在查询集执行时实现行锁docs。

【讨论】:

这能保证是原子的吗?我在文档中没有看到。如果不是,那么在我的用例中,可能有两个不同的请求进入“存入许可证”部分 这是一个单一的数据库调用。它尽可能地具有原子性。 @DanielRoseman 哪个是原子的,或者不是?也就是说,假设一个 PostgreSQL 后端,是否有可能同时调用此方法的两个并发请求都成功更新值?还是保证最多一次会成功? 添加了更多关于在调用中使用锁的查询集方法的信息。 @theWanderer4865 谢谢,太好了!您添加到 select_for_update 的文档表明您建议的第一种方法也适用于原子比较交换功能,假设行在更新时被锁定,这似乎是假设它被包装在事务中的情况。 (见postgresql.org/docs/9.1/static/explicit-locking.html:“更新或删除行时自动获取特定行上的排他行级锁。该锁一直保持到事务提交或回滚”)

以上是关于以原子方式比较-交换 Django 中的模型字段的主要内容,如果未能解决你的问题,请参考以下文章

golang 原子操作函数

Django中的自定义纬度/经度表单字段

Django模型将字段与字段进行比较[重复]

Django 1.8:删除/重命名数据迁移中的模型字段

Django模型字段实际上是对相关模型中的字段的引用

Django:上传文件并读取其内容以填充模型?