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

Django保存模型后触发PDF下载及post_save信号传参问题

问题根因

当前实现存在两个核心问题,导致功能不符合预期:

  • post_save 信号的返回值不会被Django的HTTP响应链路接收。信号本身只用于执行解耦的副作用逻辑(日志、通知、关联数据更新等),接收器的返回值会被直接丢弃。你在信号里构造的PDF响应完全不会返回给前端,admin保存模型后会走默认的重定向逻辑(跳回列表页/编辑页),自然不会弹出下载框。
  • post_save 信号默认只携带模型实例、创建状态等固定参数,拿不到ModelForm里的非模型字段、request对象等请求上下文数据,强行给信号传自定义参数会导致其他触发模型保存的场景(命令行操作、异步任务、其他业务代码调用save())因为缺参数报错。
实现方案

不要在信号里处理PDF生成和响应返回,直接把逻辑移到admin的保存流程中实现,步骤如下:

1. 移除原信号逻辑

删掉signals.py里绑定的download_contract_pdf信号接收器,避免无效执行。

2. 抽离公共PDF生成函数

新建utils.py存放PDF生成逻辑,函数只负责接收参数、生成PDF二进制内容,不耦合HTTP响应逻辑:

from datetime import datetime
from django.template.loader import render_to_string
from weasyprint import HTML
from .models import Charge, Control, Extension

def generate_contract_pdf(contract_obj, warranty_card_company, warranty_card_number, warranty_card_expiration_date, warranty_card_auth_code):
    # 格式化时间避免文件名出现特殊字符导致兼容问题
    file_name = f"contrato-{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
    charge_qs = Charge.objects.filter(contract=contract_obj.pk)
    control_qs = Control.objects.filter(contract=contract_obj.pk)
    extension_qs = Extension.objects.filter(contract=contract_obj.pk)
    credit_card_data = [
        warranty_card_company,
        warranty_card_number,
        warranty_card_expiration_date,
        warranty_card_auth_code
    ]
    html_string = render_to_string(
        'pdf_template.html',
        {
            'charge_obj': charge_qs,
            'contract_obj': contract_obj,
            'control_obj': control_qs,
            'extension_obj': extension_qs,
            'credit_card_data': credit_card_data
        }
    )
    html = HTML(string=html_string)
    pdf_content = html.write_pdf(
        stylesheets=["https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"]
    )
    return pdf_content, file_name

3. 修改Admin类逻辑

在admin.py里重写admin的保存和响应方法,直接从form中取非模型字段,保存完成后返回PDF响应:

from django.http import HttpRequest, HttpResponse
from django.contrib import admin
# 导入刚写的生成函数
from .utils import generate_contract_pdf

class ContractAdmin(admin.ModelAdmin):
    form = ContractForm
    # 原有其他admin配置保留

    def save_model(self, request: HttpRequest, obj: Contract, form: ContractForm, change: bool) -> None:
        if not change and not request.user.is_superuser:
            obj = MythLabsMultiTenancy.assign_organization_to_obj(obj, request.user)
        super().save_model(request, obj, form, change)
        # 把生成PDF需要的表单数据临时挂到request对象上,供后续响应方法使用
        request._contract_pdf_data = {
            'contract_obj': obj,
            'warranty_card_company': form.cleaned_data.get('warranty_card_company', ''),
            'warranty_card_number': form.cleaned_data.get('warranty_card_number', ''),
            'warranty_card_expiration_date': form.cleaned_data.get('warranty_card_expiration_date', ''),
            'warranty_card_auth_code': form.cleaned_data.get('warranty_card_auth_code', 0),
        }

    def response_add(self, request, obj, post_url_continue=None):
        # 新建保存后触发PDF下载
        if hasattr(request, '_contract_pdf_data'):
            pdf_content, file_name = generate_contract_pdf(**request._contract_pdf_data)
            self.message_user(request, "合同创建成功,PDF已开始下载")
            return HttpResponse(
                pdf_content,
                headers={
                    'Content-Type': 'application/pdf',
                    'Content-Disposition': f'attachment; filename="{file_name}"',
                }
            )
        return super().response_add(request, obj, post_url_continue)

    def response_change(self, request, obj):
        # 修改保存后触发PDF下载
        if hasattr(request, '_contract_pdf_data'):
            pdf_content, file_name = generate_contract_pdf(**request._contract_pdf_data)
            self.message_user(request, "合同修改成功,PDF已开始下载")
            return HttpResponse(
                pdf_content,
                headers={
                    'Content-Type': 'application/pdf',
                    'Content-Disposition': f'attachment; filename="{file_name}"',
                }
            )
        return super().response_change(request, obj)
补充说明

如果不需要每次保存都触发下载,可以在admin页面加一个额外的勾选框(比如保存后下载PDF),在save_model里判断勾选状态再决定是否挂PDF生成参数即可,逻辑更灵活。

内容的提问来源于stack exchange,提问作者Santiago Muñoz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 19:48:33