Suppose I have an abstract model which is used by concrete models that implement their own table.
from django.db import models
class Animal(models.Model):
class Meta:
abstract = True
class Bear(models.Model):
pass
class Dog(models.Model):
pass
I have a table that will hold information related to the concrete classes that I want a foreign key relationship to. Using the documentation on the contenttypes framework I have the following generic relationship:
class AnimalProfile(models.Model):
information = models.TextField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
class Meta:
indexes = [
models.Index(fields=["content_type", "object_id"]),
]
How do I enforce AnimalProfile.content_object will only accept concrete models that subclasses from the abstract Animal
?
You can use add a validator that checks using content_type
field. This is a foreign key to a record in the ContentTypes
table, which you can use just like any other model.
You can look up the app_label
and model
within the record you’re trying to add the relationship to, and use apps.get_model
to fetch the model for your comparisons.
Note that it is a ForeignKey
, and so the value will be of whatever type your keys are. In my case, it’s an int.
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
def validate_content_subtype(id):
"""Validate that the content is a subclass of Animal."""
# Get the Animal abstract model
Animal = apps.get_model("myapp", "Animal")
# Get the model you're validating
# This will incur a database query
content_type_record = ContentType.objects.get(id=id)
content_type_model = apps.get_model(
content_type_record.app_label,
content_type_record.model,
)
if not issubclass(content_type_model, Animal):
raise ValidationError(
"Attribution can only be linked to subclasses of Content."
)
Then simply plug your validator onto the field
class AnimalProfile(models.Model):
# ...
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
# ...
This will ensure a ValidationError is raised every time you pass a model that isn’t a subclass of Animal
.