Filter queryset by reverse exists check in Django
Question:
I have models:
class Post(models.Model):
content = models.CharField()
user = models.ForeignKey('users.User')
active = models.BooleanField(default=True)
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='post_comments')
And a queryset which is filtered by query params:
user = request.QUERY_PARAMS.get('user_id', None)
active = request.QUERY_PARAMS.get('active', None)
has_comments = request.QUERY_PARAMS.get('has_comments', None)
qs = Post.objects.all()
if user:
qs = qs.filter(user=user)
if active:
qs = qs.filter(active=active)
if has_comments:
???
I don’t get how I can filter this query while maintaining all the previous filters. Is it possible?
Answers:
According to the documentation:
To refer to a “reverse” relationship, just use the lowercase name of the model.
From this answer it follows your code would be:
if user:
qs = qs.filter(user=user)
if active:
qs = qs.filter(active=active)
if has_comments:
qs = qs.filter(comment__isnull=False)
With regards to performance take this answer into account:
Django doesn’t support the select_related() method for reverse foreign
key lookups, so the best you can do without leaving Python is two
database queries.
You should also have a look at prefetch_related
which can, unlike select_related
do lookups across reverse ForeignKeys
, albeit with a separate query for each element of the queryset.
The query you want is:
qs.filter(post_comments__isnull=False).distinct()
__isnull=False
on a reverse relationship allows you to filter the join for rows where a post_comment is present. However, this will give you duplicate results for each post_comment, so you need .distinct()
to dedupe.
I have models:
class Post(models.Model):
content = models.CharField()
user = models.ForeignKey('users.User')
active = models.BooleanField(default=True)
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='post_comments')
And a queryset which is filtered by query params:
user = request.QUERY_PARAMS.get('user_id', None)
active = request.QUERY_PARAMS.get('active', None)
has_comments = request.QUERY_PARAMS.get('has_comments', None)
qs = Post.objects.all()
if user:
qs = qs.filter(user=user)
if active:
qs = qs.filter(active=active)
if has_comments:
???
I don’t get how I can filter this query while maintaining all the previous filters. Is it possible?
According to the documentation:
To refer to a “reverse” relationship, just use the lowercase name of the model.
From this answer it follows your code would be:
if user:
qs = qs.filter(user=user)
if active:
qs = qs.filter(active=active)
if has_comments:
qs = qs.filter(comment__isnull=False)
With regards to performance take this answer into account:
Django doesn’t support the select_related() method for reverse foreign
key lookups, so the best you can do without leaving Python is two
database queries.
You should also have a look at prefetch_related
which can, unlike select_related
do lookups across reverse ForeignKeys
, albeit with a separate query for each element of the queryset.
The query you want is:
qs.filter(post_comments__isnull=False).distinct()
__isnull=False
on a reverse relationship allows you to filter the join for rows where a post_comment is present. However, this will give you duplicate results for each post_comment, so you need .distinct()
to dedupe.