RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1302527
Accepted
Coffee inTime
Coffee inTime
Asked:2022-07-06 20:29:37 +0000 UTC2022-07-06 20:29:37 +0000 UTC 2022-07-06 20:29:37 +0000 UTC

Django减去item单位,如何更安全正确?

  • 772

我正在尝试实现一个简单的商店,但我有一个问题:

背景:

  1. 我正在做表单验证(在表单类中),如果用户试图购买比他们拥有的更多的商品,那么表单将返回错误。

  2. 如果在表单验证的时候没有出现这样的问题,那么进一步在我的View类(ProductDetail)的form_valid()方法中,从这个产品中取出用户购买的数量。

而实际上的问题是,是否需要在第 2 点再次检查仓库中是否有足够的货物(直接在视图中),还是仅在第 1 点进行此检查就足够了?

会不会有条件竞争导致货物被双重拿走?还是其他一些与交易相关的问题?假设两个用户尝试同时购买 10 件和 10 件,而库存只有 10 件。

我附上了下面的代码,并用评论标记了有争议的地方。

我的视图类:

class ProductDetail(FormView):
    form_class = SaleForm
    template_name = "shop/product_detail.html"
    success_url = reverse_lazy('product_list')

    def get_initial(self):
        initial = super(ProductDetail, self).get_initial()
        initial.update({'amount': 1, 'product_id': self.kwargs['product_id']})
        return initial

    def dispatch(self, request, *args, **kwargs):
        product = self.check_product_exist(self.request, self.kwargs['product_id'])
        if not product: return redirect('product_list')

        sellers_qs = self.check_seller_exist(self.request, product)
        if not sellers_qs: return redirect('product_list')

        self.kwargs['product'] = product
        self.kwargs['sellers_qs'] = sellers_qs

        return super(ProductDetail, self).dispatch(request, *args, **kwargs)

    def get_form_kwargs(self):
        form_kwargs = super(ProductDetail, self).get_form_kwargs()
        form_kwargs['sellers_qs'] = self.kwargs['sellers_qs']
        form_kwargs['max_amount'] = self.kwargs['product'].amount
        return form_kwargs

    def form_valid(self, form):
        product = self.check_product_exist(self.request, form.cleaned_data['product_id'])

        """
        (!) Нужно ли здесь повторно делать проверку на то, есть ли на складе достаточное кол-во единиц товара!?
        """

        product.amount -= form.cleaned_data['amount']
        product.save()

        Sale.objects.create(seller=form.cleaned_data['sellers'], product=product, amount_sold=form.cleaned_data['amount'],
                            purchase_amount=product.price * form.cleaned_data['amount'])


        return super(ProductDetail, self).form_valid(form)

    def get_context_data(self, **kwargs):
        product = self.check_product_exist(self.request, self.kwargs['product_id'])

        context = super().get_context_data(**kwargs)
        context["product"] = product
        return context


    def check_product_exist(self, request, product_id):
        try:
            product = Product.objects.filter(pk=product_id)[0]
            return product
        except IndexError:
            messages.add_message(request, messages.ERROR, 'Нужный товар не был найден, возможно он был удален.')
            return None

    def check_seller_exist(self, request, product):
        sellers_qs = Seller.objects.filter(product=product.pk)
        if sellers_qs: return sellers_qs

        messages.add_message(request, messages.ERROR, 'Продавцы для данной позиции отсутствуют в базе данных.')
        return None

我的表单类:

class SaleForm(forms.Form):
    amount = forms.IntegerField(label='Кол-во', min_value=1)
    product_id = forms.IntegerField(widget=forms.HiddenInput())
    sellers = forms.ModelChoiceField(label='Продавцы', queryset=Seller.objects.none())

    def clean(self):
        cleaned_data = super().clean()
        amount = cleaned_data.get("amount")
        product_id = cleaned_data.get("product_id")
        seller = cleaned_data.get("sellers")

        if amount < 1:
            raise forms.ValidationError(
                "Указано неверное количесто товара."
            )

        if not Product.objects.filter(pk=product_id):
            raise forms.ValidationError(
                "Товар не найден в базе данных."
            )
        if Product.objects.get(pk=product_id).amount < amount:
            raise forms.ValidationError(
                "На складе нет столько единиц товара, выберите другое количество."
            )
        if not Seller.objects.filter(name=seller):
            raise forms.ValidationError(
                "Продавец не найден в базе данных."
            )
        if not Seller.objects.filter(product=product_id):
            raise forms.ValidationError(
                "Продавец не продает данный товар."
            )

    def __init__(self, *args, **kwargs):
        # Устанавливаем в форме продавцов, которые были переданны и максимальное кол-во товара, которое можно выбрать в форме.
        qs = kwargs.pop('sellers_qs')
        max_amount = kwargs.pop('max_amount')
        super(SaleForm, self).__init__(*args, **kwargs)
        self.fields['sellers'].queryset = qs
        self.fields['amount'].max_value = max_amount
        self.fields['amount'].widget.attrs['max'] = max_amount
python
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Roman-Stop RU aggression in UA
    2022-07-06T22:51:28Z2022-07-06T22:51:28Z

    在进行实际更改操作的地方会有问题,并且可以进行比赛。

    在表单中,需要检查数据格式和值的有效性(例如,what amount > 0),但业务逻辑(尤其是对数据库的查询)不需要检查。

    类型检查方法本身if Product.objects.get(pk=product_id).amount < amount:不起作用,因为这里也有比赛。

    第一个(简单的)选项,如何正确地做,就是在我们读取记录的时候锁定它,这样没有其他事务可以改变它:

    with transaction.atomic():
       product = Product.objects.select_for_update().get(pk=product_id)
       if product.amount < amount:
           # Обрабатываем ошибку - мало денег
    
       # проверяем еще условия
       if ...
    
       # меняем как хотим
       product.amount -= amount
    
       product.save()
    

    第二种方法是做乐观锁。您可以在此处阅读有关一般方法的信息。我不会在这里画细节(可以在 ruSO 上找到)。为此,您需要向实体添加一个字段,version并在保存时检查该字段自我们读取实体以来没有更改。

    最后一种方式。在某些情况下,您可以不使用事务并通过单个请求进行原子更改:

    Product.objects.filter(pk=product_id).update(likes=F('likes') + 1)
    
    • 2

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    表格填充不起作用

    • 2 个回答
  • Marko Smith

    提示 50/50,有两个,其中一个是正确的

    • 1 个回答
  • Marko Smith

    在 PyQt5 中停止进程

    • 1 个回答
  • Marko Smith

    我的脚本不工作

    • 1 个回答
  • Marko Smith

    在文本文件中写入和读取列表

    • 2 个回答
  • Marko Smith

    如何像屏幕截图中那样并排排列这些块?

    • 1 个回答
  • Marko Smith

    确定文本文件中每一行的字符数

    • 2 个回答
  • Marko Smith

    将接口对象传递给 JAVA 构造函数

    • 1 个回答
  • Marko Smith

    正确更新数据库中的数据

    • 1 个回答
  • Marko Smith

    Python解析不是css

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5