Hello!
I’m building an app that is using a bunch of Q objects to create user-specified filters. I have now an issue that also exhibits when creating Q objects on the shell, and I would appreciate any kinds of clarifications what’s happening.
Here are my models, with only the fields I’m querying against.
from django.db import models
from django.db.models import Q
class ArticleTag(models.Model):
key = models.CharField(max_length=255, unique=True)
class Article(models.Model):
tags = models.ManyToManyField(ArticleTag)
When I create models and associate tags, like this (I have a lot more in my DB).
sport = ArticleTag.objects.create(key="sport")
football = ArticleTag.objects.create(key="football")
skating = ArticleTag.objects.create(key="skating")
economy = ArticleTag.objects.create(key="economy")
Article.objects.create(tags=[sport, football])
Article.objects.create(tags=[sport, skating])
Article.objects.create(tags=[economy, football])
I want to get all articles that have the tag “sport” but not the tag “football”. I can’t revert to using filter and exclude in this, since I’m not writing the queries in Python code in my real use case, and Q objects serve as a nice way of moving the filters across my Django app’s modules.
sport_without_football = Article.objects.filter(Q(tags__key__in=["sport"]) & ~Q(tags__key__in=["football"]))
sport_without_football_b = Article.objects.filter(~Q(tags__key__in=["football"]) & Q(tags__key__in=["sport"]))
print([[tag.key for tag in art.tags.all()] for art in sport_without_football.all()])
# some of these have "football"
print([[tag.key for tag in art.tags.all()] for art in sport_without_football_b.all()])
# here none have "football", which is what I'm expecting.
So, if I combine the Qs one way, it works like I’m expecting, but not the other way around.
Is the order of arguments intentionally significant? Are the ~ and & operators an appropriate way to achieve this? I’m running Django 4.1.13, psycopg2 2.9.5 and PostgreSQL 15.2.