Delay when generating a PDF with Python and Reportlab

0

My doubt is in the time that my application is delayed at the moment I want to generate a PDF report, with python and reportlab. while they are up to 20 pages the application does not take long, in just seconds it generates the report in PDF, but when the report that is going to be generated has approximately 70 pages or more, the time exceeds 3 minutes, the problem is that the application generates reports of up to 1000 pages or more, how can I solve this, or what other alternative do I have to generate this type of PDF reports?

I attach the report code ...

__author__ = "FelipeMedel"
from reportlab.pdfgen import canvas    
from reportlab.lib.pagesizes import letter, portrait    
from reportlab.lib.units import cm, mm    
from reportlab.pdfbase import pdfmetrics    
from reportlab.pdfbase.ttfonts import TTFont    
from reportlab.platypus import Paragraph, Table, BaseDocTemplate, Frame, PageTemplate, Image, TableStyle    
from reportlab.lib.styles import ParagraphStyle    
from reportlab.lib.enums import TA_JUSTIFY, TA_RIGHT, TA_CENTER, TA_LEFT    
import os    
from .... import session    
import uuid    
from ....models import DefaultValue    
from ....utils.math_ext import _round    
from ....utils.image_converter import ImagesConverter    
from ...libs.functions import paragraph_over_flow, paragraph_over_flow_height    
import time    

pdfmetrics.registerFont(TTFont('Arial', 'Arial.ttf'))    
pdfmetrics.registerFont(TTFont('Arial-Bold', 'Arial_Bold.ttf'))    
pdfmetrics.registerFontFamily(
    'Arial',
    normal='Arial',
    bold='Arial-Bold'
)

class NumberedCanvas(canvas.Canvas):
    def __init__(self, *args, **kwargs):
        canvas.Canvas.__init__(self, *args, **kwargs)
        self._saved_page_states = []

    def showPage(self):
        self._saved_page_states.append(dict(self.__dict__))
        self._startPage()

    def save(self):
        num_pages = len(self._saved_page_states)
        for state in self._saved_page_states:
            self.__dict__.update(state)
            self.draw_page_footer_n_header(num_pages)
            canvas.Canvas.showPage(self)
        canvas.Canvas.save(self)

    def draw_page_footer_n_header(self, page_count):
        preview_data = AccountingYearClosePreview.preview_data

        # TABLAS HEADER -----------------------------------------------------------------------------------------

        style_table_company = TableStyle(
            [
                # ('BOX', (0, 0), (-1, -1), 1, (0, 0, 0)),
                # ('GRID', (0, 0), (-1, -1), 1, (0, 0, 0)),
                ('LEFTPADDING', (0, 0), (-1, -1), 2.5 * cm),
                ('RIGHTPADDING', (0, 0), (-1, -1), 2.5 * cm),
                ('TOPPADDING', (0, 0), (-1, -1), 0.05 * cm),
                ('VALIGN', (0, 0), (-1, -1), 'TOP')
            ]
        )

        # TABLAS HEADER - FIN ------------------------------------------------------------------------------------

        # ESTILOS HEADER ------------------------------------------------------------------------------------------

        texto_company = ParagraphStyle('Normal')
        texto_company.fontName = 'Arial'
        texto_company.fontSize = 12
        texto_company.leading = 12
        texto_company.alignment = TA_CENTER

        texto_company_nit = ParagraphStyle('Normal')
        texto_company_nit.fontName = 'Arial'
        texto_company_nit.fontSize = 10
        texto_company_nit.leading = 10
        texto_company_nit.alignment = TA_CENTER

        texto_derecha = ParagraphStyle('Normal')
        texto_derecha.fontName = 'Arial'
        texto_derecha.fontSize = 8
        texto_derecha.leading = 8
        texto_derecha.alignment = TA_RIGHT

        texto_izquierda = ParagraphStyle('Normal')
        texto_izquierda.fontName = 'Arial'
        texto_izquierda.fontSize = 8
        texto_izquierda.leading = 8
        texto_izquierda.alignment = TA_LEFT

        texto_centro = ParagraphStyle('Normal')
        texto_centro.fontName = 'Arial'
        texto_centro.fontSize = 8
        texto_centro.leading = 8
        texto_centro.alignment = TA_CENTER

        texto_justificado = ParagraphStyle('Normal')
        texto_justificado.fontName = 'Arial'
        texto_justificado.fontSize = 8
        texto_justificado.leading = 8
        texto_justificado.alignment = TA_JUSTIFY

        # ESTILOS HEADER - FIN ------------------------------------------------------------------------------------

        def header(cnv):
            cnv.saveState()

            # TABLA DATOS COMPAÑIA --------------------------------------------------------------------------------

            company_name = '{0} - {1}'.format(preview_data['company_name'], preview_data['branch_name'])
            nit = preview_data['nit'].split('-')

            x = 0 if preview_data['libro_1'] == '0' else 1
            y = 0 if preview_data['libro_2'] == '0' else 1

            datos_company = [
                [
                    Paragraph('<b>{0}</b>'.format(
                        paragraph_over_flow_height(text='' if x == 0 and y == 0 else company_name.upper(),
                                                   width=12.8,
                                                   no_par=3,
                                                   font_size=12,
                                                   leading=12,
                                                   left_indent=2.8,
                                                   right_indent=2.8)), texto_company)
                ],
                [
                    Paragraph('' if x == 0 and y == 0 else '<b>{0}-{1}</b>'.format('.'.join([str(nit[0])[i:i + 3]
                                           for i in range(0, len(str(nit[0])), 3)]), nit[1]), texto_company_nit)
                ]
            ]

            tabla_company = Table(
                datos_company,
                [19.6 * cm],
                style=style_table_company
            )

            tabla_company.wrap(19.6 * cm, 4 * cm)
            tabla_company.drawOn(cnv, 1 * cm, 25.5 * cm)

            # TABLA DATOS COMPAÑIA - FIN --------------------------------------------------------------------------

            # LOGO ------------------------------------------------------------------------------------------------

            image = None if preview_data['image'] is None \
                else "{0},{1}".format("data:image/*;base64",
                                      ImagesConverter.img_convert_to_base64(preview_data['image'].image))

            if image is not None:
                img = Image(image, width=2.3 * cm, height=2.3 * cm)
                img.wrap(2.3 * cm, 2.3 * cm)
                img.drawOn(cnv, 1 * cm, 24.5 * cm)

            # LOGO - FIN ------------------------------------------------------------------------------------------

            # FECHA Y PAGINADO ------------------------------------------------------------------------------------

            page_number = Paragraph('Página {0} de {1}'.format(self._pageNumber, page_count), texto_derecha)
            page_number.wrap(3 * cm, 0.5 * cm)
            page_number.drawOn(cnv, 17.6 * cm, 25.1 * cm)

            # FECHA Y PAGINADO - FIN ------------------------------------------------------------------------------

            cnv.restoreState()

        def footer(cnv):
            cnv.saveState()

            # FACTURA Y FECHA -------------------------------------------------------------------------------------

            fecha_hora = Paragraph(time.strftime("%d/%m/%Y %I:%M:%S %p"), texto_derecha)
            fecha_hora.wrap(4 * cm, 1 * cm)
            fecha_hora.drawOn(cnv, 16.6 * cm, 1 * cm)

            # FACTURA Y FECHA - FIN -------------------------------------------------------------------------------

            # VALIDACION PARA DOCUMENTO ANULADO -------------------------------------------------------------------

            if preview_data['annuled']:
                msm = 'ANULADO'
                anulado(cnv, msm)

            # VALIDACION PARA DOCUMENTO ANULADO - FIN -------------------------------------------------------------

            cnv.restoreState()

        def anulado(cvn, cadena):
            cvn.saveState()

            cvn.translate(18.2 * cm, 21 * cm)
            cvn.setFontSize(100, 30)
            cvn.setFillColorRGB(1, 0, 0, alpha=0.3)
            cvn.rotate(35)
            cvn.drawRightString(0, 0, cadena)

            cvn.restoreState()

        header(self)
        footer(self)


class AccountingYearClosePreview:

    preview_data = None

    @staticmethod
    def make_preview_pdf(preview_data):
        AccountingYearClosePreview.preview_data = preview_data

        outfilename = "{0}.pdf".format(uuid.uuid1())
        outfiledir = os.getcwd().replace('aplicacion/registros', 'Assets/documents')
        outfilepath = os.path.join(outfiledir, outfilename)

        # ESTILOS TABLA DETALLES ----------------------------------------------------------------------------------

        style_table_descripcion = TableStyle(
            [
                # ('BOX', (0, 0), (-1, -1), 1, (0, 0, 0)),
                # ('GRID', (0, 0), (-1, -1), 1, (0, 0, 0)),
                ('LEFTPADDING', (0, 0), (-1, -1), 0),
                ('RIGHTPADDING', (0, 0), (5, -1), 0),
                ('TOPPADDING', (0, 0), (-1, -1), 0),
                ('LINEABOVE', (0, 1), (5, 1), 1, (0, 0, 0)),
                ('LINEABOVE', (0, 0), (5, 0), 1, (0, 0, 0)),
                ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
                ('SPAN', (0, 1), (5, 1)),
                ('SPAN', (0, 2), (5, 2)),
            ]
        )

        estilo = TableStyle(
            [
                # ('BOX', (0, 0), (-1, -1), 1, (0, 0, 0)),
                # ('GRID', (0, 0), (-1, -1), 1, (0, 0, 0)),
                ('LEFTPADDING', (0, 0), (-1, -1), 0),
                ('LEFTPADDING', (1, 0), (-1, -1), 0.1 * cm),
                ('RIGHTPADDING', (0, 0), (-1, -1), 0),
                ('RIGHTPADDING', (0, 0), (0, -1), 0.1 * cm),
                ('TOPPADDING', (0, 0), (-1, -1), 0),
                ('VALIGN', (0, 0), (-1, -1), 'TOP')
            ]
        )

        estilo_total = TableStyle(
            [
                # ('BOX', (0, 0), (-1, -1), 1, (0, 0, 0)),
                # ('GRID', (0, 0), (-1, -1), 1, (0, 0, 0)),
                ('LEFTPADDING', (0, 0), (-1, -1), 0),
                ('RIGHTPADDING', (0, 0), (-1, -1), 0),
                ('TOPPADDING', (0, 0), (-1, -1), 0),
                ('LINEABOVE', (0, 0), (-1, 0), 1, (0, 0, 0)),
                ('LINEABOVE', (0, 2), (-1, 2), 1, (0, 0, 0))
            ]
        )

        estilo_cabecera = TableStyle(
            [
                ('LEFTPADDING', (0, 0), (-1, -1), 0),
                ('RIGHTPADDING', (0, 0), (-1, -1), 0),
                ('TOPPADDING', (0, 0), (-1, -1), 0),
            ]
        )

        text_right = ParagraphStyle('Normal')
        text_right.fontName = 'Arial'
        text_right.fontSize = 8
        text_right.leading = 8
        text_right.alignment = TA_RIGHT

        text_left = ParagraphStyle('Normal')
        text_left.fontName = 'Arial'
        text_left.fontSize = 8
        text_left.leading = 8
        text_left.alignment = TA_LEFT

        text_center = ParagraphStyle('Normal')
        text_center.fontName = 'Arial'
        text_center.fontSize = 8
        text_center.leading = 8
        text_center.alignment = TA_CENTER

        text_center_contabilidad = ParagraphStyle('Normal')
        text_center_contabilidad.fontName = 'Arial'
        text_center_contabilidad.fontSize = 12
        text_center_contabilidad.leading = 12
        text_center_contabilidad.alignment = TA_CENTER

        # ESTILOS TABLA DETALLES - FIN ------------------------------------------------------------------------

        # VARIABLES LIBROS  -----------------------------------------------------------------------------------

        x = 0 if preview_data['libro_1'] == '0' else 1
        y = 0 if preview_data['libro_2'] == '0' else 1

        # VARIABLES LIBROS - FIN ------------------------------------------------------------------------------

        # TABLA DESCRIPCION -----------------------------------------------------------------------------------

        if (x == 1 and y == 0) or (y == 1 and x == 0):

            if x == 0 and y == 1:
                contabilizacion = 'LIBRO 2'
            else:
                contabilizacion = 'LIBRO 1'

            datos_cabeza = [
                [
                    Paragraph('', text_center),
                ],
                [
                    Paragraph('<b>{0}</b>'.format(contabilizacion), text_center_contabilidad),
                ],
                [
                    Paragraph('', text_center)
                ]
            ]

            datos_descripcion = [
                [
                    Paragraph('<b>NÚMERO</b>', text_left),
                    Paragraph('<b>DOCUMENTO</b>', text_right),
                    Paragraph('<b>FECHA</b>', text_right),
                    Paragraph('<b>CANTIDAD</b>', text_right),
                    Paragraph('<b>VALOR</b>', text_right),
                    Paragraph('<b>TOTAL</b>', text_right)
                ],
                [
                    Paragraph('<b>{0}</b>'.format('texto de prueba'.upper()), text_left),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right)
                ],
                [
                    Paragraph('<b>OTRO TEXTO</b>'.upper(), text_left),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right)
                ]
            ]

        # TABLA DESCRIPCION - FIN -----------------------------------------------------------------------------

        # DETALLES ------------------------------------------------------------------------------------------------

        datos = []

        if (x == 1 and y == 0) or (x == 0 and y == 1):

            for i in range(20):

                datos.append(
                    [
                        Paragraph('0000 00 000', text_left),
                        Paragraph('ASD 0123456789', text_right),
                        Paragraph(time.strftime('%d/%m/%Y'), text_right),
                        Paragraph('{:20,.{}f}'.format(_round(1, default_decimals.quantityDecimals),
                                                      default_decimals.quantityDecimals), text_right),
                        Paragraph('{:20,.{}f}'.format(_round(1234567890, default_decimals.valueDecimals),
                                                      default_decimals.valueDecimals), text_right),
                        Paragraph('{:20,.{}f}'.format(_round(1234567890, default_decimals.valueDecimals),
                                                      default_decimals.valueDecimals), text_right)
                    ]
                )
        else:
            vacio = []
            vacio.append(
                [
                    Paragraph('', text_left),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right)
                ]
            )

        # DETALLES - FIN ------------------------------------------------------------------------------------------

        # TOTAL ---------------------------------------------------------------------------------------------------

        # datos_total = []

        if (x == 1 and y == 0) or (x == 0 and y == 1):

            datos_total = [
                [
                    Paragraph('', text_left),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('<b>TOTAL</b>', text_right),
                    Paragraph('<b>{:20,.{}f}</b>'.format(_round(0, default_decimals.valueDecimals),
                                                  default_decimals.valueDecimals), text_right),
                    Paragraph('<b>{:20,.{}f}</b>'.format(_round(0, default_decimals.valueDecimals),
                                                  default_decimals.valueDecimals), text_right),
                ],
                [
                    Paragraph('', text_left),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('', text_right),
                    Paragraph('<b>{:20,.{}f}</b>'.format(_round(0, default_decimals.valueDecimals),
                                                  default_decimals.valueDecimals), text_right),
                ]
            ]

        # TOTAL - FIN ---------------------------------------------------------------------------------------------

        doc = BaseDocTemplate(outfilepath, pagesize=portrait(letter))

        doc.addPageTemplates(
            [
                PageTemplate(
                    frames=[
                        Frame(
                            x1=1 * cm,
                            y1=1.6 * cm,
                            width=19.6 * cm,
                            height=23.3 * cm,
                            leftPadding=0,
                            rightPadding=0,
                            bottomPadding=0,
                            topPadding=0,
                            id=None,
                            showBoundary=False
                        )
                    ]
                )
            ]
        )

        if (x == 1 and y == 0) or (x == 0 and y == 1):

            doc.build(
                [Table(
                    datos_cabeza,
                    [19.6 * cm],
                    [0.1 * cm, 0.8 * cm, 0.4 * cm],
                    style=estilo_cabecera
                ), Table(
                    datos_descripcion,
                    [6.6 * cm, 2.5 * cm, 1.6 * cm, 1.7 * cm, 3.6 * cm, 3.6 * cm],
                    0.5 * cm,
                    style=style_table_descripcion
                ), Table(
                    datos,
                    [6.6 * cm, 2.5 * cm, 1.6 * cm, 1.7 * cm, 3.6 * cm, 3.6 * cm],
                    style=estilo
                ), Table(
                    datos_total,
                    [6.6 * cm, 2.5 * cm, 1.6 * cm, 1.7 * cm, 3.6 * cm, 3.6 * cm],
                    style=estilo_total
                )], canvasmaker=NumberedCanvas
            )
        else:
            doc.build(
                [Table(
                    vacio,
                    [6.6 * cm, 2.5 * cm, 1.6 * cm, 1.7 * cm, 3.6 * cm, 3.6 * cm],
                    style=style_table_descripcion
                )], canvasmaker=NumberedCanvas
            )

        return "{0}".format(outfi

lename)

    
asked by FelipeMedel 01.11.2017 в 14:47
source

2 answers

-1

Well, for some time now I was searching and looking for a possible solution, for the delay in the process of generating a report, so trying and testing several libraries, I found a call FPDF , information here , likewise if you want to have a clearer idea of the options that can be used to make the reports, in this link will find the Reference Manual , among all the options that I tested, this was the best, the system that I am finishing to develop is accountant, so for accounting movements it helped me a lot, 1000 Pages in 7.39 seconds , the disadvantage that has, is that it's up to make the report to code, but the result is satisfactory ...

    
answered by 21.12.2017 / 22:06
source
0

As you already know, Python is an interpreted language, and you see where all the magic of python and its functions comes from, while in other languages an implementation would take many lines, in Python it is really simple but the runtime is much greater than that of a compiled language. But finally take these considerations.

  • You can encapsulate the feature with cython
  • Consider doing background tasks with celery
  • Currently I use celery , since the same thing takes too long, so I sent the task for execution and when ends sending an email to the user notifying that their report is ready to download.

        
    answered by 01.11.2017 в 15:03