2009-07-13 93 views
79

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

考虑这一模式:

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

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

回答

130

作为seen here。然后你可以使用一个代表正确整数的单词。

像这样:

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

然后,他们依然在DB整数。

用法是thing.priority = Thing.NORMAL

+1

这是一个很好的详细的博客张贴在这个问题上。很难找到谷歌,所以谢谢。 – 2009-07-13 13:54:28

+1

FWIW,如果您需要从文字字符串(可能来自表单,用户输入或类似字符串)设置它,则可以执行:thing.priority = getattr(thing,strvalue.upper())。 – mrooney 2013-02-24 23:22:19

+1

真的很喜欢博客上的封装部分。 – 2013-03-03 21:57:10

1

只需将您的数字替换为您想要的人类可读值即可。因此:

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

这使得人类可读,但是,您必须定义自己的排序。

7

我可能会建立反向查找字典一劳永逸,但如果我不是我只是用:

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

这似乎不是建立在飞行的字典简单些把它扔掉了;-)。

+0

是的,现在你说这个词,这个词典有点愚蠢。 :) – 2009-07-13 03:37:08

4

这里有一个字段类型,我写了几分钟前,我想你想要做什么。它的构造函数需要一个参数“选项”,它可以是一个2元组的元组,其格式与IntegerField的选项相同,或者是一个简单的名称列表(即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] 
3
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) 
2

我很欣赏的常数定义的方式,但我相信Enum类型是远远最适合这个任务。它们可以在同一时间内为项目表示整数和字符串,同时保持代码更具可读性。

枚举是在3.4版本中引入Python的。如果您使用的是较低版本(例如v2.x),则仍然可以安装backported packagepip 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

我的回答是非常晚,似乎是显而易见的时下,Django的专家,但谁在这里的土地,我最近发现一个非常优雅的解决方案通过django的模型-utils的赞助商:https://django-model-utils.readthedocs.io/en/latest/utilities.html#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做;)

享受:)