Perform a logical exclusive OR for a Django Q object

I would like to perform a logical exclusive OR ( XOR ) on django.db.models.Q objects, using operator to restrict the selection of the model field to a subset of the foreign key. I do this in Django 1.4.3 along with Python 2.7.2. I had something like this:

import operator from django.conf import settings from django.db import models from django.db.models import Q from django.contrib.auth.models import User, Group def query_group_lkup(group_name): return Q(user__user__groups__name__exact=group_name) class Book(models.Model): author = models.ForeignKey( User, verbose_name=_("Author"), null=False, default='', related_name="%(app_label)s_%(class)s_author", # This would have provide an exclusive OR on the selected group name for User limit_choices_to=reduce( operator.xor, map(query_group_lkup, getattr(settings, 'AUTHORIZED_AUTHORS', '')) ) 

AUTHORIZED_AUTHORS - a list of existing group names.

But this did not work, because the Q objects do not support the ^ operator (only | and the operators from docs ). The message from stacktrace was (partially) as follows:

 File "/home/moi/.virtualenvs/venv/lib/python2.7/site-packages/django/db/models/loading.py", line 64, in _populate self.load_app(app_name, True) File "/home/moi/.virtualenvs/venv/lib/python2.7/site-packages/django/db/models/loading.py", line 88, in load_app models = import_module('.models', app_name) File "/home/moi/.virtualenvs/venv/lib/python2.7/site-packages/django/utils/importlib.py", line 35, in import_module __import__(name) File "/opt/dvpt/toto/apps/book/models.py", line 42, in <module> class Book(models.Model): File "/opt/dvpt/toto/apps/book/models.py", line 100, in Book map(query_group_lkup, getattr(settings, 'AUTHORIZED_AUTHORS', '')) TypeError: unsupported operand type(s) for ^: 'Q' and 'Q' 

Therefore, inspired by this answer, I tried to implement XOR for my specific search. This is not very flexible since the search is hard-coded (I will need to use kwargs in the query_xor arguments, for example ...). I ended up doing something like this:

 from django.conf import settings from django.db import models from django.db.models import Q from django.db.models.query import EmptyQuerySet from django.contrib.auth.models import User, Group def query_xor_group(names_group): """Get a XOR of the queries that match the group names in names_group.""" if not len(names_group): return EmptyQuerySet() elif len(names_group) == 1: return Q(user__user__groups__name__exact=names_group[0]) q_chain_or = Q(user__user__groups__name__exact=names_group[0]) q_chain_and = Q(user__user__groups__name__exact=names_group[0]) for name in names_group[1:]: query = Q(user__user__groups__name__exact=name) q_chain_or |= query q_chain_and &= query return q_chain_or & ~q_chain_and class Book(models.Model): author = models.ForeignKey( User, verbose_name=_("author"), null=False, default='', related_name="%(app_label)s_%(class)s_author", # This provides an exclusive OR on the SELECT group name for User limit_choices_to=query_xor_group(getattr(settings, 'AUTHORIZED_AUTHORS', '')) ) 

It works the way I want, but it seems to me rather not pythonic (especially the query_xor_group method). Would it be better (a more direct way) to do this?

Basically, my question may be deprived of the limit_choices_to element and summarized as:

How can I do a bitwise exclusive OR on a lot of django.db.models.Q objects in Djangonic?

+4
source share
1 answer

You can add the __xor__() method to Q, which uses and / or / does not execute XOR logic.

 from django.db.models import Q class QQ: def __xor__(self, other): not_self = self.clone() not_other = other.clone() not_self.negate() not_other.negate() x = self & not_other y = not_self & other return x | y Q.__bases__ += (QQ, ) 

After that, I was able to Q(...) ^ Q(...) make a filter() call.

 Foobar.objects.filter(Q(blah=1) ^ Q(bar=2)) 

This means that the initial attempt no longer throws an unsupported operand exception.

 limit_choices_to=reduce( operator.xor, map(query_group_lkup, getattr(settings, 'AUTHORIZED_AUTHORS', '')) ) 

Tested in Django 1.6.1 on Python 2.7.5

+9
source

All Articles