django-weasyprint
django-weasyprint copied to clipboard
Insert header / footer on every pdf pages
Is there a way to generate pages with arbitrary complex footer or header (ie.rendered template)?
Here is the snippet I use to do that:
def build_pdf(html_content, header_html=None, footer_html=None):
"""
Build a pdf and returns its binary content
For header and footer, CURRENT_PAGE and PAGES_COUNT are replaced by the actual numbers.
"""
def get_page_body(boxes):
for box in boxes:
if box.element_tag == "body":
return box
return get_page_body(box.all_children())
def get_page_and_body(html, css):
if html is None:
return None
html = weasyprint.HTML(
string=html,
base_url=getattr(settings, "WEASYPRINT_BASEURL", None),
url_fetcher=django_url_fetcher,
)
css += "@page { margin 0 !important; }"
document = html.render(stylesheets=[weasyprint.CSS(string=css)])
document_page = document.pages[0]
document_body = get_page_body(document_page._page_box.all_children())
return (
document_page,
document_body.copy_with_children(document_body.all_children()),
)
def preprocess_html(html, context):
for key, value in context.items():
html = html.replace(key, str(value))
return html
document = weasyprint.HTML(
string=html_content,
base_url=getattr(settings, "WEASYPRINT_BASEURL", None),
url_fetcher=django_url_fetcher,
).render()
if header_html is not None or footer_html is not None:
pages_count = len(document.pages)
for current_page_number, page in enumerate(document.pages, start=1):
context = {"CURRENT_PAGE": current_page_number, "PAGES_COUNT": pages_count}
header_page, header_body = get_page_and_body(
preprocess_html(header_html, context),
css="body {position: fixed; top: -1.5cm;}",
)
footer_page, footer_body = get_page_and_body(
preprocess_html(footer_html, context),
css="body {position: fixed; bottom: -1.5cm;}",
)
page_body = get_page_body(page._page_box.all_children())
if header_body is not None:
page_body.children += header_body.all_children()
if footer_body is not None:
page_body.children += footer_body.all_children()
page.links.extend(header_page.links)
page.links.extend(footer_page.links)
return document.write_pdf()
Using css for this might be an easier approach.
Change margins according to your needs.
@charset "UTF-8";
@page {
margin: 1cm;
margin-top: 6cm;
margin-bottom: 1cm;
size: landscape;
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
margin-bottom: -1cm;
}
.header {
position: fixed;
top: 0;
margin-top: -5.5cm;
width: 100%;
}
Here is the minimal html.
{% load static %}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" media="print" href="{% static 'stylesheet.css' %}">
<title>My title</title>
<meta name="description" content="pdf template">
<meta name="author" content="bars">
</head>
<body>
<div class="header">
Header content
</div>
....
Other Content
....
<div class="footer">
Footer content
</div>
</body>
</html>
Hi @barslmn thanks for this trick. It works fine with text header, but I would like to use an image instead. Do you have an idea on how I can accomplish this?
I am trying to position the image to the right (it's a small rectangular logo about 250 * 90px). My problem is that when I "position: fixed; right: 0" it, it goes into the same line with the body of text on each page. However, when I "float: right", I am able to have it where I want it to be, but then it only appears on the first page.
If I try to move the image above the text by reducing margin-top, part of the image gets cropped, and the further I keep decreasing the margin-top, the image eventually ends up moving to the previous page (meaning it starts showing on the bottom of of each previous page)
Hi @kachmul2004, Looking back at it I can see that I used columns and rows to position the image in the header. So aligning images can be done like this:
CSS:
@charset "UTF-8";
@page {
margin: 1cm;
margin-top: 6cm;
margin-bottom: 1cm;
size: landscape;
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
margin-bottom: -1cm;
}
.header {
position: fixed;
top: 0;
margin-top: -5.5cm;
width: 100%;
}
.row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
flex-direction: column;
flex-basis: 100%;
flex: 1;
}
and HTML:
{% load static %}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" media="print" href="{% static 'stylesheet.css' %}">
<title>My title</title>
<meta name="description" content="pdf template">
<meta name="author" content="bars">
</head>
<body>
<div class="header">
<div class="row">
<div class="column">
</div>
<div class="column">
</div>
<div class="column">
<img id="myrightalignedlogo" src="{% static 'path/to/my/myrightalignedlogo.png' %}">
</div>
</div>
</div>
....
Other Content
....
<div class="footer">
Footer content
</div>
</body>
</html>
@barslmn unfortunately, I am still getting the same results as before. I have even tried using pure html (without Jinja templates) and the result is still the same. Here is a jsfiddle code that will show you what I am getting.
https://jsfiddle.net/o5svhd7f/
Sorry I got some attribute in the css wrong. .column doesnt have width it has flex:1. I edited the above comment. And your jsfiddle: https://jsfiddle.net/5kphbo7x/
I ended up using a table. Hacky, but easier to manipulate. Thanks
Hi! I found a solution than not require change the position.
That's is my CSS stylesheet: _.header { position: running(header); }
.footer { position: running(footer); text-align: right; }
@page { @top-right { content: element(header, first-except); height: -4cm; width: 100%; } @bottom-center { color: #014e04; content: counter(page); height: 1cm; text-align: center; width: 1cm; } @bottom-left { content: element(footer, first-except); height: 1cm; width: 100%; } }_
My Template (HTML) is:
_