127 votes

Définir le champ IntegerField de Django par choices=... nom

Lorsque vous avez un champ de modèle avec une option choices, vous avez tendance à avoir certaines valeurs magiques associées à des noms lisibles par l'homme. Y a-t-il dans Django un moyen pratique de définir ces champs par le nom lisible par l'homme au lieu de la valeur ?

Considérez ce modèle :

class Thing(models.Model):
  PRIORITIES = (
    (0, 'Faible'),
    (1, 'Normal'),
    (2, 'Élevé'),
  )

  priority = models.IntegerField(default=0, choices=PRIORITIES)

À un moment donné, nous avons une instance de Thing et nous voulons définir sa priorité. Évidemment, vous pourriez faire :

thing.priority = 1

Mais cela vous oblige à mémoriser le mapping Valeur-Nom de PRIORITIES. Cela ne fonctionne pas :

thing.priority = 'Normal' # Lance une ValueError sur .save()

Actuellement, j'ai cette solution de contournement ridicule :

thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']

mais c'est maladroit. Étant donné à quel point ce scénario pourrait être courant, je me demandais si quelqu'un avait une meilleure solution. Y a-t-il une méthode de champ pour définir des champs par nom de choix que j'aurais totalement négligée ?

180voto

jonwd7 Points 1741

Faites comme vu ici. Ensuite, vous pouvez utiliser un mot qui représente l'entier approprié.

Comme ceci :

LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
    (LOW, 'Low'),
    (NORMAL, 'Normal'),
    (HIGH, 'High'),
)

Ensuite, ce sont toujours des entiers dans la DB.

L'utilisation serait chose.priorité = Chose.NORMAL

4 votes

C'est un article de blog très détaillé sur le sujet. Difficile à trouver avec Google aussi donc merci.

1 votes

Pour ce que ça vaut, si vous devez le définir à partir d'une chaîne littérale (peut-être à partir d'un formulaire, de l'entrée de l'utilisateur ou similaire), vous pouvez alors simplement faire : thing.priority = getattr(thing, strvalue.upper()).

1 votes

J'aime vraiment la section Encapsulation sur le blog.

45voto

David Viejo Points 21

À partir de Django 3.0, vous pouvez utiliser :

class ThingPriority(models.IntegerChoices):
    LOW = 0, 'Faible'
    NORMAL = 1, 'Normal'
    HIGH = 2, 'Élevé'

class Thing(models.Model):
    priorité = models.IntegerField(default=ThingPriority.LOW, choices=ThingPriority.choices)

# puis dans votre code
thing = get_my_thing()
thing.priority = ThingPriority.HIGH

8voto

kirpit Points 836

J'apprécie la façon constante de définir mais je crois que le type Enum est bien meilleur pour cette tâche. Ils peuvent représenter à la fois un entier et une chaîne pour un élément, tout en rendant votre code plus lisible.

Les énumérations ont été introduites dans Python en version 3.4. Si vous utilisez une version plus ancienne (comme v2.x), vous pouvez toujours l'avoir en installant le paquet rétroporté : pip install enum34.

# myapp/fields.py
from enum import Enum    

class ChoiceEnum(Enum):

    @classmethod
    def choices(cls):
        choices = list()

        # Parcourir les énumérations définies
        for item in cls:
            choices.append((item.value, item.name))

        # renvoyer sous forme de tuple
        return tuple(choices)

    def __str__(self):
        return self.name

    def __int__(self):
        return self.value

class Language(ChoiceEnum):
    Python = 1
    Ruby = 2
    Java = 3
    PHP = 4
    Cpp = 5

# Oops
Language.Cpp._name_ = 'C++'

C'est à peu près tout. Vous pouvez hériter du ChoiceEnum pour créer vos propres définitions et les utiliser dans une définition de modèle comme :

from django.db import models
from myapp.fields import Language

class MyModel(models.Model):
    language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
    # ...

Interroger est la cerise sur le gâteau comme vous pouvez le deviner :

MyModel.objects.filter(language=int(Language.Ruby))
# ou si vous préférez ne pas utiliser la méthode `__int__`..
MyModel.objects.filter(language=Language.Ruby.value)

Les représenter sous forme de chaîne est également très simple :

# Obtenir l'élément d'énumération
lang = Language(some_instance.language)

print(str(lang))
# ou si vous ne préférez pas la méthode `__str__`..
print(lang.name)

# Identique à get_FOO_display
lang.name == some_instance.get_language_display()

8voto

mmsilviu Points 132

Les options de choix du modèle acceptent une séquence constituée elle-même d'itérables de exactement deux éléments (par exemple [(A, B), (A, B) ...]) à utiliser comme choix pour ce champ.

De plus, Django fournit types d'énumération que vous pouvez sous-classer pour définir des choix de manière concise :

from django.utils.translation import gettext_lazy as _

class ThingPriority(models.IntegerChoices):
    LOW = 0, _('Faible')
    NORMAL = 1, _('Normal')
    HIGH = 2, _('Élevé')

class Thing(models.Model):
    priority = models.IntegerField(default=ThingPriority.NORMAL, choices=ThingPriority.choices)

Django prend en charge l'ajout d'une valeur texte supplémentaire à la fin de ce tuple pour être utilisée comme nom lisible par l'humain, ou libellé. Le libellé peut être une chaîne traduisible de manière paresseuse.

   # dans votre code 
   thing = get_thing() # instance de Thing
   thing.priority = ThingPriority.LOW

Remarque : vous pouvez utiliser ThingPriority.HIGH, ThingPriority.['HAUT'], ou ThingPriority(0) pour accéder ou rechercher des membres énumérés.

Vous devez importer from django.utils.translation import gettext_lazy as _

7voto

Alex Martelli Points 330805

Je configure probablement le dictionnaire de recherche inversée une fois pour toutes, mais si je ne l'avais pas fait, j'utiliserais simplement :

thing.priority = next(value for value, name in Thing.PRIORITIES
                      if name=='Normal')

ce qui semble plus simple que de construire le dictionnaire à la volée pour le jeter ensuite ;-).

0 votes

Oui, jeter le dictionnaire est un peu ridicule, maintenant que vous le dites. :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X