140

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?

Artur Gajowy
  • 2,018
  • 2
  • 18
  • 18

11 Answers11

287

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 }}.

rob
  • 36,896
  • 2
  • 55
  • 65
  • 1
    yes, I know. It's not as general (universal), though - unless you know a way to iterate in a template over all get_FOO_display methods of a model object :) I'm a bit too lazy for writing non-generic templates ;) Moreover, the docs say it's a model instance's method. Therefore it'd have to be a model form bound to an existing object which is not the case and also not general. – Artur Gajowy Jul 09 '09 at 22:40
  • 2
    Note that this usage is not limited to the views, get_FOO_display() is a method on the model object itself so you can use it in model code, too! For example, in __unicode__() it is very handy – Bogatyr Feb 27 '14 at 17:13
65

The best solution for your problem is to use helper functions. If the choices are stored in the variable CHOICES and the model field storing the selected choice is 'choices' then you can directly use

 {{ x.get_choices_display }}

in your template. Here, x is the model instance. Hope it helps.

Reema
  • 1,147
  • 1
  • 9
  • 11
  • 3
    Why would you answer like this 2 years after a useful answer is already in place? And who would vote it up? Its the same answer as @roberto just 2 years later.... – boatcoder May 26 '12 at 13:42
  • 17
    @Mark0978 the reason for upvoting this answer is because (for me) it was clearer to follow then the "top voted" answer. YMMV. – Nir Levy Jul 30 '13 at 08:04
53

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 }}
Corey Adler
  • 15,897
  • 18
  • 66
  • 80
Dan Kerchner
  • 607
  • 6
  • 3
11

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!

Artur Gajowy
  • 2,018
  • 2
  • 18
  • 18
  • +1 for mentioning your path #annoyances/templatetags/... LOL ... I use get_FOO_display(), which is mentioned on the bottom of the form docs. – fmalina Feb 15 '11 at 15:04
  • great idea with the use of hasattr on choices! – oden Jul 07 '14 at 06:56
7

We can extend the filter solution by Noah to be more universal in dealing with data and field types:

<table>
{% for item in query %}
    <tr>
        {% for field in fields %}
            <td>{{item|human_readable:field}}</td>
        {% endfor %}
    </tr>
{% endfor %}
</table>

Here's the code:

#app_name/templatetags/custom_tags.py
def human_readable(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)()
        else:
            return getattr(value, arg)
   else:
       try:
           return value[arg]
       except KeyError:
           return settings.TEMPLATE_STRING_IF_INVALID
register.filter('human_readable', human_readable)
Community
  • 1
  • 1
Ivan Kharlamov
  • 1,889
  • 2
  • 24
  • 33
  • Seems quite universal :) Can't tell for sure, because I haven't done too much Python or Django since that time. It's pretty sad, though, that it still needs a 3rd party (not included in Django) filter (otherwise you'd tell us, Ivan, wouldn't you? ;))... – Artur Gajowy Sep 30 '11 at 21:38
  • @ArturGajowy Yes, as of today there is no such default feature in Django. I have proposed it, [who knows, maybe it will be approved](https://groups.google.com/group/django-developers/browse_thread/thread/9ff2222931b381c7). – Ivan Kharlamov Oct 03 '11 at 08:18
  • 1
    PERFECT! WORKS LIKE A CHARM! CUSTOM TEMPLATE FILTERS ROX! THANK YOU! :-) – CeDeROM May 03 '20 at 16:29
6

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 %}
Noah Medling
  • 4,581
  • 1
  • 23
  • 18
4

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 }}
3

Add to your models.py one simple function:

def get_display(key, list):
    d = dict(list)
    if key in d:
        return d[key]
    return None

Now, you can get verbose value of choice fields like that:

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

    def meal_verbose(self):
        return get_display(self.meal, CHOICES)    

Upd.: I'm not sure, is that solution “pythonic” and “django-way” enough or not, but it works. :)

Ihor Pomaranskyy
  • 5,437
  • 34
  • 37
0

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 ''
alfonsrv
  • 11
  • 2
0
<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>
JaveLiner
  • 11
  • 3
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 20 '22 at 06:48
0

Hey what about that way?

in models.py

class MealOrder(models.Model):
    CHOICES = (
        ('s', 'Glorious spam'),
        ('e', 'Fabulous eggs'),
    )
    meal = models.CharField(max_length=8, choices=CHOICES)
    meal_value = models.CharField(max_length=1, blank=True, null=True, 
        editable=False)
    
    def save(self, *args, **kwargs):
        if self.meal == "s":
            self.meal_value = "Glorious spam"
        elif self.meal == "e":
            self.meal_value = "Fabulous eggs"
        super(MealOrder, self).save(*args, **kwargs)

in views.py

from .models import MealOrder

def meal_order(request):
    meals = MealOrder.objects.all()

    return render(request, "meals.html", {
        "meals": meals,
    })

in meals.html

{% for meal in meals %}
    {{meal.meal_value }}
{%endfor%}
Ping
  • 3
  • 2