如何在Django模型中指定MySQL ENUM类型字段?

问题描述 投票:0回答:10

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

python mysql django django-models enums
10个回答
113
投票

来自 Django 文档

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

并且您在模型中定义了一个 charfield :

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

如果您不喜欢字母,您可以对整数字段执行相同的操作 在你的数据库中。

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

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

39
投票
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) )

36
投票

使用

choices
参数不会使用ENUM db类型;它只会创建一个 VARCHAR 或 INTEGER,具体取决于您是否将
choices
与 CharField 或 IntegerField 一起使用。一般来说,这样就可以了。如果在数据库级别使用 ENUM 类型对您很重要,您有三个选择:

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

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

更新:由于迁移框架是在 Django 1.7 中添加的,所以上面的选项 1 和 2 已经完全过时了。无论如何,选项 3 始终是最佳选择。新版本的选项 1/2 将涉及使用

SeparateDatabaseAndState
进行复杂的自定义迁移——但您确实需要选项 3。


11
投票

在字段上设置

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

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:

这是实现枚举的另一种很好且简单的方法,尽管它并没有真正将枚举保存在数据库中。

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


6
投票

如果您确实想使用数据库 ENUM 类型:

  1. 使用 Django 1.x
  2. 认识到您的应用程序仅适用于某些数据库。
  3. 通过此文档页面困惑:http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

祝你好运!


3
投票

目前有两个基于添加这些的 github 项目,尽管我还没有具体研究它们是如何实现的:

  1. Django-EnumField
    提供具有可重用枚举和转换验证的枚举 Django 模型字段(使用 IntegerField)。
  2. Django-EnumFields
    这个包允许您在 Django 中使用真正的 Python(PEP435 风格)枚举。

我不认为使用数据库枚举类型,但它们正在开发中第一个。


3
投票

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
投票

如果您必须在 MySQL 表中拥有枚举类型而不是“假”枚举(仅在 python 端可见),那么您可以尝试 Django-MySQL 扩展及其 EnumField

型号:

from django.db import models
from django_mysql.models import EnumField


class IceCreamFlavor(models.TextChoices):
    CHOCOLATE = "chocolate"
    VANILLA = "vanilla"


class IceCream(models.Model):
    price = models.DecimalField(max_digits=4, decimal_places=2)
    ice_cream_flavor = EnumField(choices=IceCreamFlavor.choices)

结果表:

mysql> SHOW COLUMNS IN myapp.db_icecream;
+------------------+-----------------------------+------+-----+---------+----------------+
| Field            | Type                        | Null | Key | Default | Extra          |
+------------------+-----------------------------+------+-----+---------+----------------+
| id               | int                         | NO   | PRI | NULL    | auto_increment |
| price            | decimal(4,2)                | NO   |     | NULL    |                |
| ice_cream_flavor | enum('chocolate','vanilla') | NO   |     | NULL    |                |
+------------------+-----------------------------+------+-----+---------+----------------+


-2
投票

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

    enum = lambda *l: [(s,_(s)) for s in l]
© www.soinside.com 2019 - 2024. All rights reserved.