Weasyprint не удалось загрузить изображение по url: Имя или сервис неизвестны

Я работаю с weasyprint после перехода с xhtml2pdf, и я обнаружил некоторую проблему с получением статических файлов. Я получаю следующую ошибку:

2021-12-03 14:45:50,198 [ERROR] Failed to load image at "http://api.dashboard.localhost:8000/static/logos/logo.png" (URLError: <urlopen error [Errno -2] Name or service not known>)

но когда я обращаюсь к тому же URL, к которому не смог обратиться weasyprint, ни в браузере, ни в curl, я могу просмотреть/доступ к файлу.

Вот мой код:

from io import BytesIO
import mimetypes
from pathlib import Path
from urllib.parse import urlparse
import logging

from django.conf import settings
from django.contrib.staticfiles.finders import find
from django.core.files.storage import default_storage
from django.urls import get_script_prefix
from django.template.loader import render_to_string
import weasyprint
from weasyprint import HTML


logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("debug.log"),
        logging.StreamHandler()
    ]
)

# https://github.com/fdemmer/django-weasyprint/blob/main/django_weasyprint/utils.py
def url_fetcher(url, *args, **kwargs):
    # load file:// paths directly from disk
    if url.startswith('file:'):
        mime_type, encoding = mimetypes.guess_type(url)
        url_path = urlparse(url).path
        data = {
            'mime_type': mime_type,
            'encoding': encoding,
            'filename': Path(url_path).name,
        }

        default_media_url = settings.MEDIA_URL in ('', get_script_prefix())
        if not default_media_url and url_path.startswith(settings.MEDIA_URL):
            media_root = settings.MEDIA_ROOT
            if isinstance(settings.MEDIA_ROOT, Path):
                media_root = f'{settings.MEDIA_ROOT}/'
            path = url_path.replace(settings.MEDIA_URL, media_root, 1)
            data['file_obj'] = default_storage.open(path)
            return data

        elif settings.STATIC_URL and url_path.startswith(settings.STATIC_URL):
            path = url_path.replace(settings.STATIC_URL, '', 1)
            data['file_obj'] = open(find(path), 'rb')
            return data

    # fall back to weasyprint default fetcher
    return weasyprint.default_url_fetcher(url, *args, **kwargs)


def render_template_to_pdf(template_path, request, context):
    results = BytesIO()
    template_string = render_to_string(
        template_name=template_path,
        context=context,
    )
    # create the pdf report
    HTML(string=template_string, base_url=request.build_absolute_uri("/"), url_fetcher=url_fetcher).write_pdf(results)
    return results.getbuffer()

Приведенный выше код генерирует pdf, но без изображений, поскольку вышеупомянутая ошибка продолжает появляться в моих журналах.

Мои настройки для медиа/статических файлов:

DEFAULT_FILE_STORAGE = "utils.storages.CustomFileSystemStorage"
STATIC_URL = "/static/"
STATIC_ROOT = os.path.realpath(env.str("STATIC_FILES_ROOT", default=os.path.join(BASE_DIR, "staticfiles") + "/"))
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.realpath(env.str("MEDIA_FILES_ROOT", default=os.path.join(BASE_DIR, "mediafiles") + "/"))
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]  

В моем шаблоне:

{% load static %}
 
<div style="float: right;">
  <img src="{% static 'logos/logo.jpg' %}" alt="logo" width="140" height="40"/>
</div>

Я запускаю это в docker, но я думаю, что это может быть не актуально, поскольку я могу получить доступ к файлам вне приложения (браузер/ curl), но не с помощью weasyprint.

Я проверял ответы на stackoverflow/github/etc, но не смог найти ничего, объясняющего, почему это происходит или как это обойти, к сожалению. Любая информация о том, почему это происходит, будет очень признательна!

Докер не является проблемой, потому что я также получаю похожую ошибку при использовании статических данных тем же способом, что и вы, без докера. Я получаю ошибку, показанную ниже:

[weasyprint:137] ERROR: Относительная ссылка URI без базового URI:

Итак, я использовал urlsplit для получения url моего приложения и передал его в шаблон, чтобы я мог использовать полный url.

from django.utils.six.moves.urllib.parse import urlsplit

def test(request):
    scheme = urlsplit(request.build_absolute_uri(None))
    context = { 
        'host_url': f"{scheme.scheme}://{scheme.netloc}"
    }
    return render(request, 'pdf.html', context)

шаблон

<img src="{{ host_url }}/logos/logo.jpg" alt="" />

Вместо передачи host_url вы можете напрямую использовать ваш домен, но использование host_url делает приложение динамичным, и вам не нужно менять домен, когда вы находитесь в другом домене или используете локальный сервер.

Я не уверен почему, но установка base_url на "." решает проблему, и weasyprint теперь может разрешать как локальные, так и внешние статические файлы.

Изменение вступает в силу в:

HTML(string=template_string, base_url=".", url_fetcher=url_fetcher).write_pdf(results)

На это у меня ушел целый день, и я изучил исходный код как weasyprint, так и django-weasyprint, прежде чем попытаться сделать ".". Я надеюсь, что это сэкономит время тем, кто столкнется с такой же проблемой в будущем.

Вернуться на верх