Como faço para especificar e usar um ENUM em um modelo Django?
Como faço para especificar e usar um ENUM em um modelo Django?
Respostas:
Da documentação do Django :
MAYBECHOICE = (
('y', 'Yes'),
('n', 'No'),
('u', 'Unknown'),
)
E você define um charfield em seu modelo:
married = models.CharField(max_length=1, choices=MAYBECHOICE)
Você pode fazer o mesmo com campos inteiros se não quiser ter letras em seu banco de dados.
Nesse caso, reescreva suas escolhas:
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) )
Usar o choices
parâmetro não usará o tipo de banco de dados ENUM; ele apenas criará um VARCHAR ou INTEGER, dependendo se você usar choices
com CharField ou IntegerField. Geralmente, está tudo bem. Se for importante para você que o tipo ENUM seja usado no nível do banco de dados, você tem três opções:
Com qualquer uma dessas opções, seria sua responsabilidade lidar com as implicações para a portabilidade entre bancos de dados. Na opção 2, você pode usar o SQL personalizado específico do banco de dados para garantir que ALTER TABLE seja executado apenas no MySQL. Na opção 3, seu método db_type precisaria verificar o mecanismo de banco de dados e definir o tipo de coluna db para um tipo que realmente exista nesse banco de dados.
ATUALIZAÇÃO : Como a estrutura de migrações foi adicionada ao Django 1.7, as opções 1 e 2 acima estão totalmente obsoletas. A opção 3 sempre foi a melhor opção de qualquer maneira. A nova versão das opções 1/2 envolveria uma migração personalizada complexa usando SeparateDatabaseAndState
- mas você realmente quer a opção 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:
Esta é outra maneira fácil e agradável de implementar enums, embora não salve realmente enums no banco de dados.
No entanto, permite que você faça referência ao 'rótulo' sempre que consultar ou especificar padrões, ao contrário da resposta com melhor classificação, onde você deve usar o 'valor' (que pode ser um número).
A configuração choices
no campo permitirá alguma validação na extremidade do Django, mas não definirá nenhuma forma de um tipo enumerado na extremidade do banco de dados.
Como outros mencionaram, a solução é especificar db_type
em um campo personalizado.
Se estiver usando um back-end SQL (por exemplo, MySQL), você pode fazer isso da seguinte maneira:
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)
Execute syncdb
e inspecione sua tabela para ver se o ENUM
foi criado corretamente.
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', ...'
.
Se você realmente deseja usar seus bancos de dados tipo ENUM:
Boa sorte!
Atualmente, existem dois projetos github baseados na adição desses, embora eu não tenha examinado exatamente como eles são implementados:
Acho que nenhum dos dois usa tipos de enum DB, mas eles estão em andamento para o primeiro.
Da documentação :
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,
)
Agora, esteja ciente de que ele não impõe as opções em um nível de banco de dados, esta é uma construção apenas do Python. Se você também deseja impor esses valores no banco de dados, pode combinar isso com as restrições do banco de dados:
class Student(models.Model):
...
class Meta:
constraints = [
CheckConstraint(
check=Q(year_in_school__in=YearInSchool.values),
name="valid_year_in_school")
]