Jak mam się zabrać do określania i używania ENUM w modelu Django?
Jak mam się zabrać do określania i używania ENUM w modelu Django?
Odpowiedzi:
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'),
)
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) )
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:
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.
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ą).
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 | |
+--------+-----------------------------+------+-----+---------+----------------+
'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Jeśli naprawdę chcesz używać swoich baz danych typu ENUM:
Powodzenia!
Obecnie istnieją dwa projekty na githubie oparte na dodaniu tych, chociaż nie przyjrzałem się dokładnie, jak są one zaimplementowane:
Nie sądzę, aby używać typów wyliczeń DB, ale są one w przygotowaniu do pierwszego.
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")
]