How to see exception generated into django template variable?

前端 未结 6 2042
庸人自扰
庸人自扰 2020-12-29 07:22

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

相关标签:
6条回答
  • 2020-12-29 07:55

    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.

    0 讨论(0)
  • 2020-12-29 07:59

    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 讨论(0)
  • 2020-12-29 08:02

    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.

    0 讨论(0)
  • 2020-12-29 08:06

    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" %}.

    0 讨论(0)
  • 2020-12-29 08:09

    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.

    0 讨论(0)
  • 2020-12-29 08:21

    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 %}
    
    0 讨论(0)
提交回复
热议问题