在Django站点中将HTML呈现为PDF

对于我的Django动力网站,我正在寻找一个简单的解决scheme,将dynamic的HTML页面转换为PDF格式。

页面包括HTML可视化API(这是基于JavaScript,但包括这些图是必须的)的HTML和图表。

试试Reportlab的解决scheme。

下载并像往常一样安装python setup.py install

您还需要安装以下模块:xhtml2pdf,html5lib,带有easy_install的pypdf。

这是一个用法示例:

首先定义这个function:

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> 

希望能帮助到你。

我刚刚把这个提升为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' 

https://github.com/nigma/django-easy-pdf

模板:

 {% extends "easy_pdf/base.html" %} {% block content %} <div id="content"> <h1>Hi there!</h1> </div> {% endblock %} 

视图:

 from easy_pdf.views import PDFTemplateView class HelloPDFView(PDFTemplateView): template_name = "hello.html" 

如果你想在Python 3上使用django-easy-pdf,请检查这里提供的解决scheme。

试试wkhtmltopdf与下面的任何一个包装

django-wkhtmltopdf或python-pdfkit

这对我来说很好,支持javascript和css或者webkit浏览器支持的任何东西。

有关更详细的教程,请参阅此博客文章

试图让这个工作太多小时后,我终于find了这个: https : //github.com/vierno/django-xhtml2pdf

这是一个https://github.com/chrisglass/django-xhtml2pdf的分支,为generics的基于类的视图提供了一个混合。; 我这样使用它:

  # 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,你可以在你的urls.py中把它叫做“.as_view”:

  # 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 reports server中发布报告。 发布后,您可以调用其余的api来获得结果。

这是对function的testing:

 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