Spécifier un ENUM mySQL dans un modèle Django

92

Comment spécifier et utiliser un ENUM dans un modèle Django?

Steve
la source
4
Steve, si vous vouliez utiliser le type MySQL ENUM, alors vous n'avez pas de chance, pour autant que je sache, Django ne fournit pas de support pour cela (cette fonctionnalité n'est pas disponible dans toutes les bases de données prises en charge par Django). La réponse fournie par Paul fonctionne, mais elle ne définira pas le type dans la base de données.
dguaraglia

Réponses:

108

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'),
)
fulmicoton
la source
8
Cela n'empêche pas les "fausses" valeurs d'être enregistrées si elles n'ont pas été nettoyées auparavant, n'est-ce pas?
Strayer
@Strayer oui, je suppose que cela n'est utile que pour utiliser des formulaires modèles
Acute
Notez que le style Django recommandé implique que les caractères doivent être des constantes: docs.djangoproject.com/en/dev/internals/contributing
Michael Scheper
11
Comme @Carl Meyer l'a dit dans sa réponse, cela NE crée PAS de colonne ENUM dans la base de données. Il crée une colonne VARCHAR ou INTEGER, donc il ne répond pas vraiment à la question.
Ariel
Puis-je ajouter une fonctionnalité de choix avec un champ entier? @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) )
user185534
la source
2
À partir de django 1.2, vous devrez ajouter un deuxième paramètre, connection, à la db_type def.
Hans Lawrenz
2
Qu'est-il arrivé à codecatelog alors? Lokos comme ça aurait pu être une bonne idée ... Je reçois un 404 maintenant - même pour la page racine.
Danny Staple
33

L'utilisation du choicesparamè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' choicesutilisiez 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:

  1. Utilisez "./manage.py sql appname" pour voir le SQL généré par Django, modifiez-le manuellement pour utiliser le type ENUM et exécutez-le vous-même. Si vous créez d'abord la table manuellement, "./manage.py syncdb" ne le dérangera pas.
  2. Si vous ne souhaitez pas le faire manuellement à chaque fois que vous générez votre base de données, placez un SQL personnalisé dans appname / sql / modelname.sql pour exécuter la commande ALTER TABLE appropriée.
  3. Créez un type de champ personnalisé et définissez la méthode db_type de manière appropriée.

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.

Carl Meyer
la source
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:

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

keithxm23
la source
9

Le paramétrage choicessur 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_typesur 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 syncdbet 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    |                |
+--------+-----------------------------+------+-----+---------+----------------+
David Cain
la source
Réponse très utile! Mais cela ne fonctionnera pas pour PostgreSQL. La raison est que PostgreSQL ENUM ne prend pas en charge la valeur par défaut. Dans PostgreSQL, nous devons d'abord créer CREATE DOMAIN ou CREATE TYPE. Reff 8.7. Types énumérés J'ai essayé l'astuce de @ David et cela fonctionne bien avec MySQL, mais dans PostgrSQL, le travail se termine par une erreur 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Grijesh Chauhan
3

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:

  1. Django-EnumField :
    fournit un champ de modèle Django d'énumération (en utilisant IntegerField) avec des énumérations réutilisables et une validation de transition.
  2. Django-EnumFields :
    Ce paquet vous permet d'utiliser de vraies énumérations Python (style PEP435) avec Django.

Je ne pense pas non plus utiliser les types d'énumérations DB, mais ils sont en préparation pour le premier.

Pureferret
la source
1

Django 3.0 a un support intégré pour Enums

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

En haut de votre fichier models.py, ajoutez cette ligne après avoir effectué vos importations:

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