﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
30197	Template variable resolution with class objects	Alex Epshteyn	nobody	"The behavior of `if callable(current):` ... `current = current()` in [https://github.com/django/django/blob/21ff23bfeb4014bceaa3df27677fb68409c0634d/django/template/base.py#L851 django.template.base.Variable._resolve_lookup] prevents using a function (or a class!) as a **value** in a template.

This behavior only makes sense when the previous `bit` in `self.lookups` was an //instance// and the `current` bit is a //method// that should be invoked on that instance.  I would argue that it //never// makes sense in any other case (for example, [https://stackoverflow.com/q/6861601/1965404]).

== Example

Suppose you want to write a filter that returns the name of a class:

{{{#!python
@register.filter()
def type_name(someClass):
    return someClass.__name__
}}}

Using this filter in a template like:
{{{#!django 
<div>Foo.Bar is an instance of {{ foo.bar|type_name }}</div>
}}}
might seem valid, but it turns out that this expression will //never// work as intended, due to the implementation of `Variable._resolve_lookup` — the filter we defined in this example will pretty much always return an empty string (or whatever value you configured for your app's `string_if_invalid` setting).

For example, if `foo.bar` is `<type 'basestring'>`, invoking it as a callable will raise **`TypeError: The basestring type cannot be instantiated`**, and if `foo.bar` is some other type that is actually instantiable (e.g. `<type 'str'>`, or a user-defined class), our filter will receive an //instance// of that class (rather than the class itself), which will almost-certainly lead to an exception like **`AttributeError: 'str' object has no attribute '__name__'`**

== Approaches tried in the past

The workaround introduced by #15791 allows setting an attribute named `do_not_call_in_templates` on a callable object to be able to use the //object itself// as the value (rather than the //value returned from invoking it//).

However, this seems like a dirty hack, and furthermore, in many cases you may not want to (or even be able to) set an attribute on that object (e.g. if this object is a class that comes from a library and you have no control of it).

=== Current implementation of `Variable._resolve_lookup`^*^:

{{{#!python
def _resolve_lookup(self, context):
    """"""
    Perform resolution of a real variable (i.e. not a literal) against the
    given context.
    """"""
    current = context
    try:  # catch-all for silent variable failures
        for bit in self.lookups:
            try:  # dictionary lookup
                current = current[bit]
            # (followed by nested try/except blocks for attribute lookup and list-index lookup)
            # ...
            # *** and at last ***:
            if callable(current):
                if getattr(current, 'do_not_call_in_templates', False):
                    pass
                elif getattr(current, 'alters_data', False):
                    current = context.template.engine.string_if_invalid
                else:
                    try:  # method call (assuming no args required)
                        current = current()
                    # etc...
    # etc...
    return current
}}}
^*^ //abbreviated for clarity//

== Proposed solution

At the very least, I think it would make sense to add a check for `inspect.isclass` to the `if callable(current):` ... `current = current()` block of this method.

For example:
{{{#!python
if callable(current) and not inspect.isclass(current):
    if getattr(current, 'do_not_call_in_templates', False):
        pass
    # etc...
}}}
or
{{{#!python
if callable(current):
    if getattr(current, 'do_not_call_in_templates', False) or inspect.isclass(current):
        pass
    # etc...
}}}

However, ideally, it would be even better to check whether the previous `bit` in `self.lookups` was an //instance// and the `current` bit is a //method// supported by the class of that instance before trying to invoke `current` as a callable."	New feature	new	Template system	dev	Normal		template, variable, resolve		Unreviewed	0	0	0	0	1	0
