Django templates: verbose version of a choice

Viewed 74475

I have a model:

from django.db import models

CHOICES = (
    ('s', 'Glorious spam'),
    ('e', 'Fabulous eggs'),
)

class MealOrder(models.Model):
    meal = models.CharField(max_length=8, choices=CHOICES)

I have a form:

from django.forms import ModelForm

class MealOrderForm(ModelForm):
    class Meta:
        model = MealOrder

And I want to use formtools.preview. The default template prints the short version of the choice ('e' instead of 'Fabulous eggs'), becuase it uses

{% for field in form %}
<tr>
<th>{{ field.label }}:</th>
<td>{{ field.data }}</td>
</tr>
{% endfor %}.

I'd like a template as general as the mentioned, but printing 'Fabulous eggs' instead.

[as I had doubts where's the real question, I bolded it for all of us :)]

I know how to get the verbose version of a choice in a way that is itself ugly:

{{ form.meal.field.choices.1.1 }}

The real pain is I need to get the selected choice, and the only way coming to my mind is iterating through choices and checking {% ifequals currentChoice.0 choiceField.data %}, which is even uglier.

Can it be done easily? Or it needs some template-tag programming? Shouldn't that be available in django already?

10 Answers

In Django templates you can use the "get_FOO_display()" method, that will return the readable alias for the field, where 'FOO' is the name of the field.

Note: in case the standard FormPreview templates are not using it, then you can always provide your own templates for that form, which will contain something like {{ form.get_meal_display }}.

My apologies if this answer is redundant with any listed above, but it appears this one hasn't been offered yet, and it seems fairly clean. Here's how I've solved this:

from django.db import models

class Scoop(models.Model):
    FLAVOR_CHOICES = [
        ('c', 'Chocolate'),
        ('v', 'Vanilla'),
    ]

    flavor = models.CharField(choices=FLAVOR_CHOICES)

    def flavor_verbose(self):
        return dict(Scoop.FLAVOR_CHOCIES)[self.flavor]

My view passes a Scoop to the template (note: not Scoop.values()), and the template contains:

{{ scoop.flavor_verbose }}

Basing on Noah's reply, here's a version immune to fields without choices:

#annoyances/templatetags/data_verbose.py
from django import template

register = template.Library()

@register.filter
def data_verbose(boundField):
    """
    Returns field's data or it's verbose version 
    for a field with choices defined.

    Usage::

        {% load data_verbose %}
        {{form.some_field|data_verbose}}
    """
    data = boundField.data
    field = boundField.field
    return hasattr(field, 'choices') and dict(field.choices).get(data,'') or data

I'm not sure wether it's ok to use a filter for such purpose. If anybody has a better solution, I'll be glad to see it :) Thank you Noah!

I don't think there's any built-in way to do that. A filter might do the trick, though:

@register.filter(name='display')
def display_value(bf):
    """Returns the display value of a BoundField"""
    return dict(bf.field.choices).get(bf.data, '')

Then you can do:

{% for field in form %}
    <tr>
        <th>{{ field.label }}:</th>
        <td>{{ field.data|display }}</td>
    </tr>
{% endfor %}

You have Model.get_FOO_display() where FOO is the name of the field that has choices.

In your template do this :

{{ scoop.get_flavor_display }}

The extended-extended version of Noah's and Ivan's solution. Also fixed Noah's solution for Django 3.1, as ModelChoiceIteratorValue is now unhashable.

@register.filter
def display_value(value: Any, arg: str = None) -> str:
    """Returns the display value of a BoundField or other form fields"""
    if not arg:  # attempt to auto-parse
        # Returning regular field's value
        if not hasattr(value.field, 'choices'): return value.value()
        # Display select value for BoundField / Multiselect field
        # This is used to get_..._display() for a read-only form-field
        # which is not rendered as Input, but instead as text
        return list(value.field.choices)[value.value()][1]

    # usage: {{ field|display_value:<arg> }}
    if hasattr(value, 'get_' + str(arg) + '_display'):
        return getattr(value, 'get_%s_display' % arg)()
    elif hasattr(value, str(arg)):
        if callable(getattr(value, str(arg))):
            return getattr(value, arg)()
        return getattr(value, arg)

    return value.get(arg) or ''
<select class="form-select">
    {% for key, value in form.meal.field.choices %}
        {% if form.meal.value == key %}
            <option value="{{ form.key }}" selected>{{ value }}</option>
        {% else %}
            <option value="{{ key }}">{{ value }}</option>
        {% endif %}
    {% endfor %}
</select>
Related