Designing a flexible relationships model in Django

I'm designing a small Django app, and writing some code snippets to go with the design. I have some simple models in the design, like Person and Book:

from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=127)

class Person(models.Model):
    name = models.CharField(max_length=127)

I want to be able to express multiple types of relationships between multiple models in the application, as the application grows. In the example above, there are multiple possible types of relationships, for example:

  • a Person is the author of a Book
  • a Person is the editor of a Book
  • a Person is the subject of a Book (e.g. if the book is a biography)
  • a Person is both the author and the subject of a book (e.g. if the book is an autobiography)

So I'm thinking of a model that expresses relationship types, like this:

from django.contrib.contenttypes.models import ContentType

class RelationshipType(models.Model):
    model_a = models.ForeignKey(ContentType, on_delete=models.PROTECT)
    model_b = models.ForeignKey(ContentType, on_delete=models.PROTECT)
    name = models.CharField(max_length=127)

The use of ContentType allows me to create relationship types between any pair of models. Finally, I think this is approximately what the relationship would look like (pseudo-code):

class Relationship(models.Model):
    type = models.ForeignKey(RelationshipType, on_delete=models.PROTECT)

    instance_a_id = models.PositiveBigIntegerField()
    instance_a = GenericForeignKey("model_a", "instance_a_id")

    instance_b_id = models.PositiveBigIntegerField()
    instance_b = GenericForeignKey("model_b", "instance_b_id")

The use of GenericForeignKey here is meant to allow the selection of any instance of model_a and model_b. The problem is that model_a and model_b are not in the Relationship model, so I cannot use them like in this example code.

I tried to find a way to make model_a and model_b instances of GeneratedField on Relationship, with the value coming from the type field of Relationship which already has relations to model_a and model_b. I seems to me like a reasonable approach, but I have not found the correct syntax. At the risk of having created an XY problem, I'm looking for correct syntax to achieve this:

class Relationship(models.Model):
    type = models.ForeignKey(RelationshipType, on_delete=models.PROTECT)

    model_a = models.GeneratedField ...? # an instance of ContentType?
    instance_a_id = models.PositiveBigIntegerField()
    instance_a = GenericForeignKey("model_a", "instance_a_id")

    model_b = models. GeneratedField... ? # an instance of ContentType?
    instance_b_id = models.PositiveBigIntegerField()
    instance_b = GenericForeignKey("model_b", "instance_b_id")

I've also tried to think whether this can be done with a default parameter on the model_a and model_b fields on Relationship, something like:

class Relationship(models.Model):
    type = models.ForeignKey(RelationshipType, on_delete=models.PROTECT)

    model_a = models.ForeignKey(ContentType, default=???)
    model_b = models.ForeignKey(ContentType, default=???)

But I don't see how to achieve that either.

Any ideas?

Вернуться на верх