
WeasyPrint 渲染 PDF 时无法显示图片,根本原因在于其不支持 HTTP 协议路径(如 /static/upload/xxx.png),且生产环境未正确配置静态资源路径与 base_url,导致图片路径解析失败。本文提供从开发到部署的全流程修复方案。
weasyprint 渲染 pdf 时无法显示图片,根本原因在于其不支持 http 协议路径(如 `/static/upload/xxx.png`),且生产环境未正确配置静态资源路径与 `base_url`,导致图片路径解析失败。本文提供从开发到部署的全流程修复方案。
在 Django + WeasyPrint 的 PDF 生成流程中,你遇到的「本地可渲染、线上不显示图片」问题极为典型——它并非代码逻辑错误,而是 WeasyPrint 的设计约束与生产环境部署细节共同导致的路径解析失效。WeasyPrint 是一个纯 Python 渲染引擎,它不发起 HTTP 请求,也不理解 Django 的 {% static %} 模板标签或 url_for() 语义。当你在 HTML 中写入 <img src="/static/upload/logo.png">,WeasyPrint 会尝试以 file:// 协议解析该相对路径(而非通过 Web 服务器请求),而 PythonAnywhere 等托管平台的文件系统结构与开发环境不同,/static/ 路径在服务端并不存在对应的真实文件系统位置,因此图片加载必然失败。
✅ 正确做法:使用绝对文件系统路径 + base_url
WeasyPrint 唯一可靠支持的图片加载方式是 file:/// 绝对路径(注意三个斜杠)。你需要将 Django 的静态文件路径转换为服务器上的真实绝对路径,并通过 base_url 显式告知 WeasyPrint 静态资源根目录:
# views.py
import os
from django.conf import settings
from weasyprint import HTML
def pdf(request, id):
syllabus = get_object_or_404(Syllabus, user_id=request.user, id=id)
syllabus_template = get_object_or_404(
Syllabus_Template, user_id=request.user, id=syllabus.syllabus_template_id.id
)
wmsu_logo = Logo.objects.get(syllabus_template_id=syllabus_template, name='wmsu_logo')
course_logo = Logo.objects.get(syllabus_template_id=syllabus_template, name='course_logo')
iso_logo = Logo.objects.get(syllabus_template_id=syllabus_template, name='iso_logo')
# ✅ 关键:构造真实文件系统路径(非 URL!)
static_root = os.path.join(settings.BASE_DIR, 'static') # 或 settings.STATIC_ROOT(若已 collectstatic)
# 假设 Logo.img_name 是 'wmsu-logo.png',且实际存于 static/upload/
wmsu_path = os.path.join(static_root, 'upload', wmsu_logo.img_name)
course_path = os.path.join(static_root, 'upload', course_logo.img_name)
iso_path = os.path.join(static_root, 'upload', iso_logo.img_name)
template = loader.get_template('PDF_template/template.html')
html_string = template.render({
'wmsu_logo_path': wmsu_path,
'course_logo_path': course_path,
'iso_logo_path': iso_path,
})
# ✅ 关键:base_url 必须指向 static 根目录(供 WeasyPrint 解析相对路径)
# 注意:这里传的是文件系统路径,不是 URL!
pdf = HTML(
string=html_string,
base_url=static_root, # ← WeasyPrint 将以此为基准解析所有相对路径
encoding='utf8'
).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = f'inline; filename="Syllabus_{id}_{datetime.now().strftime("%Y%m%d")}.pdf"'
return response
同时,修改你的 HTML 模板,直接使用 file:// 协议的绝对路径(推荐)或依赖 base_url 的相对路径:
<!-- PDF_template/template.html -->
<div class="cont">
<!-- 方式1:显式 file:// 绝对路径(最稳妥) -->
<img class="logo1" src="file://%7B%7B%20wmsu_logo_path%20%7D%7D" alt="WMSU Logo"><div>
<p>Republic of the Philippines</p>
<p>Western Mindanao State University</p>
<p>{{ syllabus.college }}</p>
<p class="title">DEPARTMENT OF {{ syllabus.department }}</p>
</div>
<img class="logo2" src="file://%7B%7B%20course_logo_path%20%7D%7D" alt="Course Logo"><img class="logo3" src="file://%7B%7B%20iso_logo_path%20%7D%7D" alt="ISO Logo">
</div>
⚠️ 注意事项:
collectstatic必须执行:在 PythonAnywhere 部署前,务必运行python manage.py collectstatic --noinput,确保所有静态文件(含upload/下的图片)已复制到STATIC_ROOT目录。检查settings.py中STATIC_ROOT是否明确设置(如/home/yourusername/mysite/staticfiles),并在 WeasyPrint 中使用该路径作为base_url。- 路径权限与存在性:在 PythonAnywhere Bash 控制台中,用
ls -l /home/yourusername/mysite/staticfiles/upload/确认图片文件真实存在且可读(-rw-r--r--权限即可)。- 避免
position: fixed:页眉/页脚请改用 CSS Paged Media(@page { @top { content: ... } }),否则 WeasyPrint 分页渲染异常。- 中文与字体:若 PDF 中文乱码,请在 HTML
<style></style>中显式声明@font-face并使用font_config加载系统中文字体(如 Noto Sans CJK),详见 WeasyPrint 官方文档。
✅ 验证与调试技巧
-
打印调试路径:在 view 中临时添加
print("WMSU path:", wmsu_path),并在 PythonAnywhere 日志中确认路径拼接正确; -
手动测试文件可读性:在 Bash 中执行
python -c "with open('/full/path/to/logo.png', 'rb') as f: print('OK')"; -
简化测试:先用一张硬编码的本地图片(如
file:///home/.../test.png)验证基础流程是否通,再接入动态路径。
遵循以上方案,即可彻底解决 WeasyPrint 在生产环境中无法加载上传图片的问题——核心只有一条:让每一张图片的 src 最终指向一个 WeasyPrint 能用 open() 直接读取的、绝对、可访问的文件系统路径。










