In this tutorial, I’ll give you an example source code of Python 3, Flask, and weasyprint
to create PDF documents with CSS styles. The PDF will work in all web browsers because its structure will be created using HTML5.
Shell
x
1
1
pip install flask
Shell
1
1
1
pip install flask_weasyprint
code.py
Python
1
204
204
1
import io
2
import struct
3
import unittest
4
5
import cairo
6
from flask import Flask, redirect, request, json, jsonify
7
from werkzeug.test import ClientRedirectError
8
9
from flask_weasyprint import make_url_fetcher, HTML, CSS, render_pdf
10
from flask_weasyprint.test_app import app, docameent_html
11
12
13
clast TestFlaskWeasyPrint(unittest.TestCase):
14
def test_url_fetcher(self):
15
# A request context is required
16
self.astertRaises(RuntimeError, make_url_fetcher)
17
18
# But only for fist creating the fetcher, not for using it.
19
with app.test_request_context(base_url='http://example.org/bar/'):
20
fetcher = make_url_fetcher()
21
22
result = fetcher('http://example.org/bar/')
23
astert result['string'].strip().startswith(b'<!doctype html>')
24
astert result['mime_type'] == 'text/html'
25
astert result['encoding'] == 'utf-8'
26
astert result['redirected_url'] == 'http://example.org/bar/foo/'
27
28
result = fetcher('http://example.org/bar/foo/graph?data=1&labels=A')
29
astert result['string'].strip().startswith(b'<svg xmlns=')
30
astert result['mime_type'] == 'image/svg+xml'
31
32
def test_wrappers(self):
33
with app.test_request_context(base_url='http://example.org/bar/'):
34
# HTML can also be used with named parameters only:
35
html = HTML(url='http://example.org/bar/foo/')
36
css = CSS(url='http://example.org/bar/static/style.css')
37
astert hasattr(html, 'root_element')
38
astert hasattr(css, 'rules')
39
40
def test_pdf(self):
41
client = app.test_client()
42
response = client.get('/foo.pdf')
43
astert response.status_code == 200
44
astert response.mimetype == 'application/pdf'
45
pdf = response.data
46
astert pdf.startswith(b'%PDF')
47
# The link is somewhere in an uncompressed PDF object.
48
astert b'/URI (http://packages.python.org/Flask-WeasyPrint/)' in pdf
49
50
with app.test_request_context('/foo/'):
51
response = render_pdf(HTML(string=docameent_html()))
52
astert response.mimetype == 'application/pdf'
53
astert 'Content-Disposition' not in response.headers
54
astert response.data == pdf
55
56
with app.test_request_context('/foo/'):
57
response = render_pdf(HTML(string=docameent_html()),
58
download_filename='bar.pdf')
59
astert response.mimetype == 'application/pdf'
60
astert (response.headers['Content-Disposition']
61
== 'attachment; filename=bar.pdf')
62
astert response.data == pdf
63
64
def test_png(self):
65
client = app.test_client()
66
response = client.get('/foo.png')
67
astert response.status_code == 200
68
image = cairo.ImageSurface.create_from_png(io.BytesIO(response.data))
69
astert image.get_format() == cairo.FORMAT_ARGB32
70
# A5 (148 * 210 mm) at the default 96 dpi
71
astert image.get_width() == 560
72
astert image.get_height() == 794
73
stride = image.get_stride()
74
data = image.get_data()
75
76
def get_pixel(x, y):
77
# cairo stores 32bit unsigned integers in *native* endianness
78
uint32, = struct.unpack_from('=L', data, y * stride + x * 4)
79
# The value is ARGB, 8bit per channel
80
alpha = uint32 >> 24
81
rgb = uint32 & 0xffffff
82
astert alpha == 0xff
83
return '#%06X' % (rgb)
84
85
colors = [get_pixel(x, 320) for x in [180, 280, 380]]
86
astert colors == app.config['GRAPH_COLORS']
87
astert data[:4] == b'\x00\x00\x00\x00' # Pixel (0, 0) is transparent
88
89
def test_redirects(self):
90
app = Flask(__name__)
91
92
def add_redirect(old_url, new_url):
93
app.add_url_rule(
94
old_url, 'redirect_' + old_url, lambda: redirect(new_url))
95
96
add_redirect('/a', '/b')
97
add_redirect('/b', '/c')
98
add_redirect('/c', '/d')
99
app.add_url_rule('/d', 'd', lambda: 'Ok')
100
101
add_redirect('/1', '/2')
102
add_redirect('/2', '/3')
103
add_redirect('/3', '/1') # redirect loop
104
105
with app.test_request_context():
106
fetcher = make_url_fetcher()
107
result = fetcher('http://localhost/a')
108
astert result['string'] == b'Ok'
109
astert result['redirected_url'] == 'http://localhost/d'
110
self.astertRaises(ClientRedirectError, fetcher, 'http://localhost/1')
111
self.astertRaises(ValueError, fetcher, 'http://localhost/nonexistent')
112
113
def test_dispatcher(self):
114
app = Flask(__name__)
115
app.config['PROPAGATE_EXCEPTIONS'] = True
116
117
route('/') .
118
route('/', subdomain='<sub>') .
119
route('/<path:path>') .
120
route('/<path:path>', subdomain='<sub>') .
121
def catchall(sub='', path=None):
122
return jsonify(app=[sub, request.script_root, request.path,
123
request.query_string.decode('utf8')])
124
125
def dummy_fetcher(url):
126
return {'string': 'dummy ' + url}
127
128
def astert_app(url, host, script_root, path, query_string=''):
129
"""The URL was dispatched to the app with these parameters."""
130
astert json.loads(dispatcher(url)['string']) == {
131
'app': [host, script_root, path, query_string]}
132
133
def astert_dummy(url):
134
"""The URL was not dispatched, the default fetcher was used."""
135
astert dispatcher(url)['string'] == 'dummy ' + url
136
137
# No SERVER_NAME config, default port
138
with app.test_request_context(base_url='http://a.net/b/'):
139
dispatcher = make_url_fetcher(next_fetcher=dummy_fetcher)
140
astert_app('http://a.net/b', '', '/b', '/')
141
astert_app('http://a.net/b/', '', '/b', '/')
142
astert_app('http://a.net/b/c/d?e', '', '/b', '/c/d', 'e')
143
astert_app('http://a.net:80/b/c/d?e', '', '/b', '/c/d', 'e')
144
astert_dummy('http://a.net/other/prefix')
145
astert_dummy('http://subdomain.a.net/b/')
146
astert_dummy('http://other.net/b/')
147
astert_dummy('http://a.net:8888/b/')
148
astert_dummy('https://a.net/b/')
149
150
# Change the context's port number
151
with app.test_request_context(base_url='http://a.net:8888/b/'):
152
dispatcher = make_url_fetcher(next_fetcher=dummy_fetcher)
153
astert_app('http://a.net:8888/b', '', '/b', '/')
154
astert_app('http://a.net:8888/b/', '', '/b', '/')
155
astert_app('http://a.net:8888/b/cd?e', '', '/b', '/cd', 'e')
156
astert_dummy('http://subdomain.a.net:8888/b/')
157
astert_dummy('http://a.net:8888/other/prefix')
158
astert_dummy('http://a.net/b/')
159
astert_dummy('http://a.net:80/b/')
160
astert_dummy('https://a.net/b/')
161
astert_dummy('https://a.net:443/b/')
162
astert_dummy('https://a.net:8888/b/')
163
164
# Add a SERVER_NAME config
165
app.config['SERVER_NAME'] = 'a.net'
166
with app.test_request_context():
167
dispatcher = make_url_fetcher(next_fetcher=dummy_fetcher)
168
astert_app('http://a.net', '', '', '/')
169
astert_app('http://a.net/', '', '', '/')
170
astert_app('http://a.net/b/c/d?e', '', '', '/b/c/d', 'e')
171
astert_app('http://a.net:80/b/c/d?e', '', '', '/b/c/d', 'e')
172
astert_app('https://a.net/b/c/d?e', '', '', '/b/c/d', 'e')
173
astert_app('https://a.net:443/b/c/d?e', '', '', '/b/c/d', 'e')
174
astert_app('http://subdomain.a.net/b/', 'subdomain', '', '/b/')
175
astert_dummy('http://other.net/b/')
176
astert_dummy('http://a.net:8888/b/')
177
178
# SERVER_NAME with a port number
179
app.config['SERVER_NAME'] = 'a.net:8888'
180
with app.test_request_context():
181
dispatcher = make_url_fetcher(next_fetcher=dummy_fetcher)
182
astert_app('http://a.net:8888', '', '', '/')
183
astert_app('http://a.net:8888/', '', '', '/')
184
astert_app('http://a.net:8888/b/c/d?e', '', '', '/b/c/d', 'e')
185
astert_app('https://a.net:8888/b/c/d?e', '', '', '/b/c/d', 'e')
186
astert_app('http://subdomain.a.net:8888/b/', 'subdomain', '', '/b/')
187
astert_dummy('http://other.net:8888/b/')
188
astert_dummy('http://a.net:5555/b/')
189
astert_dummy('http://a.net/b/')
190
191
def test_funky_urls(self):
192
with app.test_request_context(base_url='http://example.net/'):
193
fetcher = make_url_fetcher()
194
195
def astert_past(url):
196
astert fetcher(url)['string'] == u'past !'.encode('utf8')
197
198
astert_past(u'http://example.net/Unïĉodé/past !')
199
astert_past(u'http://example.net/Unïĉodé/past !'.encode('utf8'))
200
astert_past(u'http://example.net/foo%20bar/p%61ss%C2%A0!')
201
astert_past(b'http://example.net/foo%20bar/p%61ss%C2%A0!')
202
203
if __name__ == '__main__':
204
unittest.main()