使用Django ModelField选项作为字符串的枚举是反模式吗?

5 投票
5 回答
5325 浏览
提问于 2025-04-18 12:45

我在Django里有一个字段,主要用来表示通知偏好,就像一个枚举一样。

现在我设置成这样:

class MyModel(models.Model):
    # ...
    EVERY_TIME = 'every'; WEEKLY = 'weekly'; NEVER = 'never'
    NOTIFICATION_CHOICES = ((EVERY_TIME, "Every time"), (WEEKLY, "Weekly"), (NEVER, "Never"))
    notification_preferences = models.CharField(choices=NOTIFICATION_CHOICES, default=EVERY_TIME, max_length=10)

我知道一般来说,这种枚举应该用 models.IntegerField 来设置,而不是 CharField。但是因为前端是用Angular做的,数据都是通过API提供的,我觉得前端收到 'weekly' 这样的信息,比收到 2 更有用。

CharField 来做枚举算不算不好的做法?如果算的话,我的这个用例是不是小到没关系,还是说我遗漏了什么需要改的地方?

5 个回答

0

现在使用 CharField 来表示枚举类型已经不再被认为是一个坏习惯了,因为最新的 Django 文档推荐这种做法。在它的 choices 示例中有提到,下面是相关的内容:

FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
YEAR_IN_SCHOOL_CHOICES = (
    (FRESHMAN, 'Freshman'),
    (SOPHOMORE, 'Sophomore'),
    (JUNIOR, 'Junior'),
    (SENIOR, 'Senior'),
)
year_in_school = models.CharField(max_length=2,
                                  choices=YEAR_IN_SCHOOL_CHOICES,
                                  default=FRESHMAN) 
1

在Django 3及以上版本中,这个功能是原生支持的。

https://adamj.eu/tech/2020/01/27/moving-to-django-3-field-choices-enumeration-types/

3

把CharField当作枚举使用算不算不好的做法?如果算的话,我的使用场景小到不算大问题吗,还是说我有什么地方没考虑到,应该改一下?

你这个问题想得完全不对。选择字段类型完全取决于你想存储的数据。所以,问题不是说CharField用作枚举是否不妥,而是你需要问自己:

"我需要在数据库里存储什么信息?"

有一种观点认为,如果你希望数据库能帮你对某个字段进行计算,那么你应该只存储整数;否则,存储数字作为字符串类型会更灵活。

我不想深入讨论这个,因为这是个见仁见智的话题。我想说的是,如果你觉得把数据存储为字符对你的应用有意义,那就去做吧。别想得太复杂。

我知道一般来说,这种枚举应该用models.IntegerField而不是CharField,但因为前端用的是Angular,数据都是通过API提供的,我觉得让前端接收到'weekly'而不是2,可能会提供更有用的信息。

我不太清楚你是从哪里得出这个结论的,但关于枚举应该用什么数据类型,并没有硬性规定。这取决于你的应用和你需要存储的信息——而要决定这一点,你需要知道你将如何查询数据库中的这些信息。

在你的情况下,你是从API的可用性角度来看这个问题,这完全没问题。你这个模型的主要目的是存储信息,而这些信息应该以对你的应用最有用的方式存储。

最后,记得在Python中不要使用;。虽然从技术上讲这并不算错,但一般不推荐这样做,也不符合Python风格指南

4

你可以考虑使用Django-model-utils中的Choices库,这样你就能更好地控制枚举的文本版本。

来回答你的问题,并不是所有东西都适合用整数来标识。比如说澳大利亚的州,只有7个,而且这些州是固定的:

ACT - Australian Capital Territory
NSW - New South Wales
NT  - Northern Territory
QLD - Queensland
SA  - South Australia
TAS - Tasmania
VIC - Victoria
WA  - Western Australia

因为这些州是相对固定的(国家的组成不太可能改变),所以没有必要给每个州分配一个整数,使用完整的名称作为文本编码就足够了。

我不会说使用CharField作为选择是个反模式,只是这种方法应该在你确定数据库中存储的文本缩写是合理的情况下使用。

另外,你也可以使用Enum来存储你的值。

from enum import Enum

class Countries(Enum):
   ACT = "Australian Capital Territory"
   NSW = "New South Wales"
   ...

class MyModel(models.Model):
    country = models.CharField(choices=[(tag.name, tag.value) for tag in Countries])

撰写回答