In my cart i can add same product with different size and color. but randomly when i want to to add same product with different size and color i have this error in my cart iter function. i've tracked the process in debugger and found that it somehow traped in my Product model str method and finally this error raise.
models:
from django.db import models
from django.urls import reverse
from django.conf import settings
from colorfield.fields import ColorField
class Product(models.Model):
GENDER_MALE = 'm'
GENDER_FEMALE = 'f'
GENDER_BOTH = 'b'
GENDER_CHOICE = [
(GENDER_MALE, 'Male'),
(GENDER_FEMALE, 'Female'),
(GENDER_BOTH, 'Both')
]
name = models.CharField(max_length=200)
category = models.CharField(max_length=200)
gender = models.CharField(choices=GENDER_CHOICE, max_length=1)
sizes = models.ManyToManyField(to="store.Size", related_name="sizes")
slug = models.SlugField(unique=True, allow_unicode=True, db_collation='utf8_persian_ci')
price = models.PositiveIntegerField()
description = models.TextField()
inventory = models.IntegerField()
datetime_created = models.DateTimeField(auto_now_add=True)
datetime_modified = models.DateTimeField(auto_now=True)
discounts = models.IntegerField(default=0)
available_colors = models.ManyToManyField(to="store.Color", related_name="colors")
status = models.BooleanField(default=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("product_detail", kwargs={"slug": self.slug})
class Size(models.Model):
size = models.CharField(max_length=2)
def __str__(self):
return self.size
class Color(models.Model):
color = ColorField()
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Picture(models.Model):
picture = models.ImageField(upload_to=f'static/store/images/')
product = models.ForeignKey(Product, default=None, related_name='images', on_delete=models.PROTECT)
def __str__(self):
return self.picture.url
views:
def cart_detail_view(request):
cart = Cart(request)
colors = Color.objects.all()
for item in cart:
item['product_update_quantity_form'] = AddProductToCartForm(initial={
'inplace': True,
})
return render(request, 'products/cart_detail.html', {'cart': cart, 'colors': colors})
def add_to_cart_view(request, pk):
cart = Cart(request)
product = get_object_or_404(Product, pk=pk)
form = AddProductToCartForm(request.POST)
print(request.POST)
if form.is_valid():
cleaned_data = form.cleaned_data
size = cleaned_data['size']
color = cleaned_data['color']
quantity = (cleaned_data['quantity'])
replace_current_quantity = cleaned_data['inplace']
cart.add(product, size, color, quantity, replace_current_quantity)
return redirect('cart_detail')
def remove_item_from_cart(request, cart_item):
cart = Cart(request)
cart.remove(cart_item)
return redirect('cart_detail')
@require_POST
def clear_cart(request):
cart = Cart(request)
if len(cart) != 0:
cart.clear()
return redirect('product_list')
cart.py:
from .models import Product
class Cart:
def __init__(self, request):
"""
Initialize the cart
"""
self.request = request
self.session = request.session
cart = self.session.get('cart')
if not cart:
cart = self.session['cart'] = {}
self.cart = cart
def add(self, product, size, color, quantity=1, replace_current_quantity=False):
"""
Add a product to the cart
"""
cart_item = '1'
flag = True
for item in self.cart.items():
if item[1]['product_id'] == product.id and item[1]['size'] == size and item[1]['color'] == color:
if replace_current_quantity:
item[1]['quantity'] = quantity
else:
item[1]['quantity'] += quantity
flag = False
keys = list(self.cart.keys())
if flag:
while flag:
if cart_item in keys:
cart_item = str(int(cart_item) + 1)
continue
flag = False
self.cart[cart_item] = {'product_id': product.id}
self.cart[cart_item]['size'] = size
self.cart[cart_item]['color'] = color
self.cart[cart_item]['quantity'] = quantity
self.save()
def remove(self, cart_item):
cart_item = str(cart_item)
"""
Remove a product from the cart
"""
if cart_item in self.cart.keys():
del self.cart[cart_item]
self.save()
def save(self):
"""
Mark session as modified to save changes
"""
self.session.modified = True
def __iter__(self):
product_ids = [value['product_id'] for value in self.cart.values()]
products = Product.objects.filter(id__in=product_ids)
cart = self.cart.copy()
keys = list(cart.keys())
for index,product in enumerate(products):
cart[str(keys[index])]['product_obj'] = product
cart[str(keys[index])]['cart_item'] = keys[index]
for item in cart.values():
item['total_price'] = item['product_obj'].price * item['quantity']
yield item
def __len__(self):
return len(self.cart)
def clear(self):
del self.session['cart']
self.save()
def get_total_price(self):
return sum(item['product_obj'].price * item['quantity'] for item in self.cart.values())
cart_detail.html:
<form action="{% url "cart_add" item.product_obj.pk %}" method="post">
{% csrf_token %}
{% render_field item.product_update_quantity_form.color value="{{item.color.color}}" type="hidden" %}
{% render_field item.product_update_quantity_form.size value="{{item.size.size}}" type="hidden" %}
<td>
<div class="numbers-row">
<input type="text" value="{{item.quantity}}" id="quantity_1" class="qty2" name="quantity">
<div class="inc button_inc">+</div><div class="dec button_inc">-</div></div>
</td>
<td>
{{item.product_update_quantity_form.inplace}}
<button style="color:blue; background-color: transparent; border:none;" type="submit"><i class="ti-reload"></i></button>
</td>
</form>
forms:
class AddProductToCartForm(forms.Form):
QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 31)]
quantity = forms.TypedChoiceField(choices=QUANTITY_CHOICES, coerce=int,)
color = forms.CharField(max_length=20)
size = forms.CharField(max_length=20)
inplace = forms.BooleanField(required=False, widget=forms.HiddenInput)
Traceback:
Environment:
Request Method: GET
Request URL: http://127.0.0.1:8000/cart/
Django Version: 5.0.1
Python Version: 3.12.1
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'widget_tweaks',
'colorfield',
'store',
'core']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware']
Traceback (most recent call last):
File "D:\Django\shoe_store\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "D:\Django\shoe_store\venv\Lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Django\shoe_store\store\views.py", line 67, in cart_detail_view
for item in cart:
^^^^^^
File "D:\Django\shoe_store\store\cart.py", line 81, in __iter__
item['total_price'] = item['product_obj'].price * item['quantity']
^^^^^^^^^^^^^^^^^^^
Exception Type: KeyError at /cart/
Exception Value: 'product_obj'
i've tracked the process in debugger and found that it somehow traped in my Product model str method and finally this error raise.
It is too much code. I try to include only relevant sections.
Basically, on the first for loop
you are populating cart
dictionary's item objects with product
as a value and 'product_obj'
as a key.
for index,product in enumerate(products):
cart[str(keys[index])]['product_obj'] = product
cart[str(keys[index])]['cart_item'] = keys[index]
for item in cart.values():
item['total_price'] = item['product_obj'].price * item['quantity']
yield item
On the second for loop
you are trying to retrieve the product
(and its price) using 'product_obj'
key but python is throwing a KeyError
:
File "D:\Django\shoe_store\store\cart.py", line 81, in __iter__
item['total_price'] = item['product_obj'].price * item['quantity']
^^^^^^^^^^^^^^^^^^^
Exception Type: KeyError at /cart/
Exception Value: 'product_obj'
In other words, either your all or certain item objects don't have product_obj
and product
key value pairs.
It might be happening because your cart
has more items than products
, so on the 1st loop product objects are being added to only a few cart items.
Make sure the products and cart have the same length, if they must have the same length, then you should raise error or take some other appropriate actions when there is a mismatch.
or
iterate only over cart items that have product
object.