如何在 Django 模型中指定和使用 ENUM?
9 回答
MAYBECHOICE = (
('y', 'Yes'),
('n', 'No'),
('u', 'Unknown'),
)
你在你的模型中定义了一个字符域:
married = models.CharField(max_length=1, choices=MAYBECHOICE)
如果你不喜欢在你的数据库中有字母,你可以对整数字段做同样的事情。
在这种情况下,重写你的选择:
MAYBECHOICE = (
(0, 'Yes'),
(1, 'No'),
(2, 'Unknown'),
)
from django.db import models
class EnumField(models.Field):
"""
A field class that maps to MySQL's ENUM type.
Usage:
class Card(models.Model):
suit = EnumField(values=('Clubs', 'Diamonds', 'Spades', 'Hearts'))
c = Card()
c.suit = 'Clubs'
c.save()
"""
def __init__(self, *args, **kwargs):
self.values = kwargs.pop('values')
kwargs['choices'] = [(v, v) for v in self.values]
kwargs['default'] = self.values[0]
super(EnumField, self).__init__(*args, **kwargs)
def db_type(self):
return "enum({0})".format( ','.join("'%s'" % v for v in self.values) )
使用choices
参数不会使用 ENUM db 类型;它只会创建一个 VARCHAR 或 INTEGER,具体取决于您是使用choices
CharField 还是 IntegerField。一般来说,这很好。如果在数据库级别使用 ENUM 类型对您很重要,您有三个选项:
- 使用“./manage.py sql appname”查看Django生成的SQL,手动修改为使用ENUM类型,自己运行。如果您先手动创建表,“./manage.py syncdb”不会弄乱它。
- 如果您不想在每次生成数据库时手动执行此操作,请将一些自定义 SQL 放入 appname/sql/modelname.sql 以执行适当的 ALTER TABLE 命令。
- 创建自定义字段类型并适当地定义 db_type 方法。
使用这些选项中的任何一个,您都有责任处理对跨数据库可移植性的影响。在选项 2 中,您可以使用特定于数据库后端的自定义 SQL来确保您的 ALTER TABLE 仅在 MySQL 上运行。在选项 3 中,您的 db_type 方法需要检查数据库引擎并将 db 列类型设置为该数据库中实际存在的类型。
更新:由于迁移框架是在 Django 1.7 中添加的,所以上面的选项 1 和 2 完全过时了。无论如何,选项 3 始终是最佳选择。选项 1/2 的新版本将涉及复杂的自定义迁移SeparateDatabaseAndState
,但您确实需要选项 3。
http://www.b-list.org/weblog/2007/nov/02/handle-choices-right-way/
class Entry(models.Model): LIVE_STATUS = 1 DRAFT_STATUS = 2 HIDDEN_STATUS = 3 STATUS_CHOICES = ( (LIVE_STATUS, 'Live'), (DRAFT_STATUS, 'Draft'), (HIDDEN_STATUS, 'Hidden'), ) # ...some other fields here... status = models.IntegerField(choices=STATUS_CHOICES, default=LIVE_STATUS) live_entries = Entry.objects.filter(status=Entry.LIVE_STATUS) draft_entries = Entry.objects.filter(status=Entry.DRAFT_STATUS) if entry_object.status == Entry.LIVE_STATUS:
这是实现枚举的另一种简单易用的方法,尽管它并没有真正将枚举保存在数据库中。
但是,它确实允许您在查询或指定默认值时引用“标签”,而不是必须使用“值”(可能是数字)的顶级答案。
该字段的设置choices
将允许在 Django 端进行一些验证,但不会在数据库端定义任何形式的枚举类型。
正如其他人所提到的,解决方案是db_type
在自定义字段上进行指定。
如果您使用的是 SQL 后端(例如 MySQL),您可以这样做:
from django.db import models
class EnumField(models.Field):
def __init__(self, *args, **kwargs):
super(EnumField, self).__init__(*args, **kwargs)
assert self.choices, "Need choices for enumeration"
def db_type(self, connection):
if not all(isinstance(col, basestring) for col, _ in self.choices):
raise ValueError("MySQL ENUM values should be strings")
return "ENUM({})".format(','.join("'{}'".format(col)
for col, _ in self.choices))
class IceCreamFlavor(EnumField, models.CharField):
def __init__(self, *args, **kwargs):
flavors = [('chocolate', 'Chocolate'),
('vanilla', 'Vanilla'),
]
super(IceCreamFlavor, self).__init__(*args, choices=flavors, **kwargs)
class IceCream(models.Model):
price = models.DecimalField(max_digits=4, decimal_places=2)
flavor = IceCreamFlavor(max_length=20)
运行syncdb
并检查您的表以查看是否ENUM
已正确创建。
mysql> SHOW COLUMNS IN icecream;
+--------+-----------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+-----------------------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| price | decimal(4,2) | NO | | NULL | |
| flavor | enum('chocolate','vanilla') | NO | | NULL | |
+--------+-----------------------------+------+-----+---------+----------------+
如果你真的想使用你的数据库 ENUM 类型:
- 使用 Django 1.x
- 认识到您的应用程序只能在某些数据库上运行。
- 通过此文档页面拼图: http: //docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields
祝你好运!
目前有两个基于添加这些的 github 项目,尽管我还没有详细了解它们是如何实现的:
- Django-EnumField:
提供一个枚举 Django 模型字段(使用 IntegerField),具有可重用的枚举和转换验证。 - Django-EnumFields:
这个包允许你在 Django 中使用真正的 Python(PEP435 风格)枚举。
我不认为两者都使用 DB 枚举类型,但它们正在为第一个而工作。
Django 3.0 内置了对 Enums 的支持
从文档中:
from django.utils.translation import gettext_lazy as _
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Sophomore')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
现在,请注意,它不会在数据库级别强制执行选择,这是 Python 唯一的构造。如果您还想在数据库中强制执行这些值,您可以将其与数据库约束结合起来:
class Student(models.Model):
...
class Meta:
constraints = [
CheckConstraint(
check=Q(year_in_school__in=YearInSchool.values),
name="valid_year_in_school")
]
在您的 models.py 文件的顶部,在您导入后添加此行:
enum = lambda *l: [(s,_(s)) for s in l]