Abstrakcyjne modele django a zwykłe dziedziczenie

86

Poza składnią, jaka jest różnica między używaniem abstrakcyjnego modelu django a używaniem zwykłego dziedziczenia w Pythonie z modelami django? Plusy i minusy?

AKTUALIZACJA: Myślę, że moje pytanie zostało źle zrozumiane i otrzymałem odpowiedzi dotyczące różnicy między modelem abstrakcyjnym a klasą, która dziedziczy z django.db.models.Model. Właściwie chcę poznać różnicę między klasą modelu, która dziedziczy z klasy abstrakcyjnej django (Meta: abstract = True), a zwykłą klasą Pythona, która dziedziczy powiedzmy po „obiekcie” (a nie modelach. Model).

Oto przykład:

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
źródło
1
To jest świetny przegląd kompromisów między używaniem dwóch podejść do dziedziczenia charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Odpowiedzi:

152

Właściwie chcę poznać różnicę między klasą modelu, która dziedziczy z klasy abstrakcyjnej django (Meta: abstract = True), a zwykłą klasą Pythona, która dziedziczy powiedzmy po „obiekcie” (a nie modelach. Model).

Django wygeneruje tabele tylko dla podklas models.Model, więc poprzednie ...

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)

... spowoduje wygenerowanie pojedynczej tabeli, wzdłuż linii ...

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

... podczas gdy ten ostatni ...

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)

... nie spowoduje wygenerowania żadnych tabel.

Możesz użyć wielokrotnego dziedziczenia, aby zrobić coś takiego ...

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)

... co spowoduje utworzenie tabeli, ale zignoruje pola zdefiniowane w Userklasie, więc otrzymasz taką tabelę ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);
Aya
źródło
3
Dzięki, to odpowiada na moje pytanie. Nadal nie jest jasne, dlaczego first_name nie jest tworzone dla pracownika.
rpq
4
@rpq Musiałbyś sprawdzić kod źródłowy Django, ale IIRC używa hakowania metaklasy models.Model, więc pola zdefiniowane w nadklasie nie zostaną pobrane (chyba że są również podklasami models.Model).
Aya
@rpq wiem, że to było 7 lat temu, gdy zapytałem, ale w ModelBase„s __new__zaproszenia, wykonuje wstępną kontrolę klasy” atrybutów, sprawdzając, czy wartość atrybutu ma contribute_to_classatrybut - w Fieldsktóre można zdefiniować w Django wszystko zrobić, więc są zawarte w specjalnym słowniku o nazwie, contributable_attrsktóry jest ostatecznie przekazywany podczas migracji w celu wygenerowania DDL.
Yu Chen
1
ZA KAŻDYM RAZEM SPOJRZĘ NA DJANGO Chcę tylko płakać, dlaczego ????? po co te wszystkie bzdury, dlaczego framework ma zachowywać się zupełnie inaczej niż język? A co z zasadą najmniejszego zdziwienia? Takie zachowanie (podobnie jak PRAWIE wszystko inne w django) nie jest tym, czego oczekiwałby każdy, kto rozumie Pythona.
E.Serra
36

Model abstrakcyjny tworzy tabelę z całym zestawem kolumn dla każdego dziecka podrzędnego, podczas gdy użycie „zwykłego” dziedziczenia w Pythonie tworzy zestaw tabel połączonych (inaczej „dziedziczenie wielu tabel”). Rozważ przypadek, w którym masz dwa modele:

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


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

Jeśli Vehiclejest to model abstrakcyjny, będziesz mieć jedną tabelę:

app_car:
| id | num_wheels | make | year

Jeśli jednak użyjesz zwykłego dziedziczenia w Pythonie, będziesz mieć dwie tabele:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

Gdzie vehicle_idjest link do wiersza app_vehicle, który zawierałby również liczbę kół samochodu.

Teraz Django ładnie to połączy w formie obiektowej, abyś mógł uzyskać dostęp num_wheelsjako atrybut Car, ale podstawowa reprezentacja w bazie danych będzie inna.


Aktualizacja

Aby odpowiedzieć na zaktualizowane pytanie, różnica między dziedziczeniem z klasy abstrakcyjnej Django a dziedziczeniem z klasy Pythona objectpolega na tym, że ta pierwsza jest traktowana jako obiekt bazy danych (więc tabele dla niej są synchronizowane z bazą danych) i zachowuje się jak plik Model. Dziedziczenie ze zwykłego Pythona nie objectnadaje klasie (i jej podklasom) żadnej z tych cech.

mipadi
źródło
1
Zrobienie tego models.OneToOneField(Vehicle)byłoby równoznaczne z dziedziczeniem klasy modelu, prawda? A to spowodowałoby powstanie dwóch oddzielnych tabel, prawda?
Rafay
1
@MohammadRafayAleem: Tak, ale używając dziedziczenia, Django utworzy np. num_wheelsAtrybut na a car, podczas gdy w przypadku a OneToOneFieldbędziesz musiał zrobić to samodzielnie.
mipadi
Jak za pomocą zwykłego dziedziczenia mogę wymusić, aby wszystkie pola były ponownie generowane dla app_cartabeli, tak aby zawierała również num_wheelspole zamiast vehicle_idwskaźnika?
Don Grem
@DorinGrecu: Uczyń klasę bazową modelem abstrakcyjnym i dziedzicz z tego.
mipadi
@mipadi problem polega na tym, że nie mogę uczynić klasy bazowej abstrakcyjną, jest ona używana w całym projekcie.
Don Grem
13

Główna różnica polega na sposobie tworzenia tabel baz danych dla modeli. Jeśli użyjesz dziedziczenia bez abstract = TrueDjango, utworzy oddzielną tabelę zarówno dla modelu nadrzędnego, jak i podrzędnego, która będzie przechowywać zmienne zdefiniowane w każdym modelu.

Jeśli użyjesz abstract = Truedla klasy bazowej, Django utworzy tabelę tylko dla klas, które dziedziczą po klasie bazowej - bez względu na to, czy pola są zdefiniowane w klasie bazowej, czy w klasie dziedziczącej.

Zalety i wady zależą od architektury aplikacji. Biorąc pod uwagę następujące przykładowe modele:

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

Jeśli Publishableklasa nie jest abstrakcyjna Django utworzyć tabelę dla publishables z kolumnami titlei datei oddzielnych tabelach BlogEntryi Image. Zaletą tego rozwiązania byłoby to, że można wyszukiwać we wszystkich publikowanych polach pola zdefiniowane w modelu podstawowym, niezależnie od tego, czy są to wpisy blogu czy obrazy. Ale dlatego Django będzie musiało robić łączenia, jeśli np. Robisz zapytania o obrazki ... Jeśli robisz Publishable abstract = TrueDjango, nie utworzy tabeli dla Publishable, ale tylko dla wpisów na blogu i obrazków, zawierających wszystkie pola (także dziedziczone). Byłoby to przydatne, ponieważ żadne łączenia nie byłyby potrzebne do operacji takiej jak get.

Zobacz także dokumentację Django dotyczącą dziedziczenia modeli .

Bernhard Vallant
źródło
9

Chciałem tylko dodać coś, czego nie widziałem w innych odpowiedziach.

W przeciwieństwie do klas Pythona, ukrywanie nazw pól nie jest dozwolone w przypadku dziedziczenia modelu.

Na przykład eksperymentowałem z problemami z przypadkiem użycia w następujący sposób:

Miałem model dziedziczący po auth django PermissionMixin :

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

    # ...

Potem miałem wstawek, które między innymi chciałem, aby przesłonić related_namena groupspolu. Więc było mniej więcej tak:

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

Używałem tych 2 mixinów w następujący sposób:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Więc tak, spodziewałem się, że to zadziała, ale tak się nie stało. Ale problem był poważniejszy, ponieważ błąd, który otrzymywałem, w ogóle nie wskazywał na modele, nie miałem pojęcia, co się dzieje.

Próbując rozwiązać ten problem, losowo zdecydowałem się zmienić mój miks i przekonwertować go na abstrakcyjny miks modelu. Błąd zmienił się na ten:

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

Jak widać, ten błąd wyjaśnia, co się dzieje.

To była moim zdaniem ogromna różnica :)

Adrián
źródło
Mam pytanie niezwiązane z głównym pytaniem tutaj, ale jak udało ci się to zaimplementować (aby nadpisać wartość related_name w polu groups) na końcu?
kalo
@kalo IIRC Właśnie zmieniłem nazwę na zupełnie inną. Po prostu nie ma sposobu, aby usunąć lub zamienić dziedziczone pola.
Adrián
Tak, byłem już prawie gotowy z tego zrezygnować, kiedy znalazłem sposób na zrobienie tego za pomocą metody contrib_to_class.
kalo
1

Główna różnica polega na dziedziczeniu klasy użytkownika. Jedna wersja będzie zachowywać się jak prosta klasa, a druga jak model Django.

Jeśli odziedziczysz podstawową wersję „obiektu”, twoja klasa pracownika będzie klasą standardową, a imię_nazwa nie stanie się częścią tabeli bazy danych. Nie możesz utworzyć formularza ani używać z nim żadnych innych funkcji Django.

Jeśli odziedziczysz modele. Wersja modelu, twoja klasa Employee będzie miała wszystkie metody modelu Django i odziedziczy pole first_name jako pole bazy danych, które może być użyte w formularzu.

Zgodnie z dokumentacją model abstrakcyjny „zapewnia sposób rozłożenia wspólnych informacji na poziomie Pythona, jednocześnie tworząc tylko jedną tabelę bazy danych na model podrzędny na poziomie bazy danych”.

Brent Washburne
źródło
0

W większości przypadków wolę klasę abstrakcyjną, ponieważ nie tworzy ona oddzielnej tabeli, a ORM nie musi tworzyć złączeń w bazie danych. A użycie klasy abstrakcyjnej w Django jest całkiem proste

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
źródło