KeyError в /cart/ 'product_obj' Django

В моей корзине я могу добавить один и тот же товар с разными размерами и цветами. Но случайно, когда я хочу добавить один и тот же товар с разными размерами и цветом, у меня возникает ошибка в функции корзины iter. Я проследил процесс в отладчике и обнаружил, что он каким-то образом застрял в методе модели продукта str и в итоге эта ошибка raise.

модели:

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

Просмотров:

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

Урлы:


from store import views


urlpatterns = [
    path('products/', views.ProductListView.as_view(), name='product_list'),
    path("products/<slug>/", views.ProductDetailView.as_view(), name='product_detail'),
    path("products/<slug>/comment", views.CommentView.as_view(), name='comment_create'),
    path('accounts/profile/', views.profile_view, name='account_profile'),
    path('cart/', views.cart_detail_view, name='cart_detail'),
    path('cart/add/<int:pk>/', views.add_to_cart_view, name="cart_add"),
    path('cart/remove/<int:cart_item>/', views.remove_item_from_cart, name='cart_remove'),
    path('clear/', views.clear_cart, name='cart_clear'),
    
]

Формы:

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'

Я отследил процесс в отладчике и обнаружил, что он каким-то образом застрял в методе str моей модели продукта и в итоге возникла ошибка raise.

Слишком много кода. Я стараюсь включать только нужные разделы.

По сути, на первом for loop вы заполняете объекты cart словаря элементами с product в качестве значения и 'product_obj' в качестве ключа.

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            

Во втором for loop вы пытаетесь получить product (и его цену) с помощью клавиши 'product_obj', но python выдает ошибку 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'

Другими словами, либо все, либо некоторые объекты товаров не имеют пар ключ-значение product_obj и product. Возможно, это происходит потому, что в вашем cart больше товаров, чем products, поэтому в 1-м цикле объекты товаров добавляются только к нескольким элементам корзины.

Решение:

Убедитесь, что товары и корзина имеют одинаковую длину, если они должны иметь одинаковую длину, то вы должны выдать ошибку или предпринять другие соответствующие действия, когда есть несоответствие.

или перебирать только те элементы корзины, которые имеют product object.

Вы можете обновить свой for loop следующим образом. так он будет более чистым.

for index, product in enumerate(products):
    cart[product.id] = {
        "product_price": product.price,
        "quantity": keys[index],
        "total_price": product.price * keys[index],
    }

    yield cart[product.id]
#OR return cart 

Не стоит использовать входы widget_tweaks для форм. Он не передает значения размера и цвета должным образом, а в корзину добавляет товар без product_obj. Поэтому в данном случае лучше использовать обычный input в форме. В результате в cart_detail.html вместо:

{% 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" %}

используйте это:

<input type="text" value="{{item.size}}" name="size" type='hidden'>
<input type="text" value="{{item.color}}" name="color" type='hidden'>
Вернуться на верх