modelli astratti django contro ereditarietà regolare


87

Oltre alla sintassi, qual è la differenza tra l'utilizzo di un modello astratto django e l'utilizzo della semplice ereditarietà Python con modelli django? Pro e contro?

AGGIORNAMENTO: Penso che la mia domanda sia stata fraintesa e ho ricevuto risposte per la differenza tra un modello astratto e una classe che eredita da django.db.models.Model. In realtà voglio conoscere la differenza tra una classe modello che eredita da una classe astratta django (Meta: abstract = True) e una semplice classe Python che eredita da, ad esempio, 'object' (e non models.Model).

Ecco un esempio:

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
Questa è un'ottima panoramica dei compromessi tra l'utilizzo dei due approcci di ereditarietà charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Risposte:


155

In realtà voglio conoscere la differenza tra una classe modello che eredita da una classe astratta django (Meta: abstract = True) e una semplice classe Python che eredita da, ad esempio, 'object' (e non models.Model).

Django genererà solo tabelle per sottoclassi di models.Model, quindi il primo ...

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)

... provocherà la generazione di un'unica tabella, sulla falsariga di ...

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

... mentre quest'ultimo ...

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)

... non causerà la generazione di tabelle.

Potresti usare l'ereditarietà multipla per fare qualcosa del genere ...

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)

... che creerebbe una tabella, ma ignorerà i campi definiti nella Userclasse, quindi ti ritroverai con una tabella come questa ...

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

3
Grazie, questo risponde alla mia domanda. Non è ancora chiaro il motivo per cui first_name non viene creato per Employee.
rpq

4
@rpq Dovresti controllare il codice sorgente di Django, ma IIRC usa un po 'di hackery in metaclassi models.Model, quindi i campi definiti nella superclasse non verranno presi (a meno che non siano anche sottoclassi di models.Model).
Aya

1
@rpq So che questo è stato 7 anni fa, quando lei ha chiesto, ma in ModelBase's __new__chiamata, esegue un controllo iniziale delle classi' attributi, controllando se il valore di attributo ha un contribute_to_classattributo - il Fieldsche si definiscono in Django tutti fanno, in modo da sono inclusi in un dizionario speciale chiamato contributable_attrsche vengono infine passati durante la migrazione per generare il DDL.
Yu Chen

2
OGNI VOLTA CHE GUARDO DJANGO vorrei solo piangere, perché ????? perché tutte queste sciocchezze, perché il framework deve comportarsi in modo completamente diverso dal linguaggio? E il principio del minimo stupore? Questo comportamento (come QUASI tutto il resto in django) non è quello che si aspetterebbe chiunque capisca python.
E.Serra

36

Un modello astratto crea una tabella con l'intero set di colonne per ogni figlio secondario, mentre l'uso dell'ereditarietà "semplice" di Python crea un insieme di tabelle collegate (ovvero "ereditarietà multi-tabella"). Considera il caso in cui hai due modelli:

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


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

Se Vehicleè un modello astratto, avrai un'unica tabella:

app_car:
| id | num_wheels | make | year

Tuttavia, se utilizzi la semplice ereditarietà Python, avrai due tabelle:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

Dov'è vehicle_idun collegamento a una riga app_vehicleche avrebbe anche il numero di ruote per l'auto.

Ora, Django lo metterà insieme bene in forma di oggetto in modo da poter accedere num_wheelscome un attributo Car, ma la rappresentazione sottostante nel database sarà diversa.


Aggiornare

Per rispondere alla tua domanda aggiornata, la differenza tra ereditare da una classe astratta Django ed ereditare da Python objectè che il primo viene trattato come un oggetto database (quindi le tabelle vengono sincronizzate con il database) e ha il comportamento di un file Model. Ereditare da un semplice Python non objectfornisce alla classe (e alle sue sottoclassi) nessuna di queste qualità.


1
Ciò models.OneToOneField(Vehicle)equivarrebbe anche ad ereditare una classe modello, giusto? E questo risulterebbe in due tabelle separate, non è vero?
Rafay

1
@MohammadRafayAleem: Sì, ma quando usi l'ereditarietà, Django creerà, ad esempio, num_wheelsun attributo su a car, mentre con a OneToOneFielddovrai farlo dereferenziando te stesso.
mipadi

Come posso forzare con l'ereditarietà regolare che tutti i campi vengano rigenerati per la app_cartabella in modo che abbia anche il num_wheelscampo, invece di avere il vehicle_idpuntatore?
Don Grem

@DorinGrecu: Rendi la classe base un modello astratto ed eredita da quello.
mipadi

@mipadi il problema è che non riesco a rendere astratta la classe base, viene utilizzata in tutto il progetto.
Don Grem

13

La differenza principale è il modo in cui vengono create le tabelle dei database per i modelli. Se usi l'ereditarietà senza abstract = TrueDjango creerà una tabella separata sia per il modello genitore che per il modello figlio che contengono i campi definiti in ogni modello.

Se usi abstract = Trueper la classe base, Django creerà solo una tabella per le classi che ereditano dalla classe base, indipendentemente dal fatto che i campi siano definiti nella classe base o nella classe che eredita.

I pro ei contro dipendono dall'architettura della tua applicazione. Dati i seguenti modelli di esempio:

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

Se la Publishableclasse non è astratta Django creerà un tavolo per publishables con le colonne titlee le datee le tabelle separate per BlogEntrye Image. Il vantaggio di questa soluzione sarebbe la possibilità di eseguire query su tutti i pubblicabili per i campi definiti nel modello di base, indipendentemente dal fatto che si tratti di voci di blog o immagini. Ma quindi Django dovrà fare dei join se ad esempio fai delle query per le immagini ... Se faiPublishable abstract = True Django non creerà una tabella per Publishable, ma solo per i post e le immagini del blog, contenente tutti i campi (anche quelli ereditati). Ciò sarebbe utile perché non sarebbero necessari join per un'operazione come get.

Vedi anche la documentazione di Django sull'ereditarietà del modello .


9

Volevo solo aggiungere qualcosa che non ho visto in altre risposte.

A differenza delle classi Python, nascondere il nome del campo non è consentito con l'ereditarietà del modello.

Ad esempio, ho sperimentato problemi con un caso d'uso come segue:

Avevo un modello che ereditava dall'autorizzazione di 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

    # ...

Poi ho avuto il mio mixin che tra le altre cose volevo che avesse la precedenza related_namesu quello del groupscampo. Quindi era più o meno così:

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

Stavo usando questi 2 mixin come segue:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Quindi sì, mi aspettavo che funzionasse ma non è stato così. Ma il problema era più serio perché l'errore che stavo ricevendo non puntava affatto ai modelli, non avevo idea di cosa stesse andando storto.

Durante il tentativo di risolvere questo problema, ho deciso a caso di cambiare il mio mixin e convertirlo in un modello astratto di mixin. L'errore è cambiato in questo:

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

Come puoi vedere, questo errore spiega cosa sta succedendo.

Questa è stata un'enorme differenza, secondo me :)


Ho una domanda non correlata alla domanda principale qui, ma come sei riuscito a implementarla (per sovrascrivere il related_name del campo gruppi) alla fine?
kalo

@kalo IIRC Ho appena cambiato il nome in qualcos'altro completamente. Non è possibile rimuovere o sostituire i campi ereditati.
Adrián

Sì, ero quasi pronto a rinunciare a questo, quando ho trovato un modo per farlo utilizzando il metodo contrib_to_class.
kalo

1

La differenza principale è quando erediti la classe User. Una versione si comporterà come una semplice classe e l'altra si comporterà come una modalità Django.

Se erediti la versione "oggetto" di base, la tua classe Employee sarà solo una classe standard e first_name non diventerà parte di una tabella di database. Non puoi creare un modulo o utilizzare altre funzionalità di Django con esso.

Se erediti la versione models.Model, la tua classe Employee avrà tutti i metodi di un modello Django ed erediterà il campo first_name come campo database che può essere utilizzato in un form.

Secondo la documentazione, un modello astratto "fornisce un modo per escludere le informazioni comuni a livello di Python, creando comunque una sola tabella di database per modello figlio a livello di database".


0

Preferirò la classe astratta nella maggior parte dei casi perché non crea una tabella separata e l'ORM non ha bisogno di creare join nel database. E usare la classe astratta è piuttosto semplice in 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(...)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.