You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3.11下Django以Model类为选项值的Choices实现问题

问题描述

这段代码在Python 3.10中可正常运行,但因Python 3.11的enum模块变更,在Python 3.11中启动应用时报错:

File "/home/runner/work/e/e/certification/models.py", line 3, in <module>
    from .certifications.models import Certification, QuizzCertification  # noqa: F401
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/e/e/certification/certifications/models.py", line 17, in <module>
    class CertificationType(models.TextChoices):
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/django/db/models/enums.py", line 49, in __new__
    cls = super().__new__(metacls, classname, bases, classdict, **kwds)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/enum.py", line 560, in __new__
    raise exc
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/enum.py", line 259, in __set_name__
    enum_member = enum_class._new_member_(enum_class, *args)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/enum.py", line 1278, in __new__
    raise TypeError('%r is not a string' % (values[0], ))
TypeError: <class 'certification.certifications.models.QuizzCertification'> is not a string

原代码示例(需求为让选项关联对应Model类):

class QuizzCertification(models.Model):
    ...

class OtherCertification(models.Model):
   ...

class CertificationType(models.TextChoices):
    QUIZZ = QuizzCertification, "Quizz"
    OTHER = OtherCertification, "Other"

class Certification(models.Model):
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(_("Description"), blank=True, null=True)

    type_of = models.CharField(
        _("Type of certification"),
        max_length=100,
        choices=CertificationType,
        default=CertificationType.QUIZZ,
    )

请问该如何正确实现?

解决方案

Django的TextChoices要求枚举成员的第一个值必须是字符串类型(对应数据库存储的字段值),此前直接将Model类作为第一个值的做法不符合规范,Python 3.10的enum模块类型检查较宽松所以未报错,但3.11收紧校验后触发了错误。

以下两种方式可实现“选项关联对应Model类”的需求:

方式一:自定义枚举类,继承标准库Enum

from enum import Enum

class CertificationType(Enum):
    QUIZZ = (QuizzCertification, "Quizz")
    OTHER = (OtherCertification, "Other")

    @classmethod
    def choices(cls):
        return [(member.name, member.value[1]) for member in cls]

class Certification(models.Model):
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(_("Description"), blank=True, null=True)

    type_of = models.CharField(
        _("Type of certification"),
        max_length=100,
        choices=CertificationType.choices(),
        default=CertificationType.QUIZZ.name,
    )
  • 使用标准库Enum定义,每个成员的值为包含Model类和显示文本的元组
  • 自定义choices()方法返回Django字段所需的(存储值, 显示文本)格式,用枚举的name作为数据库存储的字符串
  • 获取对应Model类时,通过CertificationType[type_of_value].value[0]即可

方式二:保留TextChoices,单独维护Model映射

如果希望继续使用TextChoices的便捷性,可分开定义存储值与Model类的映射关系:

class CertificationType(models.TextChoices):
    QUIZZ = "QUIZZ", "Quizz"
    OTHER = "OTHER", "Other"

# 单独维护枚举值到Model类的映射
CERT_TYPE_TO_MODEL = {
    CertificationType.QUIZZ: QuizzCertification,
    CertificationType.OTHER: OtherCertification,
}

class Certification(models.Model):
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(_("Description"), blank=True, null=True)

    type_of = models.CharField(
        _("Type of certification"),
        max_length=100,
        choices=CertificationType.choices,
        default=CertificationType.QUIZZ,
    )
  • 该方式完全符合TextChoices规范,不会触发Python 3.11的enum校验错误
  • 获取对应Model类时,通过CERT_TYPE_TO_MODEL[instance.type_of]即可

内容的提问来源于stack exchange,提问作者WitoldW

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:45:08