Updating Quantity in an Order Summary

Submitted 3 years, 8 months ago
Ticket #94
Views 300
Language/Framework Python
Priority Low
Status Closed

I'm trying to fix a bug in my project.

When a user goes to the product detail page, the user can select a variant size (Small, medium or Large).

Then next goes to the Order Summary page with all the items looped with its variation. If Item 1 with Small and Item 1 with Medium and the quantity for each.

In the Order Summary, there is an option to add or remove single items for each, and this is where the bug is.

Example (the sequence is important):

 - Added Item 1 to cart with a size Small.
 - Added another Item 1 to cart with size Medium

So now in the Order Summary, I have a table with the following

 - Item 1    Size: Small     Quantity: 1
 - Item 1    Size: Medium    Quantity: 1

Now if I want to change the quantity of  Item 1    Size: Medium, the very first item select is the one which is updated:

 - Item 1    Size: Small     Quantity: 2
 - Item 1    Size: Medium    Quantity: 1

The reason behind this is because when add to cart or remove to cart in the template is reflecting the slug of the item not identifying the variation of the selected item and I have tried many times to fix it but still it doesn't reflect correctly.

I have tried to change from slug to id in order to pinpoint the item that needed quantity updating but it didn't work.Here is the Views:
 

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

Submitted on Aug 04, 20
add a comment

1 Answer

@ Ahmed.Hisham87 Looks like its more business issue, we need to clone this stuff and do the investigation on where the issue exactly. I will keep you posted. Meanwhile if you are interested you could help other open tickets :) just for fun .

Submitted 3 years, 8 months ago

@ Ahmed.Hisham87 can you share the Order models details and also share relevant code which is related to this issue. May be share the code in github or pastebin that would help me to reproduce the stuff which you are trying to do.

- bhavana 3 years, 8 months ago

`class Order(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) ref_code = models.CharField(max_length=20, blank=True, null=True) items = models.ManyToManyField(OrderItem) start_date = models.DateTimeField(auto_now_add=True) ordered_date = models.DateTimeField() ordered = models.BooleanField(default=False) shipping_address = models.ForeignKey( 'Address', related_name='shipping_address', on_delete=models.SET_NULL, blank=True, null=True) billing_address = models.ForeignKey( 'Address', related_name='billing_address', on_delete=models.SET_NULL, blank=True, null=True) payment = models.ForeignKey( 'Payment', on_delete=models.SET_NULL, blank=True, null=True) coupon = models.ForeignKey( 'coupon', on_delete=models.SET_NULL, blank=True, null=True) refund_requested = models.BooleanField(default=False) refund_granted = models.BooleanField(default=False) taxes = models.DecimalField(decimal_places=2, max_digits=100, default=0.14) status = models.CharField(max_length=50, choices=[('pending', 'Pending'), ('ofd', 'Out For Delivery'), ('recieved', 'Recieved')], default='pending')

def __str__(self):
    return self.user.username`
- ahmed.hisham87 3 years, 8 months ago

Thanks for sharing the details. I am trying to recollect the issue. Is the issue in OrderSummary view and its related HTML page?

- bhavana 3 years, 8 months ago

from the Order Summary page

- ahmed.hisham87 3 years, 8 months ago

i think issue could be the order_item.qs in add-to-cart view
if order_item_qs.exists(): order_item = order_item_qs.first() order_item.quantity += 1 order_item.save()
can you try print after this save and see which item got added?

- bhavana 3 years, 8 months ago

I added the following code if order_item_qs.exists(): order_item = order_item_qs.first() order_item.quantity += 1 order_item.save() print(order_item_qs) it printed ``` <QuerySet [<OrderItem: 9 of user2 title>, <OrderItem: 1 of user2 title>]>

``` I thought the issue might be in the template as the updated quantity is returning an orderitem.slug

- ahmed.hisham87 3 years, 8 months ago

I mean print the whole queryset and check whether its returning the right quantity for an item.basicaly we need to check whether add to cart view doing the expected logic or not

- bhavana 3 years, 8 months ago

could you indicate the code to add it to print the whole queryset

- ahmed.hisham87 3 years, 8 months ago

@Ahmed.Hisham87 you need to debug the exact line code what Bhavana mentioned. as per her suggestion, you need to print the query set value and check what the value it holds in the Order Item table. From overall conversation what I understood is , when you click add to cart in order summary page then instead of the respective item addition which is always updating the first row in the table. Correct me if understanding is wrong, this could be because how the OrderItem query set has been saved in the database(i mean in your view logic).
Lets separate the logic now,
1. when you add to cart on second item then you may need to pass the second item id to your view and apply the filter on the given order Item id and then add + 1 or add -1 right?
2. But i see in your view your are using OrderItem queryset just with exact variation not with id and then return the Order.item.first() then this will always return the first set of data. So its adding to the first item for an given order for always. You need to go step by step in your add_to_cart view and check the issue where it exactly return the proper Order Item id or not.
I hope this would help you continue your debug.

- scott 3 years, 8 months ago

@Scott Thank you for breaking down the issue in an easy and simplified way. You are correct regarding your first point which explains the issue exactly. In the second point, I have started using Django and learning about it 4 months ago so, I am still learning how to debug issues like this in a simple way just as you explained everything about, and that is why I asked how to print the whole queryset so that I can pick up what is required and start applying it. If possible could you just point on how to check the add to cart view as you indicated to get the proper Quantity for each OrderItem Id?

I have been experimenting in the template to check regarding the OrderItem Id, instead of using the slug I added ID but it returns 405 Error. Thank you for your interest and enlightening comment.

- ahmed.hisham87 3 years, 8 months ago

@Ahmed.Hisham87 ah gotcha. Then i would suggest as a begineer probably you should start with same view logic, understanding the queryset before jump into the e-commerce project. Because when you start developing or understanding e-commerce project without understanding the queryset its quite to hard to debug the code. Its just my suggestion. Did you clone the project from somewhere and adding your own logic?

- scott 3 years, 8 months ago

@Scott yes I am a newbie and I actually learned Django starting with a Blog Project and after it, I started writing the E-commerce Project from scratch by following tutorials from Udemy, youtube and reading a lot of Q&As. Actually I finished the e-commerce project and everything is set except for this issue, I've been stuck with it for a while. I thought the issue with the template as it is configured to update the item related to the slug, not the id.

I learned a lot by practice but I am still practicing my debugging skills, that is why I am trying to learn how to fix this issue.

- ahmed.hisham87 3 years, 8 months ago

@Ahmed.Hisham87 as per above comments, you may need to check the what variation is return in the Order Item Query set,
like let say orderitemqs = OrderItem.object.filter(someid) for item in orderitemqs: print(item.variation) and check the details.
My sugegstion is completely remove your add_to_cart and add one by one and check the issue.

- Vengat 3 years, 8 months ago

@Aruntck I removed the add to cart and started adding the items with different variation from the project details and it worked fine, the quantity was correct, would it help if I shared the github of the project ?

- ahmed.hisham87 3 years, 8 months ago

Awesome. So its working now right? Also please share your github code too.

- Vengat 3 years, 8 months ago


Latest Blogs