Home > Back-end >  Polymorphic relationship in Django
Polymorphic relationship in Django

Time:07-05

I have this relationship between my models: Database Diagram

The model called MyModel might have a foreign key to model A or model B, How can I do this in Django? I heard that the solution called Polymorphic relationship but could not find the correct way to implement it.

My code:

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)

CodePudding user response:

It would be against sql rules.

If it does not have to be strictly set in ForeignKey, you can change approach a bit to get the proper object by MyModel.provider() function:

1st option:

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

2nd option:

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)

CodePudding user response:

It's likely you'll want to use django-polymorphic. To use their example, I think it shows what you're looking for;

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)

Querying Project.objects.all() will return all instances of Project, ArtProject and ResearchProject.

Django provides an alternative as well which you may not have heard of called a generic foreign key. It makes use of django's content types to link to any type of object that has a content type. To make use of this I use a model mixin to provide the necessary fields;

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')
  • Related