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

时间:2016-10-31 17:03:16

标签: python django postgresql atomic compare-and-swap

我如何以原子方式比较 - 交换 - 保存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 个答案:

答案 0 :(得分:1)

您正在寻找的是QuerySet对象上的update函数。

根据值,您可以与案例进行比较,当对象时 - 查看conditional updates上的文档注意该链接是针对1.10 - 案例/何时进入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()上下文管理器,但如果您需要执行任何其他数据库操作,则应继续使用select_for_update

包装该代码

编辑:

您可能还想使用在执行查询集时实现行锁的queryset %s/\(\d\+\)/"\1" 方法{。{3}}。