Comment spécifier et utiliser un ENUM dans un modèle Django?
Comment spécifier et utiliser un ENUM dans un modèle Django?
Réponses:
Depuis la documentation Django :
MAYBECHOICE = (
('y', 'Yes'),
('n', 'No'),
('u', 'Unknown'),
)
Et vous définissez un champ de caractères dans votre modèle:
married = models.CharField(max_length=1, choices=MAYBECHOICE)
Vous pouvez faire de même avec les champs entiers si vous n'aimez pas avoir de lettres dans votre base de données.
Dans ce cas, réécrivez vos choix:
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'utilisation du choices
paramètre n'utilisera pas le type de base de données ENUM; il créera simplement un VARCHAR ou un INTEGER, selon que vous l' choices
utilisiez avec un CharField ou IntegerField. En général, c'est très bien. S'il est important pour vous que le type ENUM soit utilisé au niveau de la base de données, vous avez trois options:
Avec l'une de ces options, il serait de votre responsabilité de traiter les implications pour la portabilité entre les bases de données. Dans l'option 2, vous pouvez utiliser un SQL personnalisé spécifique à la base de données pour vous assurer que votre ALTER TABLE n'est exécuté que sur MySQL. Dans l'option 3, votre méthode db_type devrait vérifier le moteur de base de données et définir le type de colonne db sur un type qui existe réellement dans cette base de données.
MISE À JOUR : Depuis que le framework de migrations a été ajouté dans Django 1.7, les options 1 et 2 ci-dessus sont entièrement obsolètes. L'option 3 était toujours la meilleure option de toute façon. La nouvelle version des options 1/2 impliquerait une migration personnalisée complexe en utilisant SeparateDatabaseAndState
- mais vous voulez vraiment l'option 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:
C'est un autre moyen simple et agréable d'implémenter les énumérations bien qu'il ne sauvegarde pas vraiment les énumérations dans la base de données.
Cependant, il vous permet de référencer le «label» chaque fois que vous interrogez ou spécifiez des valeurs par défaut, par opposition à la réponse la mieux notée où vous devez utiliser la «valeur» (qui peut être un nombre).
Le paramétrage choices
sur le champ permettra une certaine validation du côté Django, mais il ne définira aucune forme d'un type énuméré du côté de la base de données.
Comme d'autres l'ont mentionné, la solution est de spécifier db_type
sur un champ personnalisé.
Si vous utilisez un backend SQL (par exemple MySQL), vous pouvez le faire comme ceci:
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)
Exécutez syncdb
et inspectez votre table pour voir que le a ENUM
été créé correctement.
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 vous souhaitez vraiment utiliser vos bases de données de type ENUM:
Bonne chance!
Il existe actuellement deux projets github basés sur leur ajout, bien que je n'ai pas examiné exactement comment ils sont implémentés:
Je ne pense pas non plus utiliser les types d'énumérations DB, mais ils sont en préparation pour le premier.
De la documentation :
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,
)
Maintenant, sachez qu'il n'applique pas les choix au niveau de la base de données, il s'agit uniquement d'une construction Python. Si vous souhaitez également appliquer ces valeurs à la base de données, vous pouvez combiner cela avec des contraintes de base de données:
class Student(models.Model):
...
class Meta:
constraints = [
CheckConstraint(
check=Q(year_in_school__in=YearInSchool.values),
name="valid_year_in_school")
]