Lorsque vous avez un champ de modèle avec une option de choix, vous avez tendance à avoir des valeurs magiques associées à des noms lisibles par l'homme. Existe-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, 'Low'),
(1, 'Normal'),
(2, 'High'),
)
priority = models.IntegerField(default=0, choices=PRIORITIES)
À un moment donné, nous avons une instance Thing et nous voulons définir sa priorité. De toute évidence, vous pourriez le faire,
thing.priority = 1
Mais cela vous oblige à mémoriser le mappage valeur-nom des PRIORITÉS. Cela ne fonctionne pas:
thing.priority = 'Normal' # Throws ValueError on .save()
Actuellement, j'ai cette solution stupide:
thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']
mais c'est maladroit. Étant donné la fréquence de ce scénario, je me demandais si quelqu'un avait une meilleure solution. Existe-t-il une méthode de définition des champs par nom de choix que j'ai totalement ignorée?
Faites comme vu ici . Ensuite, vous pouvez utiliser un mot qui représente l'entier approprié.
Ainsi:
LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
(LOW, 'Low'),
(NORMAL, 'Normal'),
(HIGH, 'High'),
)
Ils sont alors toujours des entiers dans la base de données.
L'utilisation serait thing.priority = Thing.NORMAL
J'aurais probablement configuré la dictée 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 dict à la volée juste pour le jeter à nouveau ;-).
Voici un type de champ que j'ai écrit il y a quelques minutes et qui, je pense, fait ce que vous voulez. Son constructeur requiert un argument 'choix', qui peut être soit un Tuple de 2-tuples dans le même format que l'option choix d'IntegerField, ou plutôt une simple liste de noms (ie ChoiceField (('Low', 'Normal', 'High'), par défaut = 'Low')). La classe s'occupe du mappage de la chaîne en entier pour vous, vous ne voyez jamais l'int.
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]
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<<i for i in xrange(l)], *opts)
Utilisez-le comme ceci:
Priorities = Enum(
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High')
)
priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)
J'apprécie la façon constante de définir, mais je crois que le type Enum est de loin le meilleur pour cette tâche. Ils peuvent représenter un entier et une chaîne pour un élément en même temps, tout en gardant votre code plus lisible.
Les énumérations ont été introduites dans Python dans la version 3.4. Si vous utilisez une version inférieure (comme v2.x), vous pouvez toujours l'avoir en installant package rétroporté : pip install enum34
.
# myapp/fields.py
from enum import Enum
class ChoiceEnum(Enum):
@classmethod
def choices(cls):
choices = list()
# Loop thru defined enums
for item in cls:
choices.append((item.value, item.name))
# return as 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
# Uh oh
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))
# ...
L'interrogation est la cerise sur le gâteau comme vous pouvez le deviner:
MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)
Les représenter en chaîne est également facilité:
# Get the enum item
lang = Language(some_instance.language)
print(str(lang))
# or if you don't prefer `__str__` method..
print(lang.name)
# Same as get_FOO_display
lang.name == some_instance.get_language_display()
Ma réponse est très tardive et peut sembler évidente aux experts de Django de nos jours, 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 trois tuples où:
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)
Par ici:
Prendre plaisir :)
Remplacez simplement vos chiffres par les valeurs lisibles par l'homme que vous souhaitez. En tant que tel:
PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)
Cela le rend lisible par l'homme, cependant, vous devrez définir votre propre commande.
À l'origine, 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), [member.name for member in list(choices)]))
kwargs['choices'] = choices
super(models.IntegerField, self).__init__(**kwargs)
def to_python(self, value):
return self.choices(value)
def get_db_prep_value(self, choice):
return self.choices[choice]
models.IntegerChoiceField = IntegerChoiceField
GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
class Gear(Item, models.Model):
# Safe to assume last element is largest value member of an enum?
#type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
largest_member = GEAR(max([member.value for member in list(GEAR)]))
type = models.IntegerChoiceField(GEAR, default=largest_member)
def __init__(self, *args, **kwargs):
super(Gear, self).__init__(*args, **kwargs)
for member in GEAR:
setattr(self, member.name, member.value)
print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))
Simplifié avec le Django-enumfields
package package que j'utilise maintenant:
from enumfields import EnumIntegerField, IntEnum
GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
class Gear(Item, models.Model):
# Safe to assume last element is largest value member of an enum?
type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
#largest_member = GEAR(max([member.value for member in list(GEAR)]))
#type = EnumIntegerField(GEAR, default=largest_member)
def __init__(self, *args, **kwargs):
super(Gear, self).__init__(*args, **kwargs)
for member in GEAR:
setattr(self, member.name, member.value)