How to see exception generated into django template variable?
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 in 'def my_method(self)', it is hidden when rendering the template (there is an empty string output 开发者_JS百科instead, so no errors appears).
As I want to debug what's wrong in 'def my_method(self)', I would like to turn on something like a global django flag to receive such exception.
in settings.py, I already have
DEBUG = True
TEMPLATE_DEBUG = True
I can receive many kind of template exceptions, but none when I trig an object method.
What can I do ?
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.
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 %}
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" %}
.
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.
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.
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.
精彩评论