Melissa Stewart
Melissa Stewart

Reputation: 3615

Excluding one row from a ManytoMany relationship in Django

This is my User object,

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True, max_length=255)
    mobile = PhoneNumberField(null=True)
    username = models.CharField(null=False, unique=True, max_length=255)
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)

And this is my Quiz object,

class Quiz(Base):
    category = models.ForeignKey(Category, related_name='quizzes', on_delete=models.CASCADE)
    players = models.ManyToManyField(User)

This is the filter that I'm using to find quizzes of a particular category that the user has not played,

quiz = Quiz.objects.filter(category=category).exclude(players=user).order_by('created')[:1]

But the exclude is a list and thus doesn't work. How do I correctly frame this query to exclude the current user?

Upvotes: 1

Views: 615

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477180

This answer aims to answer the following problem (since it is rather complicated, I want to make sure that it matches exactly):

"A queryset that contains Quizzes where we exclude the Quizzes hat have exactly one participant in the list of users". So those quizzes are not part of the queryset.

I would each time count the number of overlaps, and then exclude the Quizzes where the number of overlap is equal to zero. Something like:

Quiz.objects.filter(
    Q(players__in=users) | Q(players__isnull=True)
).annotate(
    overlap_size=Count('players')
).exclude(overlap_size=1)

We thus always calculate the "overlap": the number of users that are both in your list of users, and in the list of the participants. Then we this exclude the Quizzes where that overlap is exactly one, hence quizes where there was exactly one participant that was in the users list.

In case you want quizzes where the overlap is exactly one, you only have to replace the .exclude(..) (at the end) with a .filter(..) instead.

Upvotes: 1

Related Questions