I'm looking to define an alias to a foreign key related set so that it can then be used in a generic filtering function. To explain with a little more detail here is a simplified example of my use case demonstrating what I'm trying to achieve:
models.py
from django.db import models
class Family(models.Model):
family_name = models.CharField(max_length=100)
pets: models.Manager['Pet'] = ...
members: models.Manager['Person'] = ...
def __str__(self):
return f'{self.id} - {self.family_name}'
class Pet(models.Model):
class PetType(models.TextChoices):
CAT = 'cat'
DOG = 'dog'
LIZARD = 'lizard'
name = models.CharField(max_length=100)
type = models.CharField(max_length=100, choices=PetType)
family = models.ForeignKey(Family, on_delete=models.CASCADE, related_name='pets')
def __str__(self):
return f'{self.id} - {self.name} {self.family.family_name} [{self.type}]'
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
family = models.ForeignKey(Family, on_delete=models.CASCADE, related_name='members')
@property
def pets(self) -> models.Manager[Pet]:
return self.family.pets
def __str__(self):
return f'{self.id} - {self.name} {self.family.family_name}'
If I have a Person
entity, I can use it's pets
property to get the pets from the family, what I'm looking for is a way to add an alias/annotation/whatever to a queryset of Person
so that I can define:
def has_a_cat(query_set):
return query_set.filter(pets__type='cat')
and then be able to use that for both Family
and Person
query sets. I know I can filter a Person
queryset by the pet type because Person.objects.filter(family__pets__type='cat')
works perfectly fine, but I'd like a way to alias family__pets
to pets
so I can use the shared filter.
I've tried using .annotate(pets=F('family__pets'))
and .alias(pets=F('family__pets))
but then when filtering on pets__type
I get the following error:
django.core.exceptions.FieldError: Unsupported lookup 'type' for BigAutoField or join on the field not permitted.
The solution that worked was provided by willem-van-ossem in the comments under my question.
To solve this we can annotate the query set with a FilteredRelation
which allows applying the queries I was looking to apply. Further a custom Manager
implementation like so:
class PersonManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(pets=FilteredRelation('family__pets'))
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
family = models.ForeignKey(Family, on_delete=models.CASCADE, related_name='members')
objects = PersonManager()
@property
def pets(self) -> models.Manager[Pet]:
return self.family.pets
def __str__(self):
return f'{self.id} - {self.name} {self.family.family_name}'
With the above in place we can filter based on pets
on both a Family
and Person
models and querysets (see question for remaining model classes).