model abstrak django versus pewarisan biasa

87

Selain sintaks, apa perbedaan antara menggunakan model abstrak django dan menggunakan warisan Python biasa dengan model django? Pro dan kontra?

PEMBARUAN: Saya pikir pertanyaan saya disalahpahami dan saya menerima tanggapan atas perbedaan antara model abstrak dan kelas yang mewarisi dari django.db.models.Model. Saya sebenarnya ingin mengetahui perbedaan antara kelas model yang mewarisi dari kelas abstrak django (Meta: abstract = True) dan kelas Python biasa yang mewarisi dari katakanlah, 'object' (dan bukan models.Model).

Berikut ini contohnya:

class User(object):
   first_name = models.CharField(..

   def get_username(self):
       return self.username

class User(models.Model):
   first_name = models.CharField(...

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(...
rpq
sumber
1
Ini adalah ikhtisar yang bagus tentang trade-off antara menggunakan dua pendekatan warisan charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Jawaban:

155

Saya sebenarnya ingin mengetahui perbedaan antara kelas model yang mewarisi dari kelas abstrak django (Meta: abstract = True) dan kelas Python biasa yang mewarisi dari katakanlah, 'object' (dan bukan models.Model).

Django hanya akan menghasilkan tabel untuk subkelas models.Model, jadi yang pertama ...

class User(models.Model):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(max_length=255)

... akan menyebabkan satu tabel dibuat, di sepanjang baris ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(255) NOT NULL,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

... sedangkan yang terakhir ...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User):
   title = models.CharField(max_length=255)

... tidak akan menyebabkan tabel apa pun dibuat.

Anda dapat menggunakan banyak warisan untuk melakukan sesuatu seperti ini ...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User, models.Model):
   title = models.CharField(max_length=255)

... yang akan membuat tabel, tetapi akan mengabaikan bidang yang ditentukan di Userkelas, jadi Anda akan mendapatkan tabel seperti ini ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);
Aya
sumber
3
Terima kasih, ini menjawab pertanyaan saya. Masih belum jelas mengapa first_name tidak dibuat untuk Karyawan.
rpq
4
@rpq Anda harus memeriksa kode sumber Django, tetapi IIRC menggunakan beberapa peretasan metaclass models.Model, jadi bidang yang ditentukan dalam superclass tidak akan diambil (kecuali mereka juga subkelas dari models.Model).
Aya
1
@rpq Saya tahu ini adalah 7 tahun yang lalu ketika Anda bertanya, tapi di ModelBase's __new__panggilan, ia melakukan pemeriksaan awal kelas' atribut, memeriksa apakah nilai atribut memiliki contribute_to_classatribut - yang Fieldsyang Anda tetapkan di Django semua lakukan, sehingga mereka disertakan dalam kamus khusus yang disebut contributable_attrsyang pada akhirnya diteruskan selama migrasi untuk menghasilkan DDL.
Yu Chen
2
SETIAP KALI AKU MELIHAT DJANGO Aku hanya ingin menangis, kenapa ????? mengapa semua omong kosong, mengapa kerangka harus berperilaku dengan cara yang sama sekali berbeda dari bahasa? Bagaimana dengan asas paling tidak heran? Perilaku ini (seperti HAMPIR semua hal lain di django) bukanlah apa yang diharapkan oleh siapapun yang mengerti python.
E. Serra
36

Model abstrak membuat tabel dengan seluruh kumpulan kolom untuk setiap sub-anak, sedangkan menggunakan pewarisan Python "biasa" membuat satu set tabel tertaut (alias "warisan multi-tabel"). Pertimbangkan kasus di mana Anda memiliki dua model:

class Vehicle(models.Model):
  num_wheels = models.PositiveIntegerField()


class Car(Vehicle):
  make = models.CharField(…)
  year = models.PositiveIntegerField()

Jika Vehiclemodel abstrak, Anda akan memiliki satu tabel:

app_car:
| id | num_wheels | make | year

Namun, jika Anda menggunakan warisan Python biasa, Anda akan memiliki dua tabel:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

Di mana vehicle_idada tautan ke baris app_vehicleyang juga memiliki jumlah roda untuk mobil tersebut.

Sekarang, Django akan meletakkan ini bersama-sama dengan baik dalam bentuk objek sehingga Anda dapat mengakses num_wheelssebagai atribut pada Car, tetapi representasi yang mendasari dalam database akan berbeda.


Memperbarui

Untuk menjawab pertanyaan Anda yang diperbarui, perbedaan antara mewarisi dari kelas abstrak Django dan mewarisi dari Python objectadalah yang pertama diperlakukan sebagai objek database (jadi tabel untuk itu disinkronkan ke database) dan memiliki perilaku a Model. Mewarisi dari Python biasa tidak objectmemberikan kelas (dan subkelasnya) kualitas tersebut.

mipadi
sumber
1
Melakukannya models.OneToOneField(Vehicle)juga akan setara dengan mewarisi kelas model, bukan? Dan itu akan menghasilkan dua tabel terpisah, bukan?
Rafay
1
@MohammadRafayAleem: Ya, tetapi ketika menggunakan warisan, Django akan membuat, misalnya, num_wheelsatribut pada a car, sedangkan dengan a OneToOneFieldAnda harus melakukan dereferensi itu sendiri.
mipadi
Bagaimana saya bisa memaksa dengan warisan biasa bahwa semua bidang dibuat ulang untuk app_cartabel sehingga juga memiliki num_wheelsbidang, alih-alih memiliki vehicle_idpenunjuk?
Don Grem
@DorinGrecu: Jadikan kelas dasar sebagai model abstrak, dan warisan dari itu.
mipadi
@mipadi masalahnya adalah saya tidak bisa membuat kelas dasar abstrak, itu digunakan di seluruh proyek.
Don Grem
13

Perbedaan utamanya adalah bagaimana tabel database untuk model dibuat. Jika Anda menggunakan pewarisan tanpa abstract = TrueDjango akan membuat tabel terpisah untuk model induk dan anak yang menampung bidang yang ditentukan dalam setiap model.

Jika Anda menggunakan abstract = Trueuntuk kelas dasar Django hanya akan membuat tabel untuk kelas yang mewarisi dari kelas dasar - tidak peduli apakah bidang ditentukan dalam kelas dasar atau kelas yang mewarisi.

Pro dan kontra bergantung pada arsitektur aplikasi Anda. Diberikan model contoh berikut:

class Publishable(models.Model):
    title = models.CharField(...)
    date = models.DateField(....)

    class Meta:
        # abstract = True

class BlogEntry(Publishable):
    text = models.TextField()


class Image(Publishable):
    image = models.ImageField(...)

Jika Publishablekelas bukan abstrak Django akan membuat tabel untuk terbitan dengan kolom titledan datedan tabel terpisah untuk BlogEntrydan Image. Keuntungan dari solusi ini adalah Anda dapat membuat kueri di semua yang dapat dipublikasikan untuk bidang yang ditentukan dalam model dasar, tidak peduli apakah itu entri blog atau gambar. Tetapi oleh karena itu Django harus melakukan penggabungan jika anda misalnya melakukan kueri untuk gambar ... Jika membuat Publishable abstract = TrueDjango tidak akan membuat tabel untuk Publishable, tetapi hanya untuk entri blog dan gambar, berisi semua bidang (juga yang diwariskan). Ini akan berguna karena tidak ada gabungan yang diperlukan untuk operasi seperti get.

Juga lihat dokumentasi Django tentang pewarisan model .

Bernhard Vallant
sumber
9

Hanya ingin menambahkan sesuatu yang belum pernah saya lihat di jawaban lain.

Berbeda dengan kelas python, penyembunyian nama bidang tidak diizinkan dengan pewarisan model.

Misalnya, saya telah bereksperimen dengan kasus penggunaan sebagai berikut:

Saya memiliki model yang diwarisi dari PermissionMixin auth django :

class PermissionsMixin(models.Model):
    """
    A mixin class that adds the fields and methods necessary to support
    Django's Group and Permission model using the ModelBackend.
    """
    is_superuser = models.BooleanField(_('superuser status'), default=False,
        help_text=_('Designates that this user has all permissions without '
                    'explicitly assigning them.'))
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        blank=True, help_text=_('The groups this user belongs to. A user will '
                                'get all permissions granted to each of '
                                'his/her group.'))
    user_permissions = models.ManyToManyField(Permission,
        verbose_name=_('user permissions'), blank=True,
        help_text='Specific permissions for this user.')

    class Meta:
        abstract = True

    # ...

Kemudian aku mixin saya yang antara lain Aku ingin menimpa related_namedari groupslapangan. Jadi kurang lebih seperti ini:

class WithManagedGroupMixin(object):
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        related_name="%(app_label)s_%(class)s",
        blank=True, help_text=_('The groups this user belongs to. A user will '
                            'get all permissions granted to each of '
                            'his/her group.'))

Saya menggunakan 2 mixin ini sebagai berikut:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Jadi ya, saya berharap ini berhasil tetapi ternyata tidak. Tetapi masalahnya lebih serius karena kesalahan yang saya dapatkan sama sekali tidak mengarah ke model, saya tidak tahu apa yang salah.

Ketika mencoba menyelesaikan ini, saya secara acak memutuskan untuk mengubah mixin saya dan mengubahnya menjadi mixin model abstrak. Kesalahan berubah menjadi ini:

django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'

Seperti yang Anda lihat, kesalahan ini menjelaskan apa yang sedang terjadi.

Ini adalah perbedaan yang sangat besar, menurut saya :)

Adrián
sumber
Saya memiliki pertanyaan yang tidak terkait dengan pertanyaan utama di sini, tetapi bagaimana Anda berhasil menerapkan ini (untuk menimpa bidang_kelompok terkait) di akhir?
kalo
@kalo IIRC Saya baru saja mengubah nama menjadi sesuatu yang sama sekali berbeda. Tidak ada cara untuk menghapus atau mengganti bidang yang diwariskan.
Adrián
Ya, saya hampir siap untuk menyerah dalam hal ini, ketika saya menemukan cara untuk melakukannya dengan menggunakan metode kontribusi_ke_kelas.
kalo
1

Perbedaan utamanya adalah ketika Anda mewarisi kelas Pengguna. Satu versi akan berperilaku seperti kelas sederhana, dan yang lainnya akan berperilaku seperti model Django.

Jika Anda mewarisi versi "objek" dasar, kelas Karyawan Anda hanya akan menjadi kelas standar, dan first_name tidak akan menjadi bagian dari tabel database. Anda tidak dapat membuat formulir atau menggunakan fitur Django lainnya dengannya.

Jika Anda mewarisi versi models.Model, kelas Karyawan Anda akan memiliki semua metode Model Django , dan ini akan mewarisi bidang nama_pertama sebagai bidang basis data yang dapat digunakan dalam formulir.

Menurut dokumentasi, Model Abstrak "menyediakan cara untuk memfaktorkan informasi umum di tingkat Python, sementara masih hanya membuat satu tabel database per model anak di tingkat database."

Brent Washburne
sumber
0

Saya lebih suka kelas abstrak di sebagian besar kasus karena tidak membuat tabel terpisah dan ORM tidak perlu membuat gabungan dalam database. Dan menggunakan kelas abstrak cukup sederhana di Django

class Vehicle(models.Model):
    title = models.CharField(...)
    Name = models.CharField(....)

    class Meta:
         abstract = True

class Car(Vehicle):
    color = models.CharField()

class Bike(Vehicle):
    feul_average = models.IntegerField(...)
Kamran Hasan
sumber