Kết xuất HTML sang PDF trong trang Django


117

Đối với trang web được cung cấp bởi django của tôi, tôi đang tìm kiếm một giải pháp dễ dàng để chuyển đổi các trang html động sang pdf.

Các trang bao gồm HTML và biểu đồ từ API trực quan hóa của Google (dựa trên javascript, nhưng việc bao gồm những biểu đồ đó là điều bắt buộc).


Tài liệu về Django rất sâu và bao gồm rất nhiều thứ. Bạn có gặp vấn đề gì với phương pháp được đề xuất ở đó không? http://docs.djangoproject.com/en/dev/howto/outputting-pdf/
Monut

1
Điều này không thực sự trả lời câu hỏi. Tài liệu đó hướng dẫn cách hiển thị PDF nguyên bản, không phải từ HTML được hiển thị.
Josh

Tôi nghĩ rằng điều đúng đắn cần làm là làm cho các trình duyệt tạo ra pdf vì chúng là những người duy nhất thực hiện hiển thị html / css / js đúng cách. xem câu hỏi này stackoverflow.com/q/25574082/39998
David Hofmann

Câu hỏi này lạc đề ở SO, nhưng lại lạc đề trong phần mềm.SE. Xem Làm cách nào để chuyển đổi HTML với CSS sang PDF? .
Martin Thoma

hãy thử sử dụng wkhtmltopdf learnbatta.com/blog/...
anjaneyulubatta505

Câu trả lời:


206

Hãy thử giải pháp từ Reportlab .

Tải xuống và cài đặt nó như bình thường với python setup.py install

Bạn cũng sẽ cần cài đặt các mô-đun sau: xhtml2pdf, html5lib, pypdf với easy_install.

Đây là một ví dụ sử dụng:

Đầu tiên xác định chức năng này:

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

Sau đó, bạn có thể sử dụng nó như thế này:

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

Bản mẫu:

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

Hy vọng nó giúp.


9
+1 Tôi đã sử dụng giải pháp này được một năm và nó rất tuyệt. PISA thậm chí có thể tạo mã vạch bằng một thẻ đơn giản, và nhiều hơn thế nữa. Và nó dễ dàng .
arcanum

1
Bạn ơi, reportlab là pita để cài đặt trên windows 7 64bit, python2.7 64bit. Vẫn đang cố gắng ...
Andriy Drozdyuk

5
Dường như không chạy Javascript.
dfrankow 20/02/12

3
pisa hiện đang phân phối như xhtml2pdf
Pablo Albornoz

12
Trong python3, ngoại trừ chuyển đổi cStringIO.StringIOsang io.StringIO, chúng ta phải xác định resultresult = io.BytesIO()thay vì result = StringIO.
Sebastien

12

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

Bản mẫu:

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

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

Lượt xem:

from easy_pdf.views import PDFTemplateView

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

Nếu bạn muốn sử dụng django-easy-pdf trên Python 3, hãy kiểm tra giải pháp được đề xuất tại đây .


2
Đây là tùy chọn dễ thực hiện nhất trong số các tùy chọn mà tôi đã thử cho đến nay. Đối với nhu cầu của tôi (tạo báo cáo pdf từ phiên bản html), điều này chỉ hoạt động. Cảm ơn!
The NetYeti

1
@alejoss Bạn nên sử dụng các kiểu nội tuyến thay vì CSS.
digz6666

Giải pháp này có thể không hoạt động ngay lập tức cho django 3.0 vì django-utils-six bị xóa nhưng easy_pdf phụ thuộc vào điều đó.
David

11

Tôi chỉ đánh cái này cho CBV. Không được sử dụng trong sản xuất nhưng tạo tệp PDF cho tôi. Có lẽ cần phải làm việc cho phía báo cáo lỗi của mọi thứ nhưng không lừa được cho đến nay.

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

Được sử dụng như:

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

1
Điều này đã làm việc gần như thẳng thắn đối với tôi. Điều duy nhất là để thay thế html.encode("ISO-8859-1")bởihtml.decode("utf-8")
vinyll

Tôi đã thay đổi mã như @vinyll đã đề cập và ngoài ra phải thêm một dòng vào lớp PDFTemplateView:content_type = "application/pdf"
normic

11

Hãy thử wkhtmltopdf bằng một trong các trình bao bọc sau

django-wkhtmltopdf hoặc python-pdfkit

Điều này rất hiệu quả đối với tôi, hỗ trợ javascript và css hoặc bất cứ thứ gì cho vấn đề đó mà trình duyệt webkit hỗ trợ.

Để biết hướng dẫn chi tiết hơn, vui lòng xem bài đăng trên blog này


Làm thế nào về svg được nhúng trong html, nó cũng được hỗ trợ?
mehmet


Chỉ cần cẩn thận, webkit không hỗ trợ tất cả mọi thứ chrome / firefox làm: webkit.org/status
Mehmet

1
django-wkhtmltopdf đã làm điều kỳ diệu cho tôi! cũng hãy nhớ tắt tất cả các hoạt ảnh mà công cụ lập biểu đồ / javascript của bạn thực hiện.
mehmet

@mehmet nó không hỗ trợ js biểu đồ thanh đơn giản của tôi. Tôi có rất nhiều lỗi. Bạn có thể giúp mình với ??
Manish Ojha

3

Sau khi cố gắng làm cho điều này hoạt động trong quá nhiều giờ, cuối cùng tôi đã tìm thấy điều này: https://github.com/vierno/django-xhtml2pdf

Đó là một nhánh của https://github.com/chrisglass/django-xhtml2pdf cung cấp một mixin cho chế độ xem dựa trên lớp chung. Tôi đã sử dụng nó như thế này:

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

Sử dụng tên mô hình mà bạn đã xác định trong dạng xem của mình bằng tất cả chữ thường khi điền các trường mẫu. Vì nó là GCBV, bạn chỉ có thể gọi nó là '.as_view' trong 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",
       ),

2

Bạn có thể sử dụng trình chỉnh sửa iReport để xác định bố cục và xuất bản báo cáo trong máy chủ báo cáo jasper. Sau khi xuất bản, bạn có thể gọi phần api còn lại để nhận kết quả.

Đây là bài kiểm tra chức năng:

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)

Và đây là một ví dụ về việc triển khai lời gọi:

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

Tôi nhận được mã để tạo PDF từ mẫu html:

    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

Nếu bạn có dữ liệu ngữ cảnh cùng với css và js trong mẫu html của mình. Hơn bạn có lựa chọn tốt để sử dụng pdfjs .

Trong mã của bạn, bạn có thể sử dụng như thế này.

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

Trong HTML của bạn, bạn có thể liên kết css và js bên ngoài hoặc nội bộ, nó sẽ tạo ra chất lượng tốt nhất của pdf.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.