Django CRM系统订单多商品关联:模型与动态视图实现问询
你的CRM订单多商品关联问题解决方案
一、模型设计:ForeignKey的用法是否合理?
嘿,你的思路完全在线!订单和商品属于多对多关系(一个订单包含多个商品,一个商品可属于多个订单),通过OrderItem中间表关联并存储quantity(数量)是行业标准做法。不过可以给你优化下,用Django的ManyToManyField结合through参数更贴合框架设计:
修改Order模型:
class Order(models.Model): STATUS = ( ('New', 'New'), ('Work in progress', 'Work in progress'), ('completed', 'completed'), ) customer = models.ForeignKey(Customer, null=True, on_delete=models.CASCADE) date_created = models.DateTimeField(auto_now_add=True, null=True) status = models.CharField(max_length=200, null=True, choices=STATUS) # 新增多对多关联,指定中间表为OrderItem products = models.ManyToManyField(Product, through='OrderItem') def date_createdview(self): return self.date_created.strftime('%d/%m/%Y')
这样调整的好处:
- 符合Django多对多关系的语义,你可以直接用
order.products.all()获取订单下的所有商品,也能反向用product.order_set.all()查询包含该商品的订单 OrderItem作为中间表的功能完全保留,后续还能扩展添加下单时的商品单价(避免商品价格变动影响历史订单)等字段
你的原OrderItem模型不需要修改,through参数就是指定它来维护关联关系。
二、动态添加商品的订单视图与表单实现
要实现「创建订单时动态添加商品、实时显示列表、保存订单后继续添加」的效果,咱们用Django的表单集(FormSet) + 前端JavaScript就能搞定。
1. 定义表单
在forms.py里创建订单和订单商品的表单:
from django import forms from .models import Order, OrderItem class OrderForm(forms.ModelForm): class Meta: model = Order fields = ['customer', 'status'] widgets = { 'customer': forms.Select(attrs={'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-control'}), } class OrderItemForm(forms.ModelForm): class Meta: model = OrderItem fields = ['product', 'quantity'] widgets = { 'product': forms.Select(attrs={'class': 'form-control'}), 'quantity': forms.NumberInput(attrs={'class': 'form-control', 'min': 1}), }
2. 构建视图
编写处理订单创建和商品添加的视图函数:
from django.forms import formset_factory from django.shortcuts import render, redirect, get_object_or_404 from .models import Order, OrderItem from .forms import OrderForm, OrderItemForm from django.contrib.auth.decorators import login_required @login_required(login_url="login") def create_order(request): # 创建订单商品表单集,默认显示1个商品输入框 OrderItemFormSet = formset_factory(OrderItemForm, extra=1) if request.method == 'POST': order_form = OrderForm(request.POST) item_formset = OrderItemFormSet(request.POST) if order_form.is_valid() and item_formset.is_valid(): # 先保存订单(暂不提交到数据库) order = order_form.save(commit=False) order.save() # 遍历表单集,保存每个商品项并关联到当前订单 for form in item_formset: if form.cleaned_data: # 跳过空表单 item = form.save(commit=False) item.order = order item.save() # 跳转至订单详情页,支持继续添加商品 return redirect('order_detail', order_id=order.id) else: order_form = OrderForm() item_formset = OrderItemFormSet() context = { 'order_form': order_form, 'item_formset': item_formset } return render(request, 'accounts/create_order.html', context) # 订单详情页,支持后续添加商品 @login_required(login_url="login") def order_detail(request, order_id): order = get_object_or_404(Order, id=order_id) OrderItemFormSet = formset_factory(OrderItemForm, extra=1) if request.method == 'POST': item_formset = OrderItemFormSet(request.POST) if item_formset.is_valid(): for form in item_formset: if form.cleaned_data: item = form.save(commit=False) item.order = order item.save() return redirect('order_detail', order_id=order.id) else: item_formset = OrderItemFormSet() # 获取订单已有的商品项 order_items = OrderItem.objects.filter(order=order) context = { 'order': order, 'item_formset': item_formset, 'order_items': order_items } return render(request, 'accounts/order_detail.html', context)
3. 前端模板实现动态添加
先写create_order.html的核心内容:
<form method="POST"> {% csrf_token %} <div class="mb-4"> <h3>订单基础信息</h3> {{ order_form.as_p }} </div> <div class="mb-4"> <h3>订单商品</h3> <div id="item-forms"> {{ item_formset.management_form }} {% for form in item_formset %} <div class="item-form border p-3 mb-2"> {{ form.as_p }} <button type="button" class="remove-form btn btn-sm btn-danger">删除</button> </div> {% endfor %} </div> <button type="button" id="add-item" class="btn btn-sm btn-primary mt-2">添加商品</button> </div> <button type="submit" class="btn btn-success">保存订单</button> </form> <script> // 动态添加商品表单逻辑 document.getElementById('add-item').addEventListener('click', function() { const itemFormsContainer = document.getElementById('item-forms'); const totalFormsInput = document.getElementById('id_form-TOTAL_FORMS'); const currentFormCount = parseInt(totalFormsInput.value); // 复制第一个表单作为模板 const formTemplate = document.querySelector('.item-form').cloneNode(true); // 修改表单字段的name和id属性,替换索引值 formTemplate.querySelectorAll('input, select').forEach(element => { const newName = element.name.replace('form-0-', `form-${currentFormCount}-`); element.name = newName; element.id = `id_${newName}`; // 清空输入值 element.value = element.tagName === 'SELECT' ? '' : element.value; }); // 绑定删除按钮事件 formTemplate.querySelector('.remove-form').addEventListener('click', function() { this.parentElement.remove(); totalFormsInput.value = parseInt(totalFormsInput.value) - 1; }); itemFormsContainer.appendChild(formTemplate); totalFormsInput.value = currentFormCount + 1; }); // 初始表单的删除按钮事件绑定 document.querySelectorAll('.remove-form').forEach(btn => { btn.addEventListener('click', function() { this.parentElement.remove(); const totalFormsInput = document.getElementById('id_form-TOTAL_FORMS'); totalFormsInput.value = parseInt(totalFormsInput.value) - 1; }); }); </script>
order_detail.html可以复用上面的JS逻辑,同时展示已有的订单商品:
<div class="mb-4"> <h2>订单 #{{ order.id }}</h2> <p>客户: {{ order.customer.name }}</p> <p>状态: {{ order.status }}</p> <p>创建时间: {{ order.date_createdview }}</p> </div> <div class="mb-4"> <h3>已添加商品</h3> <ul class="list-group"> {% for item in order_items %} <li class="list-group-item"> {{ item.product.name }} - 数量: {{ item.quantity }} - 单价: {{ item.product.price }} </li> {% empty %} <li class="list-group-item">暂无商品</li> {% endfor %} </ul> </div> <div class="mb-4"> <h3>添加新商品</h3> <form method="POST"> {% csrf_token %} {{ item_formset.management_form }} <div id="item-forms"> {% for form in item_formset %} <div class="item-form border p-3 mb-2"> {{ form.as_p }} <button type="button" class="remove-form btn btn-sm btn-danger">删除</button> </div> {% endfor %} </div> <button type="button" id="add-item" class="btn btn-sm btn-primary mt-2">添加商品</button> <button type="submit" class="btn btn-success mt-2">保存商品</button> </form> </div> <!-- 复制上面的动态添加JS代码即可 --> <script> // 这里粘贴之前的JS逻辑 </script>
额外优化建议
- 给
OrderItem添加price字段,存储下单时的商品价格,避免后续商品调价影响历史订单的金额计算 - 前端用
select2插件优化商品选择下拉框,支持搜索,提升用户体验 - 给
Order模型添加总价计算属性:
class Order(models.Model): # ... 其他字段 ... @property def total_price(self): return sum(item.product.price * item.quantity for item in self.orderitem_set.all())
内容的提问来源于stack exchange,提问作者Gorge Tor
相关产品推荐
相关产品推荐

