diff --git a/Aptfile b/Aptfile index 68c58524..06a52a7d 100644 --- a/Aptfile +++ b/Aptfile @@ -1,3 +1,2 @@ libjpeg62 libc6 -https://mirrors.edge.kernel.org/ubuntu/pool/main/o/openssl1.1/libssl1.1_1.1.1f-1ubuntu2_amd64.deb diff --git a/requirements.txt b/requirements.txt index 98f436e1..311c61c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ MarkupSafe==2.1.2 phonenumbers==8.13.6 pydantic[email]==1.9.2 python-multipart==0.0.6 -python-pdf==0.39 +weasyprint==65.1 requests==2.28.2 starlette==0.14.2 sentry-sdk==1.16.0 diff --git a/src/worker/email.py b/src/worker/email.py index 432a6f70..29e51267 100644 --- a/src/worker/email.py +++ b/src/worker/email.py @@ -14,8 +14,8 @@ from httpx import ConnectError, ReadTimeout from itertools import chain from pathlib import Path -from pydf import generate_pdf from typing import List, Optional +from weasyprint import HTML from src.ext import ApiError from src.render import EmailInfo, MessageDef, render_email @@ -36,6 +36,55 @@ email_retrying = [5, 10, 60, 600, 1800, 3600, 12 * 3600] +# Add a simple url_fetcher for debugging +def logging_url_fetcher(url, timeout=10, ssl_context=None): + from weasyprint.urls import default_url_fetcher + main_logger.info(f"WeasyPrint is attempting to fetch URL: {url}") + try: + result = default_url_fetcher(url, timeout=timeout, ssl_context=ssl_context) + main_logger.info(f"Successfully fetched {url}, content type: {result.get('mime_type')}") + return result + except Exception as e: + main_logger.error(f"Failed to fetch {url}: {e}", exc_info=True) + # Propagate the error to see it in PDF generation or logs + raise + + +def generate_pdf_from_html( + html: str, + page_size: str = 'A4', + zoom: str = '1.0', + margin_left: str = '10mm', + margin_right: str = '10mm', + base_url_for_html: Optional[str] = None +) -> bytes: + from weasyprint import CSS + + page_css = f""" + @page {{ + size: {page_size}; + margin-left: {margin_left}; + margin-right: {margin_right}; + }} + body {{ + zoom: {zoom}; + }} + """ + + html_doc = HTML( + string=html, + base_url=base_url_for_html, + url_fetcher=logging_url_fetcher + ) + css_doc = CSS(string=page_css) + + pdf_bytes = html_doc.write_pdf( + stylesheets=[css_doc], + presentational_hints=True, + ) + return pdf_bytes + + def utcnow(): return datetime.utcnow().replace(tzinfo=timezone.utc) @@ -201,13 +250,22 @@ async def _render_email(self, context, headers) -> Optional[EmailInfo]: await self._store_email_failed(MessageStatus.render_failed, f'Error rendering email: {e}') async def _generate_base64_pdf(self, pdf_attachments): - kwargs = dict(page_size='A4', zoom='1.25', margin_left='8mm', margin_right='8mm') for a in pdf_attachments: if a.html: try: - pdf_content = generate_pdf(a.html, **kwargs) - except RuntimeError as e: - main_logger.warning('error generating pdf, data: %s', e) + # Assuming URLs in a.html (like {{ bootstrap_url }}) are absolute, + # base_url_for_html can remain None. + # If they could be relative, a proper base_url would be needed here. + pdf_content = generate_pdf_from_html( + a.html, + page_size='A4', + zoom='1.25', + margin_left='8mm', + margin_right='8mm', + base_url_for_html=None + ) + except Exception as e: + main_logger.warning('error generating pdf, data: %s', e, exc_info=True) else: yield dict(type='application/pdf', name=a.name, content=base64.b64encode(pdf_content).decode()) diff --git a/tests/test_email.py b/tests/test_email.py index 7b0de2bc..e0d86699 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -475,26 +475,26 @@ def test_invalid_mustache_body(send_email, sync_db: SyncDb): assert m['body'] == 'Error rendering email: unclosed tag at line 1' -# def test_send_with_pdf(send_email, tmpdir, sync_db: SyncDb): -# message_id = send_email( -# recipients=[ -# { -# 'address': 'foobar@testing.com', -# 'pdf_attachments': [ -# {'name': 'testing.pdf', 'html': '