Рендеринг HTML в PDF на сайте Django
для моего сайта с питанием от django я ищу простое решение для преобразования динамических html-страниц в pdf.
страницы включают HTML и диаграммы из Google visualization API (который основан на javascript, но в том числе эти графики является обязательным).
7 ответов:
попробуйте решение от Reportlab.
загрузите его и установите как обычно с python setup.py установить
вам также нужно будет установить следующие модули: xhtml2pdf, html5lib, pypdf с easy_install.
пример использования:
сначала определите эту функцию:
import cStringIO as StringIO from xhtml2pdf import pisa from django.template.loader import get_template from django.template import Context from django.http import HttpResponse from cgi import escape def render_to_pdf(template_src, context_dict): template = get_template(template_src) context = Context(context_dict) html = template.render(context) result = StringIO.StringIO() pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("ISO-8859-1")), result) if not pdf.err: return HttpResponse(result.getvalue(), content_type='application/pdf') return HttpResponse('We had some errors<pre>%s</pre>' % escape(html))
тогда вы можете использовать его следующим образом:
def myview(request): #Retrieve data or whatever you need return render_to_pdf( 'mytemplate.html', { 'pagesize':'A4', 'mylist': results, } )
в шаблон:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>My Title</title> <style type="text/css"> @page { size: {{ pagesize }}; margin: 1cm; @frame footer { -pdf-frame-content: footerContent; bottom: 0cm; margin-left: 9cm; margin-right: 9cm; height: 1cm; } } </style> </head> <body> <div> {% for item in mylist %} RENDER MY CONTENT {% endfor %} </div> <div id="footerContent"> {%block page_foot%} Page <pdf:pagenumber> {%endblock%} </div> </body> </html>
надеюсь, что это помогает.
https://github.com/nigma/django-easy-pdf
шаблон:
{% extends "easy_pdf/base.html" %} {% block content %} <div id="content"> <h1>Hi there!</h1> </div> {% endblock %}
View:
from easy_pdf.views import PDFTemplateView class HelloPDFView(PDFTemplateView): template_name = "hello.html"
Если вы хотите использовать django-easy-pdf на Python 3, Проверьте предлагаемое решение здесь.
Я просто взбил это для CBV. Не используется в производстве, но генерирует PDF для меня. Вероятно, нужна работа для отчетности об ошибках стороны вещей, но делает трюк до сих пор.
import StringIO from cgi import escape from xhtml2pdf import pisa from django.http import HttpResponse from django.template.response import TemplateResponse from django.views.generic import TemplateView class PDFTemplateResponse(TemplateResponse): def generate_pdf(self, retval): html = self.content result = StringIO.StringIO() rendering = pisa.pisaDocument(StringIO.StringIO(html.encode("ISO-8859-1")), result) if rendering.err: return HttpResponse('We had some errors<pre>%s</pre>' % escape(html)) else: self.content = result.getvalue() def __init__(self, *args, **kwargs): super(PDFTemplateResponse, self).__init__(*args, mimetype='application/pdf', **kwargs) self.add_post_render_callback(self.generate_pdf) class PDFTemplateView(TemplateView): response_class = PDFTemplateResponse
Как использовать:
class MyPdfView(PDFTemplateView): template_name = 'things/pdf.html'
попробовать wkhtmltopdf С любой из следующих оболочек
django-wkhtmltopdf или python-pdfkit
Это отлично работало для меня, поддерживает javascript и css или что-нибудь в этом отношении, которое поддерживает браузер webkit.
для более подробного урока, пожалуйста, смотрите это блоге
после попытки заставить это работать слишком много часов, я, наконец, нашел это: https://github.com/vierno/django-xhtml2pdf
это вилка https://github.com/chrisglass/django-xhtml2pdf это обеспечивает mixin для универсального представления на основе классов. Я использовал его так:
# views.py from django_xhtml2pdf.views import PdfMixin class GroupPDFGenerate(PdfMixin, DetailView): model = PeerGroupSignIn template_name = 'groups/pdf.html' # templates/groups/pdf.html <html> <style> @page { your xhtml2pdf pisa PDF parameters } </style> </head> <body> <div id="header_content"> (this is defined in the style section) <h1>{{ peergroupsignin.this_group_title }}</h1> ...
при заполнении полей шаблона используйте имя модели, определенное в представлении, во всех строчных буквах. Потому что его GCBV, вы можете просто назвать его как'. as_view ' в ваш urls.py:
# urls.py (using url namespaces defined in the main urls.py file) url( regex=r"^(?P<pk>\d+)/generate_pdf/$", view=views.GroupPDFGenerate.as_view(), name="generate_pdf", ),
вы можете использовать редактор iReport для определения макета и публикации отчета на сервере отчетов jasper. После публикации вы можете вызвать REST api для получения результатов.
вот тест функциональности:
from django.test import TestCase from x_reports_jasper.models import JasperServerClient """ to try integraction with jasper server through rest """ class TestJasperServerClient(TestCase): # define required objects for tests def setUp(self): # load the connection to remote server try: self.j_url = "http://127.0.0.1:8080/jasperserver" self.j_user = "jasperadmin" self.j_pass = "jasperadmin" self.client = JasperServerClient.create_client(self.j_url,self.j_user,self.j_pass) except Exception, e: # if errors could not execute test given prerrequisites raise # test exception when server data is invalid def test_login_to_invalid_address_should_raise(self): self.assertRaises(Exception,JasperServerClient.create_client, "http://127.0.0.1:9090/jasperserver",self.j_user,self.j_pass) # test execute existent report in server def test_get_report(self): r_resource_path = "/reports/<PathToPublishedReport>" r_format = "pdf" r_params = {'PARAM_TO_REPORT':"1",} #resource_meta = client.load_resource_metadata( rep_resource_path ) [uuid,out_mime,out_data] = self.client.generate_report(r_resource_path,r_format,r_params) self.assertIsNotNone(uuid)
и вот пример реализации вызова:
from django.db import models import requests import sys from xml.etree import ElementTree import logging # module logger definition logger = logging.getLogger(__name__) # Create your models here. class JasperServerClient(models.Manager): def __handle_exception(self, exception_root, exception_id, exec_info ): type, value, traceback = exec_info raise JasperServerClientError(exception_root, exception_id), None, traceback # 01: REPORT-METADATA # get resource description to generate the report def __handle_report_metadata(self, rep_resourcepath): l_path_base_resource = "/rest/resource" l_path = self.j_url + l_path_base_resource logger.info( "metadata (begin) [path=%s%s]" %( l_path ,rep_resourcepath) ) resource_response = None try: resource_response = requests.get( "%s%s" %( l_path ,rep_resourcepath) , cookies = self.login_response.cookies) except Exception, e: self.__handle_exception(e, "REPORT_METADATA:CALL_ERROR", sys.exc_info()) resource_response_dom = None try: # parse to dom and set parameters logger.debug( " - response [data=%s]" %( resource_response.text) ) resource_response_dom = ElementTree.fromstring(resource_response.text) datum = "" for node in resource_response_dom.getiterator(): datum = "%s<br />%s - %s" % (datum, node.tag, node.text) logger.debug( " - response [xml=%s]" %( datum ) ) # self.resource_response_payload= resource_response.text logger.info( "metadata (end) ") except Exception, e: logger.error( "metadata (error) [%s]" % (e)) self.__handle_exception(e, "REPORT_METADATA:PARSE_ERROR", sys.exc_info()) # 02: REPORT-PARAMS def __add_report_params(self, metadata_text, params ): if(type(params) != dict): raise TypeError("Invalid parameters to report") else: logger.info( "add-params (begin) []" ) #copy parameters l_params = {} for k,v in params.items(): l_params[k]=v # get the payload metadata metadata_dom = ElementTree.fromstring(metadata_text) # add attributes to payload metadata root = metadata_dom #('report'): for k,v in l_params.items(): param_dom_element = ElementTree.Element('parameter') param_dom_element.attrib["name"] = k param_dom_element.text = v root.append(param_dom_element) # metadata_modified_text =ElementTree.tostring(metadata_dom, encoding='utf8', method='xml') logger.info( "add-params (end) [payload-xml=%s]" %( metadata_modified_text ) ) return metadata_modified_text # 03: REPORT-REQUEST-CALL # call to generate the report def __handle_report_request(self, rep_resourcepath, rep_format, rep_params): # add parameters self.resource_response_payload = self.__add_report_params(self.resource_response_payload,rep_params) # send report request l_path_base_genreport = "/rest/report" l_path = self.j_url + l_path_base_genreport logger.info( "report-request (begin) [path=%s%s]" %( l_path ,rep_resourcepath) ) genreport_response = None try: genreport_response = requests.put( "%s%s?RUN_OUTPUT_FORMAT=%s" %(l_path,rep_resourcepath,rep_format),data=self.resource_response_payload, cookies = self.login_response.cookies ) logger.info( " - send-operation-result [value=%s]" %( genreport_response.text) ) except Exception,e: self.__handle_exception(e, "REPORT_REQUEST:CALL_ERROR", sys.exc_info()) # parse the uuid of the requested report genreport_response_dom = None try: genreport_response_dom = ElementTree.fromstring(genreport_response.text) for node in genreport_response_dom.findall("uuid"): datum = "%s" % (node.text) genreport_uuid = datum for node in genreport_response_dom.findall("file/[@type]"): datum = "%s" % (node.text) genreport_mime = datum logger.info( "report-request (end) [uuid=%s,mime=%s]" %( genreport_uuid, genreport_mime) ) return [genreport_uuid,genreport_mime] except Exception,e: self.__handle_exception(e, "REPORT_REQUEST:PARSE_ERROR", sys.exc_info()) # 04: REPORT-RETRIEVE RESULTS def __handle_report_reply(self, genreport_uuid ): l_path_base_getresult = "/rest/report" l_path = self.j_url + l_path_base_getresult logger.info( "report-reply (begin) [uuid=%s,path=%s]" %( genreport_uuid,l_path) ) getresult_response = requests.get( "%s%s/%s?file=report" %(self.j_url,l_path_base_getresult,genreport_uuid),data=self.resource_response_payload, cookies = self.login_response.cookies ) l_result_header_mime =getresult_response.headers['Content-Type'] logger.info( "report-reply (end) [uuid=%s,mime=%s]" %( genreport_uuid, l_result_header_mime) ) return [l_result_header_mime, getresult_response.content] # public methods --------------------------------------- # tries the authentication with jasperserver throug rest def login(self, j_url, j_user,j_pass): self.j_url= j_url l_path_base_auth = "/rest/login" l_path = self.j_url + l_path_base_auth logger.info( "login (begin) [path=%s]" %( l_path) ) try: self.login_response = requests.post(l_path , params = { 'j_username':j_user, 'j_password':j_pass }) if( requests.codes.ok != self.login_response.status_code ): self.login_response.raise_for_status() logger.info( "login (end)" ) return True # see http://blog.ianbicking.org/2007/09/12/re-raising-exceptions/ except Exception, e: logger.error("login (error) [e=%s]" % e ) self.__handle_exception(e, "LOGIN:CALL_ERROR",sys.exc_info()) #raise def generate_report(self, rep_resourcepath,rep_format,rep_params): self.__handle_report_metadata(rep_resourcepath) [uuid,mime] = self.__handle_report_request(rep_resourcepath, rep_format,rep_params) # TODO: how to handle async? [out_mime,out_data] = self.__handle_report_reply(uuid) return [uuid,out_mime,out_data] @staticmethod def create_client(j_url, j_user, j_pass): client = JasperServerClient() login_res = client.login( j_url, j_user, j_pass ) return client class JasperServerClientError(Exception): def __init__(self,exception_root,reason_id,reason_message=None): super(JasperServerClientError, self).__init__(str(reason_message)) self.code = reason_id self.description = str(exception_root) + " " + str(reason_message) def __str__(self): return self.code + " " + self.description
Я получаю код для создания PDF из html шаблона:
import os from weasyprint import HTML from django.template import Template, Context from django.http import HttpResponse def generate_pdf(self, report_id): # Render HTML into memory and get the template firstly template_file_loc = os.path.join(os.path.dirname(__file__), os.pardir, 'templates', 'the_template_pdf_generator.html') template_contents = read_all_as_str(template_file_loc) render_template = Template(template_contents) #rendering_map is the dict for params in the template render_definition = Context(rendering_map) render_output = render_template.render(render_definition) # Using Rendered HTML to generate PDF response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename=%s-%s-%s.pdf' % \ ('topic-test','topic-test', '2018-05-04') # Generate PDF pdf_doc = HTML(string=render_output).render() pdf_doc.pages[0].height = pdf_doc.pages[0]._page_box.children[0].children[ 0].height # Make PDF file as single page file pdf_doc.write_pdf(response) return response def read_all_as_str(self, file_loc, read_method='r'): if file_exists(file_loc): handler = open(file_loc, read_method) contents = handler.read() handler.close() return contents else: return 'file not exist'