Szablony Django: pełna wersja do wyboru

127

Mam modelkę:

from django.db import models

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

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

Mam formularz:

from django.forms import ModelForm

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

I chcę użyć formtools.preview. Domyślny szablon drukuje wybraną skróconą wersję („e” zamiast „Bajeczne jajka”), ponieważ używa

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

Chciałbym mieć szablon tak ogólny, jak wspomniany, ale zamiast tego wydrukować „Bajeczne jajka”.

[ponieważ miałem wątpliwości, gdzie jest prawdziwe pytanie, pogrubiłem je za nas wszystkich :)]

Wiem, jak uzyskać pełną wersję wyboru w sposób, który sam w sobie jest brzydki:

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

Prawdziwym bólem jest to, że muszę dokonać wyboru, a jedyny sposób, który przychodzi mi do głowy, to iterowanie przez wybory i sprawdzanie {% ifequals currentChoice.0 choiceField.data %}, co jest jeszcze brzydsze.

Czy można to łatwo zrobić? A może potrzebuje trochę programowania szablonów? Czy nie powinno to już być dostępne w django?

Artur Gajowy
źródło

Odpowiedzi:

258

W szablonach Django możesz użyć metody " get_FOO_display()", która zwróci czytelny alias dla pola, gdzie 'FOO' to nazwa pola.

Uwaga: w przypadku, gdy standardowe FormPreviewszablony go nie używają, zawsze możesz dostarczyć własne szablony dla tego formularza, które będą zawierały coś podobnego {{ form.get_meal_display }}.

obrabować
źródło
1
tak, wiem. Nie jest to jednak tak ogólne (uniwersalne) - chyba że znasz sposób na iterację w szablonie po wszystkich metodach get_FOO_display obiektu modelowego :) Jestem trochę zbyt leniwy na pisanie nieogólnych szablonów;) Ponadto dokumentacja mówi jest to metoda instancji modelu. Dlatego musiałby to być modelowy formularz powiązany z istniejącym obiektem, co nie ma miejsca, a także nie jest ogólne.
Artur Gajowy
2
Zwróć uwagę, że to użycie nie ogranicza się do widoków, get_FOO_display () jest metodą na samym obiekcie modelu, więc możesz jej używać również w kodzie modelu! Na przykład w __unicode __ () jest to bardzo przydatne
Bogatyr
51

Najlepszym rozwiązaniem problemu jest użycie funkcji pomocniczych. Jeśli wybory są przechowywane w zmiennej CHOICES, a polem modelu przechowującym wybrany wybór są „ opcje ”, możesz bezpośrednio użyć

 {{ x.get_choices_display }}

w swoim szablonie. Tutaj x jest instancją modelu. Mam nadzieję, że to pomoże.

Reema
źródło
3
Dlaczego miałbyś odpowiadać w ten sposób 2 lata po tym, jak pojawiła się użyteczna odpowiedź? A kto by to zagłosował? To ta sama odpowiedź, co @roberto zaledwie 2 lata później ....
boatcoder
15
@ Mark0978 powodem głosowania za tą odpowiedzią jest to, że (dla mnie) jaśniej było śledzić odpowiedź „najczęściej głosowaną”. YMMV.
Nir Levy
49

Przepraszam, jeśli ta odpowiedź jest zbędna z żadną wymienioną powyżej, ale wygląda na to, że ta nie została jeszcze zaoferowana i wydaje się dość czysta. Oto jak to rozwiązałem:

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]

Mój widok przekazuje Scoop do szablonu (uwaga: nie Scoop.values ​​()), a szablon zawiera:

{{ scoop.flavor_verbose }}
Dan Kerchner
źródło
10

W oparciu o odpowiedź Noego, oto wersja odporna na pola bez wyboru:

#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

Nie jestem pewien, czy można używać filtra do tego celu. Jeśli ktoś ma lepsze rozwiązanie, z przyjemnością je zobaczę :) Dziękuję Noah!

Artur Gajowy
źródło
+1 za wzmiankę o twojej ścieżce # annoyances / templatetags / ... LOL ... Używam metody get_FOO_display (), o której jest mowa na dole dokumentacji formularza.
fmalina
świetny pomysł z użyciem hasattr na wyborach!
oden
7

Możemy rozszerzyć rozwiązanie filtrujące Noah, aby było bardziej uniwersalne w radzeniu sobie z danymi i typami pól:

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

Oto kod:

#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)
Ivan Kharlamov
źródło
Wydaje się to dość uniwersalne :) Nie mam pewności, ponieważ od tamtego czasu nie zrobiłem zbyt wiele w Pythonie ani Django. To dość smutne, że nadal potrzebuje filtra innej firmy (nie jest w Django) (inaczej byś nam powiedział, Ivan, prawda
?;
@ArturGajowy Tak, na dzień dzisiejszy nie ma takiej domyślnej funkcji w Django. Zaproponowałem to, kto wie, może zostanie zatwierdzone .
Ivan Kharlamov
IDEALNY! DZIAŁA JAK MARZENIE! NIESTANDARDOWE SZABLONOWE FILTRY ROX! DZIĘKUJĘ CI! :-)
CeDeROM
5

Nie sądzę, aby można było to zrobić w żaden wbudowany sposób. Filtr może jednak załatwić sprawę:

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

Następnie możesz:

{% for field in form %}
    <tr>
        <th>{{ field.label }}:</th>
        <td>{{ field.data|display }}</td>
    </tr>
{% endfor %}
Noah Medling
źródło
3

Dodaj do swojego models.py jedną prostą funkcję:

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

Teraz możesz uzyskać szczegółowe wartości pól wyboru, takich jak:

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

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

Uaktualnienie: Nie jestem pewien, czy to rozwiązanie „pythonic” i „django-way” wystarczy, czy nie, ale działa. :)

Igor Pomaranskiy
źródło
0

Masz Model.get_FOO_display (), gdzie FOO jest nazwą pola, które ma opcje.

W swoim szablonie zrób to:

{{ scoop.get_flavor_display }}
Mohamed OULD EL KORY
źródło