通过select= …名称设置Django IntegerField

当你有一个select选项的模型字段时,你往往会有一些与人类可读名字相关的魔术值。 在Django有一个方便的方法来设置这些字段的人类可读的名称,而不是值?

考虑这个模型:

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

在某个时候,我们有一个事例,我们想确定它的优先级。 显然你可以这样做,

 thing.priority = 1 

但是这迫使你记住PRIORITIES的价值 – 名称映射。 这不起作用:

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

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

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

但这是笨重的。 鉴于这种情况有多普遍,我想知道是否有人有更好的解决scheme。 有什么字段的方法来设置字段的select名称,我完全忽略了?

像在这里看到的一样 。 那么你可以使用一个代表正确整数的单词。

像这样:

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

这似乎比在飞行中build立字典更简单,只是为了抛弃它;-)。

这是几分钟前我写的一个字段types,我认为是你想要的。 它的构造函数需要一个参数“选项”,它可以是与IntegerField的选项相同格式的2元组元素,或者是一个简单的名称列表(即ChoiceField(('Low','Normal' '高'),默认='低'))。 该类负责从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) 

我很欣赏这个不变的定义方式,但是我相信Enumtypes对于这个任务来说是最好的。 它们可以在同一时间表示一个项目的整数和一个string,同时保持代码的可读性。

在3.4版本中引入了Enums。 如果您使用的是较低版本(例如v2.x),则仍然可以通过pip install enum34 移植软件包来执行此操作 : 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++' 

这几乎是所有。 您可以inheritanceChoiceEnum来创build自己的定义,并在模型定义中使用它们,如:

 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) 

用string表示它们也很容易:

 # 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() 

只需用你想要的人类可读值replace你的数字。 因此:

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

这使得它是人类可读的,但是,你必须定义你自己的顺序。

我的回答很晚,对Django的专家来说似乎很明显,但是对于这里的任何人来说,我最近发现了一个由django-model-utils带来的非常优雅的解决scheme: https : //django-model-utils.readthedocs.io/ EN /最新/ utilities.html#select

这个包允许你定义三元组的select,其中:

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

所以你可以这样做:

 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) 

这条路:

  • 你可以使用你的人类可读的价值来实际select你的领域的价值(在我的情况,这是有用的,因为我刮野生的内容,并以规范化的方式存储)
  • 一个干净的值存储在你的数据库中
  • 你没有什么非DRY要做的;)

请享用 :)