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,
...
)
Here's a hacky way to get an always true Q object:
always_true = ~Q(pk=None)
This depends on the fact that the primary key cannot be null.
Try this;
conditions = {'f1':f1,'f2':f2, 'f3':f3}
if some:
conditions['some_f1'] = some_v1
Obj.objects.filter(**conditions)
as Alasdair answered in comment:
Obj.filter(
Q(some_f1=some_v1) if some else Q(),
f1=v1,
f2=v2,
f3=v3,
f4=v4,
...
)
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
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)
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.