django-wkhtmltopdf-3.1.0/0000755000076700000240000000000012761507030015560 5ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/django_wkhtmltopdf.egg-info/0000755000076700000240000000000012761507030023137 5ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/django_wkhtmltopdf.egg-info/dependency_links.txt0000644000076700000240000000000112761507024027210 0ustar kevinstaff00000000000000 django-wkhtmltopdf-3.1.0/django_wkhtmltopdf.egg-info/not-zip-safe0000644000076700000240000000000112761507024025370 0ustar kevinstaff00000000000000 django-wkhtmltopdf-3.1.0/django_wkhtmltopdf.egg-info/PKG-INFO0000644000076700000240000000630212761507024024240 0ustar kevinstaff00000000000000Metadata-Version: 1.1 Name: django-wkhtmltopdf Version: 3.1.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: MIT 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.1.0/django_wkhtmltopdf.egg-info/SOURCES.txt0000644000076700000240000000112312761507025025024 0ustar kevinstaff00000000000000MANIFEST.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/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/footer.html wkhtmltopdf/tests/templates/sample.html wkhtmltopdf/tests/templates/unicode.htmldjango-wkhtmltopdf-3.1.0/django_wkhtmltopdf.egg-info/top_level.txt0000644000076700000240000000001412761507024025667 0ustar kevinstaff00000000000000wkhtmltopdf django-wkhtmltopdf-3.1.0/MANIFEST.in0000644000076700000240000000005612761505561017326 0ustar kevinstaff00000000000000include README.rst recursive-include * *.html django-wkhtmltopdf-3.1.0/PKG-INFO0000644000076700000240000000630212761507030016656 0ustar kevinstaff00000000000000Metadata-Version: 1.1 Name: django-wkhtmltopdf Version: 3.1.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: MIT 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.1.0/README.rst0000644000076700000240000000347212761505561017264 0ustar kevinstaff00000000000000django-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. django-wkhtmltopdf-3.1.0/setup.cfg0000644000076700000240000000013012761507030017373 0ustar kevinstaff00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 django-wkhtmltopdf-3.1.0/setup.py0000644000076700000240000000211312761505561017276 0ustar kevinstaff00000000000000from 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='MIT', 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', ) django-wkhtmltopdf-3.1.0/wkhtmltopdf/0000755000076700000240000000000012761507030020123 5ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/wkhtmltopdf/__init__.py0000644000076700000240000000017712761506646022255 0ustar kevinstaff00000000000000import os if 'DJANGO_SETTINGS_MODULE' in os.environ: from .utils import * __author__ = 'Incuna Ltd' __version__ = '3.1.0' django-wkhtmltopdf-3.1.0/wkhtmltopdf/models.py0000644000076700000240000000000012761505561021755 0ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/wkhtmltopdf/subprocess.py0000644000076700000240000000317512761505561022702 0ustar kevinstaff00000000000000from __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.1.0/wkhtmltopdf/tests/0000755000076700000240000000000012761507030021265 5ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/wkhtmltopdf/tests/__init__.py0000644000076700000240000000000012761505561023373 0ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/wkhtmltopdf/tests/models.py0000644000076700000240000000000012761505561023117 0ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/wkhtmltopdf/tests/run.py0000644000076700000240000000255012761505561022454 0ustar kevinstaff00000000000000#! /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.1.0/wkhtmltopdf/tests/templates/0000755000076700000240000000000012761507030023263 5ustar kevinstaff00000000000000django-wkhtmltopdf-3.1.0/wkhtmltopdf/tests/templates/footer.html0000644000076700000240000000022612761505561025456 0ustar kevinstaff00000000000000{% load static %} django-wkhtmltopdf-3.1.0/wkhtmltopdf/tests/templates/sample.html0000644000076700000240000000013212761505561025435 0ustar kevinstaff00000000000000

{{ title }}

django-wkhtmltopdf-3.1.0/wkhtmltopdf/tests/templates/unicode.html0000644000076700000240000000023612761505561025607 0ustar kevinstaff00000000000000

django-wkhtmltopdf-3.1.0/wkhtmltopdf/tests/tests.py0000644000076700000240000003544012761505561023016 0ustar kevinstaff00000000000000# -*- 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.client import RequestFactory from django.utils import six from django.utils.encoding import smart_str from wkhtmltopdf.subprocess import CalledProcessError from wkhtmltopdf.utils import (_options_to_args, make_absolute_paths, wkhtmltopdf, 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']) 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)) class TestViews(TestCase): template = 'sample.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, {}) django-wkhtmltopdf-3.1.0/wkhtmltopdf/utils.py0000644000076700000240000002275312761505561021655 0ustar kevinstaff00000000000000from __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.context import Context, RequestContext from django.utils import six from .subprocess import check_output def _options_to_args(**options): """Converts ``options`` into a list of command-line arguments.""" flags = [] for name in sorted(options): value = options[name] if value is None: continue flags.append('--' + name.replace('_', '-')) if value is not True: 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 = '-' # 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')) 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): # 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 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=filename, **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): # 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 return convert_to_pdf(filename=input_file.filename, header_filename=header_filename, footer_filename=footer_filename, cmd_options=cmd_options) 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, pathname2fileurl(x['root']) + occur[len(x['url']):]) return content def render_to_temporary_file(template, context, request=None, mode='w+b', bufsize=-1, suffix='.html', prefix='tmp', dir=None, delete=True): 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) content = template.render(context) else: content = template.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.1.0/wkhtmltopdf/views.py0000644000076700000240000001337612761505561021653 0ustar kevinstaff00000000000000from __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): 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 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 ) 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 # 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, **response_kwargs ) else: return super(PDFTemplateView, self).render_to_response( context=context, **response_kwargs )