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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:14:55