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

Django 新增Bill时如何关联创建Activity实例并导出存储PDF/Word文档

Django 三个需求实现方案

三个需求均可以实现,以下是分步实现指导:

一、前置:先确认模型关系定义正确

首先需要明确三个模型的关联关系,示例定义如下:

from django.db import models
from django.utils import timezone

class Clients(models.Model):
    name = models.CharField(max_length=100)
    # 其他客户字段

class Bill(models.Model):
    client = models.ForeignKey(Clients, on_delete=models.PROTECT, related_name="bills")
    create_time = models.DateTimeField(default=timezone.now)
    # 存储导出文件的字段
    word_file = models.FileField(upload_to="bill/word/", null=True, blank=True)
    pdf_file = models.FileField(upload_to="bill/pdf/", null=True, blank=True)

class Activity(models.Model):
    bill = models.ForeignKey(Bill, on_delete=models.CASCADE, related_name="activities")
    content = models.TextField() # 活动内容
    cost_hour = models.FloatField() # 耗时
    cost_amount = models.DecimalField(max_digits=10, decimal_places=2) # 成本

二、需求1:自动生成Bill的ID(对应主键pk)

  • Django 默认的自增主键pk只有在实例写入数据库后才会生成,不需要在用户点击保存前提前生成:
    • 如果仅需要给用户展示正式ID,可在Bill保存成功后,把生成的pk返回给前端展示即可
    • 如果需要提前给用户展示临时单号,可由前端生成随机字符串作为临时标识,保存后替换为正式pk即可
    • 如果业务需要自定义ID规则(如BL202405010001),可以重写Bill的save方法,在首次保存时生成自定义ID赋值给主键字段。

三、需求2:保存Bill时同步存入关联的Activity实例

推荐使用Django自带的FormSet实现多Activity数据的提交和校验,步骤如下:

  1. 后端定义Form和FormSet:
from django import forms
from .models import Bill, Activity

class BillForm(forms.ModelForm):
    class Meta:
        model = Bill
        fields = ["client"] # 其他Bill需要填写的字段

# 定义Activity的内联FormSet,绑定Bill实例
ActivityFormSet = forms.inlineformset_factory(
    Bill, Activity,
    fields=["content", "cost_hour", "cost_amount"],
    extra=1, # 默认显示1行空白行
    can_delete=True
)
  1. 前端表单处理:
    动态新增行时,按照FormSet的命名规则设置输入框的name属性,格式为activities-0-content、activities-1-content,同时要同步更新隐藏输入框id_activities-TOTAL_FORMS的值为当前总行数。
  2. 后端视图保存逻辑:
from django.shortcuts import render, redirect
from django.db import transaction
from .forms import BillForm, ActivityFormSet

@transaction.atomic # 事务保证数据一致性,Bill保存失败则Activity也不会入库
def create_bill(request):
    if request.method == "POST":
        bill_form = BillForm(request.POST)
        if bill_form.is_valid():
            # 先保存Bill实例,此时生成pk
            bill = bill_form.save()
            # 绑定Activity数据到FormSet,关联刚保存的Bill实例
            activity_formset = ActivityFormSet(request.POST, instance=bill)
            if activity_formset.is_valid():
                activity_formset.save()
                # 保存成功后跳转到详情页或者返回成功信息
                return redirect("bill_detail", pk=bill.pk)
    else:
        bill_form = BillForm()
        activity_formset = ActivityFormSet()
    return render(request, "bill/create.html", {
        "bill_form": bill_form,
        "activity_formset": activity_formset
    })

四、需求3:保存成功后自动导出Word/PDF并存入数据库

  1. 依赖库安装:
    • 导出Word用python-docx:pip install python-docx
    • 导出PDF用xhtml2pdf(可以直接渲染Django模板生成PDF,更简单):pip install xhtml2pdf
  2. 导出逻辑可以通过post_save信号触发,或者直接写在视图保存成功的逻辑之后,示例PDF导出逻辑:
from django.core.files.base import ContentFile
from xhtml2pdf import pisa
from django.template.loader import render_to_string

def export_bill_pdf(bill):
    # 渲染Bill详情模板为HTML
    html_content = render_to_string("bill/pdf_template.html", {"bill": bill})
    # 生成PDF文件
    result = ContentFile()
    pisa.CreatePDF(html_content, dest=result)
    # 保存到Bill的pdf_file字段
    bill.pdf_file.save(f"bill_{bill.pk}.pdf", result, save=False)
    bill.save(update_fields=["pdf_file"])

在视图的activity_formset.save()之后调用export_bill_pdf(bill)即可完成自动导出存储。

新手注意事项

  • 动态新增表格行时不要忘记更新FormSet的总数量隐藏字段,否则后端只能收到默认数量的Activity数据
  • 开发环境需要在settings.py中配置MEDIA_ROOT和MEDIA_URL,才能正常访问存储的导出文件
  • 导出的模板可以自己根据业务需要调整格式,不需要依赖外部服务

内容的提问来源于stack exchange,提问作者lag37

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 17:18:01