如何在Django REST Framework中选择多商品并设置对应数量?
解决方案:调整模型结构实现多商品订单
你的现有模型结构里,MealOrder直接关联单个MealMenu,没法实现一个订单选多个商品的需求。需要引入订单子项模型来关联订单和菜品,每个子项对应一个菜品和它的订购数量。
修改后的模型代码
import uuid from django.db import models class BaseModel(models.Model): branch = models.ForeignKey(Branch, on_delete=models.PROTECT, blank=True, null=True) company = models.ForeignKey(Company, on_delete=models.PROTECT, blank=True, null=True) class Meta: abstract = True class MealMenu(BaseModel): employee = models.ForeignKey(Employee, on_delete=models.PROTECT, null=True, blank=True) item_name = models.CharField(max_length=50, null=True, blank=True) # 重命名字段明确是库存数量,和订单订购数量区分开 stock_quantity = models.PositiveIntegerField() price = models.FloatField() def __str__(self): return f"{self.item_name} {self.price}" class MealOrder(BaseModel): RECEIVED = "Received" PENDING = "Pending" REJECTED = "Rejected" MEAL_CHOICES = ( ("Breakfast", "Breakfast"), ("Lunch", "Lunch"), ("Dinner", "Dinner"), ) STATUS_CHOICES = ( (RECEIVED, "Received"), (PENDING, "Pending"), (REJECTED, "Rejected"), ) id = models.UUIDField(primary_key=True, default=uuid.uuid4, null=False) meal_time = models.CharField(max_length=25, choices=MEAL_CHOICES) employee = models.ForeignKey(Employee, on_delete=models.PROTECT) date = models.DateField(auto_now=True) status = models.CharField(max_length=25, choices=STATUS_CHOICES, default=PENDING) @property def total_items(self): # 动态计算订单总商品数(所有子项数量之和) return self.order_items.aggregate(total=models.Sum('quantity'))['total'] or 0 @property def total_amounts(self): # 动态计算订单总金额(每个子项数量×菜品价格之和) total = 0 for item in self.order_items.all(): total += item.quantity * item.menu.price return total def __str__(self): return f"Order {self.id} - {self.meal_time}" class MealOrderItem(BaseModel): order = models.ForeignKey(MealOrder, on_delete=models.CASCADE, related_name='order_items') menu = models.ForeignKey(MealMenu, on_delete=models.PROTECT) quantity = models.PositiveIntegerField(default=1) # 可选:保存下单时的菜品价格,避免后续调价影响历史订单金额 ordered_price = models.FloatField() def save(self, *args, **kwargs): # 自动填充下单时的价格 if not self.ordered_price: self.ordered_price = self.menu.price super().save(*args, **kwargs) def __str__(self): return f"{self.menu.item_name} x {self.quantity}"
模型修改说明
- 把
MealMenu里的quantity改名为stock_quantity,明确这是菜品的库存数量,和订单的订购数量做区分 - 移除
MealOrder里的menu、total_items、total_amounts字段:- 用
@property动态计算总商品数和总金额,避免数据库存储冗余数据,防止子项修改后总数据不一致
- 用
- 新增
MealOrderItem模型:- 关联订单和菜品,每个子项对应一个菜品的订购数量
- 可选添加
ordered_price字段,保存下单时的价格,避免后续菜品调价影响历史订单的金额计算
前端实现多选商品和数量设置
可以用Django的ModelFormSet实现多子项表单提交,或者用JavaScript动态添加菜品选择框和数量输入框。
示例:用ModelFormSet实现
- 定义表单类:
# forms.py from django import forms from .models import MealOrder, MealOrderItem class MealOrderForm(forms.ModelForm): class Meta: model = MealOrder fields = ['meal_time', 'status'] class MealOrderItemForm(forms.ModelForm): class Meta: model = MealOrderItem fields = ['menu', 'quantity'] # 创建表单集,默认显示1个商品输入框 MealOrderItemFormSet = forms.modelformset_factory( MealOrderItem, form=MealOrderItemForm, extra=1 )
- 视图处理提交:
# views.py from django.shortcuts import render, redirect from .forms import MealOrderForm, MealOrderItemFormSet from .models import MealOrder def create_order(request): if request.method == 'POST': order_form = MealOrderForm(request.POST) item_formset = MealOrderItemFormSet(request.POST) if order_form.is_valid() and item_formset.is_valid(): # 保存订单主表(暂不提交到数据库) order = order_form.save(commit=False) order.employee = request.user.employee # 假设当前用户关联Employee模型 order.save() # 保存每个订单子项 for form in item_formset: if form.cleaned_data: item = form.save(commit=False) item.order = order item.save() return redirect('order_list') else: order_form = MealOrderForm() item_formset = MealOrderItemFormSet(queryset=MealOrderItem.objects.none()) return render(request, 'create_order.html', { 'order_form': order_form, 'item_formset': item_formset })
- 前端模板(create_order.html):
<form method="post"> {% csrf_token %} {{ order_form.as_p }} <h3>选择菜品</h3> {{ item_formset.management_form }} <div id="formset-container"> {% for form in item_formset %} <div class="form-row">{{ form.as_p }}</div> {% endfor %} </div> <button type="button" id="add-item-btn">添加更多菜品</button> <button type="submit">提交订单</button> </form> <script> // 动态添加菜品输入行 document.getElementById('add-item-btn').addEventListener('click', function() { const formset = document.querySelector('#id_form-TOTAL_FORMS'); const totalForms = parseInt(formset.value); const formRow = document.querySelector('.form-row').cloneNode(true); // 更新表单字段的name和id formRow.querySelectorAll('input, select').forEach(field => { field.name = field.name.replace('-0-', `-${totalForms}-`); field.id = field.id.replace('-0-', `-${totalForms}-`); field.value = ''; // 清空输入值 }); document.getElementById('formset-container').appendChild(formRow); formset.value = totalForms + 1; }); </script>
注意事项
- 如果要限制订购数量不超过库存,可以在
MealOrderItemForm的clean方法里添加验证逻辑 - 动态计算总数据比直接存数据库更可靠,避免订单子项修改后总数据不更新的问题
- 前端动态添加表单时,必须更新
management_form里的TOTAL_FORMS值,否则Django无法识别新增的表单行
内容的提问来源于stack exchange,提问作者Khizra Shabbir
相关产品推荐
相关产品推荐

