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:
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 Party
y 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_name
y 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.