Menentukan mySQL ENUM dalam model Django

92

Bagaimana saya pergi tentang menentukan dan menggunakan ENUM dalam model Django?

Steve
sumber
4
Steve, jika Anda bermaksud menggunakan tipe MySQL ENUM, maka Anda kurang beruntung, sejauh yang saya tahu Django tidak menyediakan dukungan untuk itu (fitur itu tidak tersedia di semua DB yang didukung oleh Django). Jawaban yang diberikan oleh Paul berfungsi, tetapi itu tidak akan menentukan tipe di DB.
dguaraglia

Jawaban:

108

Dari dokumentasi Django :

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

Dan Anda menentukan charfield dalam model Anda:

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

Anda dapat melakukan hal yang sama dengan bidang integer jika Anda tidak ingin memiliki huruf di db Anda.

Jika demikian, tulis ulang pilihan Anda:

MAYBECHOICE = (
    (0, 'Yes'),
    (1, 'No'),
    (2, 'Unknown'),
)
fulmicoton
sumber
8
Ini tidak mencegah nilai "false" disimpan jika tidak dibersihkan sebelumnya, bukan?
Strayer
@Strayer ya, saya rasa ini berguna hanya untuk menggunakan formulir model
Akut
Perhatikan bahwa gaya Django yang direkomendasikan menyiratkan bahwa karakter haruslah konstanta: docs.djangoproject.com/en/dev/internals/contributing/…
Michael Scheper
12
Seperti yang dikatakan @Carl Meyer dalam jawabannya, ini TIDAK membuat kolom ENUM di database. Ini membuat kolom VARCHAR atau INTEGER, jadi tidak benar-benar menjawab pertanyaan.
Ariel
Bisakah saya menambahkan fitur pilihan dengan bidang 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) )
pengguna185534
sumber
2
Pada django 1.2, anda perlu menambahkan parameter kedua, koneksi, ke db_type def.
Hans Lawrenz
2
Lalu apa yang terjadi dengan codecatelog? Lokos seperti itu bisa menjadi ide yang bagus .... Saya mendapatkan 404 sekarang - bahkan untuk halaman root.
Danny Staple
33

Menggunakan choicesparameter tidak akan menggunakan tipe ENUM db; itu hanya akan membuat VARCHAR atau INTEGER, tergantung pada apakah Anda menggunakan choicesCharField atau IntegerField. Secara umum, ini baik-baik saja. Jika penting bagi Anda bahwa tipe ENUM digunakan di tingkat database, Anda memiliki tiga opsi:

  1. Gunakan "./manage.py sql appname" untuk melihat SQL yang dihasilkan Django, ubah secara manual untuk menggunakan tipe ENUM, dan jalankan sendiri. Jika Anda membuat tabel secara manual terlebih dahulu, "./manage.py syncdb" tidak akan mengacaukannya.
  2. Jika Anda tidak ingin melakukan ini secara manual setiap kali Anda membuat DB, letakkan beberapa SQL kustom di appname / sql / modelname.sql untuk melakukan perintah ALTER TABLE yang sesuai.
  3. Buat jenis bidang khusus dan tentukan metode db_type dengan tepat.

Dengan salah satu opsi ini, Anda bertanggung jawab untuk menangani implikasi portabilitas lintas database. Pada opsi 2, Anda dapat menggunakan SQL kustom khusus database-backend untuk memastikan ALTER TABLE Anda hanya berjalan di MySQL. Di opsi 3, metode db_type Anda perlu memeriksa mesin database dan menyetel jenis kolom db ke jenis yang sebenarnya ada di database itu.

PEMBARUAN : Sejak kerangka migrasi ditambahkan di Django 1.7, opsi 1 dan 2 di atas seluruhnya sudah usang. Opsi 3 selalu merupakan opsi terbaik. Versi baru dari opsi 1/2 akan melibatkan migrasi ubahsuaian yang kompleks menggunakan SeparateDatabaseAndState- tetapi sebenarnya Anda menginginkan opsi 3.

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

Ini adalah cara lain yang bagus dan mudah untuk mengimplementasikan enum meskipun tidak benar-benar menyimpan enum di database.

Namun itu memungkinkan Anda untuk mereferensikan 'label' setiap kali menanyakan atau menentukan default sebagai lawan dari jawaban berperingkat teratas di mana Anda harus menggunakan 'nilai' (yang mungkin berupa angka).

keithxm23
sumber
9

Pengaturan choicespada lapangan akan memperbolehkan beberapa validasi pada akhir Django, tetapi itu tidak akan mendefinisikan bentuk apapun dari tipe yang disebutkan pada akhir basis data.

Seperti yang disebutkan orang lain, solusinya adalah menentukan db_typepada bidang khusus.

Jika Anda menggunakan backend SQL (misalnya MySQL), Anda dapat melakukannya seperti ini:

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)

Jalankan syncdb, dan periksa tabel Anda untuk melihat bahwa tabel ENUMdibuat dengan benar.

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
sumber
Jawaban yang Sangat Bermanfaat! Tetapi ini tidak akan berfungsi untuk PostgreSQL. Alasannya adalah PostgreSQL ENUM tidak mendukung default. Di PostgreSQL pertama kita harus membuat CREATE DOMAIN atau CREATE TYPE. Reff 8.7. Jenis Enumerasi Saya mencoba trik @ David dan itu berfungsi dengan baik dengan MySQL tetapi dalam pekerjaan PostgrSQL berakhir dengan kesalahan 'type "enum" does not exist LINE 1: ....tablename" ADD COLUMN "select_user" ENUM('B', ...'.
Grijesh Chauhan
3

Saat ini ada dua proyek github berdasarkan penambahan ini, meskipun saya belum melihat secara tepat bagaimana penerapannya:

  1. Django-EnumField :
    Menyediakan bidang model pencacahan Django (menggunakan IntegerField) dengan enum yang dapat digunakan kembali dan validasi transisi.
  2. Django-EnumFields :
    Paket ini memperbolehkan Anda menggunakan enum Python (gaya PEP435) nyata dengan Django.

Saya tidak berpikir keduanya menggunakan tipe enum DB, tetapi mereka sedang dalam pengerjaan untuk yang pertama.

Pureferret
sumber
1

Django 3.0 memiliki dukungan bawaan untuk Enums

Dari dokumentasi :

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

Sekarang, ketahuilah bahwa ini tidak memaksakan pilihan pada level database, ini hanya konstruksi Python. Jika Anda juga ingin menerapkan nilai tersebut di database, Anda dapat menggabungkannya dengan batasan database:

class Student(models.Model):
    ...

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

Di bagian atas file models.py Anda, tambahkan baris ini setelah Anda mengimpor:

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