Django 3.2中如何基于含多对多字段的模型渲染可批量输入的表格
Django 3.2 多产品销售计划批量录入视图实现方案
前置优化提示:当前SalesPlan模型使用
OneToOneField关联Customer和Product不符合业务逻辑,会限制一个客户/一个产品全局只能有一条销售计划记录。建议先调整模型字段:
class SalesPlan(models.Model): # 把OneToOneField改为ForeignKey customer = models.ForeignKey(Customer, null=True, on_delete=RESTRICT) period = models.DateField() product = models.ForeignKey(Product, null=True, on_delete=RESTRICT) qty = models.DecimalField(max_digits=10, decimal_places=2, null=True) # 新增联合唯一约束,保证同一个客户、同一个周期、同一个产品只有一条计划 class Meta: unique_together = [['customer', 'period', 'product']]
具体实现步骤
步骤1:定义仅开放数量字段的ModelForm
创建仅暴露qty字段的表单,其余关联字段设为隐藏控件,避免用户修改:
from django import forms from .models import SalesPlan class SalesPlanForm(forms.ModelForm): class Meta: model = SalesPlan fields = ['qty', 'customer', 'period', 'product'] widgets = { 'customer': forms.HiddenInput(), 'period': forms.HiddenInput(), 'product': forms.HiddenInput(), }
步骤2:视图层构造动态Formset
使用modelformset_factory构造表单集,表单数量和客户关联的产品数量完全匹配:
from django.forms import modelformset_factory from django.shortcuts import render, get_object_or_404, redirect from .models import Customer, SalesPlan def sales_plan_create(request, customer_code, period): # 获取当前操作的客户实例 customer = get_object_or_404(Customer, customer_code=customer_code) # 获取客户名下所有可售产品 portfolio_products = customer.customer_portfolio.all() # 构造Formset,关闭自动查询已有数据(新增场景),extra数量等于产品数 SalesPlanFormSet = modelformset_factory( SalesPlan, form=SalesPlanForm, extra=len(portfolio_products), can_delete=False ) if request.method == 'POST': formset = SalesPlanFormSet(request.POST, queryset=SalesPlan.objects.none()) if formset.is_valid(): instances = formset.save(commit=False) # 可选:校验每个实例的product属于当前客户的portfolio for instance in instances: instance.save() # 保存成功后跳转逻辑自行补充 return redirect('sales_plan_list') else: formset = SalesPlanFormSet(queryset=SalesPlan.objects.none()) # 为每个表单预填充固定字段 for form, product in zip(formset, portfolio_products): form.initial['customer'] = customer form.initial['period'] = period form.initial['product'] = product # 把产品列表传到模板,用于渲染产品名称、规格等只读信息 return render(request, 'sales_plan_form.html', { 'formset': formset, 'customer': customer, 'period': period, 'portfolio_products': portfolio_products })
如果是编辑已有销售计划的场景,仅需将Formset的queryset改为SalesPlan.objects.filter(customer=customer, period=period),同时将extra设为0即可。
步骤3:模板层渲染为表格结构
在模板中将表单集和产品信息一一对应,渲染为每行一个产品的表格,仅数量列可编辑:
<form method="post"> {% csrf_token %} {{ formset.management_form }} <table> <thead> <tr> <th>产品编码</th> <th>产品名称</th> <th>单位</th> <th>计划数量</th> </tr> </thead> <tbody> {% for form, product in formset|zip:portfolio_products %} <tr> <td>{{ product.item_code }}</td> <td>{{ product.item_name }}</td> <td>{{ product.unit_of_measure }}</td> <td> {{ form.qty }} <!-- 渲染隐藏字段 --> {% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %} <!-- 显示字段校验错误 --> {% for error in form.qty.errors %} <span class="error">{{ error }}</span> {% endfor %} </td> </tr> {% endfor %} </tbody> </table> <button type="submit">保存</button> </form>
注:如果模板不支持
zip过滤器,可在Django后台注册自定义过滤器,或者将form和product提前打包为元组列表传到模板。
内容的提问来源于stack exchange,提问作者Alexander Radovich
相关产品推荐
相关产品推荐

