Last active
August 8, 2020 10:48
-
-
Save rhenter/14fa9e4f5ed4aa1dd019fb680ca203f0 to your computer and use it in GitHub Desktop.
Imprimir para PDF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% 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 %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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