Skip to content

Commit 50ac35a

Browse files
committed
WIP: exporting PDF with weasyprint
1 parent 5948839 commit 50ac35a

File tree

4 files changed

+130
-12
lines changed

4 files changed

+130
-12
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,4 @@ dmypy.json
137137

138138
# temporary
139139
notebooks/tmp
140+
tuttle_tests/tmp

tuttle/rendering.py

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@
1111
import pandas
1212
from loguru import logger
1313

14-
# pdfkit needs wkhtmltopdf to be installed
15-
if getattr(sys, "frozen", False):
16-
os.environ["PATH"] = sys._MEIPASS + os.pathsep + os.environ["PATH"]
17-
import pdfkit
18-
1914

2015
from .model import User, Invoice, Timesheet, Project
2116
from .view import Timeline
@@ -41,6 +36,27 @@ def convert_html_to_pdf(
4136
dest_dir (_type_): _description_
4237
"""
4338
logger.info(f"converting html to pdf: {in_path} -> {out_path}")
39+
_convert_html_to_pdf_with_weasyprint(
40+
in_path=in_path,
41+
out_path=out_path,
42+
css_paths=css_paths,
43+
)
44+
45+
46+
def _convert_html_to_pdf_with_pdfkit(
47+
in_path,
48+
out_path,
49+
css_paths=[],
50+
):
51+
"""Implementation of convert_html_to_pdf using pdfkit."""
52+
# pdfkit needs wkhtmltopdf to be installed
53+
if getattr(sys, "frozen", False):
54+
os.environ["PATH"] = sys._MEIPASS + os.pathsep + os.environ["PATH"]
55+
try:
56+
import pdfkit
57+
except ImportError:
58+
logger.error("Please install pdfkit and wkhtmltopdf")
59+
raise
4460
try:
4561
pdfkit.from_file(input=in_path, output_path=out_path, css=css_paths)
4662
except OSError as ex:
@@ -49,6 +65,53 @@ def convert_html_to_pdf(
4965
pass
5066

5167

68+
def _convert_html_to_pdf_with_weasyprint(
69+
in_path,
70+
out_path,
71+
css_paths=[],
72+
):
73+
"""Implementation of convert_html_to_pdf using weasyprint."""
74+
try:
75+
import weasyprint
76+
except ImportError:
77+
logger.error("Please install weasyprint")
78+
raise
79+
css_paths = [Path(css_path).resolve() for css_path in css_paths]
80+
logger.debug(f"css_paths: {css_paths}")
81+
(
82+
weasyprint.HTML(in_path).write_pdf(
83+
out_path,
84+
stylesheets=css_paths,
85+
)
86+
)
87+
88+
89+
def _convert_html_to_pdf_with_QT(
90+
in_path,
91+
out_path,
92+
css_paths=[],
93+
):
94+
"""Implementation of convert_html_to_pdf using QT."""
95+
try:
96+
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
97+
except ImportError:
98+
logger.error("Please install PyQt5")
99+
raise
100+
app = QtWidgets.QApplication(sys.argv)
101+
loader = QtWebEngineWidgets.QWebEngineView()
102+
loader.setZoomFactor(1)
103+
loader.page().pdfPrintingFinished.connect(lambda *args: print("finished:", args))
104+
loader.load(QtCore.QUrl(in_path))
105+
106+
def emit_pdf(finished):
107+
loader.show()
108+
loader.page().printToPdf(out_path)
109+
110+
loader.loadFinished.connect(emit_pdf)
111+
112+
app.exec()
113+
114+
52115
def render_invoice(
53116
user: User,
54117
invoice: Invoice,

tuttle_tests/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ def demo_user():
5454
@pytest.fixture
5555
def demo_clients():
5656
central_services = Client(
57-
first_name="Central",
58-
last_name="Services",
57+
name="Central Services",
5958
invoicing_contact=Contact(
6059
first_name="Central",
6160
last_name="Services",

tuttle_tests/test_invoice.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Tests for the invoice module."""
22

33
import datetime
4+
from pathlib import Path
45

5-
from tuttle import invoicing, timetracking
6+
from tuttle import invoicing, timetracking, rendering
67
from tuttle.model import Invoice, InvoiceItem
78

89

@@ -65,9 +66,63 @@ def test_generate_invoice(
6566
assert invoice.total > 0
6667

6768

68-
def test_render_invoice_to_html():
69-
pass
69+
def test_render_invoice_to_html(
70+
demo_user,
71+
demo_projects,
72+
demo_calendar_timetracking,
73+
):
74+
for project in demo_projects:
75+
timesheets = []
76+
for period in ["January 2022", "February 2022"]:
77+
timesheet = timetracking.generate_timesheet(
78+
source=demo_calendar_timetracking,
79+
project=project,
80+
period_start=period,
81+
item_description=project.title,
82+
)
83+
if not timesheet.empty:
84+
timesheets.append(timesheet)
85+
invoice = invoicing.generate_invoice(
86+
timesheets=timesheets,
87+
contract=project.contract,
88+
date=datetime.date.today(),
89+
)
90+
# RENDERING
91+
rendering.render_invoice(
92+
user=demo_user,
93+
invoice=invoice,
94+
style="anvil",
95+
document_format="html",
96+
out_dir=Path("tuttle_tests/tmp"),
97+
)
7098

7199

72-
def test_render_invoice_to_pdf():
73-
pass
100+
def test_render_invoice_to_pdf(
101+
demo_user,
102+
demo_projects,
103+
demo_calendar_timetracking,
104+
):
105+
for project in demo_projects:
106+
timesheets = []
107+
for period in ["January 2022", "February 2022"]:
108+
timesheet = timetracking.generate_timesheet(
109+
source=demo_calendar_timetracking,
110+
project=project,
111+
period_start=period,
112+
item_description=project.title,
113+
)
114+
if not timesheet.empty:
115+
timesheets.append(timesheet)
116+
invoice = invoicing.generate_invoice(
117+
timesheets=timesheets,
118+
contract=project.contract,
119+
date=datetime.date.today(),
120+
)
121+
# RENDERING
122+
rendering.render_invoice(
123+
user=demo_user,
124+
invoice=invoice,
125+
style="anvil",
126+
document_format="pdf",
127+
out_dir=Path("tuttle_tests/tmp"),
128+
)

0 commit comments

Comments
 (0)