使用Django+WeasyPrint生成的PDF无法打开,求技术支持
Django + WeasyPrint生成PDF无法打开问题排查
问题背景
近期使用Django结合WeasyPrint生成PDF报告,应用已支持文件上传下载、CSV及XLSX生成下载功能,但生成的PDF文件下载后无法正常打开,后端无报错信息。环境配置正常:Docker容器已安装WeasyPrint,requirements.txt指定weasyprint==63.0,无依赖缺失提示。
相关代码实现
模型定义
import uuid from django.db import models class HelpdeskReport(models.Model): class Meta: app_label = "helpdesk" db_table = "helpdesk_report" verbose_name = "Helpdesk Report" verbose_name_plural = "Helpdesk Reports" default_permissions = ["view", "add", "delete"] permissions = [ ("download_helpdeskreport", "Can download Heldpesk Report"), ] def _report_path(instance, filename) -> str: return f"helpdesk/reports/{instance.id}" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=100, unique=True) file = models.FileField(upload_to=_report_path)
服务类代码
import uuid from io import BytesIO from django.core.files import File from django.template.loader import render_to_string from weasyprint import HTML class HeldpeskReportService: def create_report(self) -> HelpdeskReport: report_name = f"helpdesk_report_{uuid.uuid4().hex}.pdf" html_source = render_to_string(template_name="reports/global_report.html") html_report = HTML(string=html_source) pdf_report = html_report.write_pdf() # print("check : ", pdf_report[:5]) # It gives proper content beginning (PDF header) pdf_bytes = BytesIO(pdf_report) pdf_file = File(file=pdf_file, name=report_name) # 此处存在变量引用错误 report = HelpdeskReport(name=report_name, file=pdf_file) report.full_clean() report.save() return report
模板代码
<html> <head> <meta http-equiv="Content-Type" content="text/html" charset="utf-8"> </head> <body> <article> <h2>test report</h2> </article> </body> </html>
视图代码
from django.http import HttpResponse from rest_framework.decorators import api_view from rest_framework.request import Request @api_view(http_method_names=["POST"]) def create_report(request: Request) -> HttpResponse: """ Create a HelpdeskReport. """ report = HelpdeskReportService().create_report() response = HttpResponse(content=report.file) response["Content-Type"] = "application/pdf" response["Content-Disposition"] = f"attachment; filename={report.name}" return response
qpdf检测结果
qpdf helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf - WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf: file is damaged WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (offset 2612): xref not found WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf: Attempting to reconstruct cross-reference table WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 6 0, offset 196): expected endstream WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 6 0, offset 66): attempting to recover stream length WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 6 0, offset 66): recovered stream length: 224 WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 10 0, offset 1560): expected endstream WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 10 0, offset 373): attempting to recover stream length WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 10 0, offset 373): recovered stream length: 2194 WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 11 0, offset 2894): expected endstream WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 11 0, offset 2636): attempting to recover stream length WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 11 0, offset 2636): recovered stream length: 455 WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 16 0, offset 3889): expected endstream WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 16 0, offset 3187): attempting to recover stream length WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 16 0, offset 3187): recovered stream length: 1248 WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 17 0, offset 4628): expected endstream WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 17 0, offset 4567): attempting to recover stream length WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (object 17 0, offset 4567): recovered stream length: 96 WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (xref stream: object 17 0, offset 4628): expected endstream WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (xref stream: object 17 0, offset 4567): attempting to recover stream length WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (xref stream: object 17 0, offset 4567): recovered stream length: 96 WARNING: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf (offset 4567): error decoding stream data for object 17 0: stream inflate: inflate: data: incorrect header check qpdf: helpdesk_report_414b3290fe6b4aae83293694cfa64c25.pdf: error decoding candidate xref stream while recovering damaged file
解决思路与方案
1. 修复服务类中的变量引用错误
服务类中创建File对象时,错误地引用了未定义的pdf_file变量,正确的应该是使用之前生成的pdf_bytes,同时需要重置BytesIO的指针到开头,确保文件内容被完整写入:
# 修正后的服务类代码片段 pdf_bytes = BytesIO(pdf_report) pdf_bytes.seek(0) # 重置指针到文件开头 pdf_file = File(file=pdf_bytes, name=report_name)
2. 修正视图中返回文件的方式
直接将report.file传给HttpResponse可能无法正确读取文件内容,需要打开文件并读取字节流:
# 修正后的视图代码 @api_view(http_method_names=["POST"]) def create_report(request: Request) -> HttpResponse: report = HelpdeskReportService().create_report() with report.file.open('rb') as f: response = HttpResponse(content=f.read(), content_type='application/pdf') response["Content-Disposition"] = f"attachment; filename={report.name}" return response
3. 优化模板的编码设置
模板中的meta标签写法不规范,修改为标准的UTF-8编码声明,避免HTML渲染时出现编码问题:
<meta charset="utf-8"> <!-- 或者 --> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
4. 验证PDF生成的完整性
可以在服务类中临时保存生成的PDF到本地,验证WeasyPrint是否生成了完整的PDF文件:
# 在服务类中添加临时保存代码(用于调试) with open('/tmp/test.pdf', 'wb') as f: f.write(pdf_report)
如果本地保存的PDF可以正常打开,说明问题出在Django的文件存储或返回环节,否则需要检查WeasyPrint的依赖是否完整(比如libpango、libcairo等系统库)。
内容的提问来源于stack exchange,提问作者lbris
相关产品推荐
相关产品推荐

