Skip to content

Commit

Permalink
Patch django.template.base.Template.render instead of render_to_strin…
Browse files Browse the repository at this point in the history
…g().

Render often used without render_to_string, and we want to debug render
exceptions in such cases.

render_to_string calls render, so intercepting both results in duplicate
exception message mangling for render_to_string.

Patch URLNode.render to check for using a variable name that doesn't
exist, which probably means that you forgot to quote the name of the
view you wanted to link to with {% url %}. Otherwise it can be hard
to work out where on the page the mistake is, without knowing what to
look for.

Patch URLNode.render to check for using a variable name that points
to a view that doesn't exist, and report the variable name and its
value as well as the exception details, in case you used the wrong
variable name in a {% url %}, or it had a value that you didn't expect.

Patch django.db.models.fields.DateTimeField.get_prep_value to report
the field name containing the naive datetime, until
https://code.djangoproject.com/ticket/19560 gets fixed.

Allow templates to output variables with underscores (e.g. __class__)
in DEBUG mode only, to help debug weird template problems.
  • Loading branch information
qris committed Jan 9, 2013
1 parent 9861b23 commit 616b623
Showing 1 changed file with 99 additions and 5 deletions.
104 changes: 99 additions & 5 deletions monkeypatches.py
Expand Up @@ -452,12 +452,106 @@ def AutoField_to_python_with_improved_debugging(original_function, self, value):
# print "after patch: IntranetUser.id.to_python = %s" % IntranetUser.id.to_python

# Show the filename that contained the template error
import django.template.loader
"""
@patch(django.template.loader, 'render_to_string')
def template_loader_render_to_string(original_function, template_name,
dictionary=None, context_instance=None):
def template_loader_render_to_string_with_debugging(original_function,
template_name, dictionary=None, context_instance=None):
try:
return original_function(template_name, dictionary, context_instance)
except Exception as e:
raise Exception("Failed to render template: %s: %s" %
(template_name, e))
import sys
raise Exception, "Failed to render template: %s: %s" % \
(template_name, e), sys.exc_info()[2]
"""

# Show the filename that contained the template error
@patch(django.template.base.Template, 'render')
def template_render_with_debugging(original_function, self, context):
try:
return original_function(self, context)
except Exception as e:
import sys
raise Exception, "Failed to render template: %s: %s" % \
(self.name, e), sys.exc_info()[2]

@patch(django.template.defaulttags.URLNode, 'render')
def urlnode_render_with_debugging(original_function, self, context):
if not self.view_name.resolve(context):
raise Exception(("Failed to resolve %s in context: did you " +
"forget to enclose view name in quotes? Context is: %s") %
(self.view_name, context))

try:
return original_function(self, context)
except NoReverseMatch as e:
raise Exception(("Failed to reverse %s in context %s (did you " +
"forget to enclose view name in quotes?): the exception was: %s") %
(self.view_name, context, e))

from django.db.models.fields import DateTimeField
@before(DateTimeField, 'get_prep_value')
def DateTimeField_get_prep_value_check_for_naive_datetime(self, value):
value = self.to_python(value)
from django.conf import settings
from django.utils import timezone
if value is not None and settings.USE_TZ and timezone.is_naive(value):
raise ValueError(("DateTimeField %s.%s received a " +
"naive datetime (%s) while time zone support is " +
"active.") % (self.model.__name__, self.name, value))

from django.template.base import Variable
@patch(Variable, '__init__')
def Variable_init_with_underscores_allowed(original_function, self, var):
from django.conf import settings
# for security reasons, production deployments are not allowed to
# render variable names containing underscores anyway.
if not settings.DEBUG:
return original_function(self, var)

self.var = var
self.literal = None
self.lookups = None
self.translate = False
self.message_context = None

try:
# First try to treat this variable as a number.
#
# Note that this could cause an OverflowError here that we're not
# catching. Since this should only happen at compile time, that's
# probably OK.
self.literal = float(var)

# So it's a float... is it an int? If the original value contained a
# dot or an "e" then it was a float, not an int.
if '.' not in var and 'e' not in var.lower():
self.literal = int(self.literal)

# "2." is invalid
if var.endswith('.'):
raise ValueError

except ValueError:
# A ValueError means that the variable isn't a number.
if var.startswith('_(') and var.endswith(')'):
# The result of the lookup should be translated at rendering
# time.
self.translate = True
var = var[2:-1]
# If it's wrapped with quotes (single or double), then
# we're also dealing with a literal.
try:
from django.utils.text import unescape_string_literal
self.literal = mark_safe(unescape_string_literal(var))
except ValueError:
# Otherwise we'll set self.lookups so that resolve() knows we're
# dealing with a bonafide variable
"""
if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
raise TemplateSyntaxError("Variables and attributes may "
"not begin with underscores: '%s'" %
var)
"""
from django.template.base import VARIABLE_ATTRIBUTE_SEPARATOR
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))

0 comments on commit 616b623

Please sign in to comment.