django soyut modelleri normal kalıtıma karşı


87

Sözdiziminin yanı sıra, bir django soyut modeli kullanmakla django modelleriyle düz Python kalıtımı kullanmak arasındaki fark nedir? Lehte ve aleyhte olanlar?

GÜNCELLEME: Sanırım sorum yanlış anlaşıldı ve soyut bir model ile django.db.models.Model'den devralan bir sınıf arasındaki fark için yanıtlar aldım. Aslında django soyut sınıfından (Meta: abstract = True) miras alan bir model sınıfı ile örneğin 'nesne'den (models.Model değil) miras alan düz bir Python sınıfı arasındaki farkı bilmek istiyorum.

İşte bir örnek:

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

1
Bu, iki miras yaklaşımının kullanılması arasındaki değiş tokuşlara harika bir genel bakış charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Yanıtlar:


155

Aslında bir django soyut sınıfından (Meta: abstract = True) miras alan bir model sınıfı ile örneğin 'nesne'den (models.Model değil) miras alan düz bir Python sınıfı arasındaki farkı bilmek istiyorum.

Django yalnızca alt sınıfları için tablolar oluşturacaktır models.Model, bu nedenle eski ...

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)

... satırları boyunca tek bir tablonun oluşturulmasına neden olur ...

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

... oysa ikincisi ...

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)

... herhangi bir tablonun oluşturulmasına neden olmaz.

Bunun gibi bir şey yapmak için çoklu mirası kullanabilirsiniz ...

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)

... bu bir tablo oluşturur, ancak Usersınıfta tanımlanan alanları yok sayar , böylece böyle bir tablo elde edersiniz ...

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

3
Teşekkürler, sorumun cevabı bu. First_name'in Çalışan için neden oluşturulmadığı hala belirsiz.
rpq

4
@rpq Django'nun kaynak kodunu kontrol etmeniz gerekir, ancak IIRC içinde bazı meta models.Modelsınıf korsanlığı kullanır , bu nedenle üst sınıfta tanımlanan alanlar alınmaz (aynı zamanda alt sınıfları olmadıkça models.Model).
Aya

1
@rpq Bunun 7 yıl önce sorduğunuzda olduğunu biliyorum, ancak ModelBase'ın __new__çağrısında, sınıfın özniteliklerinin ilk kontrolünü gerçekleştirir, öznitelik değerinin bir contribute_to_classözniteliğe sahip olup olmadığını kontrol eder - FieldsDjango'da tanımladığınız gibi, bu yüzden onlar contributable_attrsDDL'yi oluşturmak için nihai olarak taşıma sırasında aktarılan özel bir sözlüğe dahil edilir .
Yu Chen

2
DJANGO'YA HER BAKIŞTA Sadece ağlamak istiyorum, neden ????? neden tüm bu saçmalıklar, neden çerçeve dilden tamamen farklı bir şekilde davranmak zorunda? En az şaşkınlık ilkesi ne olacak? Bu davranış (django'daki NEREDEYSE diğer her şey gibi) python'dan anlayanların bekleyeceği şey değildir.
E.Serra

36

Soyut bir model, her bir alt çocuk için tüm sütun setini içeren bir tablo oluştururken, "düz" Python kalıtımını kullanmak bir dizi bağlantılı tablo oluşturur ("çoklu tablo mirası" olarak da bilinir). İki modelinizin olduğu durumu düşünün:

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


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

Eğer Vehiclesoyut bir modeldir, tek bir tablo olur:

app_car:
| id | num_wheels | make | year

Ancak, düz Python kalıtımı kullanıyorsanız, iki tablonuz olur:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

vehicle_idBir sıraya bağlantı nerede , app_vehicleaynı zamanda arabanın tekerlek sayısı da olacaktır.

Şimdi, Django bunu nesne biçiminde güzelce bir araya getirecek, böylece num_wheelsbir öznitelik olarak erişebilirsiniz Car, ancak veritabanındaki temel temsil farklı olacaktır.


Güncelleme

Güncellenen sorunuza değinmek için, bir Django soyut sınıfından miras almak ile Python'dan miras almak arasındaki fark, ilkinin objectbir veritabanı nesnesi olarak ele alınması (bu nedenle tablolar veritabanına senkronize edilir) ve bir Model. Düz bir Python'dan miras almak object, sınıfa (ve alt sınıflarına) bu niteliklerin hiçbirini vermez.


1
Bunu yapmak models.OneToOneField(Vehicle), bir model sınıfını miras almaya eşdeğerdir, değil mi? Ve bu iki ayrı tabloyla sonuçlanır, değil mi?
Rafay

1
@MohammadRafayAleem: Evet, ancak kalıtımı kullanırken, Django, örneğin, num_wheelsa üzerinde bir öznitelik oluşturacaktır car, oysa a ile OneToOneFieldbu dereferansı kendiniz yapmanız gerekecektir.
mipadi

Düzenli kalıtımla , işaretçiye sahip olmak yerine alana sahip olması için tüm alanların app_cartablo için yeniden oluşturulmasını nasıl zorlayabilirim ? num_wheelsvehicle_id
Don Grem

@DorinGrecu: Temel sınıfı soyut bir model yapın ve bundan miras alın.
mipadi

@mipadi problem şu ki temel sınıfı soyut yapamıyorum, projenin her yerinde kullanılıyor.
Don Grem

13

Temel fark, modeller için veritabanı tablolarının nasıl oluşturulduğudur. abstract = TrueDjango olmadan kalıtım kullanırsanız , her modelde tanımlanan alanları tutan hem ana hem de alt model için ayrı bir tablo oluşturur.

abstract = TrueTemel sınıf için kullanırsanız , Django yalnızca temel sınıftan miras alan sınıflar için bir tablo oluşturacaktır - alanlar ister temel sınıfta ister miras alan sınıfta tanımlanmış olsun.

Artıları ve eksileri, uygulamanızın mimarisine bağlıdır. Aşağıdaki örnek modeller göz önüne alındığında:

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

Eğer Publishablesınıf soyut değil Django sütunlarla publishables bir tablo yaratacak titleve dateve için ayrı tablolarda BlogEntryve Image. Bu çözümün avantajı , blog girişleri veya görüntüler olsun, temel modelde tanımlanan alanlar için tüm yayınlanabilir dosyalar arasında sorgulama yapabilmenizdir . Ancak bu nedenle, örneğin resimler için sorgular yaparsanız, Django birleştirme yapmak zorunda Publishable abstract = Truekalacaktır ... Django yapmak için bir tablo oluşturmaz Publishable, ancak yalnızca blog girişleri ve tüm alanları içeren (miras alınanları da) içeren görüntüler için bir tablo oluşturur . Bu kullanışlı olacaktır çünkü get gibi bir işlem için birleştirme gerekmeyecektir.

Ayrıca Django'nun model kalıtımına ilişkin belgelerine bakın .


9

Diğer cevaplarda görmediğim bir şeyi eklemek istedim.

Python sınıflarından farklı olarak, model kalıtımıyla alan adı gizlemeye izin verilmez .

Örneğin, bir kullanım senaryosu ile ilgili sorunları aşağıdaki gibi denedim:

Django'nun PermissionMixin yetkisinden miras alan bir modelim vardı :

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

    # ...

Sonra diğer şeylerin yanı sıra bunun geçersiz kılmak istedim benim mixin vardı related_nameait groupsalanda. Yani aşağı yukarı şöyle:

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

Bu 2 mixini şu şekilde kullanıyordum:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Yani evet, bunun işe yaramasını bekliyordum ama olmadı. Ancak sorun daha ciddiydi çünkü aldığım hata modellere hiç işaret etmiyordu, neyin yanlış gittiğine dair hiçbir fikrim yoktu.

Bunu çözmeye çalışırken rastgele mixinimi değiştirmeye ve onu soyut bir model karışımına dönüştürmeye karar verdim. Hata şu şekilde değişti:

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

Gördüğünüz gibi, bu hata neler olup bittiğini açıklıyor.

Bu bence çok büyük bir farktı :)


Buradaki ana soruyla ilgili olmayan bir sorum var ama sonunda bunu (grupların related_name değerini geçersiz kılmak için) nasıl uyguladınız?
kalo

@kalo IIRC Adı tamamen başka bir şeye değiştirdim. Devralınan alanları kaldırmanın veya değiştirmenin bir yolu yoktur.
Adrián

Evet, Contrib_to_class yöntemini kullanarak bunu yapmanın bir yolunu bulduğumda, bundan vazgeçmeye neredeyse hazırdım.
kalo

1

Temel fark, User sınıfını miras aldığınız zamandır. Bir sürüm basit bir sınıf gibi davranacak ve diğeri bir Django modeli gibi davranacaktır.

Temel "nesne" sürümünü devralırsanız, Çalışan sınıfınız yalnızca standart bir sınıf olacaktır ve ad_adı bir veritabanı tablosunun parçası olmayacaktır. Onunla bir form oluşturamaz veya başka herhangi bir Django özelliğini kullanamazsınız.

Models.Model sürümünü devralırsanız, Employee sınıfınız bir Django Modelinin tüm yöntemlerine sahip olacak ve first_name alanını bir formda kullanılabilecek bir veritabanı alanı olarak devralacaktır.

Belgelere göre, bir Soyut Model "Python düzeyinde ortak bilgileri dışarıda bırakmanın bir yolunu sağlarken, aynı zamanda veritabanı düzeyinde her alt model için yalnızca bir veritabanı tablosu oluşturuyor."


0

Soyut sınıfı çoğu durumda tercih edeceğim çünkü ayrı bir tablo oluşturmaz ve ORM'nin veritabanında birleşimler oluşturması gerekmez. Soyut sınıfı kullanmak Django'da oldukça basit

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(...)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.