Django导出模板为PDF时图片无法显示的问题求助
我之前碰到过一模一样的问题,核心原因是生成PDF的WeasyPrint在请求图片资源时,没有携带当前用户的认证会话信息,导致被Django的权限系统重定向到登录页,最终加载的是登录页面的HTML而非图片文件,所以才会出现那个Pixbuf格式错误。下面是几个亲测可行的解决方案:
方案1:自定义URL Fetcher携带认证Cookie
WeasyPrint允许我们自定义资源请求的处理逻辑,我们可以在这个过程中把当前用户的Cookie(包含session信息)传递过去,让Django识别用户身份,允许访问受保护的media资源。
修改你的PDFViewMixin的gen_pdf方法,添加自定义的url_fetcher:
from weasyprint import HTML, default_url_fetcher # 自定义url_fetcher,携带当前请求的cookie def auth_url_fetcher(url, request): # 复用WeasyPrint的默认请求逻辑,添加Cookie头 headers = {} if request: headers['Cookie'] = request.META.get('HTTP_COOKIE', '') return default_url_fetcher(url, headers=headers) class PDFViewMixin(ExportViewMixin): '''Generic view that render a template into a PDF and return it as response content. ''' content_type = 'application/pdf' def gen_pdf(self, context): template = get_template(self.template_name) request = self.request # 获取当前请求对象 # 渲染模板内容 try: html_content = template.render(Context(context)) except TypeError: # Django>=1.11 html_content = template.render(context) # 使用自定义的url_fetcher,把当前请求传进去 html_doc = HTML( string=html_content, base_url=context.get('base_url'), url_fetcher=lambda url: auth_url_fetcher(url, request) ) return html_doc.render() # 其他原有方法保持不变
这个方案的关键是让WeasyPrint的资源请求带上用户的登录Cookie,这样Django就不会把它当成未登录用户重定向到登录页了。
方案2:将图片转为Base64嵌入HTML
如果自定义URL Fetcher还是有问题,或者你想彻底避免外部资源请求的麻烦,可以直接把图片内容编码成Base64格式,嵌入到HTML的<img>标签里。
首先在你的PDF视图(继承自PDFViewMixin和DetailView的那个视图)里,把图片转成Base64并传到模板:
import base64 class YourPDFDetailView(PDFViewMixin, DetailView): # 你的视图配置:model、template_name等 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) obj = self.get_object() if obj.image: # 读取图片文件并编码为base64 with obj.image.open('rb') as img_file: img_base64 = base64.b64encode(img_file.read()).decode('utf-8') # 获取图片的MIME类型(比如image/png) mime_type = obj.image.file.content_type context['image_base64'] = f'data:{mime_type};base64,{img_base64}' else: context['image_base64'] = None return context
然后修改模板里的图片渲染代码:
{% if image_base64 %} <img src="{{ image_base64 }}" alt="Object Image"> {% endif %}
这种方法完全不需要额外的HTTP请求,图片内容直接包含在HTML里,自然不会遇到权限拦截的问题,适合导出带私有图片的PDF场景。唯一的小缺点是如果图片很大,会让HTML内容变大一点,不过一般影响不大。
方案3:让Web服务器直接托管Media资源(仅适用于公开图片)
如果你的media文件不需要用户登录就能访问,可以跳过Django,让Nginx、Apache这类Web服务器直接托管media目录。这样WeasyPrint请求图片时直接从Web服务器获取,不会经过Django的权限系统。
以Nginx为例,在配置文件里添加这段:
location /media/ { alias /path/to/your/project/media/; # 替换成你实际的media目录路径 expires 30d; # 设置缓存过期时间 }
记得要确保Django的settings.py里DEBUG=False时,MEDIA_URL和MEDIA_ROOT配置正确,并且Web服务器有访问media目录的权限。
为什么之前的尝试没生效?
你提到试过自定义photo_url_fetcher和绝对URI,大概率是因为没正确传递用户的认证Cookie,或者绝对URI还是指向了Django的路由,被权限系统拦截了。上面的方案1明确传递了Cookie信息,方案2直接绕过了HTTP请求,应该能解决你的问题。
内容的提问来源于stack exchange,提问作者gccallie

