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

时间:2018-08-24 14:00:58

标签: python django transactions

我目前正在努力解决与交易相关的话题。我实现了折扣功能。每当使用折扣代码进行销售时,计数器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,

2 个答案:

答案 0 :(得分:0)

您可以通过使用django's F expression将增量的处理传递给数据库,以避免代码中出现竞争情况:

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表达式;在这种情况下,它指示数据库增加由report.stories_filed表示的数据库字段。

答案 1 :(得分:0)

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

但是回到问题-如果要查询数据库以将值增加1,请参阅schwobaseggl的答案。

最后一件事是,我认为您误会了transaction.atomic()的作用。简而言之,如果函数退出并且错误返回到调用函数时的状态,它将回滚对函数中数据库的所有查询。请参见this answerthis piece of documentation。也许它将清除一些问题。