Вказання mySQL ENUM в моделі Django


92

Як я можу визначитись із використанням ENUM у моделі Django?


4
Стів, якщо ви мали намір використовувати тип MySQL ENUM, то вам не пощастило, наскільки я знаю, Django не підтримує це (ця функція доступна не у всіх БД, що підтримуються Django). Відповідь, надана Полом, працює, але вона не визначатиме тип у БД.
dguaraglia

Відповіді:


108

З документації про Джанго :

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

І ви визначаєте в своїй моделі знак поля:

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

Ви можете зробити те ж саме з цілими полями, якщо вам не подобається мати літери у db.

У такому випадку перепишіть свій вибір:

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

8
Це не заважає зберігати "помилкові" значення, якщо раніше їх не очищати, правда?
Strayer

@Strayer так, я думаю, це корисно лише для використання модельних форм
Гострий

Зауважте, що рекомендований стиль Django передбачає, що символи повинні бути константами: docs.djangoproject.com/en/dev/internals/contributing/…
Майкл

11
Як сказав @Carl Meyer у своїй відповіді, це НЕ створює стовпець ENUM у базі даних. Він створює стовпчик VARCHAR або INTEGER, тому він насправді не відповідає на питання.
Аріель

Чи можна додати функцію вибору з цілим полем? @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
Починаючи з django 1.2, вам потрібно буде додати другий параметр, connection, до db_type def.
Ганс Лоуренц,

2
Що тоді сталося з кодекалогом? Локос, як це могло б бути гарною ідеєю .... Зараз я отримую 404 - навіть для кореневої сторінки.
Danny Staple

33

Використання choicesпараметра не використовуватиме тип ENUM db; він просто створить VARCHAR або INTEGER, залежно від того, використовуєте ви choicesCharField або IntegerField. Як правило, це чудово. Якщо вам важливо, щоб тип ENUM використовувався на рівні бази даних, у вас є три варіанти:

  1. Використовуйте "./manage.py sql appname", щоб побачити, як генерується SQL Django, вручну змініть його, щоб використовувати тип ENUM, і запустіть його самостійно. Якщо спершу ви створите таблицю вручну, "./manage.py syncdb" з нею не зіпсується.
  2. Якщо ви не хочете робити це вручну кожного разу, коли створюєте свою БД, додайте власний SQL в appname / sql / modelname.sql, щоб виконати відповідну команду ALTER TABLE.
  3. Створіть власний тип поля та визначте відповідним чином метод db_type.

Будь-який із цих варіантів відповідає за наслідки переносимості міжбаз даних. У варіанті 2 ви можете використовувати власний SQL-сервер, який відповідає базовим умовам бази даних, щоб переконатися, що ваш ALTER TABLE працює лише на MySQL. У варіанті 3, вашому методу db_type потрібно буде перевірити механізм бази даних і встановити тип стовпця db на тип, який насправді існує в цій базі даних.

ОНОВЛЕННЯ : Оскільки рамка міграції була додана в Django 1.7, варіанти 1 та 2 вище повністю застаріли. Варіант 3 у будь-якому випадку завжди був найкращим варіантом. Нова версія варіантів 1/2 передбачала б складну користувацьку міграцію з використанням SeparateDatabaseAndState- але насправді ви хочете варіант 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:

Це ще один приємний та простий спосіб впровадження перерахунків, хоча він не дійсно зберігає переліків у базі даних.

Однак це дозволяє вам посилатися на "мітку", коли ви запитуєте або вказуєте за замовчуванням, на відміну від найвищого рівня відповіді, де потрібно використовувати "значення" (яке може бути числом).


9

Встановлення choicesв полі дозволить провести певну перевірку на кінці Django, але не визначить жодної форми перерахованого типу на кінці бази даних.

Як вже згадували інші, рішення полягає в тому, щоб вказати db_typeв користувальницькому полі.

Якщо ви використовуєте сервер SQL (наприклад, MySQL), ви можете зробити це так:

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)

Запустіть syncdbі огляньте свою таблицю, щоб побачити, чи ENUMбуло створено правильно.

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

Дуже корисна відповідь! Але це не працює для PostgreSQL. Причиною є те, що PostgreSQL ENUM не підтримує за замовчуванням. У PostgreSQL спочатку ми повинні створити CREATE DOMAIN або CREATE TYPE. Реф. 8.7. Перераховані типи Я спробував фокус @ David, і він чудово працює з MySQL, але в PostgrSQL робота закінчується помилкою 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Гріеш Чаухан

6

Якщо ви дійсно хочете використовувати ваші бази даних типу ENUM:

  1. Використовуйте Django 1.x
  2. Розпізнавання вашої програми працюватиме лише на деяких базах даних.
  3. Загадка на цій сторінці документації: http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

Удачі!


3

Наразі існує два проекти github, засновані на їх додаванні, хоча я не вивчив, як саме вони реалізовані:

  1. Django-EnumField :
    надає поле моделі перерахування Django (використовуючи IntegerField) з перерахунками для багаторазового використання та перевіркою переходу.
  2. Django-EnumFields :
    Цей пакет дозволяє використовувати реальні переліки Python (PEP435) з Django.

Я не думаю, що будь-який із типів перелічення DB використовує, але вони працюють над першим.


1

Django 3.0 має вбудовану підтримку для Enums

З документації :

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

Тепер майте на увазі, що він не застосовує вибір на рівні бази даних, це лише конструкція Python. Якщо ви також хочете застосувати ці значення в базі даних, ви можете поєднати це з обмеженнями в базі даних:

class Student(models.Model):
    ...

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

-2

Зверху файлу models.py, додайте цей рядок після того, як ви імпортуєте:

    enum = lambda *l: [(s,_(s)) for s in l]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.