Django sitesinde HTML'yi PDF'ye dönüştürme


117

Django destekli sitem için, dinamik html sayfalarını pdf'ye dönüştürmek için kolay bir çözüm arıyorum.

Sayfalar, Google görselleştirme API'sinden HTML ve grafikler içerir (javascript tabanlıdır, ancak bu grafikleri dahil etmek zorunludur).


Django belgeleri derin ve çok şey kapsıyor. Orada önerilen yöntemle ilgili herhangi bir sorun yaşadınız mı? http://docs.djangoproject.com/en/dev/howto/outputting-pdf/
monkut

1
Bu aslında soruyu cevaplamıyor. Bu belgeler, bir PDF'nin işlenmiş HTML'den değil, yerel olarak nasıl işleneceğiyle ilgilidir.
Josh

Yapılması gereken doğru şeyin, tarayıcıların pdf üretmesini sağlamak olduğunu düşünüyorum, çünkü düzgün html / css / js oluşturmayı yapan tek kişiler onlar. bu soruya bakın stackoverflow.com/q/25574082/39998
David Hofmann

Bu soru SO'da konu dışı, ancak softwarerecs.SE'de konu dışı. Bkz. CSS ile HTML'yi PDF'ye nasıl dönüştürebilirim? .
Martin Thoma

Yanıtlar:


207

Reportlab'deki çözümü deneyin .

İndir ve her zamanki gibi python setup.py install ile kurun

Ayrıca aşağıdaki modülleri de kurmanız gerekecektir: xhtml2pdf, html5lib, pypdf easy_install ile.

İşte bir kullanım örneği:

Önce bu işlevi tanımlayın:

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))

O zaman bunu şu şekilde kullanabilirsiniz:

def myview(request):
    #Retrieve data or whatever you need
    return render_to_pdf(
            'mytemplate.html',
            {
                'pagesize':'A4',
                'mylist': results,
            }
        )

Şablon:

<!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>

Umarım yardımcı olur.


9
+1 Bu çözümü bir yıldır kullanıyorum ve harika. PISA, basit bir etiket ve çok daha fazlasını içeren barkodlar bile oluşturabilir. Ve çok kolay .
arcanum

1
Dostum, reportlab Windows 7 64bit, python2.7 64bit'e kurulacak pidedir. Hala deniyor ...
Andriy Drozdyuk

5
Javascript çalıştırmıyor gibi görünüyor.
dfrankow

3
pisa artık xhtml2pdf olarak dağıtılıyor
Pablo Albornoz

12
Python3, bir dönüşüm dışında cStringIO.StringIOiçin io.StringIO, biz tanımlamalıdır resultolarak result = io.BytesIO()yerine result = StringIO.
Sebastien

12

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

Şablon:

{% extends "easy_pdf/base.html" %}

{% block content %}
    <div id="content">
        <h1>Hi there!</h1>
    </div>
{% endblock %}

Görünüm:

from easy_pdf.views import PDFTemplateView

class HelloPDFView(PDFTemplateView):
    template_name = "hello.html"

Django-easy-pdf'i Python 3'te kullanmak istiyorsanız, burada önerilen çözümü kontrol edin .


2
Şimdiye kadar denediğim seçeneklerin uygulanması en kolay olanı bu. İhtiyaçlarım için (bir html sürümünden bir pdf raporu oluşturmak) bu sadece işe yarıyor. Teşekkürler!
NetYeti

1
@alejoss CSS yerine satır içi stilleri kullanmalısınız.
digz6666

Django-utils-six kaldırıldığı için bu çözüm django 3.0 için hemen işe yaramayabilir, ancak easy_pdf buna bağlıdır.
David

11

Bunu CBV için kırbaçladım. Üretimde kullanılmıyor ama benim için bir PDF oluşturuyor. Muhtemelen şeylerin hata raporlama tarafı için çalışmaya ihtiyacı var, ancak şimdiye kadar hile yapıyor.

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

Gibi kullanılır:

class MyPdfView(PDFTemplateView):
    template_name = 'things/pdf.html'

1
Bu benim için neredeyse doğrudan çalıştı. Tek şey değiştirmekti html.encode("ISO-8859-1")tarafındanhtml.decode("utf-8")
vinyll

Kodu @vinyll'de belirtildiği gibi değiştirdim ve ek olarak PDFTemplateView sınıfına bir satır eklemek zorunda kaldım:content_type = "application/pdf"
normic

11

Aşağıdaki sarmalayıcılardan biriyle wkhtmltopdf'i deneyin

django-wkhtmltopdf veya python-pdfkit

Bu benim için harika çalıştı, javascript ve css veya bir webkit tarayıcısının desteklediği herhangi bir şeyi destekliyor.

Daha ayrıntılı eğitim için lütfen bu blog gönderisine bakın


Html içinde gömülü svg'ye ne dersiniz, bu da destekleniyor mu?
mehmet


Dikkatli olun, webkit chrome / firefox'un yaptığı her şeyi desteklemez: webkit.org/status
mehmet

1
django-wkhtmltopdf benim için harikalar yarattı! ayrıca javascript / grafik motorunuzun yaptığı tüm animasyonları kapattığınızdan emin olun.
mehmet

@mehmet, basit çubuk grafik js'mi desteklemedi. Bir sürü hatam var. Bana yardım edebilir misin?
Manish Ojha

3

Bunu saatlerce çalıştırmaya çalıştıktan sonra nihayet şunu buldum: https://github.com/vierno/django-xhtml2pdf

Bu bir çatal bu https://github.com/chrisglass/django-xhtml2pdf genel sınıf tabanlı görünüm için bir mixin sağlar. Bunu şöyle kullandım:

    # 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>
            ...

Şablon alanlarını doldururken görünümünüzde tanımladığınız model adını tamamen küçük harfle kullanın. Bir GCBV olduğu için, urls.py'nizde ".as_view" olarak adlandırabilirsiniz:

    # 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",
       ),

2

Düzeni tanımlamak ve raporu jasper raporları sunucusunda yayınlamak için iReport düzenleyiciyi kullanabilirsiniz. Yayınladıktan sonra, sonuçları almak için geri kalan API'yi çağırabilirsiniz.

İşte işlevselliğin testi:

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)

Ve işte çağrı uygulamasına bir örnek:

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

1

PDF'yi html şablonundan oluşturmak için kodu alıyorum:

    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'  

0

Html şablonunuzda css ve js ile birlikte bağlam verileriniz varsa. Pdfj'leri kullanmak için iyi bir seçeneğiniz var .

Kodunuzda bunun gibi kullanabilirsiniz.

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'nizde harici veya dahili css ve js'yi bağlayabilirsiniz, en iyi kalitede pdf üretecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.