对于我的django支持的网站,我正在寻找一个简单的解决方案,将动态html页面转换为pdf。
页面包含来自Google可视化API的HTML和图表(基于javascript,但必须包含这些图表)。
答案 0 :(得分:200)
尝试Reportlab的解决方案。
下载并使用python setup.py install
照常安装您还需要安装以下模块:xhtml2pdf,html5lib,带有easy_install的pypdf。
以下是一个用法示例:
首先定义此功能:
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>
希望它有所帮助。
答案 1 :(得分:10)
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,请检查建议的解决方案here。
答案 2 :(得分:9)
我刚刚为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'
答案 3 :(得分:9)
使用以下任一包装器
尝试wkhtmltopdfdjango-wkhtmltopdf或python-pdfkit
这对我来说很有用,支持javascript和css或webkit浏览器支持的任何内容。
有关详细教程,请参阅此blog post
答案 4 :(得分:2)
在尝试让它工作了太多时间之后,我终于找到了这个: 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,您可以将其称为&#39; .as_view&#39;在你的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",
),
答案 5 :(得分:1)
您可以使用iReport编辑器定义布局,并在jasper reports server中发布报告。发布后,您可以调用其余的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
答案 6 :(得分:1)
我从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'
答案 7 :(得分:0)
如果您的html模板中包含上下文数据以及CSS和js。 比起使用pdfjs,您有更好的选择。
在您的代码中,您可以像这样使用。
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)))
在您的HTML中,您可以链接外部或内部CSS和js,它将生成最佳质量的pdf。