Especificando un ENUM de mySQL en un modelo de Django

92

¿Cómo puedo especificar y usar un ENUM en un modelo de Django?

Steve
fuente
4
Steve, si se refería a usar el tipo ENUM de MySQL, entonces no tiene suerte, hasta donde yo sé, Django no proporciona soporte para eso (esa característica no está disponible en todas las bases de datos compatibles con Django). La respuesta proporcionada por Paul funciona, pero no definirá el tipo en la base de datos.
dguaraglia

Respuestas:

108

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'),
)
fulmicoton
fuente
8
Esto no evita que se guarden valores "falsos" si no se han limpiado antes, ¿verdad?
Strayer
@Strayer sí, supongo que esto es útil solo para usar formularios modelo
Agudo
Tenga en cuenta que el estilo Django recomendado implica que los caracteres deben ser constantes: docs.djangoproject.com/en/dev/internals/contributing/…
Michael Scheper
11
Como dijo @Carl Meyer en su respuesta, esto NO crea una columna ENUM en la base de datos. Crea una columna VARCHAR o INTEGER, por lo que realmente no responde a la pregunta.
Ariel
¿Puedo agregar la función de opciones con un campo entero? @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) )
usuario 185534
fuente
2
A partir de django 1.2, deberá agregar un segundo parámetro, conexión, a db_type def.
Hans Lawrenz
2
¿Qué pasó con codecatelog entonces? Lokos como si hubiera sido una buena idea ... Ahora obtengo un 404, incluso para la página raíz.
Danny Staple
33

El uso del choicesparámetro no usará el tipo ENUM db; simplemente creará un VARCHAR o INTEGER, dependiendo de si lo usa choicescon 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:

  1. Use "./manage.py sql appname" para ver el SQL que genera Django, modifíquelo manualmente para usar el tipo ENUM y ejecútelo usted mismo. Si primero crea la tabla manualmente, "./manage.py syncdb" no la alterará.
  2. Si no desea hacer esto manualmente cada vez que genera su base de datos, coloque algo de SQL personalizado en appname / sql / modelname.sql para ejecutar el comando ALTER TABLE apropiado.
  3. Cree un tipo de campo personalizado y defina el método db_type de forma adecuada.

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.

Carl Meyer
fuente
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:

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

keithxm23
fuente
9

La configuración choicesen 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_typeen 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 syncdbe inspeccione su tabla para ver que ENUMse 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    |                |
+--------+-----------------------------+------+-----+---------+----------------+
David Caín
fuente
¡Respuesta muy útil! Pero esto no funcionará para PostgreSQL. La razón es que PostgreSQL ENUM no admite valores predeterminados. En PostgreSQL primero tenemos que crear CREATE DOMAIN o CREATE TYPE. Ref . 8.7. Tipos enumerados Probé el truco de @ David y funciona bien con MySQL, pero en PostgrSQL el trabajo termina con un error 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Grijesh Chauhan
3

Actualmente hay dos proyectos de github basados ​​en agregarlos, aunque no he investigado exactamente cómo se implementan:

  1. Django-EnumField :
    proporciona un campo de modelo Django de enumeración (utilizando IntegerField) con enumeraciones reutilizables y validación de transición.
  2. Django-EnumFields :
    este paquete le permite usar enumeraciones reales de Python (estilo PEP435) con Django.

No creo que ninguno use tipos de enumeración DB, pero están en proceso para el primero.

Pureferret
fuente
1

Django 3.0 tiene soporte incorporado para Enums

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")
        ]
Cesar Canassa
fuente
-2

En la parte superior de su archivo models.py, agregue esta línea después de realizar sus importaciones:

    enum = lambda *l: [(s,_(s)) for s in l]
Kenzo
fuente