14

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,
    ...
)
Tomasz Jakub Rup
  • 10,502
  • 7
  • 48
  • 49
Ivan Borshchov
  • 3,036
  • 5
  • 40
  • 62
  • 1
    Is there a way to do an always false Q object? – Flimm Mar 09 '16 at 12:40
  • It doesn't work. I've created the question asking how to create an always False Q object http://stackoverflow.com/q/35893867/247696 – Flimm Mar 09 '16 at 14:08

6 Answers6

16

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.

Flimm
  • 136,138
  • 45
  • 251
  • 267
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,
    ...
)
Ivan Borshchov
  • 3,036
  • 5
  • 40
  • 62
  • 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. – Flimm Mar 09 '16 at 14:42
  • `Q()` is a bad idea. In addition to what @Flimm mentioned, `Q()` also gets optimised out of e.g. `Foobar.objects.filter(Q() | CONDITION)` and we're effectively left with `Foobar.objects.filter(CONDITION)`. – Dart Dega Apr 03 '18 at 08:49
  • If you want an always true `Q()`, then `~Q(pk__in=[])` as suggested by Jonathan [in the comments](https://stackoverflow.com/a/35894763/113962) 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. – Alasdair Jan 15 '20 at 11:09
3

Try this;

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

Obj.objects.filter(**conditions)
Geo Jacob
  • 5,909
  • 1
  • 36
  • 43
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

Community
  • 1
  • 1
Ivan Borshchov
  • 3,036
  • 5
  • 40
  • 62
  • It would be great if you add some comments about the logic behind this trick (: – Mp0int Nov 04 '15 at 09:38
  • 3
    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. – Alasdair Nov 04 '15 at 10:10
  • Alasdair, your comment is the answer, please post – Ivan Borshchov Nov 04 '15 at 11:27
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.

DylanYoung
  • 2,423
  • 27
  • 30
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)
dnaranjo
  • 3,647
  • 3
  • 27
  • 41
  • 1
    Note that this can give subtly different results (versus putting all filters in one filter call) when used on related fields. See https://stackoverflow.com/a/54830561/3124256 for an alternative. – DylanYoung Feb 22 '19 at 15:45