Skip to content

Instantly share code, notes, and snippets.

@grantmcconnaughey
Forked from zyegfryed/pdf.py
Last active February 8, 2021 21:50
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save grantmcconnaughey/ce90a689050c07c61c96 to your computer and use it in GitHub Desktop.
Save grantmcconnaughey/ce90a689050c07c61c96 to your computer and use it in GitHub Desktop.
Outputting PDFs with Django 1.8 and FDFgen
# -*- coding: utf-8 -*-
import codecs
import subprocess
from fdfgen import forge_fdf
from django.core.exceptions import ImproperlyConfigured
from django.template import engines
from django.template.backends.base import BaseEngine
from django.template.engine import Engine, _dirs_undefined
class PdfTemplateError(Exception):
pass
class PdftkEngine(BaseEngine):
# Going ahead and defining this, but really PDFs should still be placed
# in the templates directory of an app because the loader checks templates
app_dirname = 'pdfs'
def __init__(self, params):
params = params.copy()
options = params.pop('OPTIONS').copy()
super(PdftkEngine, self).__init__(params)
self.engine = self._Engine(self.dirs, self.app_dirs, **options)
def get_template(self, template_name, dirs=_dirs_undefined):
return PdfTemplate(self.engine.get_template(template_name, dirs))
class _Engine(Engine):
def make_origin(self, display_name, loader, name, dirs):
# Always return an Origin object, because PDFTemplate need it to
# render the PDF Form file.
from django.template.loader import LoaderOrigin
return LoaderOrigin(display_name, loader, name, dirs)
class PdfTemplate(object):
pdftk_bin = None
def __init__(self, template):
self.template = template
self.set_pdftk_bin()
@property
def origin(self):
return self.template.origin
def render(self, context=None, request=None):
if context is None:
context = {}
context = context.items()
output, err = self.fill_form(context, self.origin.name)
if err:
raise PdfTemplateError(err)
return output
def fill_form(self, fields, src, pdftk_bin=None):
fdf_stream = forge_fdf(fdf_data_strings=fields)
cmd = [self.pdftk_bin, src, 'fill_form', '-', 'output', '-', 'flatten']
cmd = ' '.join(cmd)
return self.run_cmd(cmd, fdf_stream)
def dump_data_fields(self):
cmd = [self.pdftk_bin, self.origin.name, 'dump_data_fields']
cmd = ' '.join(cmd)
output, err = self.run_cmd(cmd, None)
if err:
raise PdfTemplateError(err)
return output
def run_cmd(self, cmd, input_data):
try:
process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, shell=True)
if input_data:
return process.communicate(input=input_data)
else:
return process.communicate()
except OSError, e:
return None, e
def set_pdftk_bin(self):
if self.pdftk_bin is None:
from django.conf import settings
if not hasattr(settings, 'PDFTK_BIN'):
msg = "PDF generation requires pdftk " \
"(http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit). " \
"Edit your PDFTK_BIN settings accordingly."
raise ImproperlyConfigured(msg)
self.pdftk_bin = settings.PDFTK_BIN
return self.pdftk_bin
def version(self):
cmd = [self.pdftk_bin, '--version']
cmd = ' '.join(cmd)
output, err = self.run_cmd(cmd, None)
if err:
raise PdfTemplateError(err)
return output
def get_template(template_name):
"""
Returns a compiled Template object for the given template name,
handling template inheritance recursively.
"""
def strict_errors(exception):
raise exception
def fake_strict_errors(exception):
return (u'', -1)
# Loading hacks
# Ignore UnicodeError, due to PDF file read
codecs.register_error('strict', fake_strict_errors)
if template_name.endswith('.pdf'):
template = engines['pdf'].get_template(template_name)
else:
template = engines['django'].get_template(template_name)
# Loading hacks
codecs.register_error('strict', strict_errors)
return template
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
},
{
'BACKEND': 'my_app.pdf.PdftkEngine',
'APP_DIRS': True,
},
]
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.utils.translation import ugettext as _
from pdf import get_template
def ticket_print(request, template_name='pdf_form/pdf/foobar.pdf', **kwargs):
context = {
'foo': 'bar',
'bar': _('baz'),
}
response = HttpResponse(mimetype='application/pdf')
response['Content-Disposition'] = \
'attachment; filename=foobar.pdf'
template = get_template(template_name)
response.write(template.render(context))
return response
@maxim25
Copy link

maxim25 commented Jan 20, 2016

How would I go about filling checkboxes with this library? Otherwise this works great! Thank you for this.

@melinath
Copy link

This would be great as a released app.

@mandrews1989
Copy link

This is extremely useful grantmcconnaughey! I'm trying to implement this solution and I've run into two roadblocks.

  1. I'm having trouble specifying the correct path for 'BACKEND': 'my_app.pdf.PdftkEngine'. Is the "my_app" path the series of folders leading to the pdf.py file?

  2. How do you pass dynamic content into the "context" array of ticket_print? For example, if the user puts in his or her name in the front end and I want that to be passed in automatically.

@specialorange
Copy link

@mandrews1989 - https://docs.djangoproject.com/en/2.0/ref/settings/#templates
You can use a template backend that doesn’t ship with Django by setting BACKEND to a fully-qualified path (i.e. 'mypackage.whatever.Backend').

@pakibuy2020
Copy link

This is not working anymore in the latest version of django

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment