通过choices =…设置Django IntegerField


109

当您拥有一个带有选项选项的模型字段时,您倾向于具有一些与人类可读名称相关的魔术值。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']

但这很笨重。考虑到这种情况有多普遍,我想知道是否有人有更好的解决方案。是否有一些我完全忽略的通过选择名称设置字段的字段方法?

Answers:


164

如做在这里看到。然后,您可以使用代表适当整数的单词。

像这样:

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

然后它们仍然是数据库中的整数。

用法是 thing.priority = Thing.NORMAL


2
这是关于该主题的非常详细的博客文章。也很难在Google上找到,非常感谢。
Alexander Ljungberg

1
FWIW,如果需要从文字字符串(可能是从表单,用户输入或类似字符串)中进行设置,则可以执行以下操作:thing.priority = getattr(thing,strvalue.upper())。
mrooney

1
真的很像博客上的封装部分。
内森·凯勒

我有一个问题:我总是在admin上看到默认值!我已经测试过该值确实发生了变化!我现在应该怎么办?
Mahdi 2013年

这是要走的路,但要注意:如果将来添加或删除选择,您的号码将不会是连续的。您可能会注释掉不推荐使用的选择,这样将来就不会造成混乱,也不会遇到数据库冲突。
grokpot 2015年

7

我可能会一劳永逸地设置反向查询字典,但是如果没有,我只会使用:

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

这似乎比立即构建dict再扔掉要简单得多;-)。


是的,既然您已经说了,就把这个字典扔掉有点傻。:)
Alexander Ljungberg

7

这是我几分钟前写的一种字段类型,我认为它满足您的要求。它的构造函数需要一个参数“ choices”,它可以是2元组的元组,格式与IntegerField的choices选项相同,也可以是简单的名称列表(例如ChoiceField(('Low','Normal', '高'),默认='低')。该类为您处理从字符串到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 2009年

5

我欣赏不断的定义方式,但我相信枚举类型最适合此任务。它们可以同时表示一个项目的整数和字符串,同时保持代码的可读性。

枚举在版本3.4中引入Python。如果您使用的是任何较低版本(例如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()

1
如果你不喜欢引入一个基类一样ChoiceEnum,你可以使用.value.name作为@kirpit描述和替换的用法choices()tuple([(x.value, x.name) for x in cls])在外地的构造--either在一个函数(干),或直接。
赛斯,

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/ zh / latest / utilities.html#choices

此程序包使您可以定义具有三元组的Choices,其中:

  • 第一项是数据库值
  • 第二项是代码可读值
  • 第三项是人类可读的值

因此,您可以执行以下操作:

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我现在使用的包简化了:

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

模型的choices选项接受一个序列,该序列本身由恰好两个项目(例如[[(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.HIGHThingPriority.['HIGH']ThingPriority(0)访问或查询枚举成员。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.