Modèles Django: version verbeuse d'un choix

127

J'ai un modèle:

from django.db import models

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

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

J'ai un formulaire:

from django.forms import ModelForm

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

Et je veux utiliser formtools.preview. Le modèle par défaut imprime la version courte du choix ('e' au lieu de 'Fabulous eggs'), car il utilise

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

Je voudrais un modèle aussi général que celui mentionné, mais imprimant plutôt des «œufs fabuleux».

[comme j'avais des doutes sur la vraie question, je l'ai mise en gras pour nous tous :)]

Je sais comment obtenir la version verbeuse d'un choix d'une manière qui est elle-même moche:

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

La vraie douleur est que j'ai besoin d'obtenir le choix choisi, et le seul moyen qui me vient à l'esprit est d'itérer les choix et les vérifications {% ifequals currentChoice.0 choiceField.data %}, ce qui est encore plus laid.

Cela peut-il être fait facilement? Ou cela a-t-il besoin d'une programmation de template-tag? Cela ne devrait-il pas déjà être disponible dans django?

Artur Gajowy
la source

Réponses:

258

Dans les modèles Django, vous pouvez utiliser la get_FOO_display()méthode " ", qui retournera l'alias lisible pour le champ, où "FOO" est le nom du champ.

Remarque: dans le cas où les FormPreviewmodèles standard ne l'utilisent pas, vous pouvez toujours fournir vos propres modèles pour ce formulaire, qui contiendra quelque chose comme {{ form.get_meal_display }}.

Rob
la source
1
Oui je sais. Ce n'est pas aussi général (universel), cependant - à moins que vous ne connaissiez un moyen d'itérer dans un modèle sur toutes les méthodes get_FOO_display d'un objet de modèle :) Je suis un peu trop paresseux pour écrire des modèles non génériques;) De plus, la documentation dit c'est la méthode d'une instance de modèle. Il faudrait donc qu'il s'agisse d'une forme modèle liée à un objet existant, ce qui n'est pas le cas et non plus général.
Artur Gajowy
2
Notez que cette utilisation n'est pas limitée aux vues, get_FOO_display () est une méthode sur l'objet de modèle lui-même afin que vous puissiez également l'utiliser dans le code du modèle! Par exemple, dans __unicode __ (), c'est très pratique
Bogatyr
51

La meilleure solution à votre problème est d'utiliser des fonctions d'assistance. Si les choix sont stockés dans la variable CHOICES et que le champ de modèle stockant le choix sélectionné est `` choix '', vous pouvez utiliser directement

 {{ x.get_choices_display }}

dans votre modèle. Ici, x est l'instance du modèle. J'espère que ça aide.

Reema
la source
3
Pourquoi répondriez-vous ainsi 2 ans après qu'une réponse utile soit déjà en place? Et qui voterait? C'est la même réponse que @roberto seulement 2 ans plus tard ....
boatcoder
15
@ Mark0978 la raison pour laquelle cette réponse a été votée à la hausse est que (pour moi) il était plus clair de suivre alors la réponse «le plus voté». YMMV.
Nir Levy
49

Je m'excuse si cette réponse est redondante avec l'une des listes ci-dessus, mais il semble que celle-ci n'ait pas encore été proposée et qu'elle semble assez claire. Voici comment j'ai résolu ceci:

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]

Ma vue passe un Scoop au modèle (note: pas Scoop.values ​​()), et le modèle contient:

{{ scoop.flavor_verbose }}
Dan Kerchner
la source
10

Sur la base de la réponse de Noah, voici une version à l'abri des champs sans choix:

#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

Je ne sais pas si vous pouvez utiliser un filtre à cette fin. Si quelqu'un a une meilleure solution, je serai heureux de la voir :) Merci Noah!

Artur Gajowy
la source
+1 pour mentionner votre chemin # ennuis / templatetags / ... LOL ... J'utilise get_FOO_display (), qui est mentionné au bas de la documentation du formulaire.
fmalina
super idée avec l'utilisation de hasattr sur les choix!
oden
7

Nous pouvons étendre la solution de filtrage de Noah pour qu'elle soit plus universelle dans le traitement des données et des types de champs:

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

Voici le 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)
Ivan Kharlamov
la source
Cela semble assez universel :) Je ne peux pas le dire avec certitude, car je n'ai pas trop fait de Python ou de Django depuis ce temps. C'est assez triste, cependant, qu'il ait encore besoin d'un filtre tiers (non inclus dans Django) (sinon vous nous le diriez, Ivan, n'est-ce pas
?;
@ArturGajowy Oui, à ce jour, il n'y a pas de telle fonctionnalité par défaut dans Django. Je l'ai proposé, qui sait, il sera peut-être approuvé .
Ivan Kharlamov
PARFAIT! FONCTIONNE COMME UN CHARME! FILTRES GABARIT SUR MESURE ROX! MERCI! :-)
CeDeROM
5

Je ne pense pas qu'il y ait de moyen intégré de faire cela. Un filtre pourrait cependant faire l'affaire:

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

Ensuite, vous pouvez faire:

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

Ajoutez à vos models.py une fonction simple:

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

Maintenant, vous pouvez obtenir la valeur détaillée des champs de choix comme celui-ci:

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

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

Upd: Je ne suis pas sûr, est-ce que cette solution «pythonique» et «django-way» est suffisante ou pas, mais ça marche. :)

Igor Pomaranskiy
la source
0

Vous avez Model.get_FOO_display () où FOO est le nom du champ qui a des choix.

Dans votre modèle, procédez comme suit:

{{ scoop.get_flavor_display }}
Mohamed OULD EL KORY
la source