Skip to content

Instantly share code, notes, and snippets.

@rhenter
Last active August 8, 2020 10:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rhenter/14fa9e4f5ed4aa1dd019fb680ca203f0 to your computer and use it in GitHub Desktop.
Save rhenter/14fa9e4f5ed4aa1dd019fb680ca203f0 to your computer and use it in GitHub Desktop.
Imprimir para PDF
{% load i18n %}
{% load l10n %}
{% load static %}
{% load app_utils %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Desc Example">
<meta name="keywords" content="">
<meta name="author" content="Example Again">
<title>
{{ page_title }}
</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
{# <link rel="stylesheet" href="{% static 'dist/css/adminlte.css' %}">#}
<link rel="stylesheet" href="{% static 'css/print.css' %}">
</head>
<body>
<div class="wrapper">
<!-- Main content -->
<section class="pdf">
<!-- title row -->
<div class="row">
<div class="col-12">
{% block content %}
{% endblock %}
</div>
</div>
</section>
<!-- /.content -->
</div>
<!-- ./wrapper -->
</body>
</html>
{% extends 'base_print.html' %}
{% load i18n %}
{% load l10n %}
{% load static %}
{% load app_utils %}
{% block content %}
<div class="card">
<div class="vertical-center text-center">
<img class="logo" src="{% static 'img/logo.png' %}" alt="">
<span class="title-text"> {{ object.name }}</span>
</div>
</div>
{% endblock %}
import tempfile
from django.http import HttpResponse
from django.template.loader import render_to_string
from weasyprint import CSS, HTML
side_margin = 2
extra_vertical_margin = 20
footer_size = 0
class PdfGenerator:
"""
Generate a PDF out of a rendered template, with the possibility to integrate nicely
a header and a footer if provided.
Notes:
------
- When Weasyprint renders an html into a PDF, it goes though several intermediate steps.
Here, in this class, we deal mostly with a box representation: 1 `Document` have 1 `Page`
or more, each `Page` 1 `Box` or more. Each box can contain other box. Hence the recursive
method `get_element` for example.
For more, see:
https://weasyprint.readthedocs.io/en/stable/hacking.html#dive-into-the-source
https://weasyprint.readthedocs.io/en/stable/hacking.html#formatting-structure
- Warning: the logic of this class relies heavily on the internal Weasyprint API. This
snippet was written at the time of the release 47, it might break in the future.
- This generator draws its inspiration and, also a bit of its implementation, from this
discussion in the library github issues: https://github.com/Kozea/WeasyPrint/issues/92
"""
OVERLAY_LAYOUT = '@page {size: A4 portrait; margin: 0;}'
def __init__(self, main_html, header_html=None, footer_html=None,
base_url=None, side_margin=2, extra_vertical_margin=30):
"""
Parameters
----------
main_html: str
An HTML file (most of the time a template rendered into a string) which represents
the core of the PDF to generate.
header_html: str
An optional header html.
footer_html: str
An optional footer html.
base_url: str
An absolute url to the page which serves as a reference to Weasyprint to fetch assets,
required to get our media.
side_margin: int, interpreted in cm, by default 2cm
The margin to apply on the core of the rendered PDF (i.e. main_html).
extra_vertical_margin: int, interpreted in pixel, by default 30 pixels
An extra margin to apply between the main content and header and the footer.
The goal is to avoid having the content of `main_html` touching the header or the
footer.
"""
self.main_html = main_html
self.header_html = header_html
self.footer_html = footer_html
self.base_url = base_url
self.side_margin = side_margin
self.extra_vertical_margin = extra_vertical_margin
def _compute_overlay_element(self, element: str):
"""
Parameters
----------
element: str
Either 'header' or 'footer'
Returns
-------
element_body: BlockBox
A Weasyprint pre-rendered representation of an html element
element_height: float
The height of this element, which will be then translated in a html height
"""
html = HTML(
string=getattr(self, f'{element}_html'),
base_url=self.base_url,
)
element_doc = html.render(stylesheets=[CSS(string=self.OVERLAY_LAYOUT)])
element_page = element_doc.pages[0]
element_body = PdfGenerator.get_element(element_page._page_box.all_children(), 'body')
element_body = element_body.copy_with_children(element_body.all_children())
element_html = PdfGenerator.get_element(element_page._page_box.all_children(), element)
element_height = 20
if element == 'header' and element_html:
element_height = element_html.height
if element == 'footer' and element_html:
element_height = element_page.height - element_html.position_y
return element_body, element_height
def _apply_overlay_on_main(self, main_doc, header_body=None, footer_body=None):
"""
Insert the header and the footer in the main document.
Parameters
----------
main_doc: Document
The top level representation for a PDF page in Weasyprint.
header_body: BlockBox
A representation for an html element in Weasyprint.
footer_body: BlockBox
A representation for an html element in Weasyprint.
"""
for page in main_doc.pages:
page_body = PdfGenerator.get_element(page._page_box.all_children(), 'body')
if header_body:
page_body.children += header_body.all_children()
if footer_body:
page_body.children += footer_body.all_children()
def render_pdf(self):
"""
Returns
-------
pdf: a bytes sequence
The rendered PDF.
"""
if self.header_html:
header_body, header_height = self._compute_overlay_element('header')
else:
header_body, header_height = None, 0
if self.footer_html:
footer_body, footer_height = self._compute_overlay_element('footer')
else:
footer_body, footer_height = None, 0
margins = '{header_size}px {side_margin} {footer_size}px {side_margin}'.format(
header_size=header_height + self.extra_vertical_margin,
footer_size=footer_height + self.extra_vertical_margin,
side_margin=f'{self.side_margin}cm',
)
content_print_layout = '@page {size: A4 portrait; margin: %s;}' % margins
html = HTML(
string=self.main_html,
base_url=self.base_url,
)
main_doc = html.render(stylesheets=[CSS(string=content_print_layout)])
if self.header_html or self.footer_html:
self._apply_overlay_on_main(main_doc, header_body, footer_body)
pdf = main_doc.write_pdf()
return pdf
@staticmethod
def get_element(boxes, element):
"""
Given a set of boxes representing the elements of a PDF page in a DOM-like way, find the
box which is named `element`.
Look at the notes of the class for more details on Weasyprint insides.
"""
for box in boxes:
if box.element_tag == element:
return box
return PdfGenerator.get_element(box.all_children(), element)
def generate_pdf(template, filename, context_data, base_url=''):
# Rendered
header_html = render_to_string('base_print_header.html', context_data)
html_string = render_to_string(template, context_data)
pdf = PdfGenerator(
main_html=html_string,
header_html=header_html,
base_url=base_url,
side_margin=side_margin,
extra_vertical_margin=extra_vertical_margin
)
result = pdf.render_pdf()
# Creating http response
response = HttpResponse(content_type='application/pdf;')
response['Content-Disposition'] = 'inline; filename={}.pdf'.format(filename)
response['Content-Transfer-Encoding'] = 'binary'
with tempfile.NamedTemporaryFile(delete=True) as output:
output.write(result)
output.flush()
with open(output.name, 'rb+') as output_file:
response.write(output_file.read())
return response
from django.views.generic import TemplateView
from .pdf import generate_pdf
...
class ExamplePrintView(TemplateView):
template_name = 'example/example_print.html'
page_title = 'Example'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
object = Example.objects.get(pk=self.kwargs.get('pk'))
context.update({
'page_title': self.page_title,
'object': object,
})
return context
def get(self, request, *args, **kwargs):
data = self.get_context_data(**kwargs)
return generate_pdf(
template=self.template_name,
filename=self.page_title,
context_data=data,
base_url=self.request.build_absolute_uri()
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment