Django 3.0的主要新增功能之一是对模型字段选择的枚举。它是一种定义和约束模型Field.choices的更好方法。
以前在Django模型中通常会通过定义一些“常量”和元组来定义choices,如下所示:
from django.db import modelsclass Book(models.Model):UNPUBLISHED = 'UN'PUBLISHED = 'PB'STATUS_CHOICES = [(UNPUBLISHED, 'Unpublished'),(PUBLISHED, 'Published'),]status = models.CharField(max_length=2,choices=STATUS_CHOICES,default=UNPUBLISHED,)
然后其他人可以按如下方式使用这些常量,例如:
unpublished_books = Book.objects.filter(status=Book.UNPUBLISHED)
如果多个模型使用同一组选项choices和常量,则可能需要把choices选项和常量从模型级别而转到模块级别,此时再也不能在通过Book.UNPUBLISHED的获取常量内容了。
from django.db import modelsUNPUBLISHED = 'UN'
PUBLISHED = 'PB'
STATUS_CHOICES = [(UNPUBLISHED, 'Unpublished'),(PUBLISHED, 'Published'),
]class Book(models.Model):status = models.CharField(max_length=2,choices=STATUS_CHOICES,default=UNPUBLISHED,)class Pamphlet(models.Model):status = models.CharField(max_length=2,choices=STATUS_CHOICES,default=PUBLISHED,)
这样在models.py文件中留下一堆没有命名空间的常量, 这有点违反《 Python之禅》:
命名空间是一个很棒的主意–让我们做更多的事!
这也使我们缺少一些有用的功能。例如,没有简单的方法可以将值转换为其显示标签。
更新(2020年1月28日): 老版模型实例Django了提供get_FOO_display()
方法来转换值choice选项值对应的标签。
存在诸如django-choices和django-enumfields之类的软件包来解决这些问题。我还看到了其他项目上几个类似功能的自定义实现。
Django 3.0现在提供了一个Choices
带有两个子类类IntegerChoices
和TextChoices
。这些类扩展了Python的Enum
类型,并增加了额外的约束和功能,以使其适用于Field.choices
。
现在我们要转换之前的示例,我们定义一个新的Status类,该类继承了TextChoices类,用于替换我们之前定义的STATUS_CHOICES元组。现在Status.choices与STATUS_CHOICES是等同的。除此以外,你还可以使用Status.UNPUBLISHED的方式调用Status类下的常量。此时常量UNPUBLISHED有了自己的命名空间,更安全,也使代码可读性更高。
class Status(models.TextChoices):UNPUBLISHED = 'UN', 'Unpublished'PUBLISHED = 'PB', 'Published'class Book(models.Model):status = models.CharField(max_length=2,choices=Status.choices,default=Status.UNPUBLISHED,)class Pamphlet(models.Model):status = models.CharField(max_length=2,choices=Status.choices,default=Status.PUBLISHED,)
我们可以通过检查未检测到迁移变化来测试是否正确转换:
$python manage.py makemigrations --dry-run
No changes detected
如果我们添加,删除或重新排序了任何成员,则这将被检测为字段中的更改。这是因为迁移框架仅看到由choices
生成的列表Status.choices
,而看不到枚举类。
QuerySet过滤器可以更新为使用以下Choices
类:
unpublished_books = Book.objects.filter(status=Status.UNPUBLISHED)
我们还可以轻松地将值转换为其显示标签:
In [2]: book = Book.objects.latest('id')In [3]: Status(book.status)
Out[3]: <Status.UNPUBLISHED: 'UN'>In [4]: Status(book.status).label
Out[4]: 'Unpublished'
更干净!
结语
我希望这可以帮助您享受这一新的Django 3.0功能。该枚举类型的文件涵盖了一些细节,值得一读。感谢Shai Berger,Nick Pope,Marius Felisiak,Carlton Gibson,以及所有其他负责添加它的人(票号#27910)。
-亚当
原文链接:https://adamj.eu/tech/2020/01/27/moving-to-django-3-field-choices-enumeration-types/
相关阅读
支持异步的Django 3.X来了,你准备好了吗? Django 3.0新特色与变化详解。