Saltar al contenido

Herencia de tabla única en Django

Hola, descubrimos la solución a tu interrogante, desplázate y la verás a continuación.

Solución:

Creo que el OP está preguntando sobre la herencia de tabla única como se define aquí:

Las bases de datos relacionales no admiten la herencia, por lo que al mapear de objetos a bases de datos tenemos que considerar cómo representar nuestras agradables estructuras de herencia en tablas relacionales. Al mapear a una base de datos relacional, intentamos minimizar las uniones que se pueden montar rápidamente al procesar una estructura de herencia en varias tablas. La herencia de tabla única mapea todos los campos de todas las clases de una estructura de herencia en una sola tabla.

Es decir, una sola tabla de base de datos para toda una jerarquía de clases de entidad. Django no admite ese tipo de herencia.

Actualmente hay dos formas de herencia en Django: MTI (herencia de tabla modelo) y ABC (clases base abstractas).

Escribí un tutorial sobre lo que está pasando bajo el capó.

También puede consultar los documentos oficiales sobre la herencia de modelos.

Resumen

Los modelos de proxy de Django proporcionan la base para la herencia de tabla única.

Sin embargo, se requiere algo de esfuerzo para que funcione.

Vaya al final para ver un ejemplo reutilizable.

Fondo

Martin Fowler describe la herencia de tabla única (STI) de la siguiente manera:

Herencia de tabla única mapea todos los campos de todas las clases de una estructura de herencia en una sola tabla.

Esto es precisamente lo que hace la herencia del modelo proxy de Django.

Tenga en cuenta que, según esta publicación de blog de 2010, proxy Los modelos han existido desde Django 1.1. Sin embargo, solo una de las otras respuestas las menciona explícitamente.

Un modelo de Django “normal” es un hormigón modelo, es decir, tiene una tabla dedicada en la base de datos. Hay dos tipos de modelo de Django que no no tener tablas de base de datos dedicadas, a saber. abstracto modelos y apoderado modelos:

  • Los modelos abstractos actúan como superclases para modelos de hormigón. Un modelo abstracto puede definir campos, pero no tiene una tabla de base de datos. Los campos solo se agregan a las tablas de la base de datos para sus subclases concretas.

  • Los modelos proxy actúan como subclases para modelos de hormigón. Un modelo de proxy no puede definir nuevos campos. En cambio, opera en la tabla de la base de datos asociada con su superclase concreta. En otras palabras, un modelo concreto de Django y sus proxies comparten una sola tabla.

Los modelos de proxy de Django proporcionan la base para la herencia de tabla única, a saber. permiten que diferentes modelos compartan una sola tabla y nos permiten definir el comportamiento específico del proxy en el lado de Python. Sin embargo, el mapeo relacional de objetos (ORM) predeterminado de Django no proporciona todo el comportamiento que se esperaría, por lo que se requiere un poco de personalización. Cuánto, eso depende de sus necesidades.

Construyamos un ejemplo mínimo, paso a paso, basado en el modelo de datos simple en la siguiente figura:

modelo de datos de partido simple

Paso 1: “herencia del modelo proxy” básico

Aquí está el contenido de models.py para una implementación básica de herencia de proxy:

from django.db import models


class Party(models.Model):
    name = models.CharField(max_length=20)
    person_attribute = models.CharField(max_length=20)
    organization_attribute = models.CharField(max_length=20)


class Person(Party):
    class Meta:
        proxy = True


class Organization(Party):
    class Meta:
        proxy = True

Person y Organization Hay dos tipos de fiestas.

Solo el Party modelo tiene una tabla de base de datos, por lo que todos los campos se definen en este modelo, incluidos los campos que son específicos para Person o para Organization.

Porque Party, Person, y Organization todos usan el Party tabla de base de datos, podemos definir una ForeignKey campo a Partyy asigne instancias de cualquiera de los tres modelos a ese campo, como implica la relación de herencia en la figura. Tenga en cuenta que, sin herencia, necesitaríamos una ForeignKey campo para cada modelo.

Por ejemplo, supongamos que definimos un Address modelo de la siguiente manera:

class Address(models.Model):
    party = models.ForeignKey(to=Party, on_delete=models.CASCADE)

Entonces podemos inicializar un Address objeto usando, por ejemplo Address(party=person_instance) o Address(party=organization_instance).

Hasta aquí todo bien.

Sin embargo, si intentamos obtener una lista de objetos correspondientes a un modelo de proxy, usando eg Person.objects.all(), obtenemos una lista de todosParty objetos en su lugar, es decir, ambos Person objetos y Organization objetos. Esto se debe a que los modelos proxy todavía usan el administrador de modelos de la superclase (es decir, Party).

Paso 2: agregue administradores de modelos de proxy

Para asegurarse de que Person.objects.all() solo devoluciones Person objetos, necesitamos asignar un administrador de modelos separado que filtre los Party queryset. Para habilitar este filtrado, necesitamos un campo que indique qué modelo de proxy debe usarse para el objeto.

Para ser claros: creando un Person El objeto implica agregar una fila al Party mesa. Lo mismo va para Organization. Para distinguir entre los dos, necesitamos una columna para indicar si una fila representa un Person o un Organization. Para mayor comodidad y claridad, agregamos un campo (es decir, una columna) llamado proxy_namey utilícelo para almacenar el nombre de la clase de proxy.

Entonces, ingrese el ProxyManager gerente de modelos y el proxy_name campo:

from django.db import models


class ProxyManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(proxy_name=self.model.__name__)


class Party(models.Model):
    proxy_name = models.CharField(max_length=20)
    name = models.CharField(max_length=20)
    person_attribute = models.CharField(max_length=20)
    organization_attribute = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        self.proxy_name = type(self).__name__
        super().save(*args, **kwargs)


class Person(Party):
    class Meta:
        proxy = True

    objects = ProxyManager()


class Organization(Party):
    class Meta:
        proxy = True

    objects = ProxyManager()

Ahora el conjunto de consultas devuelto por Person.objects.all() solo contendrá Person objetos (y lo mismo para Organization).

Sin embargo, esto no funciona en el caso de un ForeignKey relación con Party, como en Address.party arriba, porque eso siempre devolverá un Party instancia, independientemente del valor de la proxy_name campo (ver también documentos). Por ejemplo, supongamos que creamos un address = Address(party=person_instance), luego address.party devolverá un Party instancia, en lugar de una Person ejemplo.

Paso 3: extiende el Party constructor

Una forma de abordar el problema del campo relacionado es ampliar la Party.__new__ método, por lo que devuelve una instancia de la clase especificada en el campo ‘proxy_name’. El resultado final se ve así:

class Party(models.Model):
    PROXY_FIELD_NAME = 'proxy_name'

    proxy_name = models.CharField(max_length=20)
    name = models.CharField(max_length=20)
    person_attribute = models.CharField(max_length=20)
    organization_attribute = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        """ automatically store the proxy class name in the database """
        self.proxy_name = type(self).__name__
        super().save(*args, **kwargs)

    def __new__(cls, *args, **kwargs):
        party_class = cls
        try:
            # get proxy name, either from kwargs or from args
            proxy_name = kwargs.get(cls.PROXY_FIELD_NAME)
            if proxy_name is None:
                proxy_name_field_index = cls._meta.fields.index(
                    cls._meta.get_field(cls.PROXY_FIELD_NAME))
                proxy_name = args[proxy_name_field_index]
            # get proxy class, by name, from current module
            party_class = getattr(sys.modules[__name__], proxy_name)
        finally:
            return super().__new__(party_class)

Ahora address.party realmente devolverá un Person instancia si el proxy_name el campo es Person.

Como último paso, podemos hacer que todo sea reutilizable:

Paso 4: hazlo reutilizable

Para hacer que nuestra implementación rudimentaria de herencia de tabla única sea reutilizable, podemos usar la herencia abstracta de Django:

inheritance/models.py:

import sys
from django.db import models


class ProxySuper(models.Model):
    class Meta:
        abstract = True

    proxy_name = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        """ automatically store the proxy class name in the database """
        self.proxy_name = type(self).__name__
        super().save(*args, **kwargs)

    def __new__(cls, *args, **kwargs):
        """ create an instance corresponding to the proxy_name """
        proxy_class = cls
        try:
            field_name = ProxySuper._meta.get_fields()[0].name
            proxy_name = kwargs.get(field_name)
            if proxy_name is None:
                proxy_name_field_index = cls._meta.fields.index(
                    cls._meta.get_field(field_name))
                proxy_name = args[proxy_name_field_index]
            proxy_class = getattr(sys.modules[cls.__module__], proxy_name)
        finally:
            return super().__new__(proxy_class)


class ProxyManager(models.Manager):
    def get_queryset(self):
        """ only include objects in queryset matching current proxy class """
        return super().get_queryset().filter(proxy_name=self.model.__name__)

Entonces podemos implementar nuestra estructura de herencia de la siguiente manera:

parties/models.py:

from django.db import models
from inheritance.models import ProxySuper, ProxyManager


class Party(ProxySuper):
    name = models.CharField(max_length=20)
    person_attribute = models.CharField(max_length=20)
    organization_attribute = models.CharField(max_length=20)


class Person(Party):
    class Meta:
        proxy = True

    objects = ProxyManager()


class Organization(Party):
    class Meta:
        proxy = True

    objects = ProxyManager()


class Placement(models.Model):
    party = models.ForeignKey(to=Party, on_delete=models.CASCADE)

Es posible que se requiera más trabajo, según sus necesidades, pero creo que esto cubre algunos de los aspectos básicos.

Si piensas que ha resultado provechoso este post, sería de mucha ayuda si lo compartes con el resto juniors de este modo contrubuyes a difundir este contenido.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *