通过choices = ... name设置Django IntegerField

Ale*_*erg 99 python django django-models

当您有一个带有选项选项的模型字段时,您往往会有一些与人类可读名称相关联的魔术值.在Django中是否有一种方便的方法来通过人类可读的名称而不是值来设置这些字段?

考虑这个模型:

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

  priority = models.IntegerField(default=0, choices=PRIORITIES)
Run Code Online (Sandbox Code Playgroud)

在某些时候,我们有一个Thing实例,我们想要设置它的优先级.显然你可以这样做,

thing.priority = 1
Run Code Online (Sandbox Code Playgroud)

但这迫使你记住PRIORITIES的Value-Name映射.这不起作用:

thing.priority = 'Normal' # Throws ValueError on .save()
Run Code Online (Sandbox Code Playgroud)

目前我有这个愚蠢的解决方法:

thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']
Run Code Online (Sandbox Code Playgroud)

但那很笨重.考虑到这种情况有多常见,我想知道是否有人有更好的解决方案.是否有一些字段方法按选项名称设置字段我完全忽略了?

小智 158

如此处所见.然后你可以使用一个代表正确整数的单词.

像这样:

LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
    (LOW, 'Low'),
    (NORMAL, 'Normal'),
    (HIGH, 'High'),
)
Run Code Online (Sandbox Code Playgroud)

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

用法是 thing.priority = Thing.NORMAL

  • 这是一篇关于这个主题的非常详细的博客文章。用谷歌也很难找到,谢谢。 (4认同)

小智 19

从 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
Run Code Online (Sandbox Code Playgroud)


mms*_*viu 8

模型的选择选项接受一个序列,该序列本身由恰好两个项目(例如[(A,B),(A,B)...])的可迭代组成,用作该字段的选择。

此外,Django 提供了枚举类型,您可以将其子类化以简洁的方式定义选择:

from django.utils.translation import gettext_lazy as _

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)

Run Code Online (Sandbox Code Playgroud)

Django 支持在该元组末尾添加额外的字符串值,以用作人类可读的名称或标签。标签可以是惰性可翻译字符串。

   # in your code 
   thing = get_thing() # instance of Thing
   thing.priority = ThingPriority.LOW
Run Code Online (Sandbox Code Playgroud)

注意:您可以使用ThingPriority.HIGHThingPriority.['HIGH']、 或ThingPriority(0)来访问或查找枚举成员。

你需要导入from django.utils.translation import gettext_lazy as _

  • 我认为这个答案不起作用,但我忘记了“from django.utils.translation import gettext_lazy as _” (3认同)

Ale*_*lli 7

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

thing.priority = next(value for value, name in Thing.PRIORITIES
                      if name=='Normal')
Run Code Online (Sandbox Code Playgroud)

这似乎比在飞行中建立字典更简单,只是为了再把它扔掉;-).


小智 7

这是我几分钟前写的一个字段类型,我觉得你做的就是你想要的.它的构造函数需要一个参数'choices',它可以是与IntegerField的choices选项格式相同的2元组元组,或者是一个简单的名字列表(即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]
Run Code Online (Sandbox Code Playgroud)


kir*_*pit 6

我很欣赏不断定义的方式,但我相信Enum类型最适合这项任务。它们可以同时表示一个项目的整数和字符串,同时使您的代码更具可读性。

枚举是在 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++'
Run Code Online (Sandbox Code Playgroud)

这几乎就是全部。您可以继承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))
    # ...
Run Code Online (Sandbox Code Playgroud)

正如您可能猜到的那样,查询是锦上添花:

MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)
Run Code Online (Sandbox Code Playgroud)

用字符串表示它们也很容易:

# 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()
Run Code Online (Sandbox Code Playgroud)

  • 如果您不想引入像 `ChoiceEnum` 这样的基类,您可以使用 @kirpit 描述的 `.value` 和 `.name` 并将 `choices()` 的用法替换为 `tuple([(x.value) , x.name) for x in cls])`--在函数 (DRY) 中或直接在字段的构造函数中。 (2认同)

mpe*_*pen 5

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)
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

Priorities = Enum(
    ('LOW', 'Low'),
    ('NORMAL', 'Normal'),
    ('HIGH', 'High')
)

priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)
Run Code Online (Sandbox Code Playgroud)