瀏覽代碼

Fixed #18773 -- Added logging for template variable resolving

Added a django.template logger without a default handler. Added
logging if there is an exception while resolving variables in a
template.
Caroline Simpson 11 年之前
父節點
當前提交
dc5b01ad05

+ 1 - 0
django/contrib/admin/helpers.py

@@ -121,6 +121,7 @@ class AdminField(object):
         self.field = form[field]  # A django.forms.BoundField instance
         self.is_first = is_first  # Whether this field is first on the line
         self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput)
+        self.is_readonly = False
 
     def label_tag(self):
         classes = []

+ 7 - 0
django/template/base.py

@@ -51,6 +51,7 @@ u'<html></html>'
 
 from __future__ import unicode_literals
 
+import logging
 import re
 import warnings
 from functools import partial
@@ -125,6 +126,8 @@ libraries = {}
 # global list of libraries to load by default for a new parser
 builtins = []
 
+logger = logging.getLogger('django.template')
+
 
 class TemplateSyntaxError(Exception):
     pass
@@ -209,6 +212,7 @@ class Template(object):
         try:
             if context.template is None:
                 with context.bind_template(self):
+                    context.template_name = self.name
                     return self._render(context)
             else:
                 return self._render(context)
@@ -893,6 +897,9 @@ class Variable(object):
                             else:
                                 raise
         except Exception as e:
+            template_name = getattr(context, 'template_name', 'unknown')
+            logger.debug('{} - {}'.format(template_name, e))
+
             if getattr(e, 'silent_variable_failure', False):
                 current = context.template.engine.string_if_invalid
             else:

+ 1 - 0
django/template/context.py

@@ -141,6 +141,7 @@ class Context(BaseContext):
         self._current_app = current_app
         self.use_l10n = use_l10n
         self.use_tz = use_tz
+        self.template_name = "unknown"
         self.render_context = RenderContext()
         # Set to the original template -- as opposed to extended or included
         # templates -- during rendering, see bind_template.

+ 3 - 0
docs/releases/1.9.txt

@@ -188,6 +188,9 @@ Templates
 * Added a :meth:`Context.setdefault() <django.template.Context.setdefault>`
   method.
 
+* A warning will now be logged for missing context variables. These messages
+  will be logged to the :ref:`django.template <django-template-logger>` logger.
+
 Requests and Responses
 ^^^^^^^^^^^^^^^^^^^^^^
 

+ 10 - 0
docs/topics/logging.txt

@@ -478,6 +478,16 @@ Messages to this logger have the following extra context:
 * ``request``: The request object that generated the logging
   message.
 
+.. _django-template-logger:
+
+``django.template``
+~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.9
+
+Log messages related to the rendering of templates. Missing context variables
+are logged as ``DEBUG`` messages if :setting:`DEBUG` is `True`.
+
 .. _django-db-logger:
 
 ``django.db.backends``

+ 71 - 0
tests/template_tests/test_logging.py

@@ -0,0 +1,71 @@
+from __future__ import unicode_literals
+
+import logging
+
+from django.template import Template, Variable, VariableDoesNotExist
+from django.test import SimpleTestCase
+
+
+class TestHandler(logging.Handler):
+    def __init__(self):
+        super(TestHandler, self).__init__()
+        self.log_record = None
+
+    def emit(self, record):
+        self.log_record = record
+
+
+class VariableResolveLoggingTests(SimpleTestCase):
+    def setUp(self):
+        self.test_handler = TestHandler()
+        self.logger = logging.getLogger('django.template')
+        self.original_level = self.logger.level
+        self.logger.addHandler(self.test_handler)
+        self.logger.setLevel(logging.DEBUG)
+
+    def tearDown(self):
+        self.logger.removeHandler(self.test_handler)
+        self.logger.level = self.original_level
+
+    def test_log_on_variable_does_not_exist_silent(self):
+        class TestObject(object):
+            class SilentDoesNotExist(Exception):
+                silent_variable_failure = True
+
+            @property
+            def template_name(self):
+                return "template"
+
+            @property
+            def template(self):
+                return Template('')
+
+            @property
+            def article(self):
+                raise TestObject.SilentDoesNotExist("Attribute does not exist.")
+
+            def __iter__(self):
+                return iter(attr for attr in dir(TestObject) if attr[:2] != "__")
+
+            def __getitem__(self, item):
+                return self.__dict__[item]
+
+        Variable('article').resolve(TestObject())
+        self.assertEqual(
+            self.test_handler.log_record.msg,
+            'template - Attribute does not exist.'
+        )
+
+    def test_log_on_variable_does_not_exist_not_silent(self):
+        with self.assertRaises(VariableDoesNotExist):
+            Variable('article.author').resolve({'article': {'section': 'News'}})
+
+        self.assertEqual(
+            self.test_handler.log_record.msg,
+            'unknown - Failed lookup for key [author] in %r' %
+            ("{%r: %r}" % ('section', 'News'), )
+        )
+
+    def test_no_log_when_variable_exists(self):
+        Variable('article.section').resolve({'article': {'section': 'News'}})
+        self.assertIsNone(self.test_handler.log_record)