Come posso specificare e utilizzare un ENUM in un modello Django?
Come posso specificare e utilizzare un ENUM in un modello Django?
Risposte:
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'),
)
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) )
L'utilizzo del choices
parametro non utilizzerà il tipo db ENUM; creerà semplicemente un VARCHAR o un INTEGER, a seconda che si utilizzi choices
con 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:
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.
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).
L'impostazione choices
sul 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_type
su 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 syncdb
e ispeziona la tua tabella per vedere che è ENUM
stata 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 | |
+--------+-----------------------------+------+-----+---------+----------------+
'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'
.
Se vuoi davvero usare i tuoi database, digita ENUM:
In bocca al lupo!
Attualmente ci sono due progetti GitHub basati sull'aggiunta di questi, anche se non ho esaminato esattamente come vengono implementati:
Non penso nemmeno di utilizzare i tipi di enumerazione DB, ma sono in lavorazione per il primo.
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")
]