在Django模型中指定mySQL ENUM


92

如何在Django模型中指定和使用ENUM?


4
史蒂夫(Steve),如果您打算使用MySQL ENUM类型,那么据我所知Django不提供对此功能的支持(您在Django支持的所有数据库中均未提供该功能),您很不走运。Paul提供的答案有效,但不会在数据库中定义类型。
dguaraglia

Answers:


108

Django文档中

MAYBECHOICE = (
    ('y', 'Yes'),
    ('n', 'No'),
    ('u', 'Unknown'),
)

然后在模型中定义一个charfield:

married = models.CharField(max_length=1, choices=MAYBECHOICE)

如果您不希望在数据库中包含字母,则可以对整数字段执行相同的操作。

在这种情况下,请重写您的选择:

MAYBECHOICE = (
    (0, 'Yes'),
    (1, 'No'),
    (2, 'Unknown'),
)

8
这不会阻止“ false”值(如果之前未清除过的话)被保存,是吗?
Strayer 2012年

@Strayer是的,我想这仅对使用模型表格有用
急性

请注意,推荐的Django风格意味着这些字符应为常量:docs.djangoproject.com/en/dev/internals/contributing/…–
Michael Scheper

11
就像@Carl Meyer在回答中所说的那样,这不会在数据库中创建ENUM列。它创建了VARCHAR或INTEGER列,因此它并没有真正回答问题。
Ariel 2015年

我可以在整数字段中添加选择功能吗?@fulmicoton
Ilyas karim

36
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) )

2
从Django 1.2开始,您需要向db_type def添加第二个参数connection。
汉斯·

2
那时codecatelog发生了什么事?Lokos认为这可能是个好主意。
Danny Staple 2012年

33

使用choices参数将不使用ENUM db类型。它将仅创建VARCHAR或INTEGER,具体取决于您使用choices的是CharField还是IntegerField。通常,这很好。如果在数据库级别使用ENUM类型对您很重要,则有以下三种选择:

  1. 使用“ ./manage.py sql应用程序名称”查看Django生成的SQL,手动对其进行修改以使用ENUM类型,然后自行运行。如果您首先手动创建该表,则“ ./manage.py syncdb”将不会使它混乱。
  2. 如果不想在每次生成数据库时都手动执行此操作,请将一些自定义SQL放在appname / sql / modelname.sql中,以执行适当的ALTER TABLE命令。
  3. 创建自定义字段类型并适当地定义db_type方法。

使用这些选项中的任何一个,您都有责任应对跨数据库可移植性的影响。在选项2中,您可以使用特定于数据库后端的自定义SQL于来确保ALTER TABLE仅在MySQL上运行。在选项3中,您的db_type方法将需要检查数据库引擎并将db列类型设置为该数据库中实际存在的类型。

更新:由于迁移框架是在Django 1.7中添加的,因此上面的选项1和2完全过时了。无论如何,选项3始终是最佳选择。选项1/2的新版本将涉及使用SeparateDatabaseAndState- 的复杂自定义迁移,但实际上您想要选项3。


10

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:

尽管这实际上并没有将枚举保存在数据库中,但这是实现枚举的另一种不错且简便的方法。

但是,它确实允许您在查询或指定默认值时引用“标签”,而不是必须使用“值”(可能是数字)的顶级答案。


9

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    |                |
+--------+-----------------------------+------+-----+---------+----------------+

非常有帮助的答案!但这不适用于PostgreSQL。原因是PostgreSQL ENUM不支持默认值。首先在PostgreSQL中,我们必须创建CREATE DOMAIN或CREATE TYPE。参考8.7。枚举类型我尝试了@David的把戏,它在MySQL上运行正常,但在PostgrSQL中最终出现错误'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'
Grijesh Chauhan 2014年


3

目前,有两个基于添加这些内容的github项目,尽管我没有确切研究它们的实现方式:

  1. Django-EnumField
    提供枚举Django模型字段(使用IntegerField),其中包含可重用的枚举和过渡验证。
  2. Django-EnumFields
    此软件包允许您将真正的Python(PEP435样式)枚举与Django一起使用。

我不认为任何一个都使用DB枚举类型,但是它们第一个中起作用


1

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")
        ]

-2

在您的models.py文件顶部,在导入后添加以下行:

    enum = lambda *l: [(s,_(s)) for s in l]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.