abstrakte Django-Modelle versus reguläre Vererbung

86

Was ist neben der Syntax der Unterschied zwischen der Verwendung eines abstrakten Django-Modells und der Verwendung einer einfachen Python-Vererbung mit Django-Modellen? Vor-und Nachteile?

UPDATE: Ich glaube, meine Frage wurde missverstanden und ich erhielt Antworten auf den Unterschied zwischen einem abstrakten Modell und einer Klasse, die von django.db.models.Model erbt. Ich möchte den Unterschied zwischen einer Modellklasse, die von einer abstrakten Django-Klasse (Meta: abstract = True) erbt, und einer einfachen Python-Klasse, die beispielsweise von 'object' (und nicht models.Model) erbt, kennen.

Hier ist ein Beispiel:

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
quelle
1
Dies ist ein guter
jpotts18

Antworten:

152

Ich möchte den Unterschied zwischen einer Modellklasse, die von einer abstrakten Django-Klasse (Meta: abstract = True) erbt, und einer einfachen Python-Klasse, die beispielsweise von 'object' (und nicht models.Model) erbt, kennen.

Django generiert nur Tabellen für Unterklassen von models.Model, so dass die ersteren ...

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)

... bewirkt, dass eine einzelne Tabelle nach dem Vorbild von ...

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

... während letztere ...

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)

... werden keine Tabellen generiert.

Sie könnten Mehrfachvererbung verwenden, um so etwas zu tun ...

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)

... was eine Tabelle erstellen würde, aber die in der UserKlasse definierten Felder ignoriert , sodass Sie eine Tabelle wie diese erhalten ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);
Aya
quelle
3
Danke, das beantwortet meine Frage. Es ist immer noch unklar, warum der Vorname nicht für den Mitarbeiter erstellt wird.
RPQ
4
@rpq Sie müssten den Quellcode von Django überprüfen, aber im IIRC wird Metaklassen-Hackery verwendet models.Model, sodass in der Oberklasse definierte Felder nicht erfasst werden (es sei denn, sie sind auch Unterklassen von models.Model).
Aya
@rpq Ich weiß, dass dies vor 7 Jahren war, als Sie gefragt haben, aber in ModelBase's __new__Aufruf führt es eine erste Überprüfung der Attribute der Klasse durch und prüft, ob der Attributwert ein contribute_to_classAttribut hat - das Fields, was Sie in Django definieren, tun dies alle sind in einem speziellen Wörterbuch enthalten, contributable_attrsdas letztendlich während der Migration weitergegeben wird, um die DDL zu generieren.
Yu Chen
1
Jedes Mal, wenn ich DJango anschaue, möchte ich nur weinen, warum ????? Warum all der Unsinn, warum muss sich das Framework ganz anders verhalten als die Sprache? Was ist mit dem Prinzip des geringsten Erstaunens? Dieses Verhalten (wie fast alles andere in Django) ist nicht das, was jeder, der Python versteht, erwarten würde.
E.Serra
36

Ein abstraktes Modell erstellt eine Tabelle mit dem gesamten Satz von Spalten für jedes Unterkind, während die Verwendung der "einfachen" Python-Vererbung einen Satz verknüpfter Tabellen erstellt (auch als "Mehrtabellenvererbung" bezeichnet). Betrachten Sie den Fall, in dem Sie zwei Modelle haben:

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


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

Wenn Vehiclees sich um ein abstraktes Modell handelt, haben Sie eine einzige Tabelle:

app_car:
| id | num_wheels | make | year

Wenn Sie jedoch eine einfache Python-Vererbung verwenden, haben Sie zwei Tabellen:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

Wo vehicle_idist ein Link zu einer Reihe in app_vehicleder auch die Anzahl der Räder für das Auto hätte.

Jetzt wird Django dies gut in Objektform zusammenstellen, so dass Sie num_wheelsals Attribut darauf zugreifen können Car, aber die zugrunde liegende Darstellung in der Datenbank wird anders sein.


Aktualisieren

Um Ihre aktualisierte Frage zu beantworten, besteht der Unterschied zwischen dem Erben von einer abstrakten Django-Klasse und dem Erben von Pythons objectdarin, dass das erstere als Datenbankobjekt behandelt wird (also werden Tabellen dafür mit der Datenbank synchronisiert) und das Verhalten von a hat Model. Das Erben von einem einfachen Python objectverleiht der Klasse (und ihren Unterklassen) keine dieser Eigenschaften.

Mipadi
quelle
1
Tun models.OneToOneField(Vehicle)wäre auch gleichbedeutend mit dem Erben einer Modellklasse, oder? Und das würde zu zwei getrennten Tabellen führen, nicht wahr?
Rafay
1
@MohammadRafayAleem: Ja, aber wenn Sie die Vererbung verwenden, erstellt Django beispielsweise num_wheelsein Attribut für a car, während Sie bei a OneToOneFielddiese Dereferenzierung selbst durchführen müssen.
Mipadi
Wie kann ich bei regelmäßiger Vererbung erzwingen, dass alle Felder für die app_carTabelle neu generiert werden, sodass sie auch das num_wheelsFeld enthält, anstatt den vehicle_idZeiger zu haben?
Don Grem
@DorinGrecu: Machen Sie die Basisklasse zu einem abstrakten Modell und erben Sie davon.
Mipadi
@mipadi Das Problem ist, dass ich die Basisklasse nicht abstrakt machen kann, sie wird im gesamten Projekt verwendet.
Don Grem
13

Der Hauptunterschied besteht darin, wie die Datenbanktabellen für die Modelle erstellt werden. Wenn Sie die Vererbung ohne abstract = TrueDjango verwenden, wird sowohl für das übergeordnete als auch für das untergeordnete Modell eine separate Tabelle erstellt, die die in jedem Modell definierten Felder enthält.

Wenn Sie abstract = Truefür die Basisklasse verwenden, erstellt Django nur eine Tabelle für die Klassen, die von der Basisklasse erben - unabhängig davon, ob die Felder in der Basisklasse oder der ererbenden Klasse definiert sind.

Vor- und Nachteile hängen von der Architektur Ihrer Anwendung ab. Anhand der folgenden Beispielmodelle:

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

Wenn die PublishableKlasse nicht abstrakt ist Django wird eine Tabelle für publishables mit den Spalten erstellen titleund dateund getrennte Tabellen für BlogEntryund Image. Der Vorteil dieser Lösung besteht darin, dass Sie in allen Publishables nach Feldern fragen können, die im Basismodell definiert sind, unabhängig davon, ob es sich um Blogeinträge oder Bilder handelt. Daher muss Django Verknüpfungen durchführen, wenn Sie z. B. Abfragen für Bilder durchführen ... Wenn Sie Publishable abstract = TrueDjango erstellen, wird keine Tabelle Publishablefür Blogeinträge und Bilder erstellt, die alle Felder (auch die geerbten) enthalten. Dies wäre praktisch, da für eine Operation wie get keine Verknüpfungen erforderlich wären.

Siehe auch Djangos Dokumentation zur Modellvererbung .

Bernhard Vallant
quelle
9

Ich wollte nur etwas hinzufügen, was ich in anderen Antworten nicht gesehen habe.

Anders als bei Python-Klassen ist das Ausblenden von Feldnamen bei der Modellvererbung nicht zulässig .

Zum Beispiel habe ich Probleme mit einem Anwendungsfall wie folgt experimentiert:

Ich hatte ein Modell, das von Djangos auth PermissionMixin geerbt wurde :

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

    # ...

Dann hatte ich mein Mixin, das unter anderem related_namedas groupsFeld außer Kraft setzen sollte . So war es mehr oder weniger so:

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

Ich habe diese 2 Mixins wie folgt verwendet:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Also ja, ich habe erwartet, dass das funktioniert, aber es hat nicht funktioniert. Das Problem war jedoch schwerwiegender, da der Fehler, den ich erhielt, überhaupt nicht auf die Modelle hinwies und ich keine Ahnung hatte, was schief lief.

Während ich versuchte, dies zu lösen, entschied ich mich zufällig, mein Mixin zu ändern und es in ein abstraktes Modell-Mixin umzuwandeln. Der Fehler hat sich folgendermaßen geändert:

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

Wie Sie sehen können, erklärt dieser Fehler, was los ist.

Das war meiner Meinung nach ein großer Unterschied :)

Adrián
quelle
Ich habe eine Frage, die hier nicht mit der Hauptfrage zusammenhängt, aber wie haben Sie es am Ende geschafft, diese zu implementieren (um den verwandten Namen des Gruppenfelds zu überschreiben)?
Kalo
@kalo IIRC Ich habe gerade den Namen in etwas ganz anderes geändert. Es gibt einfach keine Möglichkeit, geerbte Felder zu entfernen oder zu ersetzen.
Adrián
Ja, ich war fast bereit, dies aufzugeben, als ich einen Weg fand, dies mit der Contribute_to_class-Methode zu tun.
Kalo
1

Der Hauptunterschied besteht darin, wann Sie die Benutzerklasse erben. Eine Version verhält sich wie eine einfache Klasse und die andere wie ein Django-Modus.

Wenn Sie die Basisversion "Objekt" erben, ist Ihre Employee-Klasse nur eine Standardklasse, und Vorname wird nicht Teil einer Datenbanktabelle. Sie können damit kein Formular erstellen oder andere Django-Funktionen verwenden.

Wenn Sie die models.Model-Version erben, verfügt Ihre Employee-Klasse über alle Methoden eines Django- Modells und erbt das Feld first_name als Datenbankfeld, das in einem Formular verwendet werden kann.

Laut der Dokumentation bietet ein abstraktes Modell "eine Möglichkeit, allgemeine Informationen auf Python-Ebene herauszufiltern und gleichzeitig nur eine Datenbanktabelle pro untergeordnetem Modell auf Datenbankebene zu erstellen".

Brent Washburne
quelle