如何创建批量录入无成本产品成本信息的Django表单?
批量录入无关联成本产品的表单实现
需求说明
现有Product(含产品名称字段)和一对一关联的ProductCosts(含成本、产量字段)模型,已实现单独的产品录入和成本录入功能,现在需要做一个批量表单:
- 第一列展示还没关联成本记录的产品名称
- 对应行提供空白的成本、产量输入框
- 表单行数和无成本关联的产品数量一致
模型代码(参考)
# models.py from django.db import models class Product(models.Model): name = models.CharField("产品名称", max_length=150) # 其他字段... class ProductCosts(models.Model): product = models.OneToOneField(Product, on_delete=models.CASCADE, verbose_name="关联产品") costs = models.DecimalField("成本", max_digits=10, decimal_places=2) produced = models.IntegerField("产量", null=True, blank=True) # 其他字段...
解决方案
1. 定义批量表单类
不用ModelForm,直接基于Form类动态生成字段,匹配每个无成本关联的产品:
# forms.py from django import forms from .models import Product, ProductCosts class BulkProductCostsForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 筛选所有未关联成本的产品 unassigned_products = Product.objects.filter(productcosts__isnull=True) for product in unassigned_products: # 为每个产品创建成本字段 self.fields[f'costs_{product.id}'] = forms.DecimalField( label=f"{product.name} 的成本", max_digits=10, decimal_places=2, required=True # 若允许空成本,改成False ) # 为每个产品创建产量字段 self.fields[f'produced_{product.id}'] = forms.IntegerField( label=f"{product.name} 的产量", required=False, widget=forms.NumberInput(attrs={'placeholder': '可选填'}) )
2. 编写视图处理提交逻辑
处理表单的POST请求,批量创建ProductCosts实例:
# views.py from django.shortcuts import render, redirect from .forms import BulkProductCostsForm from .models import Product, ProductCosts def bulk_add_costs(request): if request.method == 'POST': form = BulkProductCostsForm(request.POST) if form.is_valid(): # 遍历表单数据,提取产品ID和对应值 for field_name, value in form.cleaned_data.items(): if field_name.startswith('costs_'): product_id = field_name.split('_')[1] try: product = Product.objects.get(id=product_id) # 获取对应产量值 produced_val = form.cleaned_data.get(f'produced_{product_id}') # 创建成本记录 ProductCosts.objects.create( product=product, costs=value, produced=produced_val ) except Product.DoesNotExist: # 处理产品不存在的异常,可选:记录日志或返回错误 continue # 提交后跳转到产品列表或成本列表页面 return redirect('product_list') # 替换成你的URL名称 else: form = BulkProductCostsForm() # 获取未关联成本的产品,用于模板渲染产品名称 unassigned_products = Product.objects.filter(productcosts__isnull=True) return render(request, 'bulk_add_costs.html', { 'form': form, 'unassigned_products': unassigned_products })
3. 编写模板(表格布局)
用表格展示产品和对应输入框,直观清晰:
<!-- templates/bulk_add_costs.html --> {% extends 'base.html' %} {% load crispy_forms_tags %} <!-- 如果你用了crispy forms,没用到可以删掉 --> {% block content %} <h2>批量录入产品成本</h2> <form method="post"> {% csrf_token %} <table class="table table-striped table-bordered"> <thead> <tr> <th>产品名称</th> <th>成本</th> <th>产量</th> </tr> </thead> <tbody> {% for product in unassigned_products %} <tr> <td>{{ product.name }}</td> <!-- 用crispy渲染字段,没用到crispy就用下面的原生输入框 --> <td>{{ form|crispy_field:"costs_"|add:product.id }}</td> <td>{{ form|crispy_field:"produced_"|add:product.id }}</td> <!-- 原生输入框写法(替代crispy): <td> <input type="number" step="0.01" name="costs_{{ product.id }}" class="form-control" required> </td> <td> <input type="number" name="produced_{{ product.id }}" class="form-control" placeholder="可选填"> </td> --> </tr> {% empty %} <tr> <td colspan="3" class="text-center text-muted">所有产品都已关联成本记录</td> </tr> {% endfor %} </tbody> </table> {% if unassigned_products %} <button type="submit" class="btn btn-dark">批量提交</button> {% endif %} </form> {% endblock %}
补充提示
- 如果需要对表单做更复杂的样式调整,可直接修改模板里的HTML结构和CSS类
- 若要允许部分产品不填成本,把表单里的
required=True改成False,提交时跳过空值的产品 - 可以在视图里添加错误提示,比如产品不存在时返回页面并提示用户
内容的提问来源于stack exchange,提问作者LadisPavel
相关产品推荐
相关产品推荐

