3

I have this class in my models.py.

class Size(Enum):
    ONE = 1
    TWO = 2

And in my form, I wanted predefined choices for this field, which is why I used an Enum class:

size = SelectField('Shoe size', choices=[(name, member.value) for name, member in ShoeSize.__members__.items()])

The values stored in the database are ONE, TWO, etc. When displaying in Jinja template {{ shoe.size }}, the member names are what is shown. How can I display the member values in my jinja template?

In short, I want 1 displayed and not ONE. A repr function for the class will be better.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
Eric O.
  • 474
  • 4
  • 23

3 Answers3

8

You can teach your template about your class:

from enum import Enum
from jinja2 import Template

class Size(Enum):
  ONE = 1
  TWO = 2

template = Template('{{ Size[db_value].value }} == {{ db_value }}')
template.globals['Size'] = Size

template.render(db_value='ONE')

Edit: Somebody marked this as a duplicate which I dont understand, since the linked answer doesn't answer the jinja2 templating question

booleys1012
  • 671
  • 3
  • 9
  • Thank you for your answer. It has helped me get the correct way to solve my problem. I needed to avail the Size class in the view function that renders that template then access the member value and not the member name. Better off, am now storing the value and not the names in my table. – Eric O. Mar 14 '19 at 10:52
1

From @booley1012 answer, I got better insight. All I needed to do was store the values and not the names: So I imported the Size class in my view file:

from app.models import Size

Then in the view function that does the saving:

shoe = Shoe(size = Size[form.size.data].value)
Eric O.
  • 474
  • 4
  • 23
  • nice! my answer was under the presumption you were trying to go directly from database-result to template without any transformation. Glad you found your solution! – booleys1012 Mar 14 '19 at 20:06
1

You can register a custom Jinja filter that tells Jinja to use the Enum value (.value) if it receives an Enum object.

For example, you have this template.html:

<body>
    {% for obj in objects %}
        {{ obj }}<br>
    {% endfor %}
</body>

and you pass an objects to the template which can contain different types, like this:

@app.route('/', methods=['GET'])
def index():
    objects = get_objects_from_somewhere()
    # This returns different data types, maybe multiple enums
    # Ex. ['somename', (22, 44, 66), Size.ONE, AnotherEnum.XYZ]

    return render_template('template.html', objects=objects)

Define a Jinja filter function like this:

from enum import Enum

@app.template_filter()
def to_string(obj):
    if isinstance(obj, Enum):
        return obj.value

    # For all other types, let Jinja use default behavior
    return obj

A filter function receives the value from the {{ value | filter_name }} in the template, and then you need to return what Jinja will display. Read more about on custom filters here from the Jinja docs.

Then use it in your template like this:

<!doctype html>
<body>
    {% for obj in objects %}
        {{ obj | to_string }}<br>
    {% endfor %}
</body>

In this way, you can handle different types as well as reuse the filter across different templates. It also separates the view/UI code from the route codes.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
  • Using the "| string" to convert the enum could work as a temporary solution as well, but for me it includes the class name along with the enum's value. ex. "Permission.admin" – Logan Cundiff Mar 14 '23 at 22:37
  • @LoganCundiff It's the default `str` representation of an Enum object. There is also a workaround to override the `__str__` representation: https://stackoverflow.com/a/24487545/2745495 – Gino Mempin Mar 15 '23 at 01:29