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.