Commit 4397c587 authored by Carl Meyer's avatar Carl Meyer
Browse files

Fixed #16770 -- Eliminated TemplateSyntaxError wrapping of exceptions. Thanks...

Fixed #16770 -- Eliminated TemplateSyntaxError wrapping of exceptions. Thanks to Justin Myles-Holmes for report and draft patch.

Exceptions raised in templates were previously wrapped in TemplateSyntaxError
(in TEMPLATE_DEBUG mode only) in order to provide template source details on
the debug 500 page. The same debug information is now provided by annotating
exceptions rather than wrapping them. This makes catching exceptions raised
from templates more sane, as it's consistent in or out of DEBUG, and you can
catch the specific exception(s) you care about rather than having to also catch
TemplateSyntaxError and unwrap it.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16833 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 608548ba
Loading
Loading
Loading
Loading
+14 −20
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ from django.utils.html import escape
from django.utils.safestring import SafeData, EscapeData
from django.utils.formats import localize


class DebugLexer(Lexer):
    def __init__(self, template_string, origin):
        super(DebugLexer, self).__init__(template_string, origin)
@@ -44,7 +45,7 @@ class DebugParser(Parser):

    def source_error(self, source, msg):
        e = TemplateSyntaxError(msg)
        e.source = source
        e.django_template_source = source
        return e

    def create_nodelist(self):
@@ -63,25 +64,18 @@ class DebugParser(Parser):
        raise self.source_error(source, msg)

    def compile_function_error(self, token, e):
        if not hasattr(e, 'source'):
            e.source = token.source
        if not hasattr(e, 'django_template_source'):
            e.django_template_source = token.source

class DebugNodeList(NodeList):
    def render_node(self, node, context):
        try:
            result = node.render(context)
        except TemplateSyntaxError, e:
            if not hasattr(e, 'source'):
                e.source = node.source
            raise
            return node.render(context)
        except Exception, e:
            from sys import exc_info
            wrapped = TemplateSyntaxError(u'Caught %s while rendering: %s' %
                (e.__class__.__name__, force_unicode(e, errors='replace')))
            wrapped.source = getattr(e, 'template_node_source', node.source)
            wrapped.exc_info = exc_info()
            raise wrapped, None, wrapped.exc_info[2]
        return result
            if not hasattr(e, 'django_template_source'):
                e.django_template_source = node.source
            raise


class DebugVariableNode(VariableNode):
    def render(self, context):
@@ -89,12 +83,12 @@ class DebugVariableNode(VariableNode):
            output = self.filter_expression.resolve(context)
            output = localize(output, use_l10n=context.use_l10n)
            output = force_unicode(output)
        except TemplateSyntaxError, e:
            if not hasattr(e, 'source'):
                e.source = self.source
            raise
        except UnicodeDecodeError:
            return ''
        except Exception, e:
            if not hasattr(e, 'django_template_source'):
                e.django_template_source = self.source
            raise
        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
            return escape(output)
        else:
+4 −6
Original line number Diff line number Diff line
@@ -227,17 +227,15 @@ class ForNode(Node):
                    context.update(unpacked_vars)
            else:
                context[self.loopvars[0]] = item
            # In TEMPLATE_DEBUG mode providing source of the node which
            # actually raised an exception to DefaultNodeList.render_node
            # In TEMPLATE_DEBUG mode provide source of the node which
            # actually raised the exception
            if settings.TEMPLATE_DEBUG:
                for node in self.nodelist_loop:
                    try:
                        nodelist.append(node.render(context))
                    except Exception, e:
                        if not hasattr(e, 'template_node_source'):
                            from sys import exc_info
                            e.template_node_source = node.source
                            raise e, None, exc_info()[2]
                        if not hasattr(e, 'django_template_source'):
                            e.django_template_source = node.source
                        raise
            else:
                for node in self.nodelist_loop:
+5 −6
Original line number Diff line number Diff line
@@ -8,8 +8,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import (HttpResponse, HttpResponseServerError,
    HttpResponseNotFound, HttpRequest, build_request_repr)
from django.template import (Template, Context, TemplateDoesNotExist,
    TemplateSyntaxError)
from django.template import Template, Context, TemplateDoesNotExist
from django.template.defaultfilters import force_escape, pprint
from django.utils.html import escape
from django.utils.importlib import import_module
@@ -223,8 +222,8 @@ class ExceptionReporter(object):
                    'loader': loader_name,
                    'templates': template_list,
                })
        if (settings.TEMPLATE_DEBUG and hasattr(self.exc_value, 'source') and
            isinstance(self.exc_value, TemplateSyntaxError)):
        if (settings.TEMPLATE_DEBUG and
            hasattr(self.exc_value, 'django_template_source')):
            self.get_template_exception_info()

        frames = self.get_traceback_frames()
@@ -268,7 +267,7 @@ class ExceptionReporter(object):
        return t.render(c)

    def get_template_exception_info(self):
        origin, (start, end) = self.exc_value.source
        origin, (start, end) = self.exc_value.django_template_source
        template_source = origin.reload()
        context_lines = 10
        line = 0
@@ -626,7 +625,7 @@ TECHNICAL_500_TEMPLATE = """
{% endif %}
{% if template_info %}
<div id="template">
   <h2>Template error</h2>
   <h2>Error during template rendering</h2>
   <p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
   <h3>{{ template_info.message }}</h3>
   <table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}">
+5 −11
Original line number Diff line number Diff line
from django.conf import settings
from django.template import VariableNode, Context, TemplateSyntaxError
from django.template import VariableNode, Context
from django.template.loader import get_template_from_string
from django.utils.unittest import TestCase
from django.test.utils import override_settings

class NodelistTest(TestCase):

@@ -35,13 +35,7 @@ class ErrorIndexTest(TestCase):
    Checks whether index of error is calculated correctly in
    template debugger in for loops. Refs ticket #5831
    """
    def setUp(self):
        self.old_template_debug = settings.TEMPLATE_DEBUG
        settings.TEMPLATE_DEBUG = True

    def tearDown(self):
        settings.TEMPLATE_DEBUG = self.old_template_debug

    @override_settings(DEBUG=True, TEMPLATE_DEBUG = True)
    def test_correct_exception_index(self):
        tests = [
            ('{% load bad_tag %}{% for i in range %}{% badsimpletag %}{% endfor %}', (38, 56)),
@@ -58,7 +52,7 @@ class ErrorIndexTest(TestCase):
            template = get_template_from_string(source)
            try:
                template.render(context)
            except TemplateSyntaxError, e:
                error_source_index = e.source[1]
            except (RuntimeError, TypeError), e:
                error_source_index = e.django_template_source[1]
                self.assertEqual(error_source_index,
                                 expected_error_source_index)
+40 −34
Original line number Diff line number Diff line
# -*- coding: utf-8 -*-
from __future__ import with_statement

from django.conf import settings

if __name__ == '__main__':
@@ -20,7 +22,7 @@ from django.core import urlresolvers
from django.template import loader
from django.template.loaders import app_directories, filesystem, cached
from django.test.utils import (get_warnings_state, restore_warnings_state,
    setup_test_template_loader, restore_template_loaders)
    setup_test_template_loader, restore_template_loaders, override_settings)
from django.utils import unittest
from django.utils.formats import date_format
from django.utils.translation import activate, deactivate, ugettext as _
@@ -309,9 +311,9 @@ class Templates(unittest.TestCase):
            r = None
            try:
                r = tmpl.render(template.Context({}))
            except template.TemplateSyntaxError, e:
            except template.TemplateDoesNotExist, e:
                settings.TEMPLATE_DEBUG = old_td
                self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
                self.assertEqual(e.args[0], 'missing.html')
            self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
        finally:
            loader.template_source_loaders = old_loaders
@@ -336,8 +338,8 @@ class Templates(unittest.TestCase):
            r = None
            try:
                r = tmpl.render(template.Context({}))
            except template.TemplateSyntaxError, e:
                self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
            except template.TemplateDoesNotExist, e:
                self.assertEqual(e.args[0], 'missing.html')
            self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)

            # For the cached loader, repeat the test, to ensure the first attempt did not cache a
@@ -345,8 +347,8 @@ class Templates(unittest.TestCase):
            tmpl = loader.get_template(load_name)
            try:
                tmpl.render(template.Context({}))
            except template.TemplateSyntaxError, e:
                self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
            except template.TemplateDoesNotExist, e:
                self.assertEqual(e.args[0], 'missing.html')
            self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
        finally:
            loader.template_source_loaders = old_loaders
@@ -358,27 +360,31 @@ class Templates(unittest.TestCase):
        split = token.split_contents()
        self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")'])

    @override_settings(SETTINGS_MODULE=None, TEMPLATE_DEBUG=True)
    def test_url_reverse_no_settings_module(self):
        # Regression test for #9005
        from django.template import Template, Context, TemplateSyntaxError

        old_settings_module = settings.SETTINGS_MODULE
        old_template_debug = settings.TEMPLATE_DEBUG

        settings.SETTINGS_MODULE = None
        settings.TEMPLATE_DEBUG = True
        from django.template import Template, Context

        t = Template('{% url will_not_match %}')
        c = Context()
        try:
            rendered = t.render(c)
        except TemplateSyntaxError, e:
            # Assert that we are getting the template syntax error and not the
            # string encoding error.
            self.assertEqual(e.args[0], "Caught NoReverseMatch while rendering: Reverse for 'will_not_match' with arguments '()' and keyword arguments '{}' not found.")
        with self.assertRaises(urlresolvers.NoReverseMatch):
            t.render(c)


    @override_settings(DEBUG=True, TEMPLATE_DEBUG = True)
    def test_no_wrapped_exception(self):
        """
        The template system doesn't wrap exceptions, but annotates them.
        Refs #16770

        """
        c = Context({"coconuts": lambda: 42 / 0})
        t = Template("{{ coconuts }}")
        with self.assertRaises(ZeroDivisionError) as cm:
            t.render(c)

        self.assertEqual(cm.exception.django_template_source[1], (0, 14))

        settings.SETTINGS_MODULE = old_settings_module
        settings.TEMPLATE_DEBUG = old_template_debug

    def test_invalid_block_suggestion(self):
        # See #7876
@@ -666,7 +672,7 @@ class Templates(unittest.TestCase):

            # In methods that raise an exception without a
            # "silent_variable_attribute" set to True, the exception propagates
            'filter-syntax14': (r'1{{ var.method4 }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException, template.TemplateSyntaxError)),
            'filter-syntax14': (r'1{{ var.method4 }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException)),

            # Escaped backslash in argument
            'filter-syntax15': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
@@ -695,8 +701,8 @@ class Templates(unittest.TestCase):
            # In attribute and dict lookups that raise an unexpected exception
            # without a "silent_variable_attribute" set to True, the exception
            # propagates
            'filter-syntax23': (r'1{{ var.noisy_fail_key }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException, template.TemplateSyntaxError)),
            'filter-syntax24': (r'1{{ var.noisy_fail_attribute }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException, template.TemplateSyntaxError)),
            'filter-syntax23': (r'1{{ var.noisy_fail_key }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException)),
            'filter-syntax24': (r'1{{ var.noisy_fail_attribute }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException)),

            ### COMMENT SYNTAX ########################################################
            'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
@@ -753,7 +759,7 @@ class Templates(unittest.TestCase):
            ### EXCEPTIONS ############################################################

            # Raise exception for invalid template name
            'exception01': ("{% extends 'nonexistent' %}", {}, (template.TemplateDoesNotExist, template.TemplateDoesNotExist, template.TemplateSyntaxError)),
            'exception01': ("{% extends 'nonexistent' %}", {}, (template.TemplateDoesNotExist, template.TemplateDoesNotExist)),

            # Raise exception for invalid template name (in variable)
            'exception02': ("{% extends nonexistent %}", {}, (template.TemplateSyntaxError, template.TemplateDoesNotExist)),
@@ -1050,7 +1056,7 @@ class Templates(unittest.TestCase):
            'include-fail2': ('{% load broken_tag %}', {}, template.TemplateSyntaxError),
            'include-error07': ('{% include "include-fail1" %}', {}, ('', '', RuntimeError)),
            'include-error08': ('{% include "include-fail2" %}', {}, ('', '', template.TemplateSyntaxError)),
            'include-error09': ('{% include failed_include %}', {'failed_include': 'include-fail1'}, ('', '', template.TemplateSyntaxError)),
            'include-error09': ('{% include failed_include %}', {'failed_include': 'include-fail1'}, ('', '', RuntimeError)),
            'include-error10': ('{% include failed_include %}', {'failed_include': 'include-fail2'}, ('', '', template.TemplateSyntaxError)),


@@ -1481,8 +1487,8 @@ class Templates(unittest.TestCase):

            # Failures
            'old-url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),
            'old-url-fail02': ('{% url no_such_view %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
            'old-url-fail03': ('{% url regressiontests.templates.views.client %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
            'old-url-fail02': ('{% url no_such_view %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
            'old-url-fail03': ('{% url regressiontests.templates.views.client %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
            'old-url-fail04': ('{% url view id, %}', {}, template.TemplateSyntaxError),
            'old-url-fail05': ('{% url view id= %}', {}, template.TemplateSyntaxError),
            'old-url-fail06': ('{% url view a.id=id %}', {}, template.TemplateSyntaxError),
@@ -1522,8 +1528,8 @@ class Templates(unittest.TestCase):

            # Failures
            'url-fail01': ('{% load url from future %}{% url %}', {}, template.TemplateSyntaxError),
            'url-fail02': ('{% load url from future %}{% url "no_such_view" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
            'url-fail03': ('{% load url from future %}{% url "regressiontests.templates.views.client" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
            'url-fail02': ('{% load url from future %}{% url "no_such_view" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
            'url-fail03': ('{% load url from future %}{% url "regressiontests.templates.views.client" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
            'url-fail04': ('{% load url from future %}{% url "view" id, %}', {}, template.TemplateSyntaxError),
            'url-fail05': ('{% load url from future %}{% url "view" id= %}', {}, template.TemplateSyntaxError),
            'url-fail06': ('{% load url from future %}{% url "view" a.id=id %}', {}, template.TemplateSyntaxError),
@@ -1531,9 +1537,9 @@ class Templates(unittest.TestCase):
            'url-fail08': ('{% load url from future %}{% url "view" id="unterminatedstring %}', {}, template.TemplateSyntaxError),
            'url-fail09': ('{% load url from future %}{% url "view" id=", %}', {}, template.TemplateSyntaxError),

            'url-fail11': ('{% load url from future %}{% url named_url %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
            'url-fail12': ('{% load url from future %}{% url named_url %}', {'named_url': 'no_such_view'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
            'url-fail13': ('{% load url from future %}{% url named_url %}', {'named_url': 'regressiontests.templates.views.client'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
            'url-fail11': ('{% load url from future %}{% url named_url %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
            'url-fail12': ('{% load url from future %}{% url named_url %}', {'named_url': 'no_such_view'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
            'url-fail13': ('{% load url from future %}{% url named_url %}', {'named_url': 'regressiontests.templates.views.client'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
            'url-fail14': ('{% load url from future %}{% url named_url id, %}', {'named_url': 'view'}, template.TemplateSyntaxError),
            'url-fail15': ('{% load url from future %}{% url named_url id= %}', {'named_url': 'view'}, template.TemplateSyntaxError),
            'url-fail16': ('{% load url from future %}{% url named_url a.id=id %}', {'named_url': 'view'}, template.TemplateSyntaxError),
Loading