Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access Enum types in Django templates

I was having an issue where no matter what I did I couldn't access an IntEnum (from the enum34 lib) in my Django template.

I was able to get around it by converting it to a dict:

def get_context_data(self, **kwargs):
    context = super(MyView, self).get_context_data(**kwargs)
    # Django templates don't play nice with Enums
    context['DEMOS'] = {d.name: d for d in DEMOS}
    # `context['DEMOS'] = DEMOS` doesn't work
    return context

These don't work when DEMO is an IntEnum, but do when DEMO is converted to a dict:

{{ DEMO.FOO }}  # outputs nothing
{{ DEMO.FOO|default_if_none:'foo' }}  # outputs nothing
{{ DEMO.FOO.value }}  # outputs nothing
{% if DEMO.FOO == 1 %}  # no matter what I compare to, always False

Any ideas why? Is this a known issue?

like image 859
Seán Hayes Avatar asked Mar 12 '16 02:03

Seán Hayes


People also ask

Can I use filter () in Django template?

Django Template Engine provides filters which are used to transform the values of variables;es and tag arguments. We have already discussed major Django Template Tags. Tags can't modify value of a variable whereas filters can be used for incrementing value of a variable or modifying it to one's own need.

What is enum in Django?

Enumeration types Enum member values are a tuple of arguments to use when constructing the concrete data type. Django supports adding an extra string value to the end of this tuple to be used as the human-readable name, or label . The label can be a lazy translatable string.

What does {% mean in Django?

{% %} is basically used when you have an expression and are called tags while {{ }} is used to simply access the variable.

What does {% include %} do in Django?

The include tag allows you include a template inside the current template. This is useful when you have a block of content that are the same for many pages.


2 Answers

A little more digging and I found the answer.

From Django's templates documentation:

Technically, when the template system encounters a dot, it tries the following lookups, in this order:

Dictionary lookup

Attribute or method lookup

Numeric index lookup

If the resulting value is callable, it is called with no arguments. The result of the call becomes the template value.

That last line should say:

If any of the resulting/intermediate values is callable, ...

Stepping through that process:

  • lookup 'DEMOS' in the context, get <enum 'DEMOS'>

  • check if it is callable (it is)

  • call it with no arguments

  • get a TypeError

So the problem is that an Enum class is callable, and the templating system will try to call it, which will raise an error and abort (returning an empty string: '').

However, there is a way around that problem. django.templates.base's calling code contains the following guard condition:

if getattr(current, 'do_not_call_in_templates', False):
    pass

The code checks for an attribute called do_not_call_in_templates, and if True then it will skip the call portion, which should solve the problem.

Using Python's Enum (or the enum34 backport) the simplest way will be to use a decorator. If Django doesn't already have one for this purpose, you can roll your own easily enough:

def forDjango(cls):
    cls.do_not_call_in_templates = True
    return cls

and then decorate your Enum:

@forDjango
class DEMOS(Enum):
    eggs = 'runny'
    spam = 'hard'
    cheese = 'smelly'

The additional attribute does not interfere with your set of Enum constants, because Enums are fixed once defined.

like image 52
Ethan Furman Avatar answered Sep 21 '22 11:09

Ethan Furman


Using Django's Enum you can use __members__ property:

context['DEMOS'] = DEMOS.__members__

Reference

like image 29
potykion Avatar answered Sep 19 '22 11:09

potykion