django-wkhtmltopdf-3.3.0/ 0000755 0000766 0000024 00000000000 13610346024 015230 5 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/PKG-INFO 0000644 0000766 0000024 00000006313 13610346024 016330 0 ustar max staff 0000000 0000000 Metadata-Version: 1.1
Name: django-wkhtmltopdf
Version: 3.3.0
Summary: Converts HTML to PDF using wkhtmltopdf.
Home-page: https://github.com/incuna/django-wkhtmltopdf
Author: Incuna Ltd
Author-email: admin@incuna.com
License: BSD-2-Clause
Description: django-wkhtmltopdf
==================
.. image:: https://badge.fury.io/py/django-wkhtmltopdf.png
:target: http://badge.fury.io/py/django-wkhtmltopdf
:alt: Latest version
.. image:: https://travis-ci.org/incuna/django-wkhtmltopdf.png?branch=master
:target: https://travis-ci.org/incuna/django-wkhtmltopdf
:alt: Travis-CI
.. image:: https://img.shields.io/pypi/dm/django-wkhtmltopdf.svg
:target: https://badge.fury.io/py/django-wkhtmltopdf
:alt: Number of PyPI downloads on a month
Converts HTML to PDF
--------------------
Provides Django views to wrap the HTML to PDF conversion of the `wkhtmltopdf `_ binary.
Requirements
------------
Install the `wkhtmltopdf static binary `_.
This requires libfontconfig (on Ubuntu: ``sudo aptitude install libfontconfig``).
Python 2.6+ and 3.3+ are supported.
Installation
------------
Run ``pip install django-wkhtmltopdf``.
Add ``'wkhtmltopdf'`` to ``INSTALLED_APPS`` in your ``settings.py``.
By default it will execute the first ``wkhtmltopdf`` command found on your ``PATH``.
If you can't add wkhtmltopdf to your ``PATH``, you can set ``WKHTMLTOPDF_CMD`` to a
specific executable:
e.g. in ``settings.py``: ::
WKHTMLTOPDF_CMD = '/path/to/my/wkhtmltopdf'
or alternatively as env variable: ::
export WKHTMLTOPDF_CMD=/path/to/my/wkhtmltopdf
You may also set ``WKHTMLTOPDF_CMD_OPTIONS`` in ``settings.py`` to a dictionary
of default command-line options.
The default is: ::
WKHTMLTOPDF_CMD_OPTIONS = {
'quiet': True,
}
Documentation
-------------
Documentation is available at http://django-wkhtmltopdf.readthedocs.org/en/latest/.
License
-------
MIT licensed. See the bundled `LICENSE `_ file for more details.
Keywords: django wkhtmltopdf pdf
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Framework :: Django
django-wkhtmltopdf-3.3.0/wkhtmltopdf/ 0000755 0000766 0000024 00000000000 13610346024 017573 5 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/wkhtmltopdf/models.py 0000644 0000766 0000024 00000000000 11726444373 021432 0 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/wkhtmltopdf/subprocess.py 0000644 0000766 0000024 00000003175 12627330541 022347 0 ustar max staff 0000000 0000000 from __future__ import absolute_import
from subprocess import *
# Provide Python 2.7's check_output() function.
try:
check_output
except NameError:
def check_output(*popenargs, **kwargs):
r"""Run command with arguments and return its output as a byte string.
If the exit code was non-zero it raises a CalledProcessError. The
CalledProcessError object will have the return code in the returncode
attribute and output in the output attribute.
The arguments are the same as for the Popen constructor. Example:
>>> check_output(["ls", "-l", "/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
The stdout argument is not allowed as it is used internally.
To capture standard error in the result, use stderr=STDOUT.
>>> check_output(["/bin/sh", "-c",
... "ls -l non_existent_file ; exit 0"],
... stderr=STDOUT)
'ls: non_existent_file: No such file or directory\n'
"""
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = Popen(stdout=PIPE, *popenargs, **kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
error = CalledProcessError(retcode, cmd)
# Add the output attribute to CalledProcessError, which
# doesn't exist until Python 2.7.
error.output = output
raise error
return output
django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/ 0000755 0000766 0000024 00000000000 13610346024 020735 5 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/run.py 0000644 0000766 0000024 00000002550 13305565365 022131 0 ustar max staff 0000000 0000000 #! /usr/bin/env python
import os
import sys
import django
from django.conf import settings
DIRNAME = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.getcwd())
settings.configure(
DEBUG=False,
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
},
INSTALLED_APPS=(
'django.contrib.contenttypes',
'wkhtmltopdf.tests',
'wkhtmltopdf',
),
MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
),
MEDIA_ROOT=os.path.join(DIRNAME, 'media'),
MEDIA_URL='/media/',
STATIC_ROOT=os.path.join(DIRNAME, 'static'),
STATIC_URL='/static/',
TEMPLATES = [ # For Django >= 1.10. Ignored in lower versions
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'DIRS': [],
'OPTIONS': {},
},
],
WKHTMLTOPDF_DEBUG=False,
)
try:
django.setup()
except AttributeError:
pass # Django < 1.7; okay to ignore
try:
from django.test.runner import DiscoverRunner
except ImportError:
from discover_runner.runner import DiscoverRunner
test_runner = DiscoverRunner(verbosity=1)
failures = test_runner.run_tests(['wkhtmltopdf'])
if failures:
sys.exit(1)
django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/models.py 0000644 0000766 0000024 00000000000 12627330541 022564 0 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/__init__.py 0000644 0000766 0000024 00000000000 12627330541 023040 0 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/templates/ 0000755 0000766 0000024 00000000000 13610346024 022733 5 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/templates/sample.html 0000644 0000766 0000024 00000000132 12627330541 025102 0 ustar max staff 0000000 0000000
{{ title }}
django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/templates/context.html 0000644 0000766 0000024 00000000132 13305565365 025315 0 ustar max staff 0000000 0000000
{{ debug }}
django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/templates/unicode.html 0000644 0000766 0000024 00000000236 12627330541 025254 0 ustar max staff 0000000 0000000
☃
django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/templates/footer.html 0000644 0000766 0000024 00000000226 13305565365 025133 0 ustar max staff 0000000 0000000 {% load static %}
django-wkhtmltopdf-3.3.0/wkhtmltopdf/tests/tests.py 0000644 0000766 0000024 00000045631 13610345727 022473 0 ustar max staff 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import absolute_import
import os
import sys
from django.conf import settings
from django.template import loader, RequestContext
from django.test import TestCase
from django.test.utils import override_settings
from django.test.client import RequestFactory
from django.utils.encoding import smart_str
import six
from wkhtmltopdf.subprocess import CalledProcessError
from wkhtmltopdf.utils import (_options_to_args, make_absolute_paths,
wkhtmltopdf, render_pdf_from_template,
render_to_temporary_file, RenderedFile)
from wkhtmltopdf.views import PDFResponse, PDFTemplateView, PDFTemplateResponse
class UnicodeContentPDFTemplateView(PDFTemplateView):
"""
PDFTemplateView with the addition of unicode content in his context.
Used in unicode content view testing.
"""
def get_context_data(self, **kwargs):
Base = super(UnicodeContentPDFTemplateView, self)
context = Base.get_context_data(**kwargs)
context['title'] = u'♥'
return context
class TestUtils(TestCase):
def setUp(self):
# Clear standard error
self._stderr = sys.stderr
sys.stderr = open(os.devnull, 'w')
self.factory = RequestFactory()
def tearDown(self):
sys.stderr = self._stderr
def test_options_to_args(self):
self.assertEqual(_options_to_args(), [])
self.assertEqual(_options_to_args(heart=u'♥', verbose=True,
file_name='file-name'),
['--file-name', 'file-name',
'--heart', u'♥',
'--verbose'])
self.assertEqual(_options_to_args(heart=u'♥', quiet=True,
file_name='file-name'),
['--file-name', 'file-name',
'--heart', u'♥',
'--quiet'])
self.assertEqual(_options_to_args(heart=u'♥', quiet=False,
file_name='file-name'),
['--file-name', 'file-name',
'--heart', u'♥'])
def test_wkhtmltopdf(self):
"""Should run wkhtmltopdf to generate a PDF"""
title = 'A test template.'
template = loader.get_template('sample.html')
temp_file = render_to_temporary_file(template, context={'title': title})
try:
# Standard call
pdf_output = wkhtmltopdf(pages=[temp_file.name])
self.assertTrue(pdf_output.startswith(b'%PDF'), pdf_output)
# Single page
pdf_output = wkhtmltopdf(pages=temp_file.name)
self.assertTrue(pdf_output.startswith(b'%PDF'), pdf_output)
# Unicode
pdf_output = wkhtmltopdf(pages=[temp_file.name], title=u'♥')
self.assertTrue(pdf_output.startswith(b'%PDF'), pdf_output)
# Invalid arguments
self.assertRaises(CalledProcessError,
wkhtmltopdf, pages=[])
finally:
temp_file.close()
def test_wkhtmltopdf_with_unicode_content(self):
"""A wkhtmltopdf call should render unicode content properly"""
title = u'♥'
template = loader.get_template('unicode.html')
temp_file = render_to_temporary_file(template, context={'title': title})
try:
pdf_output = wkhtmltopdf(pages=[temp_file.name])
self.assertTrue(pdf_output.startswith(b'%PDF'), pdf_output)
finally:
temp_file.close()
def test_render_to_temporary_file(self):
"""Should render a template to a temporary file."""
title = 'A test template.'
template = loader.get_template('sample.html')
temp_file = render_to_temporary_file(template, context={'title': title})
temp_file.seek(0)
saved_content = smart_str(temp_file.read())
self.assertTrue(title in saved_content)
temp_file.close()
def _render_file(self, template, context):
"""Helper method for testing rendered file deleted/persists tests."""
render = RenderedFile(template=template, context=context)
render.temporary_file.seek(0)
saved_content = smart_str(render.temporary_file.read())
return (saved_content, render.filename)
def test_rendered_file_deleted_on_production(self):
"""If WKHTMLTOPDF_DEBUG=False, delete rendered file on object close."""
title = 'A test template.'
template = loader.get_template('sample.html')
debug = getattr(settings, 'WKHTMLTOPDF_DEBUG', settings.DEBUG)
saved_content, filename = self._render_file(template=template,
context={'title': title})
# First verify temp file was rendered correctly.
self.assertTrue(title in saved_content)
# Then check if file is deleted when debug=False.
self.assertFalse(debug)
self.assertFalse(os.path.isfile(filename))
def test_rendered_file_persists_on_debug(self):
"""If WKHTMLTOPDF_DEBUG=True, the rendered file should persist."""
title = 'A test template.'
template = loader.get_template('sample.html')
with self.settings(WKHTMLTOPDF_DEBUG=True):
debug = getattr(settings, 'WKHTMLTOPDF_DEBUG', settings.DEBUG)
saved_content, filename = self._render_file(template=template,
context={'title': title})
# First verify temp file was rendered correctly.
self.assertTrue(title in saved_content)
# Then check if file persists when debug=True.
self.assertTrue(debug)
self.assertTrue(os.path.isfile(filename))
def test_render_with_null_request(self):
"""If request=None, the file should render properly."""
title = 'A test template.'
template = loader.get_template('sample.html')
pdf_content = render_pdf_from_template('sample.html',
header_template=None,
footer_template=None,
context={'title': title})
self.assertTrue(pdf_content.startswith(b'%PDF-'))
self.assertTrue(pdf_content.endswith(b'%%EOF\n'))
@override_settings(STATIC_URL='/static/', STATIC_ROOT='path/to/some/dir')
def test_make_absolute_paths(self):
"""
Regression test for https://github.com/incuna/django-wkhtmltopdf/issues/22
"""
content = """
"""
expected = """
"""
self.assertEqual(make_absolute_paths(content), expected)
class TestViews(TestCase):
template = 'sample.html'
context_template = 'context.html'
footer_template = 'footer.html'
pdf_filename = 'output.pdf'
attached_fileheader = 'attachment; filename="{0}"'
inline_fileheader = 'inline; filename="{0}"'
def test_pdf_response(self):
"""Should generate correct HttpResponse object and content type."""
# 404
response = PDFResponse(content='', status=404)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.content, b'')
self.assertEqual(response['Content-Type'], 'application/pdf')
self.assertFalse(response.has_header('Content-Disposition'))
content = b'%PDF-1.4\n%%EOF'
# Without filename
response = PDFResponse(content=content)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, content)
self.assertEqual(response['Content-Type'], 'application/pdf')
self.assertFalse(response.has_header('Content-Disposition'))
# With filename
response = PDFResponse(content=content, filename="nospace.pdf")
self.assertEqual(response['Content-Disposition'],
'attachment; filename="nospace.pdf"')
response = PDFResponse(content=content, filename="one space.pdf")
self.assertEqual(response['Content-Disposition'],
'attachment; filename="one space.pdf"')
response = PDFResponse(content=content, filename="4'5\".pdf")
self.assertEqual(response['Content-Disposition'],
'attachment; filename="4\'5.pdf"')
response = PDFResponse(content=content, filename=u"♥.pdf")
try:
import unidecode
except ImportError:
filename = '?.pdf'
else:
filename = '.pdf'
self.assertEqual(response['Content-Disposition'],
'attachment; filename="{0}"'.format(filename))
# Content as a direct output
response = PDFResponse(content=content, filename="nospace.pdf",
show_content_in_browser=True)
self.assertEqual(response['Content-Disposition'],
'inline; filename="nospace.pdf"')
response = PDFResponse(content=content, filename="one space.pdf",
show_content_in_browser=True)
self.assertEqual(response['Content-Disposition'],
'inline; filename="one space.pdf"')
response = PDFResponse(content=content, filename="4'5\".pdf",
show_content_in_browser=True)
self.assertEqual(response['Content-Disposition'],
'inline; filename="4\'5.pdf"')
response = PDFResponse(content=content, filename=u"♥.pdf",
show_content_in_browser=True)
try:
import unidecode
except ImportError:
filename = '?.pdf'
else:
filename = '.pdf'
self.assertEqual(response['Content-Disposition'],
'inline; filename="{0}"'.format(filename))
# Content-Type
response = PDFResponse(content=content,
content_type='application/x-pdf')
self.assertEqual(response['Content-Type'], 'application/x-pdf')
def test_pdf_template_response(self, show_content=False):
"""Test PDFTemplateResponse."""
context = {'title': 'Heading'}
request = RequestFactory().get('/')
response = PDFTemplateResponse(request=request,
template=self.template,
context=context,
show_content_in_browser=show_content)
self.assertEqual(response._request, request)
self.assertEqual(response.template_name, self.template)
self.assertEqual(response.context_data, context)
self.assertEqual(response.filename, None)
self.assertEqual(response.header_template, None)
self.assertEqual(response.footer_template, None)
self.assertEqual(response.cmd_options, {})
self.assertFalse(response.has_header('Content-Disposition'))
# Render to temporary file
template = loader.get_template(self.template)
tempfile = render_to_temporary_file(template, context=context)
tempfile.seek(0)
html_content = smart_str(tempfile.read())
self.assertTrue(html_content.startswith(''))
self.assertTrue('{title}
'.format(**context)
in html_content)
pdf_content = response.rendered_content
self.assertTrue(pdf_content.startswith(b'%PDF-'))
self.assertTrue(pdf_content.endswith(b'%%EOF\n'))
# Footer
cmd_options = {'title': 'Test PDF'}
response = PDFTemplateResponse(request=request,
template=self.template,
context=context,
filename=self.pdf_filename,
show_content_in_browser=show_content,
footer_template=self.footer_template,
cmd_options=cmd_options)
self.assertEqual(response.filename, self.pdf_filename)
self.assertEqual(response.header_template, None)
self.assertEqual(response.footer_template, self.footer_template)
self.assertEqual(response.cmd_options, cmd_options)
self.assertTrue(response.has_header('Content-Disposition'))
footer_template = loader.get_template(self.footer_template)
tempfile = render_to_temporary_file(footer_template, context=context,
request=request)
tempfile.seek(0)
footer_content = smart_str(tempfile.read())
footer_content = make_absolute_paths(footer_content)
media_url = 'file://{0}/'.format(settings.MEDIA_ROOT)
self.assertTrue(media_url in footer_content, True)
static_url = 'file://{0}/'.format(settings.STATIC_ROOT)
self.assertTrue(static_url in footer_content, True)
pdf_content = response.rendered_content
title = '\0'.join(cmd_options['title'])
self.assertIn(six.b(title), pdf_content)
def test_pdf_template_response_to_browser(self):
self.test_pdf_template_response(show_content=True)
def test_pdf_template_view(self, show_content=False):
"""Test PDFTemplateView."""
view = PDFTemplateView.as_view(filename=self.pdf_filename,
show_content_in_browser=show_content,
template_name=self.template,
footer_template=self.footer_template)
# As PDF
request = RequestFactory().get('/')
response = view(request)
self.assertEqual(response.status_code, 200)
response.render()
fileheader = self.attached_fileheader
if show_content:
fileheader = self.inline_fileheader
self.assertEqual(response['Content-Disposition'],
fileheader.format(self.pdf_filename))
self.assertTrue(response.content.startswith(b'%PDF-'))
self.assertTrue(response.content.endswith(b'%%EOF\n'))
# As HTML
request = RequestFactory().get('/?as=html')
response = view(request)
self.assertEqual(response.status_code, 200)
response.render()
self.assertFalse(response.has_header('Content-Disposition'))
self.assertTrue(response.content.startswith(b''))
# POST
request = RequestFactory().post('/')
response = view(request)
self.assertEqual(response.status_code, 405)
def test_pdf_template_view_to_browser(self):
self.test_pdf_template_view(show_content=True)
def test_pdf_template_view_unicode(self, show_content=False):
"""Test PDFTemplateView with unicode content."""
view = UnicodeContentPDFTemplateView.as_view(
filename=self.pdf_filename,
show_content_in_browser=show_content,
template_name=self.template
)
# As PDF
request = RequestFactory().get('/')
response = view(request)
self.assertEqual(response.status_code, 200)
response.render()
fileheader = self.attached_fileheader
if show_content:
fileheader = self.inline_fileheader
self.assertEqual(response['Content-Disposition'],
fileheader.format(self.pdf_filename))
# not sure how we can test this as the contents is all encoded...
# best we can do for the moment is check it's a pdf and it worked.
# self.assertTrue('☃' in response.content)
self.assertTrue(response.content.startswith(b'%PDF-'))
self.assertTrue(response.content.endswith(b'%%EOF\n'))
def test_pdf_template_view_unicode_to_browser(self):
self.test_pdf_template_view_unicode(show_content=True)
def test_get_cmd_options(self):
# Default cmd_options
view = PDFTemplateView()
self.assertEqual(view.cmd_options, PDFTemplateView.cmd_options)
self.assertEqual(PDFTemplateView.cmd_options, {})
# Instantiate with new cmd_options
cmd_options = {'orientation': 'landscape'}
view = PDFTemplateView(cmd_options=cmd_options)
self.assertEqual(view.cmd_options, cmd_options)
self.assertEqual(PDFTemplateView.cmd_options, {})
# Update local instance of cmd_options
view = PDFTemplateView()
view.cmd_options.update(cmd_options)
self.assertEqual(view.cmd_options, cmd_options)
self.assertEqual(PDFTemplateView.cmd_options, {})
def _render_file(self, template, context, request=None):
"""Helper method for testing rendered file deleted/persists tests."""
render = RenderedFile(template=template, context=context, request=request)
render.temporary_file.seek(0)
saved_content = smart_str(render.temporary_file.read())
return (saved_content, render.filename)
@override_settings(
DEBUG=True,
INTERNAL_IPS=['127.0.0.1'],
TEMPLATES=[
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
],
},
},
]
)
def test_get_context_processor_variables_debug(self, show_content=False):
request = RequestFactory().get('/')
template = loader.get_template(self.context_template)
saved_content, filename = self._render_file(template=template, context={}, request=request)
self.assertTrue('True
' in saved_content)
with override_settings(DEBUG=False):
request = RequestFactory().get('/')
template = loader.get_template(self.context_template)
saved_content, filename = self._render_file(template=template, context={}, request=request)
self.assertTrue('' in saved_content)
view = PDFTemplateView.as_view(filename=self.pdf_filename,
show_content_in_browser=show_content,
template_name=self.context_template,
footer_template=self.footer_template)
# As HTML
request = RequestFactory().get('/?as=html')
response = view(request)
self.assertEqual(response.status_code, 200)
response.render()
self.assertIn(b'True
', response.content)
with override_settings(DEBUG=False):
request = RequestFactory().get('/?as=html')
response = view(request)
self.assertEqual(response.status_code, 200)
response.render()
self.assertIn(b'', response.content)
def test_get_context_processor_variables_debug_show_content(self):
self.test_get_context_processor_variables_debug(show_content=True)
django-wkhtmltopdf-3.3.0/wkhtmltopdf/__init__.py 0000644 0000766 0000024 00000000177 13610343425 021713 0 ustar max staff 0000000 0000000 import os
if 'DJANGO_SETTINGS_MODULE' in os.environ:
from .utils import *
__author__ = 'Incuna Ltd'
__version__ = '3.3.0'
django-wkhtmltopdf-3.3.0/wkhtmltopdf/utils.py 0000644 0000766 0000024 00000030657 13610345727 021331 0 ustar max staff 0000000 0000000 from __future__ import absolute_import
from copy import copy
from itertools import chain
import os
import re
import sys
import shlex
from tempfile import NamedTemporaryFile
from django.utils.encoding import smart_text
try:
from urllib.request import pathname2url
from urllib.parse import urljoin
except ImportError: # Python2
from urllib import pathname2url
from urlparse import urljoin
import django
from django.conf import settings
from django.template import loader
from django.template.context import Context, RequestContext
import six
from .subprocess import check_output
NO_ARGUMENT_OPTIONS = ['--collate', '--no-collate', '-H', '--extended-help', '-g',
'--grayscale', '-h', '--help', '--htmldoc', '--license', '-l',
'--lowquality', '--manpage', '--no-pdf-compression', '-q',
'--quiet', '--read-args-from-stdin', '--readme',
'--use-xserver', '-V', '--version', '--dump-default-toc-xsl',
'--outline', '--no-outline', '--background', '--no-background',
'--custom-header-propagation', '--no-custom-header-propagation',
'--debug-javascript', '--no-debug-javascript', '--default-header',
'--disable-external-links', '--enable-external-links',
'--disable-forms', '--enable-forms', '--images', '--no-images',
'--disable-internal-links', '--enable-internal-links', '-n',
'--disable-javascript', '--enable-javascript', '--keep-relative-links',
'--disable-local-file-access', '--enable-local-file-access',
'--exclude-from-outline', '--include-in-outline', '--disable-plugins',
'--enable-plugins', '--print-media-type', '--no-print-media-type',
'--resolve-relative-links', '--disable-smart-shrinking',
'--enable-smart-shrinking', '--stop-slow-scripts',
'--no-stop-slow-scripts', '--disable-toc-back-links',
'--enable-toc-back-links', '--footer-line', '--no-footer-line',
'--header-line', '--no-header-line', '--disable-dotted-lines',
'--disable-toc-links', '--verbose']
def _options_to_args(**options):
"""
Converts ``options`` into a list of command-line arguments.
Skip arguments where no value is provided
For flag-type (No argument) variables, pass only the name and only then if the value is True
"""
flags = []
for name in sorted(options):
value = options[name]
formatted_flag = '--%s' % name if len(name) > 1 else '-%s' % name
formatted_flag = formatted_flag.replace('_', '-')
accepts_no_arguments = formatted_flag in NO_ARGUMENT_OPTIONS
if value is None or (value is False and accepts_no_arguments):
continue
flags.append(formatted_flag)
if accepts_no_arguments:
continue
flags.append(six.text_type(value))
return flags
def wkhtmltopdf(pages, output=None, **kwargs):
"""
Converts html to PDF using http://wkhtmltopdf.org/.
pages: List of file paths or URLs of the html to be converted.
output: Optional output file path. If None, the output is returned.
**kwargs: Passed to wkhtmltopdf via _extra_args() (See
https://github.com/antialize/wkhtmltopdf/blob/master/README_WKHTMLTOPDF
for acceptable args.)
Kwargs is passed through as arguments. e.g.:
{'footer_html': 'http://example.com/foot.html'}
becomes
'--footer-html http://example.com/foot.html'
Where there is no value passed, use True. e.g.:
{'disable_javascript': True}
becomes:
'--disable-javascript'
To disable a default option, use None. e.g:
{'quiet': None'}
becomes:
''
example usage:
wkhtmltopdf(pages=['/tmp/example.html'],
dpi=300,
orientation='Landscape',
disable_javascript=True)
"""
if isinstance(pages, six.string_types):
# Support a single page.
pages = [pages]
if output is None:
# Standard output.
output = '-'
has_cover = kwargs.pop('has_cover', False)
# Default options:
options = getattr(settings, 'WKHTMLTOPDF_CMD_OPTIONS', None)
if options is None:
options = {'quiet': True}
else:
options = copy(options)
options.update(kwargs)
# Force --encoding utf8 unless the user has explicitly overridden this.
options.setdefault('encoding', 'utf8')
env = getattr(settings, 'WKHTMLTOPDF_ENV', None)
if env is not None:
env = dict(os.environ, **env)
cmd = 'WKHTMLTOPDF_CMD'
cmd = getattr(settings, cmd, os.environ.get(cmd, 'wkhtmltopdf'))
# Adding 'cover' option to add cover_file to the pdf to generate.
if has_cover:
pages.insert(0, 'cover')
ck_args = list(chain(shlex.split(cmd),
_options_to_args(**options),
list(pages),
[output]))
ck_kwargs = {'env': env}
# Handling of fileno() attr. based on https://github.com/GrahamDumpleton/mod_wsgi/issues/85
try:
i = sys.stderr.fileno()
ck_kwargs['stderr'] = sys.stderr
except (AttributeError, IOError):
# can't call fileno() on mod_wsgi stderr object
pass
return check_output(ck_args, **ck_kwargs)
def convert_to_pdf(filename, header_filename=None, footer_filename=None, cmd_options=None, cover_filename=None):
# Clobber header_html and footer_html only if filenames are
# provided. These keys may be in self.cmd_options as hardcoded
# static files.
# The argument `filename` may be a string or a list. However, wkhtmltopdf
# will coerce it into a list if a string is passed.
cmd_options = cmd_options if cmd_options else {}
if cover_filename:
pages = [cover_filename, filename]
cmd_options['has_cover'] = True
else:
pages = [filename]
if header_filename is not None:
cmd_options['header_html'] = header_filename
if footer_filename is not None:
cmd_options['footer_html'] = footer_filename
return wkhtmltopdf(pages=pages, **cmd_options)
class RenderedFile(object):
"""
Create a temporary file resource of the rendered template with context.
The filename will be used for later conversion to PDF.
"""
temporary_file = None
filename = ''
def __init__(self, template, context, request=None):
debug = getattr(settings, 'WKHTMLTOPDF_DEBUG', settings.DEBUG)
self.temporary_file = render_to_temporary_file(
template=template,
context=context,
request=request,
prefix='wkhtmltopdf', suffix='.html',
delete=(not debug)
)
self.filename = self.temporary_file.name
def __del__(self):
# Always close the temporary_file on object destruction.
if self.temporary_file is not None:
self.temporary_file.close()
def render_pdf_from_template(input_template, header_template, footer_template, context, request=None, cmd_options=None,
cover_template=None):
# For basic usage. Performs all the actions necessary to create a single
# page PDF from a single template and context.
cmd_options = cmd_options if cmd_options else {}
header_filename = footer_filename = None
# Main content.
input_file = RenderedFile(
template=input_template,
context=context,
request=request
)
# Optional. For header template argument.
if header_template:
header_file = RenderedFile(
template=header_template,
context=context,
request=request
)
header_filename = header_file.filename
# Optional. For footer template argument.
if footer_template:
footer_file = RenderedFile(
template=footer_template,
context=context,
request=request
)
footer_filename = footer_file.filename
cover = None
if cover_template:
cover = RenderedFile(
template=cover_template,
context=context,
request=request
)
return convert_to_pdf(filename=input_file.filename,
header_filename=header_filename,
footer_filename=footer_filename,
cmd_options=cmd_options,
cover_filename=cover.filename if cover else None)
def content_disposition_filename(filename):
"""
Sanitize a file name to be used in the Content-Disposition HTTP
header.
Even if the standard is quite permissive in terms of
characters, there are a lot of edge cases that are not supported by
different browsers.
See http://greenbytes.de/tech/tc2231/#attmultinstances for more details.
"""
filename = filename.replace(';', '').replace('"', '')
return http_quote(filename)
def http_quote(string):
"""
Given a unicode string, will do its dandiest to give you back a
valid ascii charset string you can use in, say, http headers and the
like.
"""
if isinstance(string, six.text_type):
try:
import unidecode
except ImportError:
pass
else:
string = unidecode.unidecode(string)
string = string.encode('ascii', 'replace')
# Wrap in double-quotes for ; , and the like
string = string.replace(b'\\', b'\\\\').replace(b'"', b'\\"')
return '"{0!s}"'.format(string.decode())
def pathname2fileurl(pathname):
"""Returns a file:// URL for pathname. Handles OS-specific conversions."""
return urljoin('file:', pathname2url(pathname))
def make_absolute_paths(content):
"""Convert all MEDIA files into a file://URL paths in order to
correctly get it displayed in PDFs."""
overrides = [
{
'root': settings.MEDIA_ROOT,
'url': settings.MEDIA_URL,
},
{
'root': settings.STATIC_ROOT,
'url': settings.STATIC_URL,
}
]
has_scheme = re.compile(r'^[^:/]+://')
for x in overrides:
if not x['url'] or has_scheme.match(x['url']):
continue
if not x['root'].endswith('/'):
x['root'] += '/'
occur_pattern = '''(["|']{0}.*?["|'])'''
occurences = re.findall(occur_pattern.format(x['url']), content)
occurences = list(set(occurences)) # Remove dups
for occur in occurences:
content = content.replace(occur, '"%s"' % (
pathname2fileurl(x['root']) +
occur[1 + len(x['url']): -1]))
return content
def render_to_temporary_file(template, context, request=None, mode='w+b',
bufsize=-1, suffix='.html', prefix='tmp',
dir=None, delete=True):
try:
render = template.render
except AttributeError:
content = loader.render_to_string(template, context)
else:
if django.VERSION < (1, 8):
# If using a version of Django prior to 1.8, ensure ``context`` is an
# instance of ``Context``
if not isinstance(context, Context):
if request:
context = RequestContext(request, context)
else:
context = Context(context)
# Handle error when ``request`` is None
content = render(context)
else:
content = render(context, request)
content = smart_text(content)
content = make_absolute_paths(content)
try:
# Python3 has 'buffering' arg instead of 'bufsize'
tempfile = NamedTemporaryFile(mode=mode, buffering=bufsize,
suffix=suffix, prefix=prefix,
dir=dir, delete=delete)
except TypeError:
tempfile = NamedTemporaryFile(mode=mode, bufsize=bufsize,
suffix=suffix, prefix=prefix,
dir=dir, delete=delete)
try:
tempfile.write(content.encode('utf-8'))
tempfile.flush()
return tempfile
except:
# Clean-up tempfile if an Exception is raised.
tempfile.close()
raise
django-wkhtmltopdf-3.3.0/wkhtmltopdf/views.py 0000644 0000766 0000024 00000013774 13305565365 021332 0 ustar max staff 0000000 0000000 from __future__ import absolute_import
from django.http import HttpResponse
from django.template.response import TemplateResponse
from django.views.generic import TemplateView
from .utils import (content_disposition_filename, render_pdf_from_template)
class PDFResponse(HttpResponse):
"""HttpResponse that sets the headers for PDF output."""
def __init__(self, content, status=200, content_type=None,
filename=None, show_content_in_browser=None, *args, **kwargs):
if content_type is None:
content_type = 'application/pdf'
super(PDFResponse, self).__init__(content=content,
status=status,
content_type=content_type)
self.set_filename(filename, show_content_in_browser)
def set_filename(self, filename, show_content_in_browser):
self.filename = filename
if filename:
fileheader = 'attachment; filename={0}'
if show_content_in_browser:
fileheader = 'inline; filename={0}'
filename = content_disposition_filename(filename)
header_content = fileheader.format(filename)
self['Content-Disposition'] = header_content
else:
del self['Content-Disposition']
class PDFTemplateResponse(TemplateResponse, PDFResponse):
"""Renders a Template into a PDF using wkhtmltopdf"""
def __init__(self, request, template, context=None,
status=None, content_type=None, current_app=None,
filename=None, show_content_in_browser=None,
header_template=None, footer_template=None,
cmd_options=None, *args, **kwargs):
cover_template = kwargs.pop('cover_template', None)
super(PDFTemplateResponse, self).__init__(request=request,
template=template,
context=context,
status=status,
content_type=content_type,
*args, **kwargs)
self.set_filename(filename, show_content_in_browser)
self.header_template = header_template
self.footer_template = footer_template
self.cover_template = cover_template
if cmd_options is None:
cmd_options = {}
self.cmd_options = cmd_options
@property
def rendered_content(self):
"""Returns the freshly rendered content for the template and context
described by the PDFResponse.
This *does not* set the final content of the response. To set the
response content, you must either call render(), or set the
content explicitly using the value of this property.
"""
cmd_options = self.cmd_options.copy()
return render_pdf_from_template(
self.resolve_template(self.template_name),
self.resolve_template(self.header_template),
self.resolve_template(self.footer_template),
context=self.resolve_context(self.context_data),
request=self._request,
cmd_options=cmd_options,
cover_template=self.resolve_template(self.cover_template)
)
class PDFTemplateView(TemplateView):
"""Class-based view for HTML templates rendered to PDF."""
# Filename for downloaded PDF. If None, the response is inline.
filename = 'rendered_pdf.pdf'
# Send file as attachement. If True render content in the browser.
show_content_in_browser = False
# Filenames for the content, header, and footer templates.
template_name = None
header_template = None
footer_template = None
cover_template = None
# TemplateResponse classes for PDF and HTML
response_class = PDFTemplateResponse
html_response_class = TemplateResponse
# Command-line options to pass to wkhtmltopdf
cmd_options = {
# 'orientation': 'portrait',
# 'collate': True,
# 'quiet': None,
}
def __init__(self, *args, **kwargs):
super(PDFTemplateView, self).__init__(*args, **kwargs)
# Copy self.cmd_options to prevent clobbering the class-level object.
self.cmd_options = self.cmd_options.copy()
def get(self, request, *args, **kwargs):
response_class = self.response_class
try:
if request.GET.get('as', '') == 'html':
# Use the html_response_class if HTML was requested.
self.response_class = self.html_response_class
return super(PDFTemplateView, self).get(request,
*args, **kwargs)
finally:
# Remove self.response_class
self.response_class = response_class
def get_filename(self):
return self.filename
def get_cmd_options(self):
return self.cmd_options
def render_to_response(self, context, **response_kwargs):
"""
Returns a PDF response with a template rendered with the given context.
"""
filename = response_kwargs.pop('filename', None)
cmd_options = response_kwargs.pop('cmd_options', None)
if issubclass(self.response_class, PDFTemplateResponse):
if filename is None:
filename = self.get_filename()
if cmd_options is None:
cmd_options = self.get_cmd_options()
return super(PDFTemplateView, self).render_to_response(
context=context, filename=filename,
show_content_in_browser=self.show_content_in_browser,
header_template=self.header_template,
footer_template=self.footer_template,
cmd_options=cmd_options,
cover_template=self.cover_template,
**response_kwargs
)
else:
return super(PDFTemplateView, self).render_to_response(
context=context,
**response_kwargs
)
django-wkhtmltopdf-3.3.0/MANIFEST.in 0000644 0000766 0000024 00000000066 13305565365 017004 0 ustar max staff 0000000 0000000 include README.rst
recursive-include * *.html
LICENSE
django-wkhtmltopdf-3.3.0/setup.py 0000644 0000766 0000024 00000002201 13610316217 016736 0 ustar max staff 0000000 0000000 from setuptools import setup, find_packages
import wkhtmltopdf
setup(
name='django-wkhtmltopdf',
packages=find_packages(),
include_package_data=True,
version=wkhtmltopdf.__version__,
description='Converts HTML to PDF using wkhtmltopdf.',
long_description=open('README.rst').read(),
license='BSD-2-Clause',
author=wkhtmltopdf.__author__,
author_email='admin@incuna.com',
url='https://github.com/incuna/django-wkhtmltopdf',
zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Framework :: Django',
],
keywords='django wkhtmltopdf pdf',
install_requires=[
'six',
],
)
django-wkhtmltopdf-3.3.0/django_wkhtmltopdf.egg-info/ 0000755 0000766 0000024 00000000000 13610346024 022607 5 ustar max staff 0000000 0000000 django-wkhtmltopdf-3.3.0/django_wkhtmltopdf.egg-info/PKG-INFO 0000644 0000766 0000024 00000006313 13610346023 023706 0 ustar max staff 0000000 0000000 Metadata-Version: 1.1
Name: django-wkhtmltopdf
Version: 3.3.0
Summary: Converts HTML to PDF using wkhtmltopdf.
Home-page: https://github.com/incuna/django-wkhtmltopdf
Author: Incuna Ltd
Author-email: admin@incuna.com
License: BSD-2-Clause
Description: django-wkhtmltopdf
==================
.. image:: https://badge.fury.io/py/django-wkhtmltopdf.png
:target: http://badge.fury.io/py/django-wkhtmltopdf
:alt: Latest version
.. image:: https://travis-ci.org/incuna/django-wkhtmltopdf.png?branch=master
:target: https://travis-ci.org/incuna/django-wkhtmltopdf
:alt: Travis-CI
.. image:: https://img.shields.io/pypi/dm/django-wkhtmltopdf.svg
:target: https://badge.fury.io/py/django-wkhtmltopdf
:alt: Number of PyPI downloads on a month
Converts HTML to PDF
--------------------
Provides Django views to wrap the HTML to PDF conversion of the `wkhtmltopdf `_ binary.
Requirements
------------
Install the `wkhtmltopdf static binary `_.
This requires libfontconfig (on Ubuntu: ``sudo aptitude install libfontconfig``).
Python 2.6+ and 3.3+ are supported.
Installation
------------
Run ``pip install django-wkhtmltopdf``.
Add ``'wkhtmltopdf'`` to ``INSTALLED_APPS`` in your ``settings.py``.
By default it will execute the first ``wkhtmltopdf`` command found on your ``PATH``.
If you can't add wkhtmltopdf to your ``PATH``, you can set ``WKHTMLTOPDF_CMD`` to a
specific executable:
e.g. in ``settings.py``: ::
WKHTMLTOPDF_CMD = '/path/to/my/wkhtmltopdf'
or alternatively as env variable: ::
export WKHTMLTOPDF_CMD=/path/to/my/wkhtmltopdf
You may also set ``WKHTMLTOPDF_CMD_OPTIONS`` in ``settings.py`` to a dictionary
of default command-line options.
The default is: ::
WKHTMLTOPDF_CMD_OPTIONS = {
'quiet': True,
}
Documentation
-------------
Documentation is available at http://django-wkhtmltopdf.readthedocs.org/en/latest/.
License
-------
MIT licensed. See the bundled `LICENSE `_ file for more details.
Keywords: django wkhtmltopdf pdf
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Framework :: Django
django-wkhtmltopdf-3.3.0/django_wkhtmltopdf.egg-info/not-zip-safe 0000644 0000766 0000024 00000000001 12627337302 025043 0 ustar max staff 0000000 0000000
django-wkhtmltopdf-3.3.0/django_wkhtmltopdf.egg-info/SOURCES.txt 0000644 0000766 0000024 00000001245 13610346023 024474 0 ustar max staff 0000000 0000000 MANIFEST.in
README.rst
setup.cfg
setup.py
django_wkhtmltopdf.egg-info/PKG-INFO
django_wkhtmltopdf.egg-info/SOURCES.txt
django_wkhtmltopdf.egg-info/dependency_links.txt
django_wkhtmltopdf.egg-info/not-zip-safe
django_wkhtmltopdf.egg-info/requires.txt
django_wkhtmltopdf.egg-info/top_level.txt
wkhtmltopdf/__init__.py
wkhtmltopdf/models.py
wkhtmltopdf/subprocess.py
wkhtmltopdf/utils.py
wkhtmltopdf/views.py
wkhtmltopdf/tests/__init__.py
wkhtmltopdf/tests/models.py
wkhtmltopdf/tests/run.py
wkhtmltopdf/tests/tests.py
wkhtmltopdf/tests/templates/context.html
wkhtmltopdf/tests/templates/footer.html
wkhtmltopdf/tests/templates/sample.html
wkhtmltopdf/tests/templates/unicode.html django-wkhtmltopdf-3.3.0/django_wkhtmltopdf.egg-info/requires.txt 0000644 0000766 0000024 00000000004 13610346023 025200 0 ustar max staff 0000000 0000000 six
django-wkhtmltopdf-3.3.0/django_wkhtmltopdf.egg-info/top_level.txt 0000644 0000766 0000024 00000000014 13610346023 025333 0 ustar max staff 0000000 0000000 wkhtmltopdf
django-wkhtmltopdf-3.3.0/django_wkhtmltopdf.egg-info/dependency_links.txt 0000644 0000766 0000024 00000000001 13610346023 026654 0 ustar max staff 0000000 0000000
django-wkhtmltopdf-3.3.0/setup.cfg 0000644 0000766 0000024 00000000103 13610346024 017043 0 ustar max staff 0000000 0000000 [bdist_wheel]
universal = 1
[egg_info]
tag_build =
tag_date = 0
django-wkhtmltopdf-3.3.0/README.rst 0000644 0000766 0000024 00000003472 13305565365 016741 0 ustar max staff 0000000 0000000 django-wkhtmltopdf
==================
.. image:: https://badge.fury.io/py/django-wkhtmltopdf.png
:target: http://badge.fury.io/py/django-wkhtmltopdf
:alt: Latest version
.. image:: https://travis-ci.org/incuna/django-wkhtmltopdf.png?branch=master
:target: https://travis-ci.org/incuna/django-wkhtmltopdf
:alt: Travis-CI
.. image:: https://img.shields.io/pypi/dm/django-wkhtmltopdf.svg
:target: https://badge.fury.io/py/django-wkhtmltopdf
:alt: Number of PyPI downloads on a month
Converts HTML to PDF
--------------------
Provides Django views to wrap the HTML to PDF conversion of the `wkhtmltopdf `_ binary.
Requirements
------------
Install the `wkhtmltopdf static binary `_.
This requires libfontconfig (on Ubuntu: ``sudo aptitude install libfontconfig``).
Python 2.6+ and 3.3+ are supported.
Installation
------------
Run ``pip install django-wkhtmltopdf``.
Add ``'wkhtmltopdf'`` to ``INSTALLED_APPS`` in your ``settings.py``.
By default it will execute the first ``wkhtmltopdf`` command found on your ``PATH``.
If you can't add wkhtmltopdf to your ``PATH``, you can set ``WKHTMLTOPDF_CMD`` to a
specific executable:
e.g. in ``settings.py``: ::
WKHTMLTOPDF_CMD = '/path/to/my/wkhtmltopdf'
or alternatively as env variable: ::
export WKHTMLTOPDF_CMD=/path/to/my/wkhtmltopdf
You may also set ``WKHTMLTOPDF_CMD_OPTIONS`` in ``settings.py`` to a dictionary
of default command-line options.
The default is: ::
WKHTMLTOPDF_CMD_OPTIONS = {
'quiet': True,
}
Documentation
-------------
Documentation is available at http://django-wkhtmltopdf.readthedocs.org/en/latest/.
License
-------
MIT licensed. See the bundled `LICENSE `_ file for more details.