Specifica di un ENUM mySQL in un modello Django


92

Come posso specificare e utilizzare un ENUM in un modello Django?


4
Steve, se intendevi usare il tipo MySQL ENUM, allora sei sfortunato, per quanto ne so Django non fornisce supporto per questo (quella funzione non è disponibile in tutti i DB supportati da Django). La risposta fornita da Paul funziona, ma non definirà il tipo nel DB.
— dguaraglia

Risposte:


108

Dalla documentazione di Django :

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

E definisci un campo di caratteri nel tuo modello:

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

Puoi fare lo stesso con i campi interi se non ti piace avere lettere nel tuo db.

In tal caso, riscrivi le tue scelte:

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

8
Questo non impedisce che i valori "falsi" vengano salvati se non puliti prima, vero?
— Strayer

@Strayer sì, immagino che sia utile solo per l'utilizzo di moduli modello
— Acute

Nota che lo stile Django consigliato implica che i caratteri debbano essere costanti: docs.djangoproject.com/en/dev/internals/contributing/…
— Michael Scheper

11
Come ha detto @Carl Meyer nella sua risposta, questo NON crea una colonna ENUM nel database. Crea una colonna VARCHAR o INTEGER, quindi non risponde realmente alla domanda.
— Ariel

Posso aggiungere la funzione di scelta con il campo intero? @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
A partire da django 1.2, dovrai aggiungere un secondo parametro, connection, a db_type def.
— Hans Lawrenz,

2
Che fine ha fatto il codecatelog allora? Lokos come se potesse essere una buona idea ... Ora ho un 404, anche per la pagina principale.
— Danny Staple

33

L'utilizzo del choicesparametro non utilizzerà il tipo db ENUM; creerà semplicemente un VARCHAR o un INTEGER, a seconda che si utilizzi choicescon un CharField o IntegerField. In generale, questo va bene. Se è importante per te che il tipo ENUM venga utilizzato a livello di database, hai tre opzioni:

  1. Usa "./manage.py sql appname" per vedere l'SQL generato da Django, modificalo manualmente per usare il tipo ENUM ed eseguilo tu stesso. Se crei prima la tabella manualmente, "./manage.py syncdb" non la modificherà.
  2. Se non si desidera eseguire questa operazione manualmente ogni volta che si genera il database, inserire un codice SQL personalizzato in appname / sql / modelname.sql per eseguire il comando ALTER TABLE appropriato.
  3. Crea un tipo di campo personalizzato e definisci il metodo db_type in modo appropriato.

Con una qualsiasi di queste opzioni, sarebbe tua responsabilità affrontare le implicazioni per la portabilità tra database. Nell'opzione 2, è possibile utilizzare l' SQL personalizzato specifico del backend del database per garantire che ALTER TABLE venga eseguito solo su MySQL. Nell'opzione 3, il metodo db_type dovrebbe controllare il motore di database e impostare il tipo di colonna db su un tipo che esiste effettivamente in quel database.

AGGIORNAMENTO : poiché il framework delle migrazioni è stato aggiunto in Django 1.7, le opzioni 1 e 2 sopra sono completamente obsolete. L'opzione 3 è sempre stata comunque l'opzione migliore. La nuova versione delle opzioni 1/2 comporterebbe una complessa migrazione personalizzata utilizzando SeparateDatabaseAndState, ma in realtà vuoi l'opzione 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:

Questo è un altro modo semplice e piacevole di implementare le enumerazioni, sebbene non salvi realmente le enumerazioni nel database.

Tuttavia, ti consente di fare riferimento all '"etichetta" ogni volta che esegui una query o specifica i valori predefiniti rispetto alla risposta più apprezzata in cui devi utilizzare il "valore" (che potrebbe essere un numero).


9

L'impostazione choicessul campo consentirà alcune convalide all'estremità Django, ma non definirà alcuna forma di un tipo enumerato all'estremità del database.

Come altri hanno già detto, la soluzione è specificare db_typesu un campo personalizzato.

Se stai usando un backend SQL (ad esempio MySQL), puoi farlo in questo modo:

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)

Esegui syncdbe ispeziona la tua tabella per vedere che è ENUMstata creata correttamente.

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

Risposta molto utile! Ma questo non funzionerà per PostgreSQL. Il motivo è che PostgreSQL ENUM non supporta l'impostazione predefinita. In PostgreSQL dobbiamo prima creare CREATE DOMAIN o CREATE TYPE. Reff 8.7. Tipi enumerati Ho provato il trucco di @ David e funziona bene con MySQL ma in PostgrSQL il lavoro finisce con un errore 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
— Grijesh Chauhan


3

Attualmente ci sono due progetti GitHub basati sull'aggiunta di questi, anche se non ho esaminato esattamente come vengono implementati:

  1. Django-EnumField :
    fornisce un campo del modello Django di enumerazione (utilizzando IntegerField) con enumerazioni riutilizzabili e convalida della transizione.
  2. Django-EnumFields :
    questo pacchetto consente di utilizzare enumerazioni Python reali (in stile PEP435) con Django.

Non penso nemmeno di utilizzare i tipi di enumerazione DB, ma sono in lavorazione per il primo.


1

Django 3.0 ha il supporto integrato per Enums

Dalla documentazione :

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

Ora, tieni presente che non impone le scelte a livello di database, questo è solo un costrutto di Python. Se vuoi applicare anche quei valori al database, puoi combinarli con i vincoli del database:

class Student(models.Model):
    ...

    class Meta:
        constraints = [
            CheckConstraint(
                check=Q(year_in_school__in=YearInSchool.values),
                name="valid_year_in_school")
        ]

-2

Nella parte superiore del file models.py, aggiungi questa riga dopo aver eseguito le importazioni:

    enum = lambda *l: [(s,_(s)) for s in l]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.