Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

django-weasypdf

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

django-weasypdf

A Django class-based view helper to generate PDF with WeasyPrint

  • 0.1.2
  • PyPI
  • Socket score

Maintainers
1

django-weasypdf

A Django class-based view helper to generate PDF with WeasyPrint.

Requires: WeasyPrint <https://github.com/Kozea/WeasyPrint>_

Optional requirements:

  • matplotlib (to render plots)

.. contents::

.. sectnum::

Installation

Install the package by running:

.. code:: bash

pip install django-weasypdf

or:

.. code:: bash

pip install git+https://github.com/morlandi/django-weasypdf

You will probably build your own app, for example reports, to provide custom derived Views and templates:

.. code:: bash

python manage.py startapp reports

In your settings, add:

.. code:: python

INSTALLED_APPS = [
    ...
    'reports',
    'weasypdf',
]

Note that reports is listed before weasypdf to make sure you can possibly override any template.

In your urls, add:

.. code:: python

from django.urls import path, include

urlpatterns = [
    ...
    path('reports/', include('reports.urls', namespace='reports')),
    ...

Optionally, you might want to copy the default templates from 'weasypdf/templates/weasypdf' to 'reports/templates/reports' for any required customization; see Customizing the templates_ below

A sample report

A test view has already been included in the weasypdf app to render a sample document for demonstration purposes.

To use it, add the following to your urls:

.. code:: python

path('weasypdf/', include('weasypdf.urls', namespace='weasypdf')),

then, visit:

http://127.0.0.1:8000/weasypdf/test/print/

Usage

You can copy the following to your reports app to have a sample view to start working with:

file reports/urls.py:

.. code:: python

from django.urls import path
from . import views

app_name = 'weasypdf'

urlpatterns = [
    path('test/print/', views.ReportTestView.as_view(), {'for_download': False, 'lines': 200, }, name="test-print"),
    path('test/download/', views.ReportTestView.as_view(), {'for_download': True, 'lines': 200, }, name="test-download"),
]

file reports/views.py:

.. code:: python

from weasypdf.views import WeasypdfView


class ReportView(WeasypdfView):

    #my_custom_data = None
    header_template_name = 'weasypdf/header.html'
    footer_template_name = 'weasypdf/footer.html'
    styles_template_name = 'weasypdf/styles.css'

    def get_context_data(self, **kwargs):
        context = super(ReportView, self).get_context_data(**kwargs)
        #self.my_custom_data = context.pop('my_custom_data', None)
        # context.update({
        #     'footer_line_1': config.REPORT_FOOTER_LINE_1,
        #     'footer_line_2': config.REPORT_FOOTER_LINE_2,
        # })
        return context


class ReportTestView(ReportView):
    body_template_name = 'weasypdf/pages/test.html'
    styles_template_name = 'weasypdf/pages/test.css'
    # header_template_name = None
    # footer_template_name = None
    title = "Report Test"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # Add a plot
        try:
            from .plot import build_plot_from_data
            plot_image = build_plot_from_data(data=None, as_base64=True)
            context.update({
                'plot_image': plot_image,
            })
        except:
            pass

        # Add your stuff here ...
        context.update({
            ...
        })

        return context

or replace weasypdf/header.html with reports/header.html, etc ... when using custom templates.

file reports/pages/test.html:

.. code:: html

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

{% block content %}

    <h1>Test PDF</h1>

    {% if plot_image %}
        <img class="plot" src="data:image/png;base64,{{plot_image}}">
    {% endif %}

    {% with lines=lines|default:100 %}
        {% for i in "x"|rjust:lines %}
            <div>line {{forloop.counter}} ...</div>
        {% endfor %}
    {% endwith %}

{% endblock content %}

You can now download the PDF document at:

http://127.0.0.1:8000/reports/test/download/

or open it with the browser at:

http://127.0.0.1:8000/reports/test/print/

You can inspect the HTML used for PDF rendering by appending ?format=html to the url:

http://127.0.0.1:8000/reports/test/print/?format=html

.. image:: screenshots/pdf_sample.png

Building a PDF document from a background process

A WeasypdfView.render_as_pdf_to_stream(self, base_url, extra_context, output) method is supplied for this purpose:

.. code:: python

def render_as_pdf_to_stream(self, base_url, extra_context, output):
    """
    Build the PDF document and save in into "ouput" stream.

    Automatically called when the view is invoked via HTTP (unless self.format == 'html'),
    but you can also call it explicitly from a background task:

        view = PdfTestView()
        context = view.get_context_data()
        with open(filepath, 'wb') as f:
            view.render_as_pdf_to_stream('', context, f)
    """

A sample management command to build a PDF document outside the HTML request/response cycle is available here:

weasypdf/management/commands/build_test_pdf.py <./weasypdf/management/commands/build_test_pdf.py>_

Providing "extra_context" parameters

Supply context parameters either in the urlpattern, or invoking get_context_data():

from urls.py:

.. code:: python

urlpatterns = [
    path('daily/print/', views.ReportDailyView.as_view(), {'exclude_inactives': False}, name="daily-print"),
]

from a background task:

.. code:: python

from django.core.files.base import ContentFile

# Create a View to work with
from reports.views import ReportDailyView
view = ReportDailyView()
context = view.get_context_data(
    exclude_inactives=task.exclude_inactives,
)

# Create empty file as result
filename = view.build_filename(extension="weasypdf")
task.result.save(filename, ContentFile(''))

# Open and write result
filepath = task.result.path

with open(filepath, 'wb') as f:
    view.render_as_pdf_to_stream('', context, f)

Customizing the templates

These sample files::

weasypdf
├── static
│   └── weasypdf
│       └── images
│           └── header_left.png
└── templates
    └── weasypdf
        ├── base.html
        ├── base_nomargins.html
        ├── styles.css
        ├── footer.html
        ├── header.html
        └── pages
            ├── test.css
            └── test.html

can be copied into your app's local folder reports/templates/reports, and used for any required customization:

.. code:: python

class ReportView(WeasypdfView):

    header_template_name = 'reports/header.html'
    footer_template_name = 'reports/footer.html'
    styles_template_name = 'reports/styles.css'

How to insert a page break

.. code:: html

<p style="page-break-before: always" ></p>

Adding Weasyprint to your project

Add weasyprint to your requirements::

WeasyPrint==51

and optionally to your LOGGING setting::

LOGGING = {
    ...
    'loggers': {
        ...
        'weasyprint': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

Deployment:

  1. Install Courier fonts for PDF rendering

::

# You can verify the available fonts as follows:
#    # fc-list
- name: Install Courier font for PDF rendering
    become: true
    become_user: root
    copy:
        src: deployment/project/courier.ttf
        dest: /usr/share/fonts/truetype/courier/

The font file can be downloaded here:

courier.ttf <resources/fonts/courier.ttf>_

  1. You might also need to install the following packages:

::

#weasyprint_packages:
- libffi-dev          # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- python-cffi         # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- python-dev          # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- python-pip          # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- python-lxml         # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- libcairo2           # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- libpango1.0-0       # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- libgdk-pixbuf2.0-0  # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- shared-mime-info    # http://weasyprint.readthedocs.io/en/latest/install.html#linux
- libxml2-dev         # http://stackoverflow.com/questions/6504810/how-to-install-lxml-on-ubuntu#6504860
- libxslt1-dev        # http://stackoverflow.com/questions/6504810/how-to-install-lxml-on-ubuntu#6504860

For an updated list, check here:

https://weasyprint.readthedocs.io/en/latest/install.html#linux

Customizations examples

For example you can save a custom bitmap with django-constance:

.. code :: python

CONSTANCE_ADDITIONAL_FIELDS = {
    'image_field': ['django.forms.ImageField', {}]
}

CONSTANCE_CONFIG = {
    ...
    'PDF_RECORD_LOGO': ('', 'Image for PDF logo', 'image_field'),
}

then in your header.html template:

.. code:: html

<body>
    <div class="pageHeader">
        <img class="pageLogo" title="{{ PDF_RECORD_LOGO }}" src="media://{{ PDF_RECORD_LOGO }}">
        <div class="pageTitle">{{print_date|date:'d/m/Y H:i:s'}} - {{title}}</div>
    </div>
</body>

Embed images from media (ImageFields)

If Image is a Model to keep the images you want to embed, use a templatetag like this:

.. code:: python

@register.filter
def local_image_url(image_slug):
    """
    Example:
        "/backend/images/signature_mo.png"
    """

    url = ''
    try:
        image = Image.objects.get(slug=image_slug)
        if bool(image.image):
            url = image.image.url.lstrip(settings.MEDIA_URL)
    except Image.DoesNotExist as e:
        pass

    if len(url):
        url = 'media://' + url
    else:
        url = 'static://reports/images/placeholder.png'

    return url

then, in your templates:

.. code:: html

<img class="pageLogoMiddle" src="{{'report-header-middle'|local_image_url}}">

where 'report-header-middle' is the slug used to select the image.

Adding a plot to the PDF documents

In the frontend, you have many javascript libraries available to plot data and draw fancy charts.

This doesn't help you in embedding a plot in a PDF documents built offline, however; in this case, you need to build an image server side.

An helper function has been included in this app for that purpose; to use it, matplotlib must be installed.

At the moment, it is more a POC then a complete solution; you can either use it from the package, or copy the source file weasypdf/plot.py in your project and use build_plot_from_data() as a starting point:

.. code:: python

def build_plot_from_data(data, chart_type='line', as_base64=False, dpi=300, ylabel=''):
    """
    Build a plot from given "data";
    Returns: a bitmap of the plot

    Requires:
        matplotlib

    Keyword arguments:
    data -- see sample_line_plot_data() for an example; if None, uses sample_line_plot_data()
    chart_type -- 'line', 'bar', 'horizontalBar', 'pie', 'line', 'doughnut',
    as_base64 -- if True, returns the base64 encoding of the bitmap
    dpi -- bitmap resolution
    ylabel -- optional label for Y axis

    Data layout
    ===========

    Similar to django-jchart:

    - either (shared values for x)

        {
            "labels": ["A", "B", ...],
            "x" [x1, x2, ...],
            "columns": [
                [ay1, ay2, ...],
                [by1, by2, ...],
            ],
            "colors": [
                "rgba(64, 113, 191, 0.2)",
                "rgba(191, 64, 64, 0.0)",
                "rgba(26, 179, 148, 0.0)"
            ]
        }

    - or

        {
            "labels": ["A", "B", ..., ],
            "columns": [
                [
                    {"x": ax1, "y": ay1 },
                    {"x": ax2, "y": ay2 },
                    {"x": ax3, "y": ay3 },
                ], [
                    {"x": bx1, "y": by1 },
                    {"x": bx2, "y": by2 },
                ], ...
            ],
            "colors": ["transparent", "rgba(121, 0, 0, 0.2)", "rgba(101, 0, 200, 0.2)", ]
        }

    """

then, in the view, add the resulting bitmap to context:

.. code:: python

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    try:
        from .plot import build_plot_from_data
        plot_image = build_plot_from_data(data=None, chart_type='line', as_base64=True)
        context.update({
            'plot_image': plot_image,
        })
    except:
        pass
    return context

In the template, render it as an embedded image:

.. code:: html

<style>
    .plot {
        border: 1px solid #ccc;
        width: 18cm;
        height: 6cm;
        margin: 1.0cm 0;
    }
</style>

{% if plot_image %}
    <img class="plot" src="data:image/png;base64,{{plot_image}}">
{% endif %}

Try it

The management command build_test_pdf can be used with the "--plot_data" switch to test the resulting image:

.. code:: bash

python manage.py build_test_pdf test.png -o -p '{"labels": ["sin", "cos"], "x": [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5], "columns": [[0.0, 9.09, -7.57, -2.79, 9.89, -5.44, -5.37, 9.91, -2.88, -7.51], [20.0, -13.07, -2.91, 16.88, -19.15, 8.16, 8.48, -19.25, 16.68, -2.56]]}' --plot_font Tahoma

|

.. image:: screenshots/plots.png

History

v0.1.2

  • PdfView renamed as WeasypdfView

v0.1.1

  • Rename "pdf" module as "weasypdf"
  • Sample project added

v0.1.0

  • Publish on Pypi as "django-weasypdf"
  • Support for 'line', 'bar', 'horizontalBar', 'pie', 'line', 'doughnut' plots

v0.0.5

  • Support for plot added (only "line" chart type, at the moment; requires matplotlib)
  • Customization examples added to readme

v0.0.4

  • render_to_stream() renamed as render_as_pdf_to_stream()
  • render_as_html_to_string() method added

v0.0.3

  • render_to_stream() method added to produce a PDF from a background task

v0.0.2

  • Some cleanup

v0.0.1

  • Project start

FAQs


Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc