복붙노트

[PYTHON] Django 사이트에서 HTML을 PDF로 렌더링

PYTHON

Django 사이트에서 HTML을 PDF로 렌더링

내 장고 전원 사이트 들어, 난 PDF로 동적 HTML 페이지를 변환하는 쉬운 솔루션을 찾고 있는데요.

페이지에는 Google 시각화 API의 HTML 및 차트가 포함됩니다 (자바 스크립트 기반이지만 그래프는 필수입니다).

해결법

  1. ==============================

    1.Reportlab에서 솔루션을 사용해보십시오.

    Reportlab에서 솔루션을 사용해보십시오.

    Python setup.py install을 사용하여 다운로드하고 평소대로 설치하십시오.

    easy_install과 함께 xhtml2pdf, html5lib, 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>
    

    희망이 도움이됩니다.

  2. ==============================

    2.방금 CBV를 위해 이것을 채찍질했습니다. 프로덕션에서는 사용되지 않지만 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'
    
  3. ==============================

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

    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를 사용하려면 여기에 제안 된 해결책을 확인하십시오.

  4. ==============================

    4.다음 래퍼 중 하나를 사용하여 wkhtmltopdf를 시도하십시오.

    다음 래퍼 중 하나를 사용하여 wkhtmltopdf를 시도하십시오.

    django-wkhtmltopdf 또는 python-pdfkit

    이것은 나를 위해 위대한, 웹캠 브라우저가 지원하는 그 문제에 대한 자바 스크립트와 CSS 또는 아무것도 지원했습니다.

    더 자세한 자습서를 보려면이 블로그 게시물을 참조하십시오.

  5. ==============================

    5.너무 많은 시간 동안이 작업을 시도한 후에, 나는 이것을 결국 발견 : https://github.com/vierno/django-xhtml2pdf

    너무 많은 시간 동안이 작업을 시도한 후에, 나는 이것을 결국 발견 : 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이기 때문에 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",
           ),
    
  6. ==============================

    6.iReport 편집기를 사용하여 레이아웃을 정의하고 재스퍼 보고서 서버에 보고서를 게시 할 수 있습니다. 게시 한 후 나머지 API를 호출하여 결과를 얻을 수 있습니다.

    iReport 편집기를 사용하여 레이아웃을 정의하고 재스퍼 보고서 서버에 보고서를 게시 할 수 있습니다. 게시 한 후 나머지 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
    
  7. ==============================

    7.html 템플릿에서 PDF를 생성하는 코드를 얻습니다.

    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'  
    
  8. from https://stackoverflow.com/questions/1377446/render-html-to-pdf-in-django-site by cc-by-sa and MIT license