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 ?

7voto

Voici un type de champ que j'ai écrit il y a quelques minutes et je pense qu'il fait ce que vous voulez. Son constructeur nécessite un argument 'choices', qui peut être soit un tuple de 2-tuples dans le même format que l'option choices de IntegerField, soit une simple liste de noms (c'est-à-dire ChoiceField(('Faible', 'Normal', 'Fort'), default='Faible')). La classe se charge de la correspondance entre la chaîne et l'entier pour vous, vous ne voyez jamais l'entier.

class ChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if not hasattr(choices[0],'__iter__'):
            choices = zip(range(len(choices)), choices)

        self.val2choice = dict(choices)
        self.choice2val = dict((v,k) for k,v in choices)

        kwargs['choices'] = choices
        super(models.IntegerField, self).__init__(**kwargs)

    def to_python(self, value):
        return self.val2choice[value]

    def get_db_prep_value(self, choice):
        return self.choice2val[choice]

4voto

Mark Points 49079
class Sequence(object):
    def __init__(self, func, *opts):
        keys = func(len(opts))
        self.attrs = dict(zip([t[0] for t in opts], keys))
        self.choices = zip(keys, [t[1] for t in opts])
        self.labels = dict(self.choices)
    def __getattr__(self, a):
        return self.attrs[a]
    def __getitem__(self, k):
        return self.labels[k]
    def __len__(self):
        return len(self.choices)
    def __iter__(self):
        return iter(self.choices)
    def __deepcopy__(self, memo):
        return self

class Enum(Sequence):
    def __init__(self, *opts):
        return super(Enum, self).__init__(range, *opts)

class Flags(Sequence):
    def __init__(self, *opts):
        return super(Flags, self).__init__(lambda l: [1<

`_Utilisez-le comme ceci:

Priorities = Enum(
    ('LOW', 'Faible'),
    ('NORMAL', 'Normal'),
    ('HIGH', 'Élevé')
)

priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)_`

2voto

David Guillot Points 181

Ma réponse est très tardive et peut sembler évidente pour les experts de Django d'aujourd'hui, mais pour quiconque atterrit ici, j'ai récemment découvert une solution très élégante apportée par django-model-utils : https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

Ce package vous permet de définir des Choix avec des triplets où :

  • Le premier élément est la valeur de la base de données
  • Le deuxième élément est une valeur lisible par le code
  • Le troisième élément est une valeur lisible par l'humain

Voici donc ce que vous pouvez faire :

from model_utils import Choices

class Thing(models.Model):
    PRIORITIES = Choices(
        (0, 'low', 'Low'),
        (1, 'normal', 'Normal'),
        (2, 'high', 'High'),
      )

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

thing.priority = getattr(Thing.PRIORITIES.Normal)

De cette manière :

  • Vous pouvez utiliser votre valeur lisible par l'humain pour réellement choisir la valeur de votre champ (dans mon cas, c'est utile car je récupère du contenu sauvage et le stocke de manière normalisée)
  • Une valeur propre est stockée dans votre base de données
  • Vous n'avez rien de non-DRY à faire ;)

Profitez :)

1voto

AlbertoPL Points 8644

Il suffit de remplacer vos nombres par les valeurs lisibles par l'homme que vous souhaitez. Comme ceci :

PRIORITÉS = (
('LOW', 'Faible'),
('NORMAL', 'Normal'),
('HIGH', 'Élevé'),
)

Cela le rend lisible par l'homme, cependant, vous devrez définir votre propre ordre.

0voto

atx Points 945

Initialement, j'ai utilisé une version modifiée de la réponse de @Allan :

from enum import IntEnum, EnumMeta

class IntegerChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if hasattr(choices, '__iter__') and isinstance(choices, EnumMeta):
            choices = list(zip(range(1, len(choices) + 1), [membre.name pour membre in liste(choices)]))

        kwargs['choices'] = choices
        super(models.IntegerField, self).__init__(**kwargs)

    def to_python(self, valeur):
        return self.choices(valeur)

    def get_db_prep_value(self, choix):
        return self.choices[choix]

models.IntegerChoiceField = IntegerChoiceField

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Est-il sûr de supposer que le dernier élément est le plus grand membre de valeur d'une énumération ?
    #type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
    largest_member = GEAR(max([membre.value pour membre in liste(GEAR)]))
    type = models.IntegerChoiceField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for membre in GEAR:
            setattr(self, membre.name, membre.value)

print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))

Simplifié avec le package django-enumfields que j'utilise maintenant :

from enumfields import EnumIntegerField, IntEnum

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Est-il sûr de supposer que le dernier élément est le plus grand membre de valeur d'une énumération ?
    type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
    #largest_member = GEAR(max([membre.value for membre in liste(GEAR)]))
    #type = EnumIntegerField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for membre in GEAR:
            setattr(self, membre.name, membre.value)

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