From 88261b5891ed3235841e2203255ba69bda2b2811 Mon Sep 17 00:00:00 2001 From: Ross Patterson Date: Tue, 22 Oct 2013 18:14:26 -0700 Subject: [PATCH 1/3] Support validating Django templates by compiling them --- README.rst | 4 ++ django_nose/runner.py | 7 +++- django_nose/templates.py | 80 ++++++++++++++++++++++++++++++++++++++++ setup.py | 3 +- 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 django_nose/templates.py diff --git a/README.rst b/README.rst index 6025da8..208e4e7 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,7 @@ Features * Hygienic TransactionTestCases, which can save you a DB flush per test * Support for various databases. Tested with MySQL, PostgreSQL, and SQLite. Others should work as well. +* Support for automatically validating Django templates by compiling them. .. _nose: http://somethingaboutorange.com/mrl/projects/nose/ .. _nose plugins: http://nose-plugins.jottit.com/ @@ -321,6 +322,9 @@ django-nose does not support Django 1.0. Recent Version History ---------------------- +1.3 (Unreleased) + * Support validating Django templates by compiling them (rpatterson) + 1.2 (2013-07-23) * Python 3 support (melinath and jonashaag) * Django 1.5 compat (fabiosantoscode) diff --git a/django_nose/runner.py b/django_nose/runner.py index 5e126f5..d93e96b 100644 --- a/django_nose/runner.py +++ b/django_nose/runner.py @@ -27,6 +27,7 @@ from django_nose.plugin import DjangoSetUpPlugin, ResultPlugin, TestReorderer from django_nose.utils import uses_mysql +from django_nose.templates import DjangoTemplates try: any @@ -68,7 +69,8 @@ def _get_test_db_name(self): def _get_plugins_from_settings(): plugins = (list(getattr(settings, 'NOSE_PLUGINS', [])) + - ['django_nose.plugin.TestReorderer']) + ['django_nose.plugin.TestReorderer', + 'django_nose.templates.DjangoTemplates']) for plug_path in plugins: try: dot = plug_path.rindex('.') @@ -138,7 +140,8 @@ def run_suite(self, nose_argv): result_plugin = ResultPlugin() plugins_to_add = [DjangoSetUpPlugin(self), result_plugin, - TestReorderer()] + TestReorderer(), + DjangoTemplates()] for plugin in _get_plugins_from_settings(): plugins_to_add.append(plugin) diff --git a/django_nose/templates.py b/django_nose/templates.py new file mode 100644 index 0000000..a7ca44c --- /dev/null +++ b/django_nose/templates.py @@ -0,0 +1,80 @@ +""" +Generate tests for compiling each Django template to validate them. +""" + +import os +import re +import unittest + +from nose.plugins import base + + +def is_descendant(ancestor, descendant): + return os.path.join(descendant, '').startswith(os.path.join(ancestor, '')) + + +class DjangoTemplates(base.Plugin): + __doc__ + + name = 'django-templates' + test_name_pattern = re.compile(r'\W') + + def configure(self, options, config): + """Make sure the Django template loader cache is populated.""" + base.Plugin.configure(self, options, config) + from django.template import loader + try: + loader.find_template('') + except loader.TemplateDoesNotExist: + pass + + self.sources = set() + self.loader = unittest.TestLoader() + + def wantDirectory(self, path): + """Select all files in a Django templates directory if enabled.""" + from django.template import loader + for source in self.sources: + if is_descendant(source, path): + return None + for source_loader in loader.template_source_loaders: + try: + sources = source_loader.get_template_sources('.') + except loader.TemplateDoesNotExist: + continue + for source in sources: + if is_descendant(source, path): + self.sources.add(source) + return True + return None + + def loadTestsFromDir(self, path): + """Construct tests for all files in a Django templates directory.""" + if path not in self.sources: + return None + + from django.template import loader + from django import test + + tests = {} + for root, dirs, files in os.walk(path): + for filename in files: + file_path = os.path.join(root, filename) + if not os.path.isfile(file_path): + # self.wantDirectory will handle directories + continue + + def generatedDjangoTemplateTest(test, file_path=file_path): + template = loader.get_template(file_path[len(path) + 1:]) + test.assertTrue( + callable(getattr(template, 'render', None))) + + generatedDjangoTemplateTest.__doc__ = ( + "The {0!r} template compiles".format(file_path)) + test_name = 'test_django_template_{}'.format( + self.test_name_pattern.sub('_', filename)) + generatedDjangoTemplateTest.func_name = test_name + tests[test_name] = generatedDjangoTemplateTest + + case = type('DjangoTemplateTestCase', (test.TestCase, ), tests) + return self.loader.loadTestsFromTestCase(case) diff --git a/setup.py b/setup.py index 329eedb..2077b4d 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='django-nose', - version='1.2', + version='1.3', description='Makes your Django tests simple and snappy', long_description=open(os.path.join(ROOT, 'README.rst')).read(), author='Jeff Balogh', @@ -29,6 +29,7 @@ #entry_points=""" # [nose.plugins.0.10] # fixture_bundler = django_nose.fixture_bundling:FixtureBundlingPlugin + # django_templates = django_nose.templates:DjangoTemplates # """, classifiers=[ 'Development Status :: 5 - Production/Stable', From 0c4ae36777fa99fdffb81352ee4027f882c8d0e5 Mon Sep 17 00:00:00 2001 From: Ross Patterson Date: Tue, 22 Oct 2013 19:00:44 -0700 Subject: [PATCH 2/3] Python 2.6 compat --- django_nose/templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_nose/templates.py b/django_nose/templates.py index a7ca44c..d071063 100644 --- a/django_nose/templates.py +++ b/django_nose/templates.py @@ -71,7 +71,7 @@ def generatedDjangoTemplateTest(test, file_path=file_path): generatedDjangoTemplateTest.__doc__ = ( "The {0!r} template compiles".format(file_path)) - test_name = 'test_django_template_{}'.format( + test_name = 'test_django_template_{0}'.format( self.test_name_pattern.sub('_', filename)) generatedDjangoTemplateTest.func_name = test_name tests[test_name] = generatedDjangoTemplateTest From 1d16b5bd4cf38213e77df5519da9582e6c571a4d Mon Sep 17 00:00:00 2001 From: Ross Patterson Date: Sun, 17 Nov 2013 07:45:23 -0800 Subject: [PATCH 3/3] Better template compile test shortDescription --- django_nose/templates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_nose/templates.py b/django_nose/templates.py index d071063..06d5e2a 100644 --- a/django_nose/templates.py +++ b/django_nose/templates.py @@ -70,7 +70,8 @@ def generatedDjangoTemplateTest(test, file_path=file_path): callable(getattr(template, 'render', None))) generatedDjangoTemplateTest.__doc__ = ( - "The {0!r} template compiles".format(file_path)) + "Template compiles: {0!r}".format( + os.path.relpath(file_path))) test_name = 'test_django_template_{0}'.format( self.test_name_pattern.sub('_', filename)) generatedDjangoTemplateTest.func_name = test_name