選択オプションのあるモデルフィールドがある場合、人間が読める名前に関連付けられたいくつかの魔法の値を持つ傾向があります。 Djangoには、値ではなく人間が読める名前でこれらのフィールドを設定する便利な方法がありますか?
このモデルを検討してください:
class Thing(models.Model):
PRIORITIES = (
(0, 'Low'),
(1, 'Normal'),
(2, 'High'),
)
priority = models.IntegerField(default=0, choices=PRIORITIES)
ある時点でThingインスタンスがあり、その優先度を設定する必要があります。明らかにできます、
thing.priority = 1
しかし、それにより、優先順位の値と名前のマッピングを記憶する必要があります。これは機能しません:
thing.priority = 'Normal' # Throws ValueError on .save()
現在、私はこの愚かな回避策を持っています:
thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']
しかし、それは不格好です。このシナリオがどれほど一般的であるかを考えると、誰かがより良い解決策を持っているかどうか疑問に思っていました。私が完全に見落とした選択名でフィールドを設定するためのフィールドメソッドはありますか?
ここに表示 として実行します。次に、適切な整数を表すWordを使用できます。
そのようです:
LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
(LOW, 'Low'),
(NORMAL, 'Normal'),
(HIGH, 'High'),
)
その後、それらはまだDB内の整数です。
使用法はthing.priority = Thing.NORMAL
私はおそらく逆引き辞書を一度だけセットアップしたでしょうが、もし私が使用していなかったなら、私はただ使用します:
thing.priority = next(value for value, name in Thing.PRIORITIES
if name=='Normal')
これは、ただそれを投げ捨てるだけでその場で辞書を作成するよりも簡単に思えます;-)。
数分前に書いたフィールドタイプがあります。そのコンストラクタには、引数 'choices'が必要です。これは、IntegerFieldのchoicesオプションと同じ形式の2タプルのタプル、または代わりに名前の単純なリスト(つまり、ChoiceField(( 'Low'、 'Normal'、 'High')、default = 'Low'))。クラスは文字列からintへのマッピングを処理します。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)
次のように使用します。
Priorities = Enum(
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High')
)
priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)
一定の定義方法に感謝していますが、このタスクには Enum typeが最適です。コードを読みやすくしながら、アイテムの整数と文字列を同時に表すことができます。
列挙型はPythonバージョン3.4で導入されました。下位バージョン(v2.xなど)を使用している場合は、 バックポートパッケージ :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++'
これでほとんどすべてです。 ChoiceEnum
を継承して独自の定義を作成し、次のようなモデル定義で使用できます。
from Django.db import models
from myapp.fields import Language
class MyModel(models.Model):
language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
# ...
あなたが推測するかもしれないように、クエリはケーキのアイシングです:
MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)
文字列で表現することも簡単になります。
# 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()
私の答えは非常に遅く、最近のDjangoの専門家には明白に思えるかもしれませんが、ここに着いた人なら誰でも、Django-model-utilsによってもたらされた非常にエレガントなソリューションを最近発見しました: https:// Django-model-utils。 readthedocs.io/en/latest/utilities.html#choices
このパッケージを使用すると、次の3つのタプルで選択肢を定義できます。
そのため、次のことができます。
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)
こちらです:
楽しい :)
数字を人間が読み取れる値に置き換えてください。など:
PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)
これにより、人間が読めるようになりますが、独自の順序を定義する必要があります。
もともと私は@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))
現在使用しているDjango-enumfields
パッケージパッケージで簡略化:
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)