Полиморфные отношения в Django

У меня такие отношения между моими моделями: Database Diagram

Модель под названием MyModel может иметь внешний ключ к модели A или модели B, Как я могу сделать это в Django? Я слышал, что решение называется Polymorphic relationship, но не смог найти правильный способ его реализации.

Мой код:

from django.db import models


class A(models.Model):
    email = models.EmailField()
    national_id = models.IntegerField()


class B(models.Model):
    name = models.CharField(max_length=255)
    age = models.IntegerField()


class MyModel(models.Model):
    name = models.CharField(max_length=255)
    provider = models.ForeignKey(to=A, on_delete=models.CASCADE)

Это противоречит правилам sql.

Если он не должен быть строго задан в ForeignKey, можно немного изменить подход, чтобы получить нужный объект с помощью функции MyModel.provider():

1-й вариант:

class MyModel(models.Model):
    ...
    a = models.ForeignKey(to=A, on_delete=models.CASCADE)
    b = models.ForeignKey(to=B, on_delete=models.CASCADE)

    def provider(self):
        return self.a if self.a else self.b

2-й вариант:

class MyModel(models.Model):
    CHOICES = (('a', 'a'), ('b', 'b'))
    ...
    a = models.ForeignKey(to=A, on_delete=models.CASCADE)
    b = models.ForeignKey(to=B, on_delete=models.CASCADE)
    provider_type = models.CharField(max_length=255, choices=CHOICES)

    def provider(self):
        return getattr(self, self.provider_type)

Вероятно, вы захотите использовать django-polymorphic. Используя их пример, я думаю, он показывает, что вы ищете;

from polymorphic.models import PolymorphicModel

class Project(PolymorphicModel):
    topic = models.CharField(max_length=30)

class ArtProject(Project):
    artist = models.CharField(max_length=30)

class ResearchProject(Project):
    supervisor = models.CharField(max_length=30)

Запрос Project.objects.all() вернет все экземпляры Project, ArtProject и ResearchProject.

Django предоставляет альтернативу, о которой вы, возможно, не слышали, называемую общим внешним ключом. Он использует типы содержимого django для связи с любым типом объекта, который имеет тип содержимого. Чтобы воспользоваться этим, я использую модель mixin для обеспечения необходимых полей;

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.translation import gettext_lazy as _


class GenericForeignKeyMixin(models.Model):
    """
    Abstract mixin for adding a GenericForeignKey (named reference) to a model.
    """
    class Meta:
        """
        Metadata
        """
        abstract = True

    content_type = models.ForeignKey(
        verbose_name=_('Content type'),
        to=ContentType,
        blank=True,
        null=True,
        on_delete=models.CASCADE
    )
    object_id = models.PositiveIntegerField(
        verbose_name=_('Object ID'),
        blank=True,
        null=True,
        db_index=True,
    )
    reference = GenericForeignKey('content_type', 'object_id')
Вернуться на верх