Always True Q object
Asked Answered
M

6

16

I want to create some part of Django ORM filter query dinamicly, now I can do:

if some:
   Obj.filter(
       some_f1=some_v1,
       f1=v1,
       f2=v2,
       f3=v3,
       f4=v4,
       ...
   )
else:
   Obj.filter(
       f1=v1,
       f2=v2,
       f3=v3,
       f4=v4,
       ...
   )

I want something without code duplicate like this:

Obj.filter(
    Q(some_f1=some_v1) if some else True,  # what to use instead of True?
    f1=v1,
    f2=v2,
    f3=v3,
    f4=v4,
    ...
)
Morten answered 4/11, 2015 at 8:55 Comment(2)
Is there a way to do an always false Q object?Unpolite
It doesn't work. I've created the question asking how to create an always False Q object https://mcmap.net/q/748962/-always-false-q-object/247696Unpolite
U
20

Here's one way to get an always true Q object:

always_true = ~Q(pk__in=[])

The ORM optimizer recognises that Q(pk__in=[]) always evaluates to False, and the ~ negates it to make it True.

Unpolite answered 9/3, 2016 at 14:45 Comment(0)
M
4

as Alasdair answered in comment:

Obj.filter(
    Q(some_f1=some_v1) if some else Q(), 
    f1=v1,
    f2=v2,
    f3=v3,
    f4=v4,
    ...
)
Morten answered 20/1, 2016 at 14:28 Comment(3)
Note that Q() isn't really an always true Q object, it's an empty Q object. For example, both Foobar.objects.filter(Q()) and Foobar.objects.exclude(Q()) return all objects.Unpolite
Q() is a bad idea. In addition to what @Unpolite mentioned, Q() also gets optimised out of e.g. Foobar.objects.filter(Q() | CONDITION) and we're effectively left with Foobar.objects.filter(CONDITION).Calton
If you want an always true Q(), then ~Q(pk__in=[]) as suggested by Jonathan in the comments seems to be the best solution. However in the code in the question, the OP does want the Q() to be optimised out, so I disagree with @DartDega that it's a bad idea.Arrival
V
3

Try this;

conditions = {'f1':f1,'f2':f2, 'f3':f3}
if some:
    conditions['some_f1'] = some_v1

Obj.objects.filter(**conditions)
Vestpocket answered 4/11, 2015 at 9:5 Comment(0)
M
1

Based on this answer we can make conditional argument passing

Obj.filter(
    *( (Q(some_f1=some_v1),) if some  else ()),    
    f1=v1,
    f2=v2,
    f3=v3,
    f4=v4,
    ...
)

So, if some is True we add tuple (Q(some_f1=some_v1),) to arguments list, in other case we add empty tuple (), also we need to wrap it in *(), as always when we passing tuple of non-keyword args

Morten answered 4/11, 2015 at 9:11 Comment(3)
It would be great if you add some comments about the logic behind this trick (:Apothem
Personally, I think that moving the if else logic out of the filter() makes it easier to read. But if you prefer this style, you can use a Q() with no arguments: Q(some_f1=some_v1) if condition else Q(). This avoids the need for *args unpacking.Arrival
Alasdair, your comment is the answer, please postMorten
G
1

An "always true" Q object is equivalent to No Q object when using AND (basic filter syntax; i.e. x and True == x), thus a DRYer alternative for your use case is as follows:

filters = dict(f1=v1,
               f2=v2,
               f3=v3,
               f4=v4,
               ...)
if some:
    filters['some_f1'] = some_v1

qs = obj.filter(**filters)

Modify according to your needs.

Ghana answered 22/2, 2019 at 15:44 Comment(0)
H
0

Since QuerySets are lazy, you can create the default filter, and then add others base on your conditions. Django won't run the query until the QuerySet is evaluated (for example, iterating through it in a for loop)

filtered_objects = Obj.filter(
    some_f1=some_v1,
    f1=v1,
    f2=v2,
    f3=v3,
    f4=v4,
    ...
)
if some:
    filtered_objects.filter(some_f1=some_v1)
Hildredhildreth answered 9/3, 2016 at 14:18 Comment(1)
Note that this can give subtly different results (versus putting all filters in one filter call) when used on related fields. See https://mcmap.net/q/723905/-always-true-q-object for an alternative.Ghana

© 2022 - 2024 — McMap. All rights reserved.