Полиморфные отношения в Django
У меня такие отношения между моими моделями:
Модель под названием 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')