Antwane Antwane - 1 month ago 9
Python Question

Django templates: why does __call__ magic method breaks the rendering of a non-model object?

Today I faced a strange issue on one of my development. I reproduced it with a very minimal example. Have a look at these 2 dummy classes (non Django model subclasses):

class DummyClassA(object):
def __init__(self, name):
self.name = name

def __repr__(self):
return 'Dummy1 object called ' + self.name


class DummyClassB(object):
"""Same as ClassA, with the __call__ method added"""
def __init__(self, name):
self.name = name

def __repr__(self):
return 'Dummy2 object called ' + self.name

def __call__(self, *args, **kwargs):
return "bar"


They are identical, but the second have a special
__call__()
method.

I want to display instances of these 2 objects in a view using the builtin Django template engine:

class MyView(TemplateView):

template_name = 'myapp/home.html'

def get_context_data(self, **kwargs):
ctx = super(MyView, self).get_context_data(**kwargs)

list1 = [
DummyClassA(name="John"),
DummyClassA(name="Jack"),
]

list2 = [
DummyClassB(name="Albert"),
DummyClassB(name="Elmer"),
]

ctx.update({
'list1': list1,
'list2': list2,
})
return ctx


and the corresponding template:

<h1>Objects repr</h1>
<ul>
{% for element in list1 %}
<li>{{ element }}</li>
{% endfor %}
</ul>
<ul>
{% for element in list2 %}
<li>{{ element }}</li>
{% endfor %}
</ul>

<h1>Access members</h1>
<ul>
{% for element in list1 %}
<li>{{ element.name }}</li>
{% endfor %}
</ul>
<ul>
{% for element in list2 %}
<li>{{ element.name }}</li>
{% endfor %}
</ul>


I obtain this result:

html result

When displaying instances of the second class (
{{ element }}
), the
__call__
method is executed instead of
__repr__()
, and when I want to access a member of the class, it returns nothing.

I don't understand why defining the
__call__()
change the way Django template engine will handle those instances. I imagine this is not a bug but mostly a feature, but I am curious, why
__call__()
is run in such case. And why I can't get the value of
element.name
in the 2nd list ?

Answer

Because that's what the template language is designed to do. As the docs state:

If the resulting value [of looking up a variable] is callable, it is called with no arguments. The result of the call becomes the template value.

Without this, there would be no way of calling methods in templates, since the template syntax does not allow using parentheses.

Comments