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
相关产品推荐
相关产品推荐

