产品变体未反映 Django 电子商务项目中订单摘要中的更新数量

Posted

技术标签:

【中文标题】产品变体未反映 Django 电子商务项目中订单摘要中的更新数量【英文标题】:Product variants not reflecting updated quantity in Order Summary in a Django E-commerce Project 【发布时间】:2020-11-20 17:17:49 【问题描述】:

我对订单摘要页面中相关的变体和数量有疑问。

当我将 2 件商品添加到购物车时:

商品 X 尺寸小 数量:1 商品 X 中号 数量:1

当我更改 item X size medium 的数量时,此更改反映在 item X size small 中,这是首先选择的。

变成这样:

商品 X 尺寸小 数量:2 商品 X 中号 数量:1

在订单汇总中,模板中有加号和减号,可以改变数量。

我最近明白了这一点,因为模板中没有表单。将带有表单数据的 POST 请求发送到添加到购物车视图的代码不存在,因为 item_var 将始终是一个空列表,因此 order_item.variation.add(*item_var) 什么都不做。我不知道如何向这个模板添加 POST 请求。

在模板中有一个 URL to add-to-cart”,但是 URL 是通过 GET 传输的,所以 if request.method == 'POST': 之后的代码永远不会命中。此外,即使它会, add_to_cart url 对变化一无所知,因为它只获取项目 slug。

这是模板:

    <main>
        <div class="container">
        <div class="table-responsive text-nowrap" style="margin-top:90px">
        <h2> Order Summary</h2>
        <table class="table">
            <thead>
            <tr>
                <th scope="col">#</th>
                <th scope="col">Item Title</th>
                <th scope="col">Price</th>
                <th scope="col">Quantity</th>
                <th scope="col">Size</th> 
                <th scope="col">Total Item Price</th>
            </tr>
            </thead>
            <tbody>
            % for order_item in object.items.all %
            <tr>
                <th scope="row"> forloop.counter </th>
                <td> order_item.item.title </td>
                <td> order_item.item.price </td>
                <td>
                <a href="% url 'core:remove-single-item-from-cart' order_item.item.slug %"><i class="fas fa-minus mr-2"></a></i>
                 order_item.quantity 
                <a href="% url 'core:add-to-cart' order_item.item.slug %"><i class="fas fa-plus ml-2"></a></i>
                </td>                
                <td>
                % if order_item.variation.all %
                % for variation in order_item.variation.all %
                 variation.title|capfirst 
                % endfor %
                % endif %
                </td> 
                <td>
                % if order_item.item.discount_price %
                    $  order_item.get_total_discount_item_price 
                    <span class="badge badge-primary" style="margin-left:10px">Saving $ order_item.get_amount_saved </span>
                % else %
                    $  order_item.get_total_item_price 
                % endif %
                <a style="color:red" href="% url 'core:remove-from-cart' order_item.item.slug %">
                <i class="fas fa-trash float-right"></i>
                </a>
                </td>
            </tr>
            % empty %
            <tr>
                <td colspan='5'>Your Cart is Empty</td>
            </tr>
            <tr>
                <td colspan="5">
                <a class='btn btn-primary float-right ml-2'href='/'>Continue Shopping</a>
            </tr>                
            % endfor %
            % if object.coupon %
            <tr>
                <td colspan="4"><b>Coupon</b></td>
                <td><b>-$ object.coupon.amount </b></td>
            </tr>            
            % endif %
            <tr>
                <td colspan="5"><b>Sub total</b></td>
                <td><b>$ object.get_total </b></td>
            </tr>
            <tr>
                <td colspan="5">Taxes</td>
                <td>$ object.get_taxes|floatformat:2  </td>
            </tr>
            % if object.grand_total %
            <tr>
                <td colspan="5"><b>Grand Total</b></td>
                <td><b>$ object.grand_total|floatformat:2 </b></td>
            </tr>
            <tr>
                <td colspan="6">
                <a class='btn btn-primary float-right ml-2'href='/'>Continue Shopping</a>
                <a class='btn btn-warning float-right'href='/checkout/'>Proceed to Checkout</a></td>
            </tr> 
            % endif %                          
            </tbody>
        </table>
        </div>
        </div>
    </main>

这里是views.py

class OrderSummaryView(LoginRequiredMixin, View):
    def get(self, *args, **kwargs):

        try:
            order = Order.objects.get(user=self.request.user, ordered=False)
            context = 
                'object': order
            
            return render(self.request, 'order_summary.html', context)
        except ObjectDoesNotExist:
            messages.warning(self.request, "You do not have an active order")
            return redirect("/")


@login_required
def add_to_cart(request, slug):
    item = get_object_or_404(Item, slug=slug)

    order_item_qs = OrderItem.objects.filter(
        item=item,
        user=request.user,
        ordered=False
    )

    item_var = []  # item variation
    if request.method == 'POST':
        for items in request.POST:
            key = items
            val = request.POST[key]
            try:
                v = Variation.objects.get(
                    item=item,
                    category__iexact=key,
                    title__iexact=val
                )
                item_var.append(v)
            except:
                pass

        if len(item_var) > 0:
            for items in item_var:
                order_item_qs = order_item_qs.filter(
                    variation__exact=items,
                )

    if order_item_qs.exists():
        order_item = order_item_qs.first()
        order_item.quantity += 1
        order_item.save()
    else:
        order_item = OrderItem.objects.create(
            item=item,
            user=request.user,
            ordered=False
        )
        order_item.variation.add(*item_var)
        order_item.save()

    order_qs = Order.objects.filter(user=request.user, ordered=False)
    if order_qs.exists():
        order = order_qs[0]
        # check if the order item is in the order
        if not order.items.filter(item__id=order_item.id).exists():
            order.items.add(order_item)
            messages.info(request, "This item quantity was updated.")
            return redirect("core:order-summary")
    else:
        ordered_date = timezone.now()
        order = Order.objects.create(
            user=request.user, ordered_date=ordered_date)
        order.items.add(order_item)
        messages.info(request, "This item was added to cart.")
        return redirect("core:order-summary")


@login_required
def remove_from_cart(request, slug):
    item = get_object_or_404(Item, slug=slug)
    order_qs = Order.objects.filter(
        user=request.user,
        ordered=False
    )
    if order_qs.exists():
        order = order_qs[0]
        # check if the order item is in the order
        if order.items.filter(item__slug=item.slug).exists():
            order_item = OrderItem.objects.filter(
                item=item,
                user=request.user,
                ordered=False
            )[0]
            order.items.remove(order_item)
            order_item.delete()
            messages.info(request, "This item was removed from your cart")
            return redirect("core:order-summary")

        else:
            messages.info(request, "This item was not in your cart")
            return redirect("core:product", slug=slug)
    else:
        messages.info(request, "You don't have an active order")
        return redirect("core:product", slug=slug)


@login_required
def remove_single_item_from_cart(request, slug):
    item = get_object_or_404(Item, slug=slug)
    order_qs = Order.objects.filter(
        user=request.user,
        ordered=False
    )
    if order_qs.exists():
        order = order_qs[0]
        # check if the order item is in the order
        if order.items.filter(item__slug=item.slug).exists():
            order_item = OrderItem.objects.filter(
                item=item,
                user=request.user,
                ordered=False
            )[0]
            if order_item.quantity > 1:
                order_item.quantity -= 1
                order_item.save()
            else:
                order.items.remove(order_item)
            messages.info(request, "This item quantity was updated")
            return redirect("core:order-summary")
        else:
            messages.info(request, "This item was not in your cart")
            return redirect("core:product", slug=slug)
    else:
        messages.info(request, "You do not have an active order")
        return redirect("core:product", slug=slug)

这里是models.py

class Item(models.Model):
    title             = models.CharField(max_length=100)
    -------------------------------------------------------------------------
    updated           = models.DateTimeField(auto_now_add=False, auto_now=True)
    active            = models.BooleanField(default=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("core:product", kwargs=
            'slug': self.slug
        )

    def get_add_to_cart_url(self):
        return reverse("core:add-to-cart", kwargs=
            'slug': self.slug
        )

    def get_remove_from_cart_url(self):
        return reverse("core:remove-from-cart", kwargs=
            'slug': self.slug
        )


class VariationManager(models.Manager):
    def all(self):
        return super(VariationManager, self).filter(active=True)

    def sizes(self):
        return self.all().filter(category='size')

    def colors(self):
        return self.all().filter(category='color')


VAR_CATEGORIES = (
    ('size', 'size',),
    ('color', 'color',),
    ('package', 'package'),
)


class Variation(models.Model):
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    category = models.CharField(
        max_length=120, choices=VAR_CATEGORIES, default='size')
    title = models.CharField(max_length=120)
    image = models.ImageField(null=True, blank=True)
    price = models.DecimalField(
        decimal_places=2, max_digits=100, null=True, blank=True)
    objects = VariationManager()
    active = models.BooleanField(default=True)

    def __str__(self):
        return self.title

class OrderItem(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.CASCADE)
    ordered = models.BooleanField(default=False)
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    quantity = models.IntegerField(default=1)
    variation = models.ManyToManyField(Variation)

    def __str__(self):
        return f"self.quantity of self.item.title"

网址.py

app_name = 'core'

urlpatterns = [
    path('', HomeView.as_view(), name='home'),
    path('order-summary/', OrderSummaryView.as_view(), name='order-summary'),
    path('product/<slug>/', ItemDetailView.as_view(), name='product'),
    path('add-to-cart/<slug>/', add_to_cart, name='add-to-cart'),
    path('remove-item-from-cart/<slug>/', remove_single_item_from_cart,
         name='remove-single-item-from-cart'),
    path('remove-from-cart/<slug>/', remove_from_cart, name='remove-from-cart'),
]

【问题讨论】:

不要对这类事情使用 GET 请求!浏览器可能会预取或缓存这些链接,从而为您的用户带来可能非常奇怪的错误。对于修改某些状态的事物,始终使用 POST 或 PUT 等动词(取决于它所代表的操作类型),无论是使用表单还是在 javascript 中调用 fetch() @Jasmijn 我该如何解决? 【参考方案1】:

当然,对于 django 来说,这两个项目都是一样的。

对此负责的行是:

if order_item_qs.exists():
    order_item = order_item_qs.first()  # Here, you are always picking the first item that your filter returned, see: https://docs.djangoproject.com/en/dev/ref/models/querysets/#first
    order_item.quantity += 1
    order_item.save()

取自https://docs.djangoproject.com/en/dev/ref/models/querysets/#first:

first()¶

返回查询集匹配的第一个对象,如果有,则返回 None 没有匹配的对象。如果 QuerySet 没有定义排序,则 queryset 按主键自动排序。这可能会影响 聚合结果,如与默认排序的交互中所述 或 order_by()。

然而,罪魁祸首是:

order_item_qs = OrderItem.objects.filter(
    item=item,
    user=request.user,
    ordered=False
)

您想将变体传递给它,例如

order_item_qs = OrderItem.objects.filter(
    item=item,
    user=request.user,
    ordered=False,
    variation=variation
)

您可以考虑将尺寸添加为产品的一个字段,并根据变体创建单个产品。但是,简单地检索变化并确保仅通过将正确的项目传递给 filter() 来增加正确的项目也是一种正确的方法。

【讨论】:

返回了错误名称'variation' is not defined 您需要定义变体。您很可能希望将其包含在 url 调用中。要么就是这样,要么创建一个单独的视图来编辑订单项目(特别是它们的数量)并将 id 传递给 url。【参考方案2】:
order_item_qs = order_item_qs.filter(
            Q(item_variations__exact=v)
        )

with:
order_item_qs = order_item_qs.filter(item_variations__variation=v)

【讨论】:

感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。 proper explanation 将通过展示为什么这是解决问题的好方法,并使其对有其他类似问题的未来读者更有用,从而大大提高其长期价值。请edit您的回答添加一些解释,包括您所做的假设。【参考方案3】:

我认为是块问题

if order_item_qs.exists():
        order_item = order_item_qs.first()
        order_item.quantity += 1
        order_item.save()

这段代码只执行一次 您需要将其重写为

if len(item_var) > 0:
   for items in item_var:
      order_item_qs = OrderItem.objects.filter(
        item=item,
        user=request.user,
        ordered=False
    ).filter(variation__exact=items,)

      if order_item_qs.exists():
          order_item = order_item_qs.first()
          order_item.quantity += 1
          order_item.save()
      else:
          order_item = OrderItem.objects.create(
             item=item,
            user=request.user,
            ordered=False
        )
          order_item.variation.add(*item_var)
          order_item.save()
    ```

【讨论】:

以上是关于产品变体未反映 Django 电子商务项目中订单摘要中的更新数量的主要内容,如果未能解决你的问题,请参考以下文章

获取产品变体描述以显示在 woocommerce 中的订单项目上?

将可变产品的“描述”字段内容添加到 woocommerce“已完成订单”电子邮件

在电子邮件中显示自定义数据,订单详情 woocommerce

Ajax Django 添加按钮未在第一次单击时添加

如何在 django rest 框架中仅使用特定变体对象将项目添加到愿望清单?

如何在 Django 中显示相对值?