基于Django Through的多对多关联:发票与产品表单开发问询
嘿,我来帮你搞定这个Django里的发票表单需求!你已经有了Product模型的基础,接下来咱们先把模型补全,然后一步步搭建包含内联产品表单的发票表单,这样就能完美记录发票数据,还能方便计算卖家积分啦~
第一步:完善模型结构
首先,咱们需要给Invoice模型加上和卖家(User)的关联,而且如果要记录每个产品的售出数量(毕竟积分计算可能和数量挂钩),最好加一个中间模型InvoiceItem来关联发票和产品,这样能灵活记录数量。完善后的模型代码如下:
from django.contrib.auth.models import User from django.db import models class Product(models.Model): group = models.CharField(max_length=200, blank=False) mark = models.CharField(max_length=200, blank=True) points = models.IntegerField(blank=False) def __str__(self): return f"{self.group} - {self.mark}" # 后台显示更友好 class Invoice(models.Model): seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='invoices') price = models.FloatField(blank=False) invoice_date = models.DateField(auto_now_add=True) # 自动记录创建日期 # 通过中间模型关联产品 products = models.ManyToManyField(Product, through='InvoiceItem') class InvoiceItem(models.Model): invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField(default=1) # 售出数量,默认1件 def get_total_points(self): # 计算单个产品项的积分 return self.product.points * self.quantity
第二步:创建表单与内联表单集
接下来要做的是发票的主表单,以及用来添加售出产品的内联表单集。Django的inlineformset_factory正好用来处理这种关联模型的内联表单:
from django import forms from .models import Invoice, InvoiceItem class InvoiceForm(forms.ModelForm): class Meta: model = Invoice fields = ['price'] # 卖家字段在视图里自动填充当前登录用户,不用让用户选 class InvoiceItemForm(forms.ModelForm): class Meta: model = InvoiceItem fields = ['product', 'quantity'] # 创建内联表单集:关联Invoice和InvoiceItem,默认显示3个空表单,允许删除 InvoiceItemFormSet = forms.inlineformset_factory( Invoice, InvoiceItem, form=InvoiceItemForm, extra=3, can_delete=True )
第三步:编写视图处理表单提交
视图里要处理GET和POST请求,验证表单并保存数据,同时把当前登录用户设置为发票的卖家:
from django.shortcuts import render, redirect from .forms import InvoiceForm, InvoiceItemFormSet from django.contrib.auth.decorators import login_required @login_required # 只有登录用户才能创建发票 def create_invoice(request): if request.method == 'POST': invoice_form = InvoiceForm(request.POST) # 绑定表单集到发票实例 formset = InvoiceItemFormSet(request.POST, instance=invoice_form.instance) if invoice_form.is_valid() and formset.is_valid(): # 先不保存发票,先设置卖家 invoice = invoice_form.save(commit=False) invoice.seller = request.user invoice.save() # 保存内联表单集的数据 formset.save() # 保存后跳转到发票列表页(你可以换成自己的URL名称) return redirect('invoice_list') else: # GET请求时初始化空表单和表单集 invoice_form = InvoiceForm() formset = InvoiceItemFormSet(instance=Invoice()) return render(request, 'create_invoice.html', { 'invoice_form': invoice_form, 'formset': formset })
第四步:编写模板渲染表单
最后是模板,要记得渲染表单集的management_form(Django用来管理表单集的隐藏字段),不然表单集提交会出错:
{% extends 'base.html' %} {% block content %} <h2>创建新发票</h2> <form method="post"> {% csrf_token %} <!-- 渲染发票主表单 --> {{ invoice_form.as_p }} <h3>售出产品</h3> <!-- 必须渲染表单集的管理表单 --> {{ formset.management_form }} {% for form in formset %} <div style="margin: 10px 0; padding: 10px; border: 1px solid #eee;"> {{ form.as_table }} <!-- 如果允许删除,Django会自动生成删除按钮 --> {% if form.instance.pk %} {{ form.DELETE }} 删除此产品 {% endif %} </div> {% endfor %} <button type="submit" style="margin-top: 15px;">保存发票</button> </form> {% endblock %}
额外:快速计算卖家总积分
如果要给卖家计算总积分,可以在Invoice模型里加一个方法,或者直接通过查询聚合:
# 在Invoice模型里添加 def get_total_points(self): total = 0 for item in self.invoiceitem_set.all(): total += item.get_total_points() return total # 或者用更高效的聚合查询 from django.db.models import Sum, F def get_total_points(self): return self.invoiceitem_set.aggregate( total=Sum(F('product__points') * F('quantity')) )['total'] or 0
这样,你就能在视图或者模板里通过invoice.get_total_points()拿到单张发票的积分,要计算卖家的总积分的话,直接用request.user.invoices.aggregate(total=Sum(F('invoiceitem__product__points') * F('invoiceitem__quantity')))就行啦~
内容的提问来源于stack exchange,提问作者Stanislav Vasko
相关产品推荐
相关产品推荐

