¿Cómo puedo especificar y usar un ENUM en un modelo de Django?
¿Cómo puedo especificar y usar un ENUM en un modelo de Django?
Respuestas:
De la documentación de Django :
MAYBECHOICE = (
('y', 'Yes'),
('n', 'No'),
('u', 'Unknown'),
)
Y define un charfield en su modelo:
married = models.CharField(max_length=1, choices=MAYBECHOICE)
Puede hacer lo mismo con los campos enteros si no le gusta tener letras en su base de datos.
En ese caso, vuelva a escribir sus opciones:
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) )
El uso del choices
parámetro no usará el tipo ENUM db; simplemente creará un VARCHAR o INTEGER, dependiendo de si lo usa choices
con CharField o IntegerField. Generalmente, esto está bien. Si es importante para usted que el tipo ENUM se utilice en el nivel de la base de datos, tiene tres opciones:
Con cualquiera de estas opciones, sería su responsabilidad lidiar con las implicaciones para la portabilidad entre bases de datos. En la opción 2, puede usar SQL personalizado específico del backend de la base de datos para asegurarse de que su ALTER TABLE solo se ejecute en MySQL. En la opción 3, su método db_type necesitaría verificar el motor de la base de datos y establecer el tipo de columna db en un tipo que realmente exista en esa base de datos.
ACTUALIZACIÓN : Dado que el marco de migraciones se agregó en Django 1.7, las opciones 1 y 2 anteriores están completamente obsoletas. La opción 3 siempre fue la mejor opción de todos modos. La nueva versión de las opciones 1/2 implicaría una migración personalizada compleja utilizando SeparateDatabaseAndState
, pero realmente desea la opción 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:
Esta es otra forma agradable y fácil de implementar enumeraciones, aunque realmente no guarda enumeraciones en la base de datos.
Sin embargo, le permite hacer referencia a la 'etiqueta' siempre que consulte o especifique valores predeterminados en lugar de la respuesta mejor calificada en la que debe usar el 'valor' (que puede ser un número).
La configuración choices
en el campo permitirá cierta validación en el extremo de Django, pero no definirá ninguna forma de un tipo enumerado en el extremo de la base de datos.
Como han mencionado otros, la solución es especificar db_type
en un campo personalizado.
Si está utilizando un backend SQL (por ejemplo, MySQL), puede hacer esto así:
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)
Ejecute syncdb
e inspeccione su tabla para ver que ENUM
se creó correctamente.
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 | |
+--------+-----------------------------+------+-----+---------+----------------+
'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'
.
Si realmente desea utilizar sus bases de datos, escriba ENUM:
¡Buena suerte!
Actualmente hay dos proyectos de github basados en agregarlos, aunque no he investigado exactamente cómo se implementan:
No creo que ninguno use tipos de enumeración DB, pero están en proceso para el primero.
De la documentación :
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,
)
Ahora, tenga en cuenta que no impone las opciones a nivel de base de datos, esto es solo una construcción de Python. Si también desea aplicar esos valores en la base de datos, puede combinarlo con las restricciones de la base de datos:
class Student(models.Model):
...
class Meta:
constraints = [
CheckConstraint(
check=Q(year_in_school__in=YearInSchool.values),
name="valid_year_in_school")
]