Especificando um mySQL ENUM em um modelo Django

92

Como faço para especificar e usar um ENUM em um modelo Django?

Steve
fonte
4
Steve, se você quisesse usar o tipo ENUM MySQL, então você está sem sorte, pelo que eu sei o Django não fornece suporte para isso (esse recurso não está disponível em todos os bancos de dados suportados pelo Django). A resposta fornecida por Paul funciona, mas não definirá o tipo no DB.
dguaraglia de

Respostas:

108

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'),
)
fulmicoton
fonte
8
Isso não impede que valores "falsos" sejam salvos se não forem limpos antes, não é?
Strayer
@Strayer sim, acho que isso é útil apenas para usar formulários de modelo
agudo de
Observe que o estilo Django recomendado implica que os caracteres devem ser constantes: docs.djangoproject.com/en/dev/internals/contributing/…
Michael Scheper
11
Como @Carl Meyer disse em sua resposta, isso NÃO cria uma coluna ENUM no banco de dados. Ele cria uma coluna VARCHAR ou INTEGER, portanto, não responde realmente à pergunta.
Ariel de
Posso adicionar recursos de opções com campo inteiro? @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
fonte
2
A partir do django 1.2, você precisará adicionar um segundo parâmetro, conexão, ao db_type def.
Hans Lawrenz
2
O que aconteceu com codecatelog então? Lokos como se pudesse ter sido uma boa ideia ... Eu recebo um 404 agora - até mesmo para a página raiz.
Danny Staple
33

Usar o choicesparâmetro não usará o tipo de banco de dados ENUM; ele apenas criará um VARCHAR ou INTEGER, dependendo se você usar choicescom 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:

  1. Use "./manage.py sql appname" para ver o SQL gerado pelo Django, modifique-o manualmente para usar o tipo ENUM e execute-o você mesmo. Se você criar a tabela manualmente primeiro, "./manage.py syncdb" não vai mexer com ela.
  2. Se você não quiser fazer isso manualmente toda vez que gerar seu banco de dados, coloque algum SQL personalizado em appname / sql / modelname.sql para executar o comando ALTER TABLE apropriado.
  3. Crie um tipo de campo customizado e defina o método db_type apropriadamente.

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.

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

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

keithxm23
fonte
9

A configuração choicesno 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_typeem 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 syncdbe inspecione sua tabela para ver se o ENUMfoi 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    |                |
+--------+-----------------------------+------+-----+---------+----------------+
David Cain
fonte
Resposta muito útil! Mas isso não funcionará para PostgreSQL. A razão é que PostgreSQL ENUM não suporta o padrão. No PostgreSQL, primeiro temos que criar CREATE DOMAIN ou CREATE TYPE. Reff 8.7. Tipos enumerados Eu tentei o truque de @David e está funcionando bem com o MySQL, mas no PostgrSQL o trabalho acabou com um erro 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Grijesh Chauhan
3

Atualmente, existem dois projetos github baseados na adição desses, embora eu não tenha examinado exatamente como eles são implementados:

  1. Django-EnumField :
    Fornece um campo de modelo Django de enumeração (usando IntegerField) com enums reutilizáveis ​​e validação de transição.
  2. Django-EnumFields :
    Este pacote permite que você use enums reais do Python (estilo PEP435) com Django.

Acho que nenhum dos dois usa tipos de enum DB, mas eles estão em andamento para o primeiro.

Pureferret
fonte
1

Django 3.0 tem suporte integrado para Enums

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

No início de seu arquivo models.py, adicione esta linha depois de fazer suas importações:

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