Angeben einer mySQL ENUM in einem Django-Modell

92

Wie spezifiziere und verwende ich eine ENUM in einem Django-Modell?

Steve
quelle
4
Steve, wenn Sie den MySQL ENUM-Typ verwenden wollten, haben Sie Pech, soweit ich weiß, dass Django dies nicht unterstützt (diese Funktion ist nicht in allen von Django unterstützten DBs verfügbar). Die Antwort von Paul funktioniert, definiert jedoch nicht den Typ in der Datenbank.
Dguaraglia

Antworten:

108

Aus der Django-Dokumentation :

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

Und Sie definieren ein Zeichenfeld in Ihrem Modell:

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

Sie können dasselbe mit Ganzzahlfeldern tun, wenn Sie keine Buchstaben in Ihrer Datenbank haben möchten.

Schreiben Sie in diesem Fall Ihre Auswahl neu:

MAYBECHOICE = (
    (0, 'Yes'),
    (1, 'No'),
    (2, 'Unknown'),
)
Fulmicoton
quelle
8
Dies verhindert nicht, dass "falsche" Werte gespeichert werden, wenn sie vorher nicht bereinigt wurden, oder?
Strayer
@Strayer ja, ich denke, dies ist nur für die Verwendung von Modellformularen nützlich
Akut
Beachten Sie, dass der empfohlene Django-Stil impliziert, dass die Zeichen Konstanten sein sollten: docs.djangoproject.com/de/dev/internals/contributing/…
Michael Scheper
11
Wie @Carl Meyer in seiner Antwort sagte, erstellt dies KEINE ENUM-Spalte in der Datenbank. Es wird eine VARCHAR- oder INTEGER-Spalte erstellt, sodass die Frage nicht wirklich beantwortet wird.
Ariel
Kann ich eine Auswahlfunktion mit einem Ganzzahlfeld hinzufügen? @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) )
Benutzer185534
quelle
2
Ab django 1.2 müssen Sie dem db_type def einen zweiten Parameter, connection, hinzufügen.
Hans Lawrenz
2
Was ist dann mit Codecatelog passiert? Lokos wie es hätte eine gute Idee sein können ... Ich bekomme jetzt eine 404 - sogar für die Root-Seite.
Danny Staple
33

Bei Verwendung des choicesParameters wird der ENUM-DB-Typ nicht verwendet. Es wird nur ein VARCHAR oder INTEGER erstellt, je nachdem, ob Sie choicesmit einem CharField oder IntegerField verwenden. Im Allgemeinen ist dies in Ordnung. Wenn es Ihnen wichtig ist, dass der ENUM-Typ auf Datenbankebene verwendet wird, haben Sie drei Möglichkeiten:

  1. Verwenden Sie "./manage.py sql appname", um zu sehen, wie SQL Django generiert wird, ändern Sie es manuell, um den ENUM-Typ zu verwenden, und führen Sie es selbst aus. Wenn Sie die Tabelle zuerst manuell erstellen, wird "./manage.py syncdb" nichts damit anfangen.
  2. Wenn Sie dies nicht jedes Mal manuell tun möchten, wenn Sie Ihre Datenbank generieren, fügen Sie in appname / sql / modelname.sql benutzerdefiniertes SQL ein, um den entsprechenden Befehl ALTER TABLE auszuführen.
  3. Erstellen Sie einen benutzerdefinierten Feldtyp und definieren Sie die Methode db_type entsprechend.

Bei jeder dieser Optionen liegt es in Ihrer Verantwortung, sich mit den Auswirkungen auf die datenbankübergreifende Portabilität zu befassen. In Option 2 können Sie datenbank-backendspezifisches benutzerdefiniertes SQL verwenden, um sicherzustellen, dass ALTER TABLE nur unter MySQL ausgeführt wird. In Option 3 müsste Ihre Methode db_type das Datenbankmodul überprüfen und den Spaltentyp db auf einen Typ setzen, der tatsächlich in dieser Datenbank vorhanden ist.

UPDATE : Da das Migrationsframework in Django 1.7 hinzugefügt wurde, sind die obigen Optionen 1 und 2 völlig veraltet. Option 3 war sowieso immer die beste Option. Die neue Version von Optionen 1/2 würde eine komplexe benutzerdefinierte Migration mit beinhalten SeparateDatabaseAndState- aber Sie möchten wirklich Option 3.

Carl Meyer
quelle
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:

Dies ist eine weitere nette und einfache Möglichkeit, Enums zu implementieren, obwohl Enums nicht wirklich in der Datenbank gespeichert werden.

Sie können jedoch bei jeder Abfrage oder Angabe von Standardeinstellungen auf das 'Label' verweisen, im Gegensatz zu der am besten bewerteten Antwort, bei der Sie den 'Wert' (der eine Zahl sein kann) verwenden müssen.

keithxm23
quelle
9

Die Einstellung choicesauf dem Feld ermöglicht eine gewisse Validierung am Django-Ende, wird dies jedoch nicht tun jede Form eines Aufzählungstypen auf der Datenbank Ende definieren.

Wie andere bereits erwähnt haben, besteht die Lösung darin, anzugeben db_type in einem benutzerdefinierten Feld .

Wenn Sie ein SQL-Backend (z. B. MySQL) verwenden, können Sie dies folgendermaßen tun:

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)

Führen syncdbSie die Tabelle aus und überprüfen Sie sie, um festzustellen, ob die Tabelle ENUMordnungsgemäß erstellt wurde.

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
quelle
Sehr hilfreiche Antwort! Dies funktioniert jedoch nicht für PostgreSQL. Der Grund dafür ist, dass PostgreSQL ENUM die Standardeinstellungen nicht unterstützt. In PostgreSQL müssen wir zuerst CREATE DOMAIN oder CREATE TYPE erstellen. Reff 8.7. Aufgezählte Typen Ich habe @ Davids Trick ausprobiert und es funktioniert gut mit MySQL, aber in PostgrSQL endet die Arbeit mit einem Fehler 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Grijesh Chauhan
3

Derzeit gibt es zwei Github-Projekte, die auf dem Hinzufügen dieser Projekte basieren, obwohl ich nicht genau untersucht habe, wie sie implementiert sind:

  1. Django-EnumField :
    Stellt ein Aufzählungs- (unter Verwendung von IntegerField) mit wiederverwendbaren Aufzählungen und Übergangsvalidierung bereit.
  2. Django-EnumFields : Mit
    diesem Paket können Sie echte Python-Enums (PEP435-Stil) mit Django verwenden.

Ich glaube auch nicht, dass DB-Aufzählungstypen verwendet werden, aber sie sind für den ersten in Arbeit .

Pureferret
quelle
1

Django 3.0 bietet integrierte Unterstützung für Enums

Aus der Dokumentation :

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

Beachten Sie nun, dass die Auswahl auf Datenbankebene nicht erzwungen wird. Dies ist nur ein Python-Konstrukt. Wenn Sie diesen Wert auch in der Datenbank erzwingen möchten, können Sie dies mit Datenbankeinschränkungen kombinieren:

class Student(models.Model):
    ...

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

Fügen Sie oben in Ihrer Datei models.py diese Zeile hinzu, nachdem Sie Ihre Importe durchgeführt haben:

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