Django 枚举属性相等性

3

我在django模型中使用枚举,例如:

class AwesomeNess(Enum):
    slight = "SLIGHT"
    very = "VERY"

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    awesomeness = models.CharField(
        max_length=255,
        choices=[(tag.value, tag.name) for tag in AwesomeNess],
        default=AwesomeNess.slight
    )

当我使用Django的filter函数时,这个工作得很好,例如:

d = Choice.objects.filter(awesomeness=AwesomeNess.slight)

但如果我这样做,它就不能工作:

choice_obj = Choice.objects.get(id=1)
choice_obj.awesomeness == AwesomeNess.slight # will return False
choice_obj.awesomeness == "AwesomeNess.slight" # will return True

由于值以字符串形式存储,因此在返回数据时,Django看起来忘记将它们强制转换回枚举。

这会导致编码时出现不一致性,因为django模型过滤器可以查询枚举,而属性平等过滤需要我使用枚举的字符串表示。

编辑:枚举是从另一个模型类导入的,因此无法用Django内置的choices类替换它。

编辑2:在调查此问题时,我也注意到以下情况:

myobj = Choice.objects.get(id=1)
myobj.awesomeness  # <<-- string type
myobj.awesomeness =  AwesomeNess.very <<-- object changes type to enum
myobj.save()
myobj2 = Choice.objects.get(id=1) <<-- upon reload from db, the updated value is returned as string

作为一个初学 Django 的用户,这对我来说似乎非常危险,而且在我阅读的任何“如何在 Django 中使用枚举”的文章中都没有提到。这仅仅是因为我使用的 Django 版本太旧了吗?
有没有什么方法可以解决这个问题?我做错了什么吗?谢谢!顺便说一下:这是在 Django 2.2.24 上。也许更新版本的枚举支持更好了吗?

Django 3+内置了对枚举类型的支持,用于选择 https://docs.djangoproject.com/en/3.0/ref/models/fields/#enumeration-types,但这并不是回答你的问题,抱歉。 - Iain Shelvington
1个回答

4
这种情况发生的原因是因为选项对给定的值调用str,这意味着它将字符串"AwesomeNess.slight"存储为默认字符串(如果设置了默认值)。
以来,您可以使用TextChoices类[Django-doc],它与使用Enum相同。因此,您可以定义一个TextChoices模型:
class Choice(models.Model):

    class <strong>AwesomNess</strong>(models.TextChoices):
        SLIGHT = 'SLIGHT', 'slight'
        VERY = 'VERY', 'very'

    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    awesomeness = models.CharField(
        max_length=255,
        choices=AwesomeNess<strong>.choices</strong>,
        default=AwesomeNess.SLIGHT
    )

对于旧版本,您需要指定使用AwesomeNess

class AwesomeNess(Enum):
    slight = 'SLIGHT'
    very = 'VERY'

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    awesomeness = models.CharField(
        max_length=255,
        choices=[(tag.value, tag.name) for tag in AwesomeNess],
        default=<strong>AwesomeNess.slight.value</strong>
    )

即使使用tag.value,tag.name,根据模型对象的状态,字符串和枚举之间仍存在不一致性,请参见上文中的EDIT2。我们得出结论,在django 2.x中以任何形式使用枚举类型都太危险了。 - Trondh
@Trondh:是的,你应该总是使用.value。在表单中这是不必要的,因为表单会自动使用与文本相关联的值。你可以简单地在一个类中定义常量(所以没有枚举逻辑),并将其用作常量。 - Willem Van Onsem
@WillemVanOnsem 是的,这就是我们最终采取的方案。由于对象状态在字符串和枚举表示之间的复杂性太大,对我们来说存在太多风险。在升级到3.x版本后,我们将回归使用“本地”枚举。感谢您所有的评论和建议! - Trondh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接