Django实现勾选Checkbox添加带选中尺码的商品至购物车
问题描述
目前已实现商品添加至购物车功能,但需优化为:用户通过Checkbox/Radio选择商品尺码后,点击“加入购物车”按钮,将商品与选中尺码一同添加至购物车。现有代码无法实现该逻辑,需调整。
现有代码展示
商品、尺码及购物车模型
class Product(models.Model): article = models.CharField(max_length=12,null=False,blank=False,unique=True,primary_key=True) img = models.ImageField(upload_to='products_images') price = models.DecimalField(max_digits=9,decimal_places=2) description = models.TextField(null=True,blank=True) gender = models.CharField(max_length=12,null=False,blank=False) is_child = models.BooleanField(null=False) season = models.CharField(max_length=12,null=False) color = models.CharField(max_length=24,null=False) sizes = models.ForeignKey(to='Size',on_delete=models.CASCADE,to_field='article') def __str__(self): return f"{self.article}" class Size(models.Model): article = models.CharField(max_length=12,null=False,unique=True) article_size = models.ForeignKey(to='ArticleSize',on_delete=models.CASCADE,to_field='article_size') def __str__(self): return f"{self.article}" class ArticleSize(models.Model): article_size = models.CharField(max_length=12, null=False, unique=True) size_name = models.CharField(max_length=12,null=False,blank=False) qty = models.SmallIntegerField() to_article = models.CharField(max_length=12, null=False) def __str__(self): return f"{self.article_size}" class Basket(models.Model): user = models.ForeignKey(to=User, on_delete=models.CASCADE) product = models.ForeignKey(to=Product, on_delete=models.CASCADE,to_field='article') size = models.ForeignKey(to=ArticleSize,on_delete=models.CASCADE,to_field='article_size') qty = models.PositiveSmallIntegerField(default=0) create_time_stamp = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Корзина для {self.user.email} товар {self.product.article},размер {self.size.size_name}"
表单模型
class CheckboxForm(forms.Form): checkbox_size = forms.CharField(widget=forms.CheckboxInput(attrs={ 'class':'size_checkbox', 'type':'checkbox', 'name':'size_checkbox'}),required=True) class Meta: model = ArticleSize fields = ('checkbox_size')
视图代码
def products(request): form = CheckboxForm() context = { 'title': "Магазин", 'footer_1': "127015, Москва, Бумажный пр-д., д. 14, стр. 2 ООО «НИКАМЕД».", 'footer_2': "Копирование материалов запрещено.", 'products': Product.objects.all(), 'sizes': Size.objects.all(), 'article_size': ArticleSize.objects.all(), 'form': form, } return render(request, 'products/products.html', context) def basket_add(request,product,size): product = Product.objects.get(article=product) size = ArticleSize.objects.get(article_size=size) basket = Basket.objects.filter(user=request.user,product=product,size=size) if not basket.exists(): Basket.objects.create(user=request.user,product=product,qty=1,size=size) else: basket = basket.last() basket.qty += 1 basket.save() return HttpResponseRedirect(request.META['HTTP_REFERER'])
HTML模板
<div class="goods-group"> {% for product in products %} <div class="goods"> <div class="shoe-photo"> <a href="{% url 'products:item_info' %}"> <img src="{{ product.img.url }}" alt="нет фото"> </a> <a href="#"> <svg class="photo-heart" fill="#C0BFBF" width="64px" height="64px" viewBox="0 0 200 200" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><title></title><path d="M171.28,41.69a48.29,48.29,0,0,0-68.5,0l-2.5,3-2.5-2.5a48.29,48.29,0,0,0-68.5,0c-19,18.5-19,49-1,68l50,53a29.92,29.92,0,0,0,43.5,0l50.5-53.5c17.5-19.5,17-49.5-1-68Zm-14,53.5-50.5,53.5a10.26,10.26,0,0,1-14.5,0l-50-53c-10.5-11.5-10.5-29,.5-40s29-11,40.5,0l2.5,2.5c8,8,20.5,8,28,0l2.5-2.5a28.37,28.37,0,0,1,40,0,27.78,27.78,0,0,1,1,39.5Z"></path></g></svg> </a> </div> <a href="#"> <div class="price">{{product.price}} руб</div> </a> <a href="#"> <div class="description">{{product.description}}</div> </a> <div class="sizes-group"> <form action="" method="post"> {% for item in sizes %} {% if item.article == product.article %} {% for size in article_size %} {% if size.to_article == item.article %} <label> {{form.checkbox_size}} <span class="title_checkbox">{{size.size_name}}</span> </label> {% else %} {% endif %} {% endfor %} {% else %} {% endif %} {% endfor %} </form> </div> <a href="{% url 'products:basket_add' product=product.article size=product.sizes.article_size %}"> <button class="btn" type="submit"> <svg class="button-cart" fill="#C0BFBF" width="64px" height="64px" viewBox="-2 -2 204.00 204.00" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><title></title><path d="M183.25,52.75a18.22,18.22,0,0,0-7-1h-91c-3.5,0-8-.5-11.5.5a9.64,9.64,0,0,0-7.5,9.5,10,10,0,0,0,10,10h90.5l-8,42a10.22,10.22,0,0,1-10,8h-75a10.66,10.66,0,0,1-10-8l-16-74a20.3,20.3,0,0,0-19.5-16h-7a10,10,0,0,0,0,20h7l16,74c3,14,15,23.5,29.5,23.5h75c12,0,24.5-8,28-19.5,2.5-8,4-17,5.5-25,1.5-8.5,3.5-17,5-25a17.76,17.76,0,0,0,1-5,17,17,0,0,0,.5-5c0-4-2-8-5.5-9Zm-132,108.5a15,15,0,0,0,30,0h0a15,15,0,0,0-30,0Zm90,0a15,15,0,1,0,15-15,15,15,0,0,0-15,15v0Z"></path></g></svg> В корзину</button> </a> </div> {% endfor %} </div> </div>
解决方案
一、优化模型结构(推荐)
原模型存在冗余的Size中间表,直接关联Product与ArticleSize即可,简化查询逻辑:
class Product(models.Model): article = models.CharField(max_length=12,null=False,blank=False,unique=True,primary_key=True) img = models.ImageField(upload_to='products_images') price = models.DecimalField(max_digits=9,decimal_places=2) description = models.TextField(null=True,blank=True) gender = models.CharField(max_length=12,null=False,blank=False) is_child = models.BooleanField(null=False) season = models.CharField(max_length=12,null=False) color = models.CharField(max_length=24,null=False) def __str__(self): return f"{self.article}" class ArticleSize(models.Model): article_size = models.CharField(max_length=12,</think_never_used_51bce0c785ca2f68081bfa7d91973934> 我继续完成优化后的模型和后续步骤: ```python class ArticleSize(models.Model): article_size = models.CharField(max_length=12, null=False, unique=True) size_name = models.CharField(max_length=12,null=False,blank=False) qty = models.SmallIntegerField() # 直接关联Product,替代原有的to_article字段 product = models.ForeignKey(to=Product, on_delete=models.CASCADE, related_name='sizes') def __str__(self): return f"{self.size_name} ({self.product.article})" # Basket模型保持不变 class Basket(models.Model): user = models.ForeignKey(to=User, on_delete=models.CASCADE) product = models.ForeignKey(to=Product, on_delete=models.CASCADE,to_field='article') size = models.ForeignKey(to=ArticleSize,on_delete=models.CASCADE,to_field='article_size') qty = models.PositiveSmallIntegerField(default=0) create_time_stamp = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Корзина для {self.user.email} товар {self.product.article},размер {self.size.size_name}"
二、修改表单类
针对每个商品动态生成尺码选择表单,使用单选框(用户通常仅选一个尺码):
class AddToBasketForm(forms.Form): size = forms.ModelChoiceField( queryset=ArticleSize.objects.none(), widget=forms.RadioSelect, required=True, label='Выберите размер' ) def __init__(self, *args, **kwargs): # 接收商品ID,动态过滤尺码选项 product_article = kwargs.pop('product_article') super().__init__(*args, **kwargs) self.fields['size'].queryset = ArticleSize.objects.filter(product__article=product_article)
三、调整视图逻辑
- 商品列表视图:为每个商品绑定对应的尺码表单
def products(request): products_with_forms = [] for product in Product.objects.all(): form = AddToBasketForm(product_article=product.article) products_with_forms.append({'product': product, 'form': form}) context = { 'title': "Магазин", 'footer_1': "127015, Москва, Бумажный пр-д., д. 14, стр. 2 ООО «НИКАМЕД».", 'footer_2': "Копирование материалов запрещено.", 'products_with_forms': products_with_forms, } return render(request, 'products/products.html', context)
- 购物车添加视图:处理POST请求获取选中的尺码
from django.shortcuts import get_object_or_404 def basket_add(request): if request.method == 'POST': product_article = request.POST.get('product_article') form = AddToBasketForm(request.POST, product_article=product_article) if form.is_valid(): product = get_object_or_404(Product, article=product_article) size = form.cleaned_data['size'] # 用get_or_create简化重复商品的数量累加逻辑 basket, created = Basket.objects.get_or_create( user=request.user, product=product, size=size, defaults={'qty': 1} ) if not created: basket.qty += 1 basket.save() return HttpResponseRedirect(request.META['HTTP_REFERER'])
四、更新HTML模板
将表单与提交按钮整合,确保选中的尺码能随POST请求传递:
<div class="goods-group"> {% for item in products_with_forms %} {% with product=item.product form=item.form %} <div class="goods"> <div class="shoe-photo"> <a href="{% url 'products:item_info' %}"> <img src="{{ product.img.url }}" alt="нет фото"> </a> <a href="#"> <svg class="photo-heart" fill="#C0BFBF" width="64px" height="64px" viewBox="0 0 200 200" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"> <g id="SVGRepo_bgCarrier" stroke-width="0"></g> <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g> <g id="SVGRepo_iconCarrier"> <title></title> <path d="M171.28,41.69a48.29,48.29,0,0,0-68.5,0l-2.5,3-2.5-2.5a48.29,48.29,0,0,0-68.5,0c-19,18.5-19,49-1,68l50,53a29.92,29.92,0,0,0,43.5,0l50.5-53.5c17.5-19.5,17-49.5-1-68Zm-14,53.5-50.5,53.5a10.26,10.26,0,0,1-14.5,0l-50-53c-10.5-11.5-10.5-29,.5-40s29-11,40.5,0l2.5,2.5c8,8,20.5,8,28,0l2.5-2.5a28.37,28.37,0,0,1,40,0,27.78,27.78,0,0,1,1,39.5Z"></path> </g> </svg> </a> </div> <a href="#"> <div class="price">{{product.price}} руб</div> </a> <a href="#"> <div class="description">{{product.description}}</div> </a> <div class="sizes-group"> <form action="{% url 'products:basket_add' %}" method="post"> {% csrf_token %} <!-- 隐藏字段传递商品ID --> <input type="hidden" name="product_article" value="{{ product.article }}"> <!-- 渲染尺码选择框
相关产品推荐
相关产品推荐

