Django IntegerFieldをchoices =…名前で設定します


109

選択肢オプション付きのモデルフィールドがある場合、人間が読める名前に関連付けられたいくつかの魔法の値を持つ傾向があります。Djangoには、値の代わりに人間が読める名前でこれらのフィールドを設定する便利な方法はありますか?

このモデルを考えてみましょう:

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

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

ある時点でThingインスタンスがあり、その優先順位を設定したいと考えています。明らかに、

thing.priority = 1

ただし、これにより、PRIORITIESの値と名前のマッピングを覚えておく必要があります。これは機能しません:

thing.priority = 'Normal' # Throws ValueError on .save()

現在、私はこの愚かな回避策を持っています:

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

しかし、それは不格好です。このシナリオがどれほど一般的であるかを考えると、誰かがより良い解決策を持っているかどうか疑問に思っていました。私が完全に見落とした選択名でフィールドを設定するためのいくつかのフィールドメソッドはありますか?

回答:


164

行い、ここで見られます。次に、適切な整数を表す単語を使用できます。

そのようです:

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

その後、それらはまだDB内の整数です。

使用法は thing.priority = Thing.NORMAL


2
これは、この件についてのブログの投稿です。グーグルでも見つけるのは難しいのでありがとう。
Alexander Ljungberg、

1
FWIW、リテラル文字列(おそらくフォーム、ユーザー入力など)から設定する必要がある場合は、次のようにすることができます:thing.priority = getattr(thing、strvalue.upper())。
mrooney 2013

1
ブログのカプセル化セクションが本当に好きです。
ネイサンケラー

問題があります:管理者には常にデフォルト値が表示されます!値が本当に変わることをテストしました!私は今どうすればいい?
Mahdi、

これは進むべき道ですが、注意してください。将来的に選択肢を追加または削除した場合、番号は連続しません。廃止予定の選択肢をコメント化して、将来の混乱やdbの衝突に遭遇しないようにすることができます。
grokpot 2015

7

私はたぶん逆引き辞書をセットアップするだろうが、もしそうでなければ私はただ使うだけだ:

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

それは、ただ捨てるだけでその場でdictを構築するよりも簡単に思えます;-)


はい、あなたがそれを言うようになった今、口述を投げることは少しばかげています。:)
Alexander Ljungberg

7

ここに、数分前に書いたフィールドタイプを示します。そのコンストラクターには引数 'choices'が必要です。これは、IntegerFieldの選択肢オプションと同じ形式の2タプルのタプルか、名前の単純なリスト(つまり、ChoiceField(( 'Low'、 'Normal'、 'High')、デフォルト= '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]

1
それは悪いアランではありません。ありがとう!
Alexander Ljungberg

5

私は定数の定義方法に感謝していますが、このタスクにはEnum型がはるかに最適だと思います。コードを読みやすくしつつ、アイテムの整数と文字列を同時に表すことができます。

列挙型はバージョン3.4でPythonに導入されました。あなたが任意の下(バージョン2.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()

1
あなたは、基本クラスのように紹介したくない場合ChoiceEnumは、使用することができます.valueし、.name@kirpitで説明するようにしての使用置き換えるchoices()tuple([(x.value, x.name) for x in cls])、フィールドのコンストラクタで直接機能(DRY)で--eitherをか。
Seth

4
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)

3

Django 3.0以降では、以下を使用できます。

class ThingPriority(models.IntegerChoices):
    LOW = 0, 'Low'
    NORMAL = 1, 'Normal'
    HIGH = 2, 'High'


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

# then in your code
thing = get_my_thing()
thing.priority = ThingPriority.HIGH

1

数値を人間が読める値に置き換えてください。など:

PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)

これにより人間が読めるようになりますが、独自の順序を定義する必要があります。


1

私の答えは非常に遅く、最近のDjangoの専門家には明らかなように思えるかもしれませんが、ここに着く人なら誰でも、django-model-utilsによってもたらされる非常にエレガントなソリューションを最近発見しました:https : //django-model-utils.readthedocs.io/ en / latest / utilities.html#choices

このパッケージでは、次の3つのタプルで選択肢を定義できます。

  • 最初の項目はデータベースの値です
  • 2番目の項目はコード読み取り可能な値です
  • 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)

こちらです:

  • 人間が読める値を使用して、実際にフィールドの値を選択できます(私の場合、野生のコンテンツをスクレイピングして正規化された方法で保存しているので便利です)
  • クリーンな値がデータベースに保存されています
  • 非DRYを行う必要はありません;)

楽しい :)


1
django-model-utilsを起動するのに完全に有効です。読みやすさを向上させるための小さな提案。デフォルトのパラメーターにもドット表記を使用しますpriority = models.IntegerField(default=PRIORITIES.Low, choices=PRIORITIES)(言うまでもなく、そうするためには、優先順位の割り当てをThingクラス内にインデントする必要があります)。また、Python識別子には小文字の単語/文字を使用することを検討してください。クラスを参照するのではなく、代わりにパラメーターを使用します(選択肢は次のようになります(0, 'low', 'Low'),)。
キム

0

元々、@ 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私が現在使用しているパッケージpackageで簡略化:

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)

0

モデルの選択肢オプションは、このフィールドの選択肢として使用するために、正確に2つのアイテムの反復可能オブジェクト([(A、B)、(A、B)...]など)から構成されるシーケンスを受け入れます。

さらに、Djangoには列挙型が用意れており、サブクラス化して簡潔な方法で選択肢を定義できます。

class ThingPriority(models.IntegerChoices):
    LOW = 0, _('Low')
    NORMAL = 1, _('Normal')
    HIGH = 2, _('High')

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

Djangoは、このタプルの末尾に追加の文字列値を追加して、人間が読める名前またはラベルとして使用できるようにします。ラベルは、遅延翻訳可能な文字列にすることができます。

   # in your code 
   thing = get_thing() # instance of Thing
   thing.priority = ThingPriority.LOW

注:、、またはを使用してそれを使用してThingPriority.HIGH、列挙型メンバーにアクセスまたは検索できます。ThingPriority.['HIGH']ThingPriority(0)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.