我正在尝试实现xhtml2pdf,并且在我的html文件中,它引用了一个静态文件。我的项目中的文件位于
jobs/static/jobs/style.css
。当使用 xhtml2pdf 在他们的文档中提供的代码时,当它到达 result = finders.find(uri)
并说 django.core.exceptions.SuspiciousFileOperation: The joined path (/static/jobs/css/style.css) is located outside of the base path component (/opt/project/myproject/static)
时,我收到错误。如果我导航到该文件应位于的 url,http://127.0.0.1:8000/static/jobs/css/style.css
,我会看到 css 文件的代码,因此它通常位于正确的位置。
这个项目正在 docker 容器中提供服务,并且 /opt 中没有存储任何内容,所以我不知道为什么它会在 (/opt/project/myproject/static) 中出现这个“基本路径组件”。我不知道这条路是从哪里来的。我搜索了整个项目,在任何文件中都没有包含
opt
或 project
的路径。该项目存储在容器中的/app中。
这是我从他们的网站获得的代码:
from xhtml2pdf import pisa
from django.template.loader import get_template
from django.http import HttpResponse
from django.conf import settings
import os
import io
from django.contrib.staticfiles import finders
def link_callback(uri, rel):
"""
Convert HTML URIs to absolute system paths so xhtml2pdf can access those
resources
"""
result = finders.find(uri)
if result:
if not isinstance(result, (list, tuple)):
result = [result]
result = list(os.path.realpath(path) for path in result)
path = result[0]
else:
sUrl = settings.STATIC_URL # Typically /static/
sRoot = settings.STATIC_ROOT # Typically /home/userX/project_static/
mUrl = settings.MEDIA_URL # Typically /media/
mRoot = settings.MEDIA_ROOT # Typically /home/userX/project_static/media/
if uri.startswith(mUrl):
path = os.path.join(mRoot, uri.replace(mUrl, ""))
elif uri.startswith(sUrl):
path = os.path.join(sRoot, uri.replace(sUrl, ""))
else:
return uri
# make sure that file exists
if not os.path.isfile(path):
raise Exception(
'media URI must start with %s or %s' % (sUrl, mUrl)
)
return path
def render_to_pdf(template_src, context_dict, bytes=False):
template = get_template(template_src)
context = context_dict
if bytes:
response = io.BytesIO()
else:
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="contract.pdf"'
html = template.render(context)
pisa_status = pisa.CreatePDF(html, dest=response, link_callback=link_callback)
# if error then show some funny view
if pisa_status.err:
return HttpResponse('We had some errors <pre>' + html + '</pre>')
return response
这是我的settings.py的相关部分:
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent # Resolves to /app
APPS_DIR = ROOT_DIR / "myproject" # Resolves to /app/myproject
STATIC_ROOT = str(ROOT_DIR / "staticfiles") # Resolves to /app/staticfiles
STATIC_URL = "/static/"
STATICFILES_DIRS = [
str(APPS_DIR / "static"),
str(ROOT_DIR / "node_modules"),
] # Resolves to ['/app/myproject/static', '/app/node_modules']
STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
]
MEDIA_ROOT = str(APPS_DIR / "media") # Resolves to /app/media/myproject/media
MEDIA_URL = "/media/"
我的 html 模板中存在问题的行是:
<link rel="stylesheet" href="{% static 'jobs/css/style.css' %}">
我有另一个项目,我已经使用它大约一年了,使用相同的代码,并且它一直在运行,没有出现此错误。另一个项目不在 docker 容器内,但我不明白为什么这会产生影响。
我还应该看哪里?
将
xhtml2pdf
与 Django 集成时,尤其是在 Docker 中,由于 Django 处理静态文件的方式,您可能会遇到文件路径问题。当路径被评估为位于预期基目录之外时,通常会出现 SuspiciousFileOperation
错误。
这是一个综合解决方案,分解了可能的原因并提供了建议的修复方法:
静态文件处理:
xhtml2pdf
需要绝对系统路径来访问静态和媒体资源。在 Docker 容器中运行时,容器内的文件路径可能与主机上的文件路径不同。确保正确设置静态设置以反映 Docker 容器内的路径。
增强
link_callback
功能:
修改
link_callback
函数以适应绝对路径和相对路径。理想情况下,该功能应该:
这是使用设置方法优化的
link_callback
功能:
import os
from django.conf import settings
def link_callback(uri, rel):
if uri.startswith(settings.MEDIA_URL):
path = os.path.join(settings.MEDIA_ROOT, uri.replace(settings.MEDIA_URL, ""))
elif uri.startswith(settings.STATIC_URL):
path = os.path.join(settings.STATIC_ROOT, uri.replace(settings.STATIC_URL, ""))
else:
return uri
# Ensure the file exists
if not os.path.isfile(path):
raise Exception(f"Path does not exist: {path}")
return path
如果您使用
finders
,您将尝试根据文件的 URL 获取文件的实际路径。如果由于 Docker 容器内的文件夹结构或卷映射不同而返回的路径不是您期望的路径,则可能会出现问题。
from django.contrib.staticfiles import finders
path = finders.find(uri)
鉴于您当前的情况以及您面临的
SuspiciousFileOperation
错误,我建议坚持使用 settings
方法。它更直接,对项目结构的假设更少。
但是,如果您更喜欢使用
finders
,请确保您了解它如何解析路径,并确保解析的路径与 Docker 容器内的实际文件结构一致。
Docker 卷映射:
确保您的主机和 Docker 容器之间的卷映射配置正确。仔细检查 Docker Compose 或 Docker 命令以验证应用程序目录(在您的情况下为
/app
)是否已准确映射。
收集静态文件:
如果 Django 设置中的
DEBUG
设置为 False
,请确保运行 collectstatic
以收集 STATIC_ROOT
位置中的所有静态文件:
python manage.py collectstatic
此步骤确保所有静态文件在指定的
STATIC_ROOT
目录中可用,允许 xhtml2pdf
访问它们。
文件权限:
有时,这可能是权限问题。确保在 Docker 容器内运行 Django 应用程序的用户具有访问和读取静态文件和媒体文件的必要权限。
通过结合这些建议,您应该能够在 Docker 容器内运行的 Django 项目中使用
xhtml2pdf
成功生成 PDF。