You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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}"

模型修改说明

  1. 把MealMenu里的quantity改名为stock_quantity,明确这是菜品的库存数量,和订单的订购数量做区分
  2. 移除MealOrder里的menu、total_items、total_amounts字段:
    • 用@property动态计算总商品数和总金额,避免数据库存储冗余数据,防止子项修改后总数据不一致
  3. 新增MealOrderItem模型:
    • 关联订单和菜品,每个子项对应一个菜品的订购数量
    • 可选添加ordered_price字段,保存下单时的价格,避免后续菜品调价影响历史订单的金额计算

前端实现多选商品和数量设置

可以用Django的ModelFormSet实现多子项表单提交,或者用JavaScript动态添加菜品选择框和数量输入框。

示例:用ModelFormSet实现

  1. 定义表单类:
# 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
)
  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
    })
  1. 前端模板(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 18:55:18