代码之家  ›  专栏  ›  技术社区  ›  Alexander Ljungberg

按选项设置Django整数字段=…名称

  •  87
  • Alexander Ljungberg  · 技术社区  · 17 年前

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

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

    7 回复  |  直到 17 年前
        1
  •  179
  •   user136565 user136565    17 年前

    seen here . 然后,您可以使用表示正确整数的单词。

    像这样:

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

    用法将是 thing.priority = Thing.NORMAL

        2
  •  19
  •   David Viejo    6 年前

    从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
    
        3
  •  7
  •   Alex Martelli    17 年前

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

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

    这似乎比在飞行中构建dict只是为了再次扔掉它更简单;-)。

        4
  •  7
  •   Allan Allan    16 年前

    这是我几分钟前写的一个字段类型,我认为它符合您的要求。它的构造函数需要一个参数“choices”,它可以是一个2元组的元组,格式与IntegerField的choices选项相同,也可以是一个简单的名称列表(即ChoiceField(('Low','Normal','High'),default='Low'))。该类为您负责从字符串到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]
    
        5
  •  6
  •   kirpit    10 年前

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

    枚举是在3.4版Python中引入的。如果您使用的是任何较低版本(如v2.x),您仍然可以通过安装 backported package : 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()
    
        6
  •  4
  •   mpen    15 年前
    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)
    
        7
  •  3
  •   mmsilviu    5 年前

    模型的“选项”选项接受一个由恰好两个项(例如[(a,B),(a,B)…])的iterables组成的序列,以用作此字段的选项。

    此外,Django还提供 enumeration types 您可以子类化以简洁的方式定义选项:

    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.HIGH , ThingPriority.['HIGH'] ThingPriority(0) 访问或查找枚举成员。

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

        8
  •  1
  •   AlbertoPL    17 年前

    只需将数字替换为所需的可读值即可。像这样的:

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

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

        9
  •  1
  •   David Guillot    8 年前

    我的答案很晚了,对于现在的Django专家来说可能很明显,但是对于来到这里的任何人来说,我最近发现了Django模型UTIL带来的一个非常优雅的解决方案: 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)
    

    这种方式:

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

    享受:)

        10
  •  0
  •   atx    7 年前

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