Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Product variants not reflecting updated quantity in Order Summary in a Django E-commerce Project

I have a problem with the variations and the quantity related to it in the order summary page.

when I add to the cart 2 items:

  • Item X with a size small Quantity:1
  • Item X with a size medium Quantity:1

When I change the quantity of item X size medium, this change is reflecting in item X size small which was chosen first.

To be like this:

  • Item X with a size small Quantity:2
  • Item X with a size medium Quantity:1

In the order summary, there are a plus and minus in the template to change the quantity.

I have recently understood that because there's no form in the template. The code that sends a POST request with form data to the add to cart view is not there, because item_var will always be an empty list so order_item.variation.add(*item_var) does nothing. I do not know how to add a POST request to this template.

in the template there is a URL to add-to-cart", but URLs are transmitted by GET, so the code after if request.method == 'POST': never hits. Furthermore, even if it would, the add_to_cart url knows nothing about variations cause it only gets item slugs.

Here is the template:

    <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>

Here is the 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)

here is the 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}"

Urls.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'),
]

enter image description here

like image 704
A_K Avatar asked Jan 20 '26 20:01

A_K


1 Answers

Of course, to django both those items are the same.

The lines responsible for this are:

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()

As taken from https://docs.djangoproject.com/en/dev/ref/models/querysets/#first :

first()¶

Returns the first object matched by the queryset, or None if there is no matching object. If the QuerySet has no ordering defined, then the queryset is automatically ordered by the primary key. This can affect aggregation results as described in Interaction with default ordering or order_by().

However, the main culprit is:

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

You want to pass the variation to it, e.g.

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

You could consider adding the size as a field of product and creating individual products based on the variations. However, simply retrieving the variation and making sure you only increment the correct item by passing it to your filter() is also a correct approach.

like image 87
alexisdevarennes Avatar answered Jan 23 '26 08:01

alexisdevarennes