장고 사이트에서 HTML을 PDF로 렌더링
나의 django powered 사이트를 위해 동적 html 페이지를 pdf로 쉽게 변환할 수 있는 솔루션을 찾고 있습니다.
페이지에는 HTML과 Google 시각화 API의 차트가 포함되어 있습니다(자바스크립트 기반이지만 이러한 그래프를 포함하는 것은 필수입니다).
Reportlab의 솔루션을 사용해 보십시오.
다운로드하여 python setup.py 설치와 함께 정상적으로 설치합니다.
다음 모듈도 설치해야 합니다. xhtml2pdf, html5lib, pypdf with 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>
다음 래퍼 중 하나를 사용하여 wkhtmltopdf를 시도합니다.
django-wkhtmltopdf 또는 python-pdfkit
이것은 나에게 아주 잘 작동했고, 자바스크립트와 CSS 또는 웹킷 브라우저가 지원하는 그 문제에 대한 모든 것을 지원합니다.
자세한 튜토리얼은 이 블로그 게시물을 참조하십시오.
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를 사용하려면 여기에 제시된 솔루션을 확인하십시오.
방금 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'
저는 이 스레드에서 최선의 답변을 시도했지만 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))
난 바꿔야 했다.cgi
로.html
부터cgi.escape
감소했고, 제가 대체했습니다.StringIO
와 함께io.ByteIO()
제가 사용한 렌더링에 대해서는render_to_string
변환하는 대신dict
로.context
오류를 던지고 있었습니다.
너무 많은 시간 동안 이 작업을 수행한 후, 저는 마침내 이것을 발견했습니다: https://github.com/vierno/django-xhtml2pdf
일반 클래스 기반 보기를 위한 믹스인을 제공하는 https://github.com/chrisglass/django-xhtml2pdf 의 포크입니다.저는 이렇게 사용했습니다.
# 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이기 때문에 URL에서 '.as_view'라고 부르면 됩니다.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 Editor를 사용하여 레이아웃을 정의하고 보고서를 jasper reports 서버에 게시할 수 있습니다.게시 후 나머지 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
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'
- 이것은 장고 >=3를 위한 것입니다.
- 이 코드는 HTML 템플릿을 모든 페이지에 대해 pdf 파일로 변환합니다.예: post/1/new1, post/2/new2
- pdf 파일 이름은 url의 마지막 부분입니다.예를 들어 post/2/new2의 경우 파일 이름은 new2입니다.
처음 설치 xhtml2pdf
pip install xhtml2pdf
urls.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
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
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
구조:
myappname/
|___views.py
|___urls.py
|___utils.py
|___templates/myappname/your.html
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를 생성합니다.
언급URL : https://stackoverflow.com/questions/1377446/render-html-to-pdf-in-django-site
'programing' 카테고리의 다른 글
CSS3를 사용한 SVG 드롭 섀도우 (0) | 2023.08.10 |
---|---|
jQuery.html()에 대한 콜백 함수? (0) | 2023.08.10 |
이 쿼리에 인덱스가 사용되지 않는 이유는 무엇입니까? (0) | 2023.08.10 |
기본 응용프로그램 개발을 위한 Phonegap 사용 (0) | 2023.08.10 |
경고 "Kotlin 플러그인 버전이 라이브러리 버전과 동일하지 않습니다." (하지만 그렇습니다!) (0) | 2023.08.10 |