modelos abstractos de django versus herencia regular

86

Además de la sintaxis, ¿cuál es la diferencia entre usar un modelo abstracto de django y usar la herencia simple de Python con modelos de django? ¿Pros y contras?

ACTUALIZACIÓN: Creo que mi pregunta fue mal entendida y recibí respuestas para la diferencia entre un modelo abstracto y una clase que hereda de django.db.models.Model. De hecho, quiero saber la diferencia entre una clase modelo que hereda de una clase abstracta de django (Meta: abstract = True) y una clase Python simple que hereda de digamos, 'objeto' (y no modelos.Modelo).

Aquí hay un ejemplo:

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
fuente
1
Esta es una excelente descripción general de las compensaciones entre el uso de los dos enfoques de herencia charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Respuestas:

152

De hecho, quiero saber la diferencia entre una clase modelo que hereda de una clase abstracta de django (Meta: abstract = True) y una clase Python simple que hereda de digamos, 'objeto' (y no modelos.Modelo).

Django solo generará tablas para subclases de models.Model, por lo que el primero ...

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)

... hará que se genere una sola tabla, en la línea de ...

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

... mientras que el último ...

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)

... no hará que se generen tablas.

Podría usar herencia múltiple para hacer algo como esto ...

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)

... que crearía una tabla, pero ignorará los campos definidos en la Userclase, por lo que terminará con una tabla como esta ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);
Aya
fuente
3
Gracias, esto responde a mi pregunta. Aún no está claro por qué no se crea first_name para Employee.
rpq
4
@rpq Tendría que verificar el código fuente de Django, pero IIRC usa alguna piratería de metaclase models.Model, por lo que los campos definidos en la superclase no serán seleccionados (a menos que también sean subclases de models.Model).
Aya
@rpq Sé que esto fue hace 7 años cuando preguntaste, pero en ModelBasela __new__llamada, realiza una verificación inicial de los atributos de la clase, verificando si el valor del atributo tiene un contribute_to_classatributo, el Fieldsque definiste en Django, todos lo tienen, por lo que se incluyen en un diccionario especial llamado contributable_attrsque finalmente se pasa durante la migración para generar el DDL.
Yu Chen
1
CADA VEZ QUE Miro a DJANGO Solo quiero llorar, ¿por qué ??? ¿Por qué todas las tonterías, por qué el marco tiene que comportarse de una manera completamente diferente al lenguaje? ¿Qué pasa con el principio del menor asombro? Este comportamiento (como CASI todo lo demás en django) no es lo que esperaría cualquiera que entienda python.
E.Serra
36

Un modelo abstracto crea una tabla con el conjunto completo de columnas para cada subhijo, mientras que el uso de la herencia "simple" de Python crea un conjunto de tablas vinculadas (también conocido como "herencia de múltiples tablas"). Considere el caso en el que tiene dos modelos:

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


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

Si Vehiclees un modelo abstracto, tendrá una sola tabla:

app_car:
| id | num_wheels | make | year

Sin embargo, si usa la herencia simple de Python, tendrá dos tablas:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

¿Dónde vehicle_idhay un enlace a una fila en app_vehicleque también tendría el número de ruedas del automóvil?

Ahora, Django reunirá esto muy bien en forma de objeto para que pueda acceder num_wheelscomo un atributo Car, pero la representación subyacente en la base de datos será diferente.


Actualizar

Para abordar su pregunta actualizada, la diferencia entre heredar de una clase abstracta de Django y heredar de Python objectes que el primero se trata como un objeto de base de datos (por lo que las tablas para él se sincronizan con la base de datos) y tiene el comportamiento de un Model. Heredar de un Python simple objectle da a la clase (y sus subclases) ninguna de esas cualidades.

mipadi
fuente
1
Hacerlo models.OneToOneField(Vehicle)también sería equivalente a heredar una clase modelo, ¿verdad? Y eso daría como resultado dos tablas separadas, ¿no es así?
Rafay
1
@MohammadRafayAleem: Sí, pero al usar la herencia, Django creará, por ejemplo, num_wheelsun atributo en a car, mientras que con a OneToOneFieldtendrás que hacerlo desreferenciando tú mismo.
mipadi
¿Cómo puedo forzar con herencia regular que todos los campos se vuelvan a generar para la app_cartabla para que también tenga el num_wheelscampo, en lugar de tener el vehicle_idpuntero?
Don Grem
@DorinGrecu: Haga de la clase base un modelo abstracto y herede de eso.
mipadi
@mipadi, el problema es que no puedo hacer que la clase base sea abstracta, se está utilizando en todo el proyecto.
Don Grem
13

La principal diferencia es cómo se crean las tablas de bases de datos para los modelos. Si usa la herencia sin abstract = TrueDjango, se creará una tabla separada para el modelo padre y el hijo, que contiene los campos definidos en cada modelo.

Si usa abstract = Truepara la clase base, Django solo creará una tabla para las clases que heredan de la clase base, sin importar si los campos están definidos en la clase base o en la clase heredada.

Los pros y los contras dependen de la arquitectura de su aplicación. Dados los siguientes modelos de ejemplo:

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

Si la Publishableclase no es abstracta, Django creará una tabla para publicables con las columnas titley datetablas separadas para BlogEntryy Image. La ventaja de esta solución sería que puede realizar consultas en todos los elementos publicables para los campos definidos en el modelo base, sin importar si son entradas de blog o imágenes. Pero, por lo tanto, Django tendrá que hacer uniones si, por ejemplo, haces consultas de imágenes ... Si al hacer Publishable abstract = TrueDjango no se creará una tabla para Publishable, sino solo para, entradas de blog e imágenes, que contenga todos los campos (también los heredados). Esto sería útil porque no se necesitarían combinaciones para una operación como get.

Consulte también la documentación de Django sobre la herencia de modelos .

Bernhard Vallant
fuente
9

Solo quería agregar algo que no he visto en otras respuestas.

A diferencia de las clases de Python, la ocultación del nombre de campo no está permitida con la herencia del modelo.

Por ejemplo, he experimentado problemas con un caso de uso de la siguiente manera:

Tenía un modelo que heredaba de la autenticación PermissionMixin de 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

    # ...

Luego tuve mi mezcla que, entre otras cosas, quería que anulara la related_namedel groupscampo. Entonces fue más o menos así:

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

Estaba usando estos 2 mixins de la siguiente manera:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Así que sí, esperaba que esto funcionara, pero no fue así. Pero el problema era más serio porque el error que estaba obteniendo no apuntaba a los modelos en absoluto, no tenía idea de lo que estaba saliendo mal.

Mientras trataba de resolver esto, decidí al azar cambiar mi mezcla y convertirla en una mezcla de modelo abstracto. El error cambió a esto:

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

Como puede ver, este error explica lo que está sucediendo.

Esta fue una gran diferencia, en mi opinión :)

Adrián
fuente
Tengo una pregunta que no está relacionada con la pregunta principal aquí, pero ¿cómo logró implementar esto (para anular el nombre relacionado del campo de grupos) al final?
kalo
@kalo IIRC Acabo de cambiar el nombre por otro completamente diferente. Simplemente no hay forma de eliminar o reemplazar campos heredados.
Adrián
Sí, estaba casi listo para renunciar a esto, cuando encontré una manera de hacerlo usando el método contrib_to_class.
kalo
1

La principal diferencia es cuando hereda la clase User. Una versión se comportará como una clase simple y la otra se comportará como un modelo de Django.

Si hereda la versión base del "objeto", su clase de Empleado será simplemente una clase estándar y first_name no se convertirá en parte de una tabla de base de datos. No puede crear un formulario ni usar ninguna otra característica de Django con él.

Si hereda la versión de models.Model, su clase de Empleado tendrá todos los métodos de un Modelo de Django y heredará el campo first_name como un campo de base de datos que se puede usar en un formulario.

De acuerdo con la documentación, un modelo abstracto "proporciona una manera de factorizar la información común a nivel de Python, mientras se crea una sola tabla de base de datos por modelo hijo a nivel de base de datos".

Brent Washburne
fuente
0

Preferiré la clase abstracta en la mayoría de los casos porque no crea una tabla separada y el ORM no necesita crear uniones en la base de datos. Y usar la clase abstracta es bastante simple en 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
fuente