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

当您有一个带有选项的模型字段时,您往往会有一些与人类可读名称相关联的神奇值。在 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']

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

104896 次浏览

简单地将你的数字替换成你想要的人类可读的值,如下:

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

这使它具有人类可读性,但是,您必须定义自己的顺序。

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

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

这似乎比匆忙制定结论只是为了再次抛弃它更简单; ——)。

然后你可以使用一个表示正确整数的单词。

像这样:

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

那么它们在数据库中仍然是整数。

使用方法是 thing.priority = Thing.NORMAL

这是我几分钟前写的一个字段类型,我认为它可以满足您的需要。它的构造函数需要一个参数‘ options’,这个参数可以是与 IntegerField 选项相同格式的两个元组的元组,也可以是一个简单的名称列表(例如 ChoiceField ((‘ Low’,‘ Normal’,‘ High’) ,default = ‘ Low’))。这个类负责你从 string 到 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类型是最好的这项任务。它们可以同时表示项的整数和字符串,同时保持代码的可读性。

Enums 是在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()

我的回答是非常晚,现在看来可能是显而易见的-Django 专家,但对于谁在这里登陆,我最近发现了一个非常优雅的解决方案带来的 django-model-utils: https://django-model-utils.readthedocs.io/en/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)

这边走:

  • 您可以使用人类可读的值来实际选择字段的值(在我的例子中,这很有用,因为我正在刮取野生内容并以规范化的方式存储它)
  • 数据库中存储了一个干净的值
  • 你没有什么不干的事情要做;)

享受:)

最初,我使用了@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)

在 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

Model 的 select 选项接受一个序列,该序列本身包含两个项(例如[(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)


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

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

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

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