With related lookups, I can easily get access to all the models I have to have a generic foreign key. Obviously, this is not what I want to do. I want to restrict it to just a sub set of the models I have -- specifically all the inherit from the abstract model Registry
.
My models look like thus:
class Registry(models.Model):
"""A base registry class."""
number = models.BigAutoField(primary_key=True)
when = models.DateField(default=timezone.now)
title = models.CharField(
max_length=1024, default='', blank=True, null=True)
class Meta:
"""The meta class."""
abstract = True
[…]
class Revision(models.Model):
"""A revision model."""
when = models.DateTimeField(default=timezone.now)
identification = models.BigIntegerField()
content_type = models.ForeignKey(
ContentType, on_delete=models.CASCADE, related_name='+')
object_id = models.PositiveIntegerField()
parent = GenericForeignKey('content_type', 'object_id')
[…]
class Document(Registry):
[…]
class Drawing(Registry):
[…]
So that each Registry
derived instances can have many different revisions.
And the relevant admin:
class RevisionAdmin(admin.ModelAdmin):
"""Revision administration definition."""
fieldsets = [
('Revision', {
'fields': [
'when',
'identification',
]
}),
('Registry', {
'classes': ('grp-collapse grp-open',),
'fields': ('content_type', 'object_id', )
}),
]
You can use a limit_choices_to
[Django-doc]. Since you want to limit the choices to the descendants, we will need to write some extra logic to calculate these first:
We can for example first calculate all the subclasses with this function:
def get_descendants(klass): gen = { klass } desc = set() while gen: gen = { skls for kls in gen for skls in kls.__subclasses__() } desc.update(gen) return desc
Now we can define a callable to obtain the primary keys of the ContentType
s that are subclasses of a class, in this case Registry
:
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
def filter_qs():
if not hasattr(filter_qs_registry, '_q'):
models = get_descendants(Registry)
pks = [v.pk for v in ContentType.objects.get_for_models(*models).values()]
filter_qs_registry._q = Q(pk__in=pks)
return filter_qs_registry._q
In the ForeignKey
to the ContentType
, we can then use the limited_choices_to
field:
class Revision(models.Model):
"""A revision model."""
when = models.DateTimeField(default=timezone.now)
identification = models.BigIntegerField()
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
limit_choices_to=filter_qs_registry,
related_name='+'
)
object_id = models.PositiveIntegerField()
parent = GenericForeignKey('content_type', 'object_id')
We can generalize the number of ascents, by generalizing for example the get_descendants
function:
def get_descendants(*klass): gen = { *klass } desc = set() while gen: gen = { skls for kls in gen for skls in kls.__subclasses__() } desc.update(gen) return desc
Next we can simply call it with:
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
def filter_qs():
if not hasattr(filter_qs_registry, '_q'):
models = get_descendants(Registry, OtherAbstractModel)
pks = [v.pk for v in ContentType.objects.get_for_models(*models).values()]
filter_qs_registry._q = Q(pk__in=pks)
return filter_qs_registry._q