Reputation: 311
I have 4 parameters: city, province, strtype, direction, when the parameter is not empty I want to use it in filter, when it is empty, do not set it as filter keyword.
The part of current code I use is as below:
if (direction == '') & (strtype == '') & (city == '') & (province == ''):
queryset = address.objects.filter(addline__startswith=keyword)[:10]
if not queryset.exists():
queryset = address.objects.filter(strname__startswith=keyword)[:10]
return queryset
else:
return queryset
if (direction != '') & (strtype == '') & (city == '') & (province == ''):
queryset = address.objects.filter(addline__startswith=keyword,
strdir=direction)[:10]
if not queryset.exists():
queryset = address.objects.filter(strname__startswith=keyword,
strdir=direction)[:10]
return queryset
else:
return queryset
There are 16 posibilities, it means i need to write 16 if statements! This is too many codes and ungraceful, is there a terse solution?
Upvotes: 3
Views: 3421
Reputation: 476557
There are 16 posibilities, it means i need to write 16 if statements!
No!, not if these parameters act more or less independent. We can for example first abstract common logic away:
def add_custom_filters(qs, direction, strtype, city, province):
if direction:
qs = qs.filter(strdir=direction)
if strtype:
qs = qs.filter(strype=strtype)
if city:
qs = qs.filter(strcity=city)
if privince:
qs = qs.filter(strprov=privince)
return qs
(might need some altering)
So now we can use this logic like:
queryset = address.objects.filter(addline__startswith=keyword)
queryset = add_custom_filters(queryset, direction, strtype, city, province)[:10]
if not queryset:
queryset = address.objects.filter(strname__startswith=keyword)
queryset = add_custom_filters(queryset, direction, strtype, city, province)[:10]
return queryset
We thus only need four if
cases, and we reuse this function for the two-attempt approach.
Since filtering if not truthiness is True is a common pattern, we can encapsulate this in a helper function:
def filter_if_truthfull(qs, **kwargs):
retrurn qs.filter(**{k: v for k, v in kwargs.items() if v})
then we can use it like:
queryset = address.objects.filter(addline__startswith=keyword)
queryset = filter_if_truthfull(queryset, strdir=direction, strtype=strtype, strcity=city, strprov=province)[:10]
if not queryset:
queryset = address.objects.filter(strname__startswith=keyword)
queryset = filter_if_truthfull(queryset, strdir=direction, strtype=strtype, strcity=city, strprov=province)[:10]
return queryset
This allows us to add an arbitrary amount of named filter criteria that are only applied in case the value has truthiness true (for a string, this happens if the string not empty, in case it is None
, it is not a string, but these filters are also not considered).
In case you are going to use the results of a QuerySet
anyway, it is better to check if queryset
, since this will perform a query, that loads the elements into the queryset as well, whereas .exists()
will query with an EXISTS
query, and if you want to process the elements later, you need to perform an extra query to fetch them in memory.
Upvotes: 1
Reputation: 114461
You can build a dictionary and then pass it as keyword parameters using the double-star syntax f(**kwargs)
:
conds = {}
if direction != '': conds["strdir"] = direction
if strtype != '': conds["strtype"] = strtype
if province != '': conds["province"] = province
if city != '': conds["city"] = city
queryset = address.objects.filter(addline__startswith=keyword,
**conds)[:10]
if not queryset.exists():
queryset = address.objects.filter(strname__startswith=keyword,
**conds)[:10]
Upvotes: 7