From 1acf9a80050054535f056ae1ba40c84e7b52700a Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 25 Feb 2016 21:01:43 -0800 Subject: Refactor to better support multiple pages. --- wkhtmltopdf/utils.py | 81 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/wkhtmltopdf/utils.py b/wkhtmltopdf/utils.py index 1866b4a..d4a9ee4 100644 --- a/wkhtmltopdf/utils.py +++ b/wkhtmltopdf/utils.py @@ -109,7 +109,7 @@ def wkhtmltopdf(pages, output=None, **kwargs): return check_output(ck_args, **ck_kwargs) -def convert_to_pdf(filename, header_filename=None, footer_filename=None, cmd_options=None): +def convert_to_pdf(filename_list, 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. @@ -119,52 +119,65 @@ def convert_to_pdf(filename, header_filename=None, footer_filename=None, cmd_opt cmd_options['header_html'] = header_filename if footer_filename is not None: cmd_options['footer_html'] = footer_filename - return wkhtmltopdf(pages=[filename], **cmd_options) + return wkhtmltopdf(pages=filename_list, **cmd_options) -def render_pdf_from_template(input_template, header_template, footer_template, context, request=None, cmd_options=None): +def create_rendered_file(template, context, request=None): + # Create a temporary file of the rendered template with context. + # Return the filename for later conversion to PDF. debug = getattr(settings, 'WKHTMLTOPDF_DEBUG', settings.DEBUG) - cmd_options = cmd_options if cmd_options else {} - input_file = header_file = footer_file = None - header_filename = footer_filename = None + temporary_file = None try: - input_file = render_to_temporary_file( - template=input_template, + temporary_file = render_to_temporary_file( + template=template, context=context, request=request, prefix='wkhtmltopdf', suffix='.html', delete=(not debug) ) - if header_template: - header_file = render_to_temporary_file( - template=header_template, - context=context, - request=request, - prefix='wkhtmltopdf', suffix='.html', - delete=(not debug) - ) - header_filename = header_file.name - - if footer_template: - footer_file = render_to_temporary_file( - template=footer_template, - context=context, - request=request, - prefix='wkhtmltopdf', suffix='.html', - delete=(not debug) - ) - footer_filename = footer_file.name - - return convert_to_pdf(filename=input_file.name, - header_filename=header_filename, - footer_filename=footer_filename, - cmd_options=cmd_options) + return temporary_file.name finally: # Clean up temporary files - for f in filter(None, (input_file, header_file, footer_file)): - f.close() + if temporary_file is not None: + 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 {} + + input_filename = header_filename = footer_filename = None + + # Main contemt. + input_filename = create_rendered_file( + template=input_template, + context=context, + request=request + ) + + # Optional. For header template argument. + if header_template: + header_filename = create_rendered_file( + template=header_temlate, + context=context, + request=request + ) + + # Optional. For footer template argument. + if footer_template: + footer_filename = create_rendered_file( + template=footer_template, + context=context, + request=request + ) + + return convert_to_pdf(filename_list=[input_filename], + header_filename=header_filename, + footer_filename=footer_filename, + cmd_options=cmd_options) + def content_disposition_filename(filename): """ -- cgit v1.2.3 From d7bb07ad4468d738f5ba321ee730921f3886e5a3 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 26 Feb 2016 16:24:09 -0800 Subject: Fix premature file delete on DEBUG=False. Modify test runner to check both DEBUG options. --- wkhtmltopdf/tests/run.py | 40 +++++++++++++++++++---------- wkhtmltopdf/utils.py | 66 ++++++++++++++++++++++++++++-------------------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/wkhtmltopdf/tests/run.py b/wkhtmltopdf/tests/run.py index 5004ee1..b969a29 100644 --- a/wkhtmltopdf/tests/run.py +++ b/wkhtmltopdf/tests/run.py @@ -9,6 +9,26 @@ DIRNAME = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, os.getcwd()) +def run_tests(): + # Utility function to executes tests. + # Will be called twice. Once with DEBUG=True and once with 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) + settings.configure( DEBUG=True, DATABASES={ @@ -32,19 +52,11 @@ settings.configure( WKHTMLTOPDF_DEBUG=True, ) -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 +# Run tests with True debug settings (persistent temporary files). +run_tests() +settings.DEBUG = False +settings.WKHTMLTOPDF_DEBUG = False -test_runner = DiscoverRunner(verbosity=1) -failures = test_runner.run_tests(['wkhtmltopdf']) -if failures: - sys.exit(1) +# Run tests with False debug settings to test temporary file delete operations. +run_tests() diff --git a/wkhtmltopdf/utils.py b/wkhtmltopdf/utils.py index d4a9ee4..bfec068 100644 --- a/wkhtmltopdf/utils.py +++ b/wkhtmltopdf/utils.py @@ -109,49 +109,58 @@ def wkhtmltopdf(pages, output=None, **kwargs): return check_output(ck_args, **ck_kwargs) -def convert_to_pdf(filename_list, header_filename=None, footer_filename=None, cmd_options=None): +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` should be a list. However, wkhtmltopdf will + # coerce 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_list, **cmd_options) - -def create_rendered_file(template, context, request=None): - # Create a temporary file of the rendered template with context. - # Return the filename for later conversion to PDF. - debug = getattr(settings, 'WKHTMLTOPDF_DEBUG', settings.DEBUG) + 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 = '' - try: - temporary_file = render_to_temporary_file( - template=template, - context=context, - request=request, - prefix='wkhtmltopdf', suffix='.html', - delete=(not debug) - ) + def __init__(self, template, context, request=None): + debug = getattr(settings, 'WKHTMLTOPDF_DEBUG', settings.DEBUG) - return temporary_file.name - finally: - # Clean up temporary files - if temporary_file is not None: - temporary_file.close() + try: + 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 + except: + # In case something fails, return an empty filename string. + self.filename = '' + + 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 {} - input_filename = header_filename = footer_filename = None + header_filename = footer_filename = None - # Main contemt. - input_filename = create_rendered_file( + # Main content. + input_file = RenderedFile( template=input_template, context=context, request=request @@ -159,26 +168,27 @@ def render_pdf_from_template(input_template, header_template, footer_template, c # Optional. For header template argument. if header_template: - header_filename = create_rendered_file( - template=header_temlate, + header_file = RenderedFile( + template=header_template, context=context, request=request ) + header_filename = header_file.filename # Optional. For footer template argument. if footer_template: - footer_filename = create_rendered_file( + footer_file = RenderedFile( template=footer_template, context=context, request=request ) + footer_filename = footer_file.filename - return convert_to_pdf(filename_list=[input_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 -- cgit v1.2.3 From 31e8590845bbc94c72eabea798e1c0ea3a5571d2 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 26 Feb 2016 23:53:55 -0800 Subject: Remove extraneous try/except. Revert test run script changes, but set DEBUG=False. --- wkhtmltopdf/tests/run.py | 44 ++++++++++++++++---------------------------- wkhtmltopdf/utils.py | 20 ++++++++------------ 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/wkhtmltopdf/tests/run.py b/wkhtmltopdf/tests/run.py index b969a29..e0021ab 100644 --- a/wkhtmltopdf/tests/run.py +++ b/wkhtmltopdf/tests/run.py @@ -9,28 +9,8 @@ DIRNAME = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, os.getcwd()) -def run_tests(): - # Utility function to executes tests. - # Will be called twice. Once with DEBUG=True and once with 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) - settings.configure( - DEBUG=True, + DEBUG=False, DATABASES={ 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -49,14 +29,22 @@ settings.configure( MEDIA_URL='/media/', STATIC_ROOT=os.path.join(DIRNAME, 'static'), STATIC_URL='/static/', - WKHTMLTOPDF_DEBUG=True, + WKHTMLTOPDF_DEBUG=False, ) -# Run tests with True debug settings (persistent temporary files). -run_tests() +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 -settings.DEBUG = False -settings.WKHTMLTOPDF_DEBUG = False -# Run tests with False debug settings to test temporary file delete operations. -run_tests() +test_runner = DiscoverRunner(verbosity=1) +failures = test_runner.run_tests(['wkhtmltopdf']) +if failures: + sys.exit(1) diff --git a/wkhtmltopdf/utils.py b/wkhtmltopdf/utils.py index bfec068..94af5de 100644 --- a/wkhtmltopdf/utils.py +++ b/wkhtmltopdf/utils.py @@ -134,18 +134,14 @@ class RenderedFile(object): def __init__(self, template, context, request=None): debug = getattr(settings, 'WKHTMLTOPDF_DEBUG', settings.DEBUG) - try: - 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 - except: - # In case something fails, return an empty filename string. - self.filename = '' + 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. -- cgit v1.2.3 From ca8b99207a8a135ea1b970bd610ac8fa8fe2741a Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 2 Mar 2016 03:49:59 -0800 Subject: Pass filename as string instead of a list. Modify comment to explain. --- wkhtmltopdf/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wkhtmltopdf/utils.py b/wkhtmltopdf/utils.py index 94af5de..c871bd0 100644 --- a/wkhtmltopdf/utils.py +++ b/wkhtmltopdf/utils.py @@ -113,8 +113,8 @@ def convert_to_pdf(filename, header_filename=None, footer_filename=None, cmd_opt # 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` should be a list. However, wkhtmltopdf will - # coerce into a list if a string is passed. + # 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: @@ -180,7 +180,7 @@ def render_pdf_from_template(input_template, header_template, footer_template, c ) footer_filename = footer_file.filename - return convert_to_pdf(filename=[input_file.filename], + return convert_to_pdf(filename=input_file.filename, header_filename=header_filename, footer_filename=footer_filename, cmd_options=cmd_options) -- cgit v1.2.3 From 39b61bff13f41b7c779d57e16eef717dfffbe6b9 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 2 Mar 2016 04:54:50 -0800 Subject: Add test for temporary file is closed/deleted. --- wkhtmltopdf/tests/tests.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/wkhtmltopdf/tests/tests.py b/wkhtmltopdf/tests/tests.py index 822f529..b3c9ea1 100644 --- a/wkhtmltopdf/tests/tests.py +++ b/wkhtmltopdf/tests/tests.py @@ -14,7 +14,8 @@ 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) + wkhtmltopdf, render_to_temporary_file, + RenderedFile) from wkhtmltopdf.views import PDFResponse, PDFTemplateView, PDFTemplateResponse @@ -30,6 +31,7 @@ class UnicodeContentPDFTemplateView(PDFTemplateView): context['title'] = u'♥' return context + class TestUtils(TestCase): def setUp(self): # Clear standard error @@ -94,6 +96,28 @@ class TestUtils(TestCase): self.assertTrue(title in saved_content) temp_file.close() + def _render_file(self, template, context): + """Helper method for test_rendered_filed_deletion test.""" + 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_filed_deletion(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 actually rendered. + self.assertTrue(title in saved_content) + + # Then check if file is deleted when DEBUG=False. + self.assertEqual(os.path.isfile(filename), debug) + class TestViews(TestCase): template = 'sample.html' -- cgit v1.2.3 From 87b4aad9e9a88fafbf77dffd9694d613e721add6 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 3 Mar 2016 19:21:21 -0800 Subject: Test rendered file deletion or persistence for both debug options. --- wkhtmltopdf/tests/tests.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/wkhtmltopdf/tests/tests.py b/wkhtmltopdf/tests/tests.py index b3c9ea1..7b48421 100644 --- a/wkhtmltopdf/tests/tests.py +++ b/wkhtmltopdf/tests/tests.py @@ -97,14 +97,14 @@ class TestUtils(TestCase): temp_file.close() def _render_file(self, template, context): - """Helper method for test_rendered_filed_deletion test.""" + """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_filed_deletion(self): + 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') @@ -112,11 +112,28 @@ class TestUtils(TestCase): saved_content, filename = self._render_file(template=template, context={'title': title}) - # First verify temp file was actually rendered. + # First verify temp file was rendered correctly. self.assertTrue(title in saved_content) - # Then check if file is deleted when DEBUG=False. - self.assertEqual(os.path.isfile(filename), debug) + # 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): -- cgit v1.2.3