Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 4397c587a43ff9bfddd295d48d850676778c6e77 1 parent 608548b
Carl Meyer authored September 16, 2011
34  django/template/debug.py
@@ -4,6 +4,7 @@
4 4
 from django.utils.safestring import SafeData, EscapeData
5 5
 from django.utils.formats import localize
6 6
 
  7
+
7 8
 class DebugLexer(Lexer):
8 9
     def __init__(self, template_string, origin):
9 10
         super(DebugLexer, self).__init__(template_string, origin)
@@ -42,9 +43,9 @@ def exit_command(self):
42 43
     def error(self, token, msg):
43 44
         return self.source_error(token.source, msg)
44 45
 
45  
-    def source_error(self, source,msg):
  46
+    def source_error(self, source, msg):
46 47
         e = TemplateSyntaxError(msg)
47  
-        e.source = source
  48
+        e.django_template_source = source
48 49
         return e
49 50
 
50 51
     def create_nodelist(self):
@@ -63,25 +64,18 @@ def unclosed_block_tag(self, parse_until):
63 64
         raise self.source_error(source, msg)
64 65
 
65 66
     def compile_function_error(self, token, e):
66  
-        if not hasattr(e, 'source'):
67  
-            e.source = token.source
  67
+        if not hasattr(e, 'django_template_source'):
  68
+            e.django_template_source = token.source
68 69
 
69 70
 class DebugNodeList(NodeList):
70 71
     def render_node(self, node, context):
71 72
         try:
72  
-            result = node.render(context)
73  
-        except TemplateSyntaxError, e:
74  
-            if not hasattr(e, 'source'):
75  
-                e.source = node.source
76  
-            raise
  73
+            return node.render(context)
77 74
         except Exception, e:
78  
-            from sys import exc_info
79  
-            wrapped = TemplateSyntaxError(u'Caught %s while rendering: %s' %
80  
-                (e.__class__.__name__, force_unicode(e, errors='replace')))
81  
-            wrapped.source = getattr(e, 'template_node_source', node.source)
82  
-            wrapped.exc_info = exc_info()
83  
-            raise wrapped, None, wrapped.exc_info[2]
84  
-        return result
  75
+            if not hasattr(e, 'django_template_source'):
  76
+                e.django_template_source = node.source
  77
+            raise
  78
+
85 79
 
86 80
 class DebugVariableNode(VariableNode):
87 81
     def render(self, context):
@@ -89,12 +83,12 @@ def render(self, context):
89 83
             output = self.filter_expression.resolve(context)
90 84
             output = localize(output, use_l10n=context.use_l10n)
91 85
             output = force_unicode(output)
92  
-        except TemplateSyntaxError, e:
93  
-            if not hasattr(e, 'source'):
94  
-                e.source = self.source
95  
-            raise
96 86
         except UnicodeDecodeError:
97 87
             return ''
  88
+        except Exception, e:
  89
+            if not hasattr(e, 'django_template_source'):
  90
+                e.django_template_source = self.source
  91
+            raise
98 92
         if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
99 93
             return escape(output)
100 94
         else:
10  django/template/defaulttags.py
@@ -227,17 +227,15 @@ def render(self, context):
227 227
                     context.update(unpacked_vars)
228 228
             else:
229 229
                 context[self.loopvars[0]] = item
230  
-            # In TEMPLATE_DEBUG mode providing source of the node which
231  
-            # actually raised an exception to DefaultNodeList.render_node
  230
+            # In TEMPLATE_DEBUG mode provide source of the node which
  231
+            # actually raised the exception
232 232
             if settings.TEMPLATE_DEBUG:
233 233
                 for node in self.nodelist_loop:
234 234
                     try:
235 235
                         nodelist.append(node.render(context))
236 236
                     except Exception, e:
237  
-                        if not hasattr(e, 'template_node_source'):
238  
-                            from sys import exc_info
239  
-                            e.template_node_source = node.source
240  
-                            raise e, None, exc_info()[2]
  237
+                        if not hasattr(e, 'django_template_source'):
  238
+                            e.django_template_source = node.source
241 239
                         raise
242 240
             else:
243 241
                 for node in self.nodelist_loop:
11  django/views/debug.py
@@ -8,8 +8,7 @@
8 8
 from django.core.exceptions import ImproperlyConfigured
9 9
 from django.http import (HttpResponse, HttpResponseServerError,
10 10
     HttpResponseNotFound, HttpRequest, build_request_repr)
11  
-from django.template import (Template, Context, TemplateDoesNotExist,
12  
-    TemplateSyntaxError)
  11
+from django.template import Template, Context, TemplateDoesNotExist
13 12
 from django.template.defaultfilters import force_escape, pprint
14 13
 from django.utils.html import escape
15 14
 from django.utils.importlib import import_module
@@ -223,8 +222,8 @@ def get_traceback_html(self):
223 222
                     'loader': loader_name,
224 223
                     'templates': template_list,
225 224
                 })
226  
-        if (settings.TEMPLATE_DEBUG and hasattr(self.exc_value, 'source') and
227  
-            isinstance(self.exc_value, TemplateSyntaxError)):
  225
+        if (settings.TEMPLATE_DEBUG and
  226
+            hasattr(self.exc_value, 'django_template_source')):
228 227
             self.get_template_exception_info()
229 228
 
230 229
         frames = self.get_traceback_frames()
@@ -268,7 +267,7 @@ def get_traceback_html(self):
268 267
         return t.render(c)
269 268
 
270 269
     def get_template_exception_info(self):
271  
-        origin, (start, end) = self.exc_value.source
  270
+        origin, (start, end) = self.exc_value.django_template_source
272 271
         template_source = origin.reload()
273 272
         context_lines = 10
274 273
         line = 0
@@ -626,7 +625,7 @@ def empty_urlconf(request):
626 625
 {% endif %}
627 626
 {% if template_info %}
628 627
 <div id="template">
629  
-   <h2>Template error</h2>
  628
+   <h2>Error during template rendering</h2>
630 629
    <p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
631 630
    <h3>{{ template_info.message }}</h3>
632 631
    <table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}">
16  tests/regressiontests/templates/nodelist.py
... ...
@@ -1,7 +1,7 @@
1  
-from django.conf import settings
2  
-from django.template import VariableNode, Context, TemplateSyntaxError
  1
+from django.template import VariableNode, Context
3 2
 from django.template.loader import get_template_from_string
4 3
 from django.utils.unittest import TestCase
  4
+from django.test.utils import override_settings
5 5
 
6 6
 class NodelistTest(TestCase):
7 7
 
@@ -35,13 +35,7 @@ class ErrorIndexTest(TestCase):
35 35
     Checks whether index of error is calculated correctly in
36 36
     template debugger in for loops. Refs ticket #5831
37 37
     """
38  
-    def setUp(self):
39  
-        self.old_template_debug = settings.TEMPLATE_DEBUG
40  
-        settings.TEMPLATE_DEBUG = True
41  
-
42  
-    def tearDown(self):
43  
-        settings.TEMPLATE_DEBUG = self.old_template_debug
44  
-
  38
+    @override_settings(DEBUG=True, TEMPLATE_DEBUG = True)
45 39
     def test_correct_exception_index(self):
46 40
         tests = [
47 41
             ('{% load bad_tag %}{% for i in range %}{% badsimpletag %}{% endfor %}', (38, 56)),
@@ -58,7 +52,7 @@ def test_correct_exception_index(self):
58 52
             template = get_template_from_string(source)
59 53
             try:
60 54
                 template.render(context)
61  
-            except TemplateSyntaxError, e:
62  
-                error_source_index = e.source[1]
  55
+            except (RuntimeError, TypeError), e:
  56
+                error_source_index = e.django_template_source[1]
63 57
                 self.assertEqual(error_source_index,
64 58
                                  expected_error_source_index)
74  tests/regressiontests/templates/tests.py
... ...
@@ -1,4 +1,6 @@
1 1
 # -*- coding: utf-8 -*-
  2
+from __future__ import with_statement
  3
+
2 4
 from django.conf import settings
3 5
 
4 6
 if __name__ == '__main__':
@@ -20,7 +22,7 @@
20 22
 from django.template import loader
21 23
 from django.template.loaders import app_directories, filesystem, cached
22 24
 from django.test.utils import (get_warnings_state, restore_warnings_state,
23  
-    setup_test_template_loader, restore_template_loaders)
  25
+    setup_test_template_loader, restore_template_loaders, override_settings)
24 26
 from django.utils import unittest
25 27
 from django.utils.formats import date_format
26 28
 from django.utils.translation import activate, deactivate, ugettext as _
@@ -309,9 +311,9 @@ def test_extends_include_missing_baseloader(self):
309 311
             r = None
310 312
             try:
311 313
                 r = tmpl.render(template.Context({}))
312  
-            except template.TemplateSyntaxError, e:
  314
+            except template.TemplateDoesNotExist, e:
313 315
                 settings.TEMPLATE_DEBUG = old_td
314  
-                self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
  316
+                self.assertEqual(e.args[0], 'missing.html')
315 317
             self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
316 318
         finally:
317 319
             loader.template_source_loaders = old_loaders
@@ -336,8 +338,8 @@ def test_extends_include_missing_cachedloader(self):
336 338
             r = None
337 339
             try:
338 340
                 r = tmpl.render(template.Context({}))
339  
-            except template.TemplateSyntaxError, e:
340  
-                self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
  341
+            except template.TemplateDoesNotExist, e:
  342
+                self.assertEqual(e.args[0], 'missing.html')
341 343
             self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
342 344
 
343 345
             # For the cached loader, repeat the test, to ensure the first attempt did not cache a
@@ -345,8 +347,8 @@ def test_extends_include_missing_cachedloader(self):
345 347
             tmpl = loader.get_template(load_name)
346 348
             try:
347 349
                 tmpl.render(template.Context({}))
348  
-            except template.TemplateSyntaxError, e:
349  
-                self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html')
  350
+            except template.TemplateDoesNotExist, e:
  351
+                self.assertEqual(e.args[0], 'missing.html')
350 352
             self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r)
351 353
         finally:
352 354
             loader.template_source_loaders = old_loaders
@@ -358,27 +360,31 @@ def test_token_smart_split(self):
358 360
         split = token.split_contents()
359 361
         self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")'])
360 362
 
  363
+    @override_settings(SETTINGS_MODULE=None, TEMPLATE_DEBUG=True)
361 364
     def test_url_reverse_no_settings_module(self):
362 365
         # Regression test for #9005
363  
-        from django.template import Template, Context, TemplateSyntaxError
364  
-
365  
-        old_settings_module = settings.SETTINGS_MODULE
366  
-        old_template_debug = settings.TEMPLATE_DEBUG
367  
-
368  
-        settings.SETTINGS_MODULE = None
369  
-        settings.TEMPLATE_DEBUG = True
  366
+        from django.template import Template, Context
370 367
 
371 368
         t = Template('{% url will_not_match %}')
372 369
         c = Context()
373  
-        try:
374  
-            rendered = t.render(c)
375  
-        except TemplateSyntaxError, e:
376  
-            # Assert that we are getting the template syntax error and not the
377  
-            # string encoding error.
378  
-            self.assertEqual(e.args[0], "Caught NoReverseMatch while rendering: Reverse for 'will_not_match' with arguments '()' and keyword arguments '{}' not found.")
  370
+        with self.assertRaises(urlresolvers.NoReverseMatch):
  371
+            t.render(c)
  372
+
  373
+
  374
+    @override_settings(DEBUG=True, TEMPLATE_DEBUG = True)
  375
+    def test_no_wrapped_exception(self):
  376
+        """
  377
+        The template system doesn't wrap exceptions, but annotates them.
  378
+        Refs #16770
  379
+
  380
+        """
  381
+        c = Context({"coconuts": lambda: 42 / 0})
  382
+        t = Template("{{ coconuts }}")
  383
+        with self.assertRaises(ZeroDivisionError) as cm:
  384
+            t.render(c)
  385
+
  386
+        self.assertEqual(cm.exception.django_template_source[1], (0, 14))
379 387
 
380  
-        settings.SETTINGS_MODULE = old_settings_module
381  
-        settings.TEMPLATE_DEBUG = old_template_debug
382 388
 
383 389
     def test_invalid_block_suggestion(self):
384 390
         # See #7876
@@ -666,7 +672,7 @@ def get_template_tests(self):
666 672
 
667 673
             # In methods that raise an exception without a
668 674
             # "silent_variable_attribute" set to True, the exception propagates
669  
-            'filter-syntax14': (r'1{{ var.method4 }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException, template.TemplateSyntaxError)),
  675
+            'filter-syntax14': (r'1{{ var.method4 }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException)),
670 676
 
671 677
             # Escaped backslash in argument
672 678
             'filter-syntax15': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
@@ -695,8 +701,8 @@ def get_template_tests(self):
695 701
             # In attribute and dict lookups that raise an unexpected exception
696 702
             # without a "silent_variable_attribute" set to True, the exception
697 703
             # propagates
698  
-            'filter-syntax23': (r'1{{ var.noisy_fail_key }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException, template.TemplateSyntaxError)),
699  
-            'filter-syntax24': (r'1{{ var.noisy_fail_attribute }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException, template.TemplateSyntaxError)),
  704
+            'filter-syntax23': (r'1{{ var.noisy_fail_key }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException)),
  705
+            'filter-syntax24': (r'1{{ var.noisy_fail_attribute }}2', {"var": SomeClass()}, (SomeOtherException, SomeOtherException)),
700 706
 
701 707
             ### COMMENT SYNTAX ########################################################
702 708
             'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
@@ -753,7 +759,7 @@ def get_template_tests(self):
753 759
             ### EXCEPTIONS ############################################################
754 760
 
755 761
             # Raise exception for invalid template name
756  
-            'exception01': ("{% extends 'nonexistent' %}", {}, (template.TemplateDoesNotExist, template.TemplateDoesNotExist, template.TemplateSyntaxError)),
  762
+            'exception01': ("{% extends 'nonexistent' %}", {}, (template.TemplateDoesNotExist, template.TemplateDoesNotExist)),
757 763
 
758 764
             # Raise exception for invalid template name (in variable)
759 765
             'exception02': ("{% extends nonexistent %}", {}, (template.TemplateSyntaxError, template.TemplateDoesNotExist)),
@@ -1050,7 +1056,7 @@ def get_template_tests(self):
1050 1056
             'include-fail2': ('{% load broken_tag %}', {}, template.TemplateSyntaxError),
1051 1057
             'include-error07': ('{% include "include-fail1" %}', {}, ('', '', RuntimeError)),
1052 1058
             'include-error08': ('{% include "include-fail2" %}', {}, ('', '', template.TemplateSyntaxError)),
1053  
-            'include-error09': ('{% include failed_include %}', {'failed_include': 'include-fail1'}, ('', '', template.TemplateSyntaxError)),
  1059
+            'include-error09': ('{% include failed_include %}', {'failed_include': 'include-fail1'}, ('', '', RuntimeError)),
1054 1060
             'include-error10': ('{% include failed_include %}', {'failed_include': 'include-fail2'}, ('', '', template.TemplateSyntaxError)),
1055 1061
 
1056 1062
 
@@ -1481,8 +1487,8 @@ def get_template_tests(self):
1481 1487
 
1482 1488
             # Failures
1483 1489
             'old-url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),
1484  
-            'old-url-fail02': ('{% url no_such_view %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
1485  
-            'old-url-fail03': ('{% url regressiontests.templates.views.client %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
  1490
+            'old-url-fail02': ('{% url no_such_view %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
  1491
+            'old-url-fail03': ('{% url regressiontests.templates.views.client %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
1486 1492
             'old-url-fail04': ('{% url view id, %}', {}, template.TemplateSyntaxError),
1487 1493
             'old-url-fail05': ('{% url view id= %}', {}, template.TemplateSyntaxError),
1488 1494
             'old-url-fail06': ('{% url view a.id=id %}', {}, template.TemplateSyntaxError),
@@ -1522,8 +1528,8 @@ def get_template_tests(self):
1522 1528
 
1523 1529
             # Failures
1524 1530
             'url-fail01': ('{% load url from future %}{% url %}', {}, template.TemplateSyntaxError),
1525  
-            'url-fail02': ('{% load url from future %}{% url "no_such_view" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
1526  
-            'url-fail03': ('{% load url from future %}{% url "regressiontests.templates.views.client" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
  1531
+            'url-fail02': ('{% load url from future %}{% url "no_such_view" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
  1532
+            'url-fail03': ('{% load url from future %}{% url "regressiontests.templates.views.client" %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
1527 1533
             'url-fail04': ('{% load url from future %}{% url "view" id, %}', {}, template.TemplateSyntaxError),
1528 1534
             'url-fail05': ('{% load url from future %}{% url "view" id= %}', {}, template.TemplateSyntaxError),
1529 1535
             'url-fail06': ('{% load url from future %}{% url "view" a.id=id %}', {}, template.TemplateSyntaxError),
@@ -1531,9 +1537,9 @@ def get_template_tests(self):
1531 1537
             'url-fail08': ('{% load url from future %}{% url "view" id="unterminatedstring %}', {}, template.TemplateSyntaxError),
1532 1538
             'url-fail09': ('{% load url from future %}{% url "view" id=", %}', {}, template.TemplateSyntaxError),
1533 1539
 
1534  
-            'url-fail11': ('{% load url from future %}{% url named_url %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
1535  
-            'url-fail12': ('{% load url from future %}{% url named_url %}', {'named_url': 'no_such_view'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
1536  
-            'url-fail13': ('{% load url from future %}{% url named_url %}', {'named_url': 'regressiontests.templates.views.client'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch, template.TemplateSyntaxError)),
  1540
+            'url-fail11': ('{% load url from future %}{% url named_url %}', {}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
  1541
+            'url-fail12': ('{% load url from future %}{% url named_url %}', {'named_url': 'no_such_view'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
  1542
+            'url-fail13': ('{% load url from future %}{% url named_url %}', {'named_url': 'regressiontests.templates.views.client'}, (urlresolvers.NoReverseMatch, urlresolvers.NoReverseMatch)),
1537 1543
             'url-fail14': ('{% load url from future %}{% url named_url id, %}', {'named_url': 'view'}, template.TemplateSyntaxError),
1538 1544
             'url-fail15': ('{% load url from future %}{% url named_url id= %}', {'named_url': 'view'}, template.TemplateSyntaxError),
1539 1545
             'url-fail16': ('{% load url from future %}{% url named_url a.id=id %}', {'named_url': 'view'}, template.TemplateSyntaxError),
2  tests/regressiontests/views/tests/debug.py
@@ -75,7 +75,7 @@ def test_template_exceptions(self):
75 75
         for n in range(len(except_args)):
76 76
             try:
77 77
                 self.client.get(reverse('template_exception', args=(n,)))
78  
-            except TemplateSyntaxError, e:
  78
+            except Exception:
79 79
                 raising_loc = inspect.trace()[-1][-2][0].strip()
80 80
                 self.assertFalse(raising_loc.find('raise BrokenException') == -1,
81 81
                     "Failed to find 'raise BrokenException' in last frame of traceback, instead found: %s" %

0 notes on commit 4397c58

Please sign in to comment.
Something went wrong with that request. Please try again.