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

如何使用FastAPI同时返回PDF文件与Jinja2 TemplateResponse?

问题解答

HTTP一次请求只能返回一个响应,所以没法直接同时返回FileResponse和Jinja2 TemplateResponse。不过可以用两种方式实现类似需求,既给用户展示页面,又提供PDF下载:


先给你的代码加中文注释

def generate_report(request: Request, start_date: date = Form(...), end_date: Optional[date] = Form(None)):
    # 将前端传来的date对象转为datetime对象
    start_date = datetime(start_date.year, start_date.month, start_date.day)
    end_date = datetime(end_date.year, end_date.month, end_date.day)
    # 按日期筛选考勤数据
    attendance = filter_by_date(zk, user_list, start_date, end_date)
    # 把考勤数据转成字典格式方便处理
    users_history = attendance_to_dict(attendance)
    # 统计用户工作日天数
    worked = count_days(users_history, 0)
    # 生成PDF文档
    pdf = create_pdf(users_history, worked, user_list, start_date, end_date)
    # 临时保存PDF到本地
    pdf_temp = "attendance.pdf"
    pdf.output(pdf_temp)
    # 用户下载时显示的文件名
    name = "report.pdf"

    # 返回PDF文件让用户下载
    return FileResponse(pdf_temp, media_type="application/pdf", filename=name)

方案1:返回页面后自动触发PDF下载

先渲染一个结果页面,然后用页面里的JavaScript自动发起请求下载PDF,用户既看到页面,又能自动拿到文件。

代码修改示例

先拆分出专门返回PDF的接口,原接口返回模板页面:

from fastapi.templating import Jinja2Templates

# 初始化模板引擎,指定模板文件所在目录
templates = Jinja2Templates(directory="templates")

# 专门用来返回PDF的接口
def get_report_pdf(start_date: date, end_date: Optional[date] = None):
    start_date = datetime(start_date.year, start_date.month, start_date.day)
    end_date = datetime(end_date.year, end_date.month, end_date.day) if end_date else datetime.now()
    attendance = filter_by_date(zk, user_list, start_date, end_date)
    users_history = attendance_to_dict(attendance)
    worked = count_days(users_history, 0)
    pdf = create_pdf(users_history, worked, user_list, start_date, end_date)
    pdf_temp = "attendance.pdf"
    pdf.output(pdf_temp)
    return FileResponse(pdf_temp, media_type="application/pdf", filename="report.pdf")

# 原接口返回模板页面,页面自动触发下载
def generate_report(request: Request, start_date: date = Form(...), end_date: Optional[date] = Form(None)):
    # 把日期参数传给模板,方便JS使用
    context = {
        "request": request,
        "start_date": start_date.isoformat(),
        "end_date": end_date.isoformat() if end_date else ""
    }
    return templates.TemplateResponse("report_result.html", context)

然后在templates/report_result.html里加JS逻辑:

<!DOCTYPE html>
<html>
<head>
    <title>报告生成完成</title>
</head>
<body>
    <h1>报告已生成,正在自动下载...</h1>
    <p>如果下载没自动开始,<a id="manual-download">点这里手动下载</a></p>

    <script>
        // 从模板获取日期参数
        const startDate = "{{ start_date }}";
        const endDate = "{{ end_date }}";
        
        // 构建PDF请求的URL
        let downloadUrl = `/get-report-pdf?start_date=${startDate}`;
        if (endDate) {
            downloadUrl += `&end_date=${endDate}`;
        }
        
        // 创建隐藏的a标签触发下载
        const downloadLink = document.createElement('a');
        downloadLink.href = downloadUrl;
        downloadLink.style.display = 'none';
        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
        
        // 给手动下载链接赋值
        document.getElementById('manual-download').href = downloadUrl;
    </script>
</body>
</html>

方案2:返回页面,让用户手动点击下载按钮

直接渲染一个包含下载按钮的页面,用户看到页面后,自己点击按钮下载PDF,逻辑更简单。

代码修改示例

def generate_report(request: Request, start_date: date = Form(...), end_date: Optional[date] = Form(None)):
    # 先生成PDF文件(和你原来的逻辑一致)
    start_date = datetime(start_date.year, start_date.month, start_date.day)
    end_date = datetime(end_date.year, end_date.month, end_date.day)
    attendance = filter_by_date(zk, user_list, start_date, end_date)
    users_history = attendance_to_dict(attendance)
    worked = count_days(users_history, 0)
    pdf = create_pdf(users_history, worked, user_list, start_date, end_date)
    pdf_temp = "attendance.pdf"
    pdf.output(pdf_temp)
    
    # 返回带下载按钮的模板页面
    context = {
        "request": request,
        "pdf_name": "report.pdf"
    }
    return templates.TemplateResponse("report_download.html", context)

对应的templates/report_download.html:

<!DOCTYPE html>
<html>
<head>
    <title>报告生成完成</title>
</head>
<body>
    <h1>报告已准备好</h1>
    <a href="/download-pdf" download="{{ pdf_name }}">点击下载PDF报告</a>
</body>
</html>

最后还要加一个专门处理下载的接口:

def download_pdf():
    return FileResponse("attendance.pdf", media_type="application/pdf", filename="report.pdf")

小提醒

生成的临时PDF文件记得定期清理,比如用Python的tempfile模块创建临时文件,避免占满磁盘空间。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 01:02:37