Określanie mySQL ENUM w modelu Django


93

Jak mam się zabrać do określania i używania ENUM w modelu Django?


4
Steve, jeśli miałeś na myśli użycie typu MySQL ENUM, to nie masz szczęścia, o ile wiem, Django nie zapewnia tego wsparcia (ta funkcja nie jest dostępna we wszystkich bazach danych obsługiwanych przez Django). Odpowiedź udzielona przez Paula działa, ale nie definiuje typu w bazie danych.
dguaraglia

Odpowiedzi:


109

Z dokumentacji Django :

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

I definiujesz charfielda w swoim modelu:

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

Możesz zrobić to samo z polami liczb całkowitych, jeśli nie chcesz, aby w Twojej bazie danych znajdowały się litery.

W takim przypadku przepisz swoje wybory:

MAYBECHOICE = (
    (0, 'Yes'),
    (1, 'No'),
    (2, 'Unknown'),
)

8
Nie zapobiega to zapisywaniu „fałszywych” wartości, jeśli nie zostały one wcześniej wyczyszczone, prawda?
Strayer

@Strayer tak, myślę, że jest to przydatne tylko do korzystania z formularzy modelowych
ostry

Zwróć uwagę, że zalecany styl Django oznacza, że ​​znaki powinny być stałymi: docs.djangoproject.com/en/dev/internals/contributing/…
Michael

13
Jak powiedział @Carl Meyer w swojej odpowiedzi, NIE tworzy to kolumny ENUM w bazie danych. Tworzy kolumnę VARCHAR lub INTEGER, więc tak naprawdę nie odpowiada na pytanie.
Ariel

Czy mogę dodać funkcję wyborów z polem liczby całkowitej? @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
Od wersji django 1.2 będziesz musiał dodać drugi parametr, connection, do parametru db_type def.
Hans Lawrenz,

2
Co się wtedy stało z kodekatelogiem? Takie lokosy mogłyby być dobrym pomysłem… Dostaję teraz 404 - nawet dla strony głównej.
Danny Staple

33

Użycie choicesparametru nie spowoduje użycia typu ENUM db; po prostu utworzy VARCHAR lub INTEGER, w zależności od tego, czy używasz choicesz CharField czy IntegerField. Ogólnie jest to w porządku. Jeśli jest dla Ciebie ważne, aby typ ENUM był używany na poziomie bazy danych, masz trzy opcje:

  1. Użyj "./manage.py sql appname", aby zobaczyć, jak SQL Django generuje, ręcznie zmodyfikuj go tak, aby używał typu ENUM i uruchom go samodzielnie. Jeśli najpierw utworzysz tabelę ręcznie, „./manage.py syncdb” nie zadziała z nią.
  2. Jeśli nie chcesz robić tego ręcznie za każdym razem, gdy generujesz swoją bazę danych, umieść niestandardowy kod SQL w pliku nazwa_aplikacji / sql / modelname.sql, aby wykonać odpowiednie polecenie ALTER TABLE.
  3. Utwórz niestandardowy typ pola i odpowiednio zdefiniuj metodę db_type.

W przypadku każdej z tych opcji do Twojej odpowiedzialności należałoby zajęcie się implikacjami dla przenoszenia między bazami danych. W opcji 2 możesz użyć niestandardowego SQL specyficznego dla zaplecza bazy danych, aby upewnić się, że ALTER TABLE działa tylko w MySQL. W opcji 3 metoda db_type musiałaby sprawdzić silnik bazy danych i ustawić typ kolumny db na typ, który faktycznie istnieje w tej bazie danych.

AKTUALIZACJA : Ponieważ ramy migracji zostały dodane w Django 1.7, powyższe opcje 1 i 2 są całkowicie przestarzałe. Opcja 3 i tak była zawsze najlepsza. Nowa wersja opcji 1/2 wymagałaby złożonej migracji niestandardowej przy użyciu SeparateDatabaseAndState- ale naprawdę potrzebujesz opcji 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:

Jest to kolejny przyjemny i łatwy sposób implementacji wyliczeń, chociaż tak naprawdę nie zapisuje wyliczeń w bazie danych.

Pozwala jednak na odwoływanie się do „etykiety” przy każdym zapytaniu lub określaniu wartości domyślnych, w przeciwieństwie do najwyżej ocenianej odpowiedzi, w przypadku której należy użyć „wartości” (która może być liczbą).


9

Ustawienie choicesw tym polu pozwoli na pewną walidację po stronie Django, ale nie będzie definiować żadnej formy wyliczonego typu na końcu bazy danych.

Jak wspominali inni, rozwiązaniem jest określenie db_typew niestandardowym polu.

Jeśli używasz zaplecza SQL (np. MySQL), możesz to zrobić w następujący sposób:

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)

Uruchom syncdbi sprawdź tabelę, aby zobaczyć, czy ENUMplik został utworzony poprawnie.

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    |                |
+--------+-----------------------------+------+-----+---------+----------------+

Bardzo pomocna odpowiedź! Ale to nie zadziała w przypadku PostgreSQL. Powodem jest to, że PostgreSQL ENUM nie obsługuje wartości domyślnych. W PostgreSQL najpierw musimy stworzyć CREATE DOMAIN lub CREATE TYPE. Por . 8.7. Wyliczone typy Wypróbowałem sztuczkę @ Davida i działa dobrze z MySQL, ale w PostgrSQL praca kończy się błędem 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Grijesh Chauhan


3

Obecnie istnieją dwa projekty na githubie oparte na dodaniu tych, chociaż nie przyjrzałem się dokładnie, jak są one zaimplementowane:

  1. Django-EnumField :
    zapewnia pole modelu wyliczenia Django (przy użyciu IntegerField) z wyliczeniami wielokrotnego użytku i walidacją przejścia.
  2. Django-EnumFields :
    Ten pakiet umożliwia używanie prawdziwych wyliczeń Pythona (w stylu PEP435) z Django.

Nie sądzę, aby używać typów wyliczeń DB, ale są one w przygotowaniu do pierwszego.


1

Django 3.0 ma wbudowaną obsługę Enum

Z dokumentacji :

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

Teraz pamiętaj, że nie wymusza wyborów na poziomie bazy danych, to jest konstrukcja tylko Pythona. Jeśli chcesz wymusić również te wartości w bazie danych, możesz to połączyć z ograniczeniami bazy danych:

class Student(models.Model):
    ...

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

-2

U góry pliku models.py dodaj ten wiersz po przeprowadzeniu importu:

    enum = lambda *l: [(s,_(s)) for s in l]
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.