PDF generation advanced configuration

In order to redefine the PDF layout you must create a new function in the settings.py file. For this example we will call it pdf_invoice_new. Once the new function is created you can modify it to change the default PDF layout.

In order to edit the settings.py file please see How to edit settings.py file.

def footer_pagination(canvas, doc):
    canvas.saveState()
    canvas.translate(0, 0)
    canvas.setFont('Helvetica-Bold', 6)
    width, height = pagesizes.A4
    canvas.drawCentredString(width / 2, cm, "Page {}".format(doc.page))
    canvas.restoreState()

def pdf_invoice_new(
    pdf_file, invoice_display_number, invoice_status, customer_details, company_details,
    invoice_items, invoice_totals, invoice_issue_date=None, invoice_due_date=None,
    text_after_invoice_items=None, invoice_title=None, invoice_author=None, invoice_creator=None,
    invoice_lang=None, invoice_subject=None, invoice_currency='',
):
    # Define paragraph styles
    title_style = ParagraphStyle(name='invTitleStyle',
                                 fontName='Helvetica-Bold',
                                 fontSize=16,
                                 textColor='#404040',
                                 testTransform='upper',
                                 leading=20)
    sub_title_style = ParagraphStyle(name='invTitleStyle',
                                     fontName='Helvetica-Bold',
                                     fontSize=8,
                                     leading=10,
                                     textColor='#404040',
                                     testTransform='upper')
    norm_style = ParagraphStyle(name='invNormStyle',
                                fontName='Helvetica',
                                textColor='#606060',
                                fontSize=8,
                                leading=10)
    norm_style_bold = ParagraphStyle(name='invNormStyle',
                                     fontName='Helvetica-Bold',
                                     textColor='#606060',
                                     fontSize=8,
                                     leading=10)
    totals_style_bold = ParagraphStyle(name='invNormStyle',
                                       fontName='Helvetica-Bold',
                                       textColor='#000000',
                                       fontSize=8,
                                       leading=10,
                                       alignment=enums.TA_RIGHT)
    cost_style = ParagraphStyle(name='invCostStyle',
                                fontName='Helvetica',
                                textColor='#000000',
                                fontSize=8,
                                leading=10,
                                alignment=enums.TA_CENTER)
    norm_style_right = ParagraphStyle(name='invNormStyleRight',
                                      fontName='Helvetica',
                                      textColor='#606060',
                                      fontSize=8,
                                      leading=10,
                                      alignment=enums.TA_RIGHT)
    head_style = ParagraphStyle(name='headStyle',
                                fontName='Helvetica',
                                textColor='#ffffff',
                                backColor='#404040',
                                fontSize=10,
                                leading=12)
    # Begin invoice pdf generation
    invoice_title = invoice_title or invoice_display_number
    invoice_author = invoice_author or 'Fleio Billing'
    invoice_creator = invoice_creator or 'Fleio Billing'
    invoice_lang = invoice_lang or 'en'
    invoice_subject = invoice_subject or '{} {}'.format(invoice_display_number, invoice_status)

    doc = SimpleDocTemplate(pdf_file,
                            pagesize=pagesizes.A4,
                            leftMargin=cm,
                            rightMargin=cm,
                            topMargin=cm,
                            bottomMargin=cm,
                            title=invoice_title,
                            author=invoice_author,
                            creator=invoice_creator,
                            lang=invoice_lang,
                            subject=invoice_subject)

    # noinspection PyListCreation
    pdf_story = [Spacer(1, cm)]

    # Add invoice display number and header
    pdf_story.append(Paragraph(invoice_display_number, style=title_style))
    pdf_story.append(Paragraph(invoice_status, style=sub_title_style))
    pdf_story.append(Spacer(1, cm / 3))

    # Add customer and company info
    customer_paragraph = []
    # adds issue and due dates
    if invoice_issue_date:
        customer_paragraph.append(Paragraph(_('Issue Date: {}').format(invoice_issue_date), norm_style))
    if invoice_due_date:
        customer_paragraph.append(Paragraph(_('Due Date: {}').format(invoice_due_date), norm_style))
    # adds customer info
    customer_paragraph.append(Paragraph(_('Invoiced to:'), norm_style_bold))
    customer_details = customer_details or 'Customer details missing'
    for cinf in customer_details.splitlines():
        customer_paragraph.append(Paragraph(cinf, norm_style))
    # adds company info
    company_details = company_details or 'Company details missing'
    company_paragraph = [Paragraph(cinf, norm_style_right) for cinf in company_details.splitlines()]

    invoice_info = Table(data=[[customer_paragraph, company_paragraph]],
                         style=[('VALIGN', (0, 0), (-1, -1), 'TOP'),
                                ('LEFTPADDING', (0, 0), (0, 0), 0),
                                ('RIGHTPADDING', (0, 0), (-1, -1), 0)])

    pdf_story.append(invoice_info)

    # Add invoice items
    # Set some items styles first
    table_style = [('BACKGROUND', (0, 0), (-1, 0), '#404040'),
                   ('ALIGN', (3, 0), (-1, -1), 'CENTER'),
                   ('VALIGN', (3, 0), (-1, -1), 'MIDDLE'),
                   ('TEXTCOLOR', (1, 0), (-1, 0), '#ffffff'),
                   ('SPAN', (0, 0), (2, 0)),
                   ('LINEAFTER', (0, 1), (-2, -len(invoice_totals) - 1), 0.1, '#707070', None, (2, 2, 2)),
                   ('LINEBELOW', (0, 1), (-1, -len(invoice_totals) - 1), 0.1, '#707070', None, (2, 2, 2))]

    items_table = []
    items_header = [Paragraph(_('Description'), head_style), '', '', _('Quantity'), _('Unit Price'), _('Cost')]
    items_table.append(items_header)

    last_item_num = 1
    for item in invoice_items:
        item_description = item['description']
        for option in item.get('options', []):
            item_description = '{} <br/> - {}'.format(item_description, option['display'])
            if option.get('price', 0) > 0:
                item_description = '{} ({} {})'.format(item_description, option['price'], invoice_currency)
        items_table.append([Paragraph(item_description, norm_style), '', '',
                            Paragraph(str(item.get('quantity', '1')), cost_style),
                            Paragraph(str(item.get('unit_price')), cost_style),
                            Paragraph(str(item.get('cost')), cost_style)])
        table_style.append(('SPAN', (0, last_item_num), (2, last_item_num)))
        last_item_num += 1

    # Add totals
    for inv_total in invoice_totals:
        items_table.append(['', '', '', '',
                            Paragraph(str(inv_total['name']), totals_style_bold),
                            Paragraph(str(inv_total['value']), cost_style)])
    table_style.append(('LINEAFTER', (4, last_item_num), (-2, -1), 0.1, '#707070', None, (2, 2, 2)))
    table_style.append(('LINEBELOW', (4, last_item_num), (-1, -2), 0.1, '#707070', None, (2, 2, 2)))

    invoice_items_table = Table(data=items_table,
                                style=table_style)

    pdf_story.append(invoice_items_table)

    if text_after_invoice_items:
        pdf_story.append(Paragraph(text=text_after_invoice_items, style=norm_style))

    doc.build(pdf_story, onFirstPage=footer_pagination, onLaterPages=footer_pagination)

After you create the function you will also have to solve dependencies, by adding the following lines at the begginning of the settings.py file:

from reportlab.lib import enums, pagesizes
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Spacer, Paragraph, Table
from django.utils.translation import ugettext_lazy as _

Finally, add the following line after the function that you added earlier and restart fleio, celery and uwsgi services:

PDF_INVOICE_CALLABLE = pdf_invoice_new