-
-
Save grantmcconnaughey/ce90a689050c07c61c96 to your computer and use it in GitHub Desktop.
# -*- 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 |
This would be great as a released app.
This is extremely useful grantmcconnaughey! I'm trying to implement this solution and I've run into two roadblocks.
-
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?
-
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.
@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').
This is not working anymore in the latest version of django
How would I go about filling checkboxes with this library? Otherwise this works great! Thank you for this.