[英]Render HTML to PDF in Django site
For my django powered site, I am looking for an easy solution to convert dynamic html pages to pdf.对于我的 django 网站,我正在寻找一种将动态 html 页面转换为 pdf 的简单解决方案。
Pages include HTML and charts from Google visualization API (which is javascript based, yet including those graphs is a must).页面包括来自 Google 可视化 API 的 HTML 和图表(基于 javascript,但必须包含这些图表)。
Try the solution from Reportlab .尝试Reportlab的解决方案。
Download it and install it as usual with python setup.py install下载它并像往常一样使用 python setup.py install 安装它
You will also need to install the following modules: xhtml2pdf, html5lib, pypdf with easy_install.您还需要安装以下模块:xhtml2pdf、html5lib、pypdf 和 easy_install。
Here is an usage example:这是一个使用示例:
First define this 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))
Then you can use it like this:然后你可以像这样使用它:
def myview(request):
#Retrieve data or whatever you need
return render_to_pdf(
'mytemplate.html',
{
'pagesize':'A4',
'mylist': results,
}
)
The template:模板:
<!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>
Hope it helps.希望能帮助到你。
Try wkhtmltopdf with either one of the following wrappers使用以下任一包装器尝试wkhtmltopdf
django-wkhtmltopdf or python-pdfkit django-wkhtmltopdf或python-pdfkit
This worked great for me,supports javascript and css or anything for that matter which a webkit browser supports.这对我来说非常有用,支持 javascript 和 css 或任何 webkit 浏览器支持的东西。
For more detailed tutorial please see this blog post有关更详细的教程,请参阅此博客文章
https://github.com/nigma/django-easy-pdf https://github.com/nigma/django-easy-pdf
Template:模板:
{% 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"
If you want to use django-easy-pdf on Python 3 check the solution suggested here .如果您想在 Python 3 上使用 django-easy-pdf,请查看此处建议的解决方案。
I just whipped this up for CBV.我刚刚为 CBV 准备了这个。 Not used in production but generates a PDF for me.
未在生产中使用,但为我生成 PDF。 Probably needs work for the error reporting side of things but does the trick so far.
可能需要为事情的错误报告方面工作,但到目前为止就可以了。
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
Used like:像这样使用:
class MyPdfView(PDFTemplateView):
template_name = 'things/pdf.html'
After trying to get this to work for too many hours, I finally found this: https://github.com/vierno/django-xhtml2pdf在试图让它工作太多小时后,我终于找到了这个: https ://github.com/vierno/django-xhtml2pdf
It's a fork of https://github.com/chrisglass/django-xhtml2pdf that provides a mixin for a generic class-based view.它是https://github.com/chrisglass/django-xhtml2pdf的一个分支,它为基于类的通用视图提供了一个 mixin。 I used it like this:
我这样使用它:
# 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>
...
Use the model name you defined in your view in all lowercase when populating the template fields.填充模板字段时,使用您在视图中定义的模型名称全部小写。 Because its a GCBV, you can just call it as '.as_view' in your urls.py:
因为它是 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",
),
You can use iReport editor to define the layout, and publish the report in jasper reports server.您可以使用 iReport 编辑器定义布局,并在 jasper 报告服务器中发布报告。 After publish you can invoke the rest api to get the results.
发布后,您可以调用其余的 api 来获取结果。
Here is the test of the functionality:下面是功能测试:
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)
And here is an example of the invocation implementation:这是调用实现的示例:
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
I get the code to generate the PDF from html template :我得到了从 html 模板生成 PDF 的代码:
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'
If you have context data along with css and js in your html template.如果您的 html 模板中有上下文数据以及 css 和 js。 Than you have good option to use pdfjs .
比你有很好的选择使用pdfjs 。
In your code you can use like this.在您的代码中,您可以像这样使用。
from django.template.loader import get_template
import pdfkit
from django.conf import settings
context={....}
template = get_template('reports/products.html')
html_string = template.render(context)
pdfkit.from_string(html_string, os.path.join(settings.BASE_DIR, "media", 'products_report-%s.pdf'%(id)))
In your HTML you can link extranal or internal css and js, it will generate best quality of pdf.在您的 HTML 中,您可以链接外部或内部 css 和 js,它将生成最佳质量的 pdf。
First install xhtml2pdf首先安装xhtml2pdf
pip install xhtml2pdf
urls.py网址.py
from .views import generatePdf as GeneratePdf
from django.urls import re_path
urlpatterns = [
#...
re_path(r'^pdf/(?P<cid>[0-9]+)/(?P<value>[a-zA-Z0-9 :._-]+)/$', GeneratePdf, name='pdf'),
#...
]
views.py视图.py
from django.template.loader import get_template
from .utils import render_to_pdf
# pdf
def generatePdf(request,cid,value):
print(cid,value)
pdf = render_to_pdf('myappname/pdf/your.html',cid)
return HttpResponse(pdf, content_type='application/pdf')
utils.py实用程序.py
from io import BytesIO #A stream implementation using an in-memory bytes buffer
# It inherits BufferIOBase
from django.http import HttpResponse
from django.template.loader import get_template
#pisa is a html2pdf converter using the ReportLab Toolkit,
#the HTML5lib and pyPdf.
from xhtml2pdf import pisa
#difine render_to_pdf() function
from .models import myappname
from django.shortcuts import get_object_or_404
def render_to_pdf(template_src,cid, context_dict={}):
template = get_template(template_src)
node = get_object_or_404(myappname, id =cid)
context = {'node':node}
context_dict=context
html = template.render(context_dict)
result = BytesIO()
#This part will create the pdf.
pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), result)
if not pdf.err:
return HttpResponse(result.getvalue(), content_type='application/pdf')
return None
Structure:结构:
myappname/
|___views.py
|___urls.py
|___utils.py
|___templates/myappname/your.html
I tried the best answer in this thread and it didn't work for python3.8, hence I had to do some changes as follows ( for anyone working on python3.8 ) :我在此线程中尝试了最佳答案,但它不适用于 python3.8,因此我必须进行如下更改(对于使用 python3.8 的任何人):
import io
from xhtml2pdf import pisa
from django.http import HttpResponse
from html import escape
from django.template.loader import render_to_string
def render_to_pdf(template_src, context_dict):
html = render_to_string(template_src, context_dict)
result = io.BytesIO()
pdf = pisa.pisaDocument(io.BytesIO (html.encode("utf-8")), result)
if not pdf.err:
return HttpResponse(result.getvalue(), content_type='application/pdf')
return HttpResponse('We had some errors<pre>%s</pre>' % escape(html))
I had to change cgi
to html
since cgi.escape
is depricated, and I replaced StringIO
with io.ByteIO()
as for the rendering I used render_to_string
instead of converting the dict
to context
which was throwing an error.我不得不将
cgi
更改为html
,因为cgi.escape
已被贬低,并且我用StringIO
io.ByteIO()
替换了 StringIO,因为我使用render_to_string
而不是将dict
转换为引发错误的context
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.