Inside a Django template, one can call an object method like this :
{{ my_object.my_method }}
The problem is when you get an exception/bug
What can I do ?
Evaluate the exception-generating method in your view function.
def someView( request ):
.... all the normal work ...
my_object.my_method() # Just here for debugging.
return render_to_response( ... all the normal stuff... )
You can remove that line of code when you're done debugging.
Similar to S. Lott's answer, activate the management shell (python manage.py shell) and create the appropriate instance of my_object, call my_method. Or put exception handling in my_method and log the exception.
Here's a nice trick I just implemented for doing exactly this. Put this in your debug settings:
class InvalidString(str):
def __mod__(self, other):
from django.template.base import TemplateSyntaxError
raise TemplateSyntaxError(
"Undefined variable or unknown value for: %s" % other)
// this option is deprecated since django 1.8
TEMPLATE_STRING_IF_INVALID = InvalidString("%s")
// put it in template's options instead
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
// ...
'OPTIONS': {
'string_if_invalid': InvalidString("%s"),
},
},
]
This will cause a TemplateSyntaxError to be raised when the parses sees an unknown or invalid value. I've tested this a little (with undefined variable names) and it works great. I haven't tested with function return values, etc. Things could get complicated.
TEMPLATE_STRING_IF_INVALID
doesn't work for me. A quick fix is to open env/lib64/python2.7/site-packages/django/template/base.py
, find except Exception
and throw a print e
inside it (assuming you're using manage.py runserver
and can see print output).
However, a few lines down is current = context.template.engine.string_if_invalid
. I noticed string_if_invalid
was empty despite having set TEMPLATE_STRING_IF_INVALID
. This lead me to this part of the docs:
https://docs.djangoproject.com/en/1.8/ref/templates/upgrading/#the-templates-settings
Django’s template system was overhauled in Django 1.8 when it gained support for multiple template engines.
...
If your settings module defines
ALLOWED_INCLUDE_ROOTS
orTEMPLATE_STRING_IF_INVALID
, include their values under the 'allowed_include_roots
' and 'string_if_invalid
' keys in the 'OPTIONS
' dictionary.
So in addition to @slacy's TemplateSyntaxError trick,
class InvalidString(str): def __mod__(self, other): from django.template.base import TemplateSyntaxError raise TemplateSyntaxError( "Undefined variable or unknown value for: %s" % other) TEMPLATE_STRING_IF_INVALID = InvalidString("%s")
you also need to define string_if_invalid
as follows
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'string_if_invalid': TEMPLATE_STRING_IF_INVALID,
...
Straight away this found a bunch of issues I had I didn't even know about. It really should be enabled by default. To solve tags and filters that expect to fail silently I threw conditionals around them:
{% if obj.might_not_exist %}
{{ obj.might_not_exist }}
{% endif %}
Although I suspect this only works because the {% if %}
fails silently. Another approach might be to create a hasattr
filter: {% if obj|hasattr:"might_not_exist" %}
.
I'd use a Unit tests to isolate the problem. I know this is an indirect answer but I feel this is the ideal way to solve and prevent the problem from returning.
Finally I Found a solution: I developed a template debug tag :
from django import template
import traceback
class DebugVariable(template.Variable):
def _resolve_lookup(self, context):
current = context
for bit in self.lookups:
try: # dictionary lookup
current = current[bit]
except (TypeError, AttributeError, KeyError):
try: # attribute lookup
current = getattr(current, bit)
if callable(current):
if getattr(current, 'alters_data', False):
current = settings.TEMPLATE_STRING_IF_INVALID
else:
try: # method call (assuming no args required)
current = current()
except:
raise Exception("Template Object Method Error : %s" % traceback.format_exc())
except (TypeError, AttributeError):
try: # list-index lookup
current = current[int(bit)]
except (IndexError, # list index out of range
ValueError, # invalid literal for int()
KeyError, # current is a dict without `int(bit)` key
TypeError, # unsubscriptable object
):
raise template.VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
except Exception, e:
if getattr(e, 'silent_variable_failure', False):
current = settings.TEMPLATE_STRING_IF_INVALID
else:
raise
except Exception, e:
if getattr(e, 'silent_variable_failure', False):
current = settings.TEMPLATE_STRING_IF_INVALID
else:
raise
return current
class DebugVarNode(template.Node):
def __init__(self, var):
self.var = DebugVariable(var)
def render(self, context):
return self.var.resolve(context)
@register.tag('debug_var')
def do_debug_var(parser, token):
"""
raise every variable rendering exception, TypeError included (usually hidden by django)
Syntax::
{% debug_var obj.my_method %} instead of {{ obj.my_method }}
"""
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError("'%s' tag takes one argument" % bits[0])
return DebugVarNode(bits[1])
So now in my template I just replace
{{ my_object.my_method }} by {% debug_var my_object.my_method %}