Django:交易以及如何避免错误计数?



我目前正在为与交易相关的主题而苦苦挣扎。我实现了折扣功能。每当使用折扣代码进行销售时,计数器redeemed_quantity都会增加 + 1。

现在我想到了这个案子。如果一个或多个用户同时兑换折扣怎么办?假设redeemed_quantity是 10。用户 1 购买产品,redeemed_quantity增加 +1 = 11。现在用户 2 同时点击"支付",redeemed_quantity增加 +1 = 11。即便如此,它也应该是 12。我了解了@transaction.atomic但我认为我在这里实施它们的方式不会帮助我实际试图阻止的事情。谁能帮我?

view.py

class IndexView(TemplateView):
template_name = 'website/index.html'
initial_price_of_course = 100000  # TODO: Move to settings
def check_discount_and_get_price(self):
discount_code_get = self.request.GET.get('discount')
discount_code = Discount.objects.filter(code=discount_code_get).first()
if discount_code:
discount_available = discount_code.available()
if not discount_available:
messages.add_message(
self.request,
messages.WARNING,
'Discount not available anymore.'
)
if discount_code and discount_available:
return discount_code, self.initial_price_of_course - discount_code.value
else:
return discount_code, self.initial_price_of_course
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['stripe_pub_key'] = settings.STRIPE_PUB_KEY
discount_object, course_price = self.check_discount_and_get_price()
context['course_price'] = course_price
return context
@transaction.atomic
def post(self, request, *args, **kwargs):
stripe.api_key = settings.STRIPE_SECRET_KEY
token = request.POST.get('stripeToken')
email = request.POST.get('stripeEmail')
discount_object, course_price = self.check_discount_and_get_price()
charge = stripe.Charge.create(
amount=course_price,
currency='EUR',
description='My Description',
source=token,
receipt_email=email,
)
if charge.paid:
if discount_object:
discount_object.redeemed_quantity += 1
discount_object.save()
order = Order(
total_gross=course_price,
discount=discount_object
)
order.save()
return redirect('website:index')

models.py

class Discount(TimeStampedModel):
code = models.CharField(max_length=20)
value = models.IntegerField()  # Smallest currency unit, as amount charged
max_quantity = models.IntegerField()
redeemed_quantity = models.IntegerField(default=0)
def available(self):
available_quantity = self.max_quantity - self.redeemed_quantity
if available_quantity > 0:
return True

class Order(TimeStampedModel):
total_gross = models.IntegerField()
discount = models.ForeignKey(
Discount,
on_delete=models.PROTECT,  # Can't delete discount if used.
related_name='orders',
null=True,

你可以通过使用 django 的 F 表达式将增量处理传递给数据库,以避免代码中的竞争条件:

from django.db.models import F
# ...
discount_object.redeemed_quantity = F('redeemed_quantity') + 1 
discount_object.save()

来自带有完全类似示例的文档:

尽管reporter.stories_filed = F('stories_filed') + 1看起来像是实例属性的正常 Python 值赋值,但实际上它是一个描述数据库操作的 SQL 构造。

当 Django 遇到F()的实例时,它会覆盖标准的 Python 运算符来创建封装的 SQL 表达式;在这种情况下,它会指示数据库递增由 reporter.stories_filed 表示的数据库字段。

Django 是一段同步代码。这意味着您向服务器发出的每个请求都是单独处理的。当有多个服务器工作线程(例如 uwsgi 工作线程(时,可能会出现此问题,但同样 - 实际上不可能做到这一点。我们运行一个有多个工人的网上商店应用程序,这样的事情从未发生过。

但回到问题 - 如果你想查询数据库以增加一个值,请参阅 schwobaseggl 的答案。

最后一件事是我认为你误解了 transaction.atomic(( 的作用。简单地说,如果函数退出并出现调用函数时的状态错误,它会回滚对函数中的数据库所做的任何查询。请参阅此答案和此文档。也许它会澄清一些事情。

最新更新