【问题标题】:Django: How to create enum of classes to define choicesDjango:如何创建类枚举来定义选择
【发布时间】:2020-08-15 23:56:56
【问题描述】:

如何对模型进行子类化。定义类枚举的选择?

以下代码引发 TypeError:

from django.db.models import Choices

class DataTypes(type, Choices):
    CHAR = str, _('Short string')
    INTEGER = int, _('Integer')

错误信息:

dynamic_attributes = {k for c in enum_class.mro()
TypeError: descriptor 'mro' of 'type' object needs an argument

更新:

当不使用 mixin 时,则不会出现错误。但都一样,成员的值并没有正确转换为所需的数据类型。

class DataTypes(Choices):
    CHAR = str, _('Short string')
    INTEGER = int, _('Integer')

测试:

str in (DataTypes.CHAR, DataTypes.INTEGER) # False

【问题讨论】:

  • 你使用的是哪个 Django 版本?
  • 我使用的是 Django 3.1
  • 这是Enum 类本身的结果:一个新的 Enum 类必须有一个基本 Enum 类,最多一个具体数据类型。 (docs.python.org/3/library/enum.html#restricted-enum-subclassing)。
  • 我建议只使用Choices,因此不要输入它。
  • object 也不起作用。

标签: python django enums


【解决方案1】:

我同意Willem Van Onsem 在cmets 中的观点。我认为不可能将models.Choices 用于类等数据类型。所以我试图找到一种能够扩展其他类的不同方法。

还实现了为选择定义标签(如 Django Choices 所做的那样)。

def _is_dunder(name):
    """
    Stolen liberally from 'aenum'.

    Returns True if a __dunder__ name, False otherwise.
    """
    return (len(name) > 4 and
            name[:2] == name[-2:] == '__' and
            name[2] != '_' and
            name[-3] != '_')


class ChoicesMeta(type):

    def __new__(mcs, cls, bases, attrs):
        # ignore any keys listed in _ignore_
        ignore = attrs.setdefault('_ignore_', [])
        ignore.append('_ignore_')

        # save constant names into list.
        names = [k for k in attrs if not(_is_dunder(k) or k in ignore)]

        # save constant labels into list.
        labels = []
        for k in names:
            value = attrs[k]
            if (
                isinstance(value, (list, tuple)) and
                len(value) > 1 and
                isinstance(value[-1], (Promise, str))
            ):
                value, label = value
            else:
                label = k.replace('_', ' ').title()
            labels.append(label)
            attrs[k] = value

        new_cls = super().__new__(mcs, cls, bases, attrs)
        new_cls.local_names = names
        new_cls.local_labels = labels
        return new_cls

    @property
    def names(cls):
        names = []
        for c in reversed(cls.__mro__):
            names.extend(getattr(c, 'local_names', []))
        return list(dict.fromkeys(names))

    @property
    def choices(cls):
        empty = [(None, cls.__empty__)] if hasattr(cls, '__empty__') else []
        return empty + [(getattr(cls, name), cls.labels[i]) for i, name in enumerate(cls.names)]

    @property
    def labels(cls):
        labels = []
        for c in reversed(cls.__mro__):
            labels.extend(getattr(c, 'local_labels', []))
        return list(dict.fromkeys(labels))


class Choices(metaclass=ChoicesMeta):
    pass

现在您可以创建自己的 Choices 类并对其进行子类化:

class DataTypes(Choices):
    CHAR = str, _('Short string')
    INTEGER = int


class OtherTypes(DataTypes):
    BOOLEAN = bool, _('Boolean (Either True or False)')
    FLOAT = float, _('Floating point number')

测试:

str in (DataTypes.CHAR, DataTypes.INTEGER) # True

bool in (OtherTypes.CHAR, OtherTypes.BOOLEAN)  # True

DataTypes.choices  # [(<class 'str'>, 'Short string'), (<class 'int'>, 'Integer')]

OtherTypes.choices # [(<class 'str'>, 'Short string'), (<class 'int'>, 'Integer'), (<class 'bool'>, 'Boolean (Either True or False)'), (<class 'float'>, 'Floating point number')]

【讨论】:

    猜你喜欢
    • 2011-05-04
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 2015-02-10
    • 2012-03-10
    相关资源
    最近更新 更多