Herencias de modelos en Django



@eduardo_gpg

Número de visitas 6665

Tiempo de lectura 5 min

16 Noviembre 2020

El tema de modelos en Django es sin duda uno de los temas más complejos e interesantes que posee el Framework. No solo se trata de definir qué valores se van almacenar o de qué tipo serán, también hay que tomar en consideración cómo se van a almacenar y el por qué de ellos.

Definir un modelo de forma incorrecta es algo que solo acarreará problemas a largo plazo; algo que sin duda nadie quiere. 😰

Es por ello que para este post me gustaría habláramos acerca de las diferentes formas en las cuales podemos definir modelos en Django utilizando herencia, pudiendo así crear clases flexibles, escalables e inclusive dinámicas.

Es un post bastante interesante, así que te invito a que te quedes. Bien, sin más que decir, comencemos.

Herencia

Comencemos con un ejemplo. Hemos definido el modelo Article y el Modelo Video. En este caso ambos modelos comparten campos en común (Titulo, image path, slug, created at).

class Video(models.Model):
    title = models.CharField(max_length=50)
    description = models.TextField()
    image_path = models.CharField(max_length=100, default='', null=True, blank=True)
    slug = models.SlugField(null=False, blank=False, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

class Article(models.Model):
    title = models.CharField(max_length=100)
    image_path = models.CharField(max_length=100, default='', null=True, blank=True)
    slug = models.SlugField(null=False, blank=False, unique=True)
    markdown_content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

En primera instancia uno pudiera pasar por alto "el problema" que existe para estos 2 modelos. Fácilmente podemos crear las migraciones, aplicarlas y continuar con el desarrollo. Sin embargo, dejar los model tal y cómo se encuentran actualmente, aun que puede funcionar, no es lo correcto.'

Tener líneas de código duplicadas es sinónimo que aun podemos mejorar nuestro código. Basta realizar un pequeño refactor y listo. 🥳

Para este ejemplo, y como estamos utilizando clases, la respuesta más obvia para solucionar el "problema" sería implementar herencia.

Y en Django existen 3 formas en las cuales es posible implementar herencia sobre nuestros modelos:

  • Abstracta
  • Multi tabla
  • Proxy

Veamos una a una en que consisten estos tipos de herencia.

Herencia Abstracta

Implementar uno u otro tipo de herencia depende completamente de nuestras necesidad, y de exactamente qué deseamos construir. Por ejemplo, para nuestros modelos Article y Video podemos abstraer los campos duplicados y colocarlos en una clase padre. Nuestros modelos heredarían de dicha clase, y con esto tendrían acceso a los campos en común.

Para lograr esto será necesario implementar la herencia abstracta. Este tipo de herencias nos permite definir una clase padre en la cual colocaremos todos los campos en común. Las clases que hereden (Clases hijas) tendrán acceso tanto a sus atributos como a los métodos. 🐧

Para nuestro ejemplo una solución pudiese ser la siguiente.

class Resource(models.Model):
    title = models.CharField(max_length=50)
    image_path = models.CharField(max_length=100, default='', null=True, blank=True)
    slug = models.SlugField(null=False, blank=False, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True

class Video(Resource):
    description = models.TextField()

class Article(Resource):
    markdown_content = models.TextField()

Aquí hay un par de cosas a resaltar.

  • Para que una clase puede considerarse abstracta, debe, obligatoriamente, establecer el atributo abstract =True para su clase Meta. En caso de no hacerse, Django tomará esta clase como un modelo convencional y creará una nueva tabla en la base de datos. Algo que sin duda no queremos suceda. Bueno, no para este ejemplo. 😅

  • Las clases hijas no heredarán de Model, si no que ahora heredarán de la clase abstracta.

Con esto sin duda reducimos drásticamente nuestras líneas de código, y si en un futuro otro modelo debe poseer los mismos atributos, únicamente deber heredar. 🐙

Y por si te lo estabas preguntando, sí, es posible tener una n cantidad de clases padre para nuestros modelos. Entre más abstracto, mejor.

Herencia Multi tabla

Contrario a la herencia abstracta, con la herencia Multi tabla Django creará una nueva tabla para nuestra clase padre. Veamos un ejemplo.

class Admin(models.Model):
    username = models.CharField(max_length=50)
    name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

class Employee(Admin):
    RFC = models.CharField(max_length=50)

Para este ejemplo se crean 2 nuevas tablas: admin y employee. Ambas tablas comparte los campos: username, name y last name. Al igual que con la herencia abstracta, con la herencia multi tabla definimos los campos duplicados en la clase padre, y es la clase hija quien accede a ellos.

Lo interesante de utilizar este tipo de herencia será la relación que se crea para los modelos. Una relación uno a uno. Veamos un ejemplo.

>>> admin1 = Admin.objects.create(username='admin1', name='Eduardo', last_name='García')    
>>> employee1 = Employee.objects.create(admin_ptr=admin1, RFC='EXAMPLE')  

>>> admin1.employee                                                                                      
<Employee: Employee object (1)>

>>> employee1.admin_ptr
<Admin: Admin object (1)>

En este caso la relación se crea utilizando el atributo admin_ptr de nuestra clase Employee. Tanto un empleado como un administrado puede acceder a dicha relación.

NOTA: Al nosotros utilizar herencia multi tabla se creará, de forma automática, un campo oculto para nuestra clase hija. Será a través de este atributo que podremos establecer la relación uno a uno entre nuestros modelos. Este campo tiene por nombre la siguiente estructura: Nombre de la clase padre (Minúsculas) + _prt .

Este tipo de herencia sin duda nos es de mucha utilidad cuando deseamos crear instancias y registros para todos los modelos, tanto para la clase padre, como para su(s) clase(s) hija(s). 🧝‍♂️

Herencia Proxy

Toca el turno de hablar de la herencia por Proxy. Este tipo de herencia nos permite extender funcionalidades para modelos ya existentes. Con esto me refiero a poder añadir nuevos métodos a los modelos sin la necesidad de modificarlos. Veamos un ejemplo.

En este caso tenemos nuestro modelo principal, Article.

class Article(models.Model):
    title = models.CharField(max_length=100)
    image_path = models.CharField(max_length=100, default='', null=True, blank=True)
    slug = models.SlugField(null=False, blank=False, unique=True)
    premium = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

Si queremos añadir funcionalidades (métodos)a nuestros objetos de tipo Article, pero no deseamos (o no podemos) modificar la clase, haremos uso de herencia por Proxy.

Para ello debemos hacer lo siguiente.

  • Definiendo una nueva clase que herede de la clase a la cual deseamos extender (añadir) funcionalidades.
  • Para evitar que Django cree una nueva tabla en la base de datos, será necesario establecer proxy = True en la clase Meta. Esto por supuesto para nuestra nueva clase.
  • Dentro de la clase hija se debemos definir los nuevos métodos a añadir.
class SuperArticle(Article):
    class Meta:
        proxy = True

    def new_super_title(self):
        return f'Nuevo título: {self.title}'

En este caso, al mi clase SuperArticle heredar de Article, podrá acceder a todos los atributos y métodos de Article.

Ahora, quizás te preguntes, ok, no se creo ninguna tabla, por lo tanto no se crearán nuevos registros, ¿Cómo puedo utilizar la clase Proxy? Y aquí esta lo más interesante de todo. Podemos utilizar nuestra clase proxy como si de la case principal se tratará, solo que con un mayor número de métodos. Veamos un par de ejemplos.

>>> article = SuperArticle.objects.filter(pk=1).get() 
>>> article.slug
'realmente-conoces-los-strings'

>>> article.title
'¿Realmente conoces los Strings en Python?'

>>> article.new_super_title()                                                                            
'Nuevo título: ¿Realmente conoces los Strings en Python?'

Cómo podemos observar, obtenemos un objeto de tipo Article a través de la clase SuperArticle. Este objeto puede acceder a todos los métodos y atributos de su clase padre, y por supuesto a los métodos de su clase.

# Utilizamos la clase Article
>>> article1 = Article.objects.filter(pk=1).get() 

# Utilizamos la clase SuperArticle
>>> article2 = SuperArticle.objects.filter(pk=1).get() 

# Mismo resultado
>>> article1
<Article: ¿Realmente conoces los Strings en Python?>

>>> article2 
<SuperArticle: ¿Realmente conoces los Strings en Python?>

El manejo de los objetos es exactamente el mismo, estos se obtienen, procesan y se pintan cómo si de un modelo convencional se tratará, solo con la única diferencia que estos objetos poseen métodos extras al original.

Este tipo de herencia sin duda nos será de mucha utilidad siempre que deseemos añadir nuevos métodos a modelos ya existentes, pero por alguna u otra razón no podamos modificarlos. 🎮


¿El contenido te resulto de ayuda?

Para poder dejar tu opinión es necesario ser un usuario autenticado. Login

Más Tips y Ejercicios 🐍

Adquiere una subscripción PyWombat por tan solo $3 USD. al mes.

Conoce los beneficios de ser usuario premium:
Niveles desbloqueados: Ten accesos a todos los niveles de ejercicios. 🔓
Nuevo límite: Incrementa tu límite de ejercicios por semana. 🚀
Contenido único: Recibe semanalmente recursos exclusivos de Python (Videos, Artículos y Capitulos del libro PyWombat, comienza como desarrollador Python. 🐍