20

I'm using python's mongoengine to query MongoDB, and have loved it for the most part, but I'm having an issue with an advanced query.

Here's my model

class ContentItem(Document):
    account = ReferenceField(Account)
    creator = ReferenceField(User)
    public = BooleanField(default=False) 
    last_used = DateTimeField(default=datetime.now)

I would like to make a query for all ContentItem's that are of a particular account, and are either created by the logged in user or are public. Here's the query I wrote

query = ContentItem.objects.filter( (Q(account=account) & Q(public=True)) |  (Q(account=account) & Q(creator=logged_in_user)) ).order_by('-last_used')

or:

query = ContentItem.objects.filter( Q(account=account) & ( Q(public=True) |  Q(creator=logged_in_user) ) ).order_by('-last_used')

But these seem to be XOR where if either public, or the creator but not both. Is this expected?

Am I overlooking something? Should I do this directly with mongodb instead of mongoengine?

My current workaround is to make two different queries and combine the results, but as the # of Content Items gets larger the result is taking a long time to come back because I need to get all items before I can order them, thereby losing all the benefit of (django) paginated results.

bagerard
  • 5,681
  • 3
  • 24
  • 48
MattoTodd
  • 14,467
  • 16
  • 59
  • 76

5 Answers5

12

MongoEngine docs say otherwise. Please check this: http://docs.mongoengine.org/guide/querying.html

Gianfranco P.
  • 10,049
  • 6
  • 51
  • 68
Richil
  • 141
  • 1
  • 3
6

The correct way to do the query is to use bitwise operations | and & the way you wrote it in your question:

query = ContentItem.objects.filter( (Q(account=account) & Q(public=True)) |  (Q(account=account) & Q(creator=logged_in_user)) ).order_by('-last_used')

Note: using the standard Python boolean operators and and or will not work. This is explained in the MongoEngine documentation.

qff
  • 5,524
  • 3
  • 37
  • 62
5

The mongoengine documentation is apparently incorrect in this case. Instead of using the bitwise operators "&" and "|", you should use the standard operators "and" and "or".

So your first query becomes:

query = ContentItem.objects.filter( (Q(account=account) and Q(public=True)) or  (Q(account=account) and Q(creator=logged_in_user)) ).order_by('-last_used')
apiguy
  • 5,282
  • 1
  • 23
  • 24
  • that did the trick. i added a note on the github account to fix the documentation https://github.com/hmarr/mongoengine/issues/363 – MattoTodd Nov 21 '11 at 21:44
  • This is NOT the case; see the github issue referenced above. I tried this, but using `or` instead of `|` will not apply the filter. Using `&` and `|` works just fine for me. – Paul Aug 27 '12 at 12:33
  • 2
    @Paul - perhaps you did not notice that this post was 9 months old, and that the bug has since been fixed? – apiguy Aug 28 '12 at 01:58
  • 8
    @apiguy Nope. You were wrong at the first place. It's impossible to override the `and`/`or` in Python, instead you can override `|`/`&`. The source code of Q (mongoengine/queryset/visitor.py )and its history confirms that – Leonardo.Z Nov 05 '13 at 07:56
  • 5
    You have to use bitwise operators. You cannot use or, and to combine queries as Q(a=a) or Q(b=b) is not the same as Q(a=a) | Q(b=b). As Q(a=a) equates to true Q(a=a) or Q(b=b) is the same as Q(a=a). Docs -> https://mongoengine-odm.readthedocs.org/guide/querying.html#advanced-queries – Adil Abbasi Aug 03 '15 at 06:33
2

https://github.com/MongoEngine/mongoengine/blob/master/tests/queryset/transform.py

from line 134:

def test_raw_query_and_Q_objects(self):
    query = Foo.objects(__raw__={'$nor': [{'name': 'bar'}]})._query
    self.assertEqual(query, {'$nor': [{'name': 'bar'}]})

    q1 = {'$or': [{'a': 1}, {'b': 1}]}
    query = Foo.objects(Q(__raw__=q1) & Q(c=1))._query
    self.assertEqual(query, {'$or': [{'a': 1}, {'b': 1}], 'c': 1})
egvo
  • 1,493
  • 18
  • 26
likaiguo.happy
  • 2,138
  • 1
  • 15
  • 9
2

you are probably import the wrong Q

from mongoengine.queryset.visitor import Q as mongo_Q

from django.db.models import Q as normal_Q
Ghasem
  • 14,455
  • 21
  • 138
  • 171