开发者

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 or TEMPLATE_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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜