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

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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With