如何查看django模板变量中的异常?

17 投票
6 回答
5133 浏览
提问于 2025-04-16 07:46

在Django的模板中,你可以这样调用一个对象的方法:

{{ my_object.my_method }}

但是,如果在'定义我的方法(def my_method(self))'的时候出现了异常或者错误,这个错误在渲染模板的时候是看不见的(输出的只是一个空字符串,所以不会显示任何错误信息)。

因为我想调试一下'定义我的方法(def my_method(self))'里面出了什么问题,所以我想开启一个类似于全局的Django标志,以便捕捉到这样的异常。

在settings.py文件中,我已经有了:

DEBUG = True 
TEMPLATE_DEBUG = True

我可以接收到很多种模板异常,但是当我触发一个对象方法的时候却没有任何异常。

我该怎么办呢?

6 个回答

2

TEMPLATE_STRING_IF_INVALID对我来说没用。一个简单的解决办法是打开 env/lib64/python2.7/site-packages/django/template/base.py 文件,找到 except Exception 这一行,然后在里面加上 print e(假设你在用 manage.py runserver,这样可以看到打印的输出)。

不过,往下几行有 current = context.template.engine.string_if_invalid。我注意到 string_if_invalid 是空的,尽管我已经设置了 TEMPLATE_STRING_IF_INVALID。这让我看到了文档中的这一部分:

https://docs.djangoproject.com/en/1.8/ref/templates/upgrading/#the-templates-settings

Django 的模板系统在 1.8 版本时进行了大改进,增加了对多种模板引擎的支持。

...

如果你的设置模块定义了 ALLOWED_INCLUDE_ROOTSTEMPLATE_STRING_IF_INVALID,那么需要在 OPTIONS 字典中,把它们的值放在 'allowed_include_roots' 和 'string_if_invalid' 这两个键下。

所以除了 @slacy的 TemplateSyntaxError 解决办法,

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")

你还需要这样定义 string_if_invalid

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,
            ...

这样一来,我发现了很多我之前都不知道的问题。其实这个功能应该默认就开启。为了处理那些期望静默失败的标签和过滤器,我在它们周围加了条件判断:

{% if obj.might_not_exist %}
{{ obj.might_not_exist }}
{% endif %}

不过我怀疑这只是因为 {% if %} 语句会静默失败。另一种方法可能是创建一个 hasattr 过滤器: {% if obj|hasattr:"might_not_exist" %}

3

最后我找到了解决办法:我开发了一个模板调试标签:

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])

所以现在在我的模板里,我只需要替换

{{ my_object.my_method }} by {% debug_var my_object.my_method %}
16

我刚刚实现了一个很不错的小技巧,可以做到这一点。你可以把这个放在你的调试设置里:

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"),
        },
    },
]

这样一来,当解析器遇到一个未知或无效的值时,就会抛出一个模板语法错误。 我试过一些(用未定义的变量名),效果很好。 但是我还没有测试过函数返回值之类的,可能会变得复杂。

撰写回答