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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:32:57