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