I want to filter multiple fields with multiple queries like this:
api/listings/?subburb=Subburb1, Subburb2&property_type=House,Apartment,Townhouse,Farm .. etc
Are there any built in ways, I looked at django-filters but it seems limited, and I think I would have to do this manually in my api view, but its getting messy, filtering on filters on filters
filtering on filters on filters is not messy it is called chained filters
.
And chain filters are necessary because sometime there is going to be property_type
some time not:
if property_type:
qs = qs.filter(property_type=property_type)
If you are thinking there is going to be multiple queries then not, it will still executed in one query because queryset are lazy.
Alternatively you can build a dict and pass it just one time:
d = {'property_type:': property_type, 'subburb': subburb}
qs = MyModel.objects.filter(**d)
Complex filters are not out of the box supported by DRF or even by django-filter plugin. For simple cases you can define your own get_queryset method
This is straight from the documentation
def get_queryset(self):
queryset = Purchase.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username=username)
return queryset
However this can quickly become messy if you are supported multiple filters and even some of them complex.
The solution is to define a custom filterBackend class and a ViewSet Mixin. This mixins tells the viewset how to understand a typical filter backend and this backend can understand very complex filters all defined explicitly, including rules when those filters should be applied.
A sample filter backend is like this (I have defined three different filters on different query parameters in the increasing order of complexity:
class SomeFiltersBackend(FiltersBackendBase):
"""
Filter backend class to compliment GenericFilterMixin from utils/mixin.
"""
mapping = {'owner': 'filter_by_owner',
'catness': 'filter_by_catness',
'context': 'filter_by_context'}
def rule(self):
return resolve(self.request.path_info).url_name == 'pet-owners-list'
Straight forward filter on ORM lookups.
def filter_by_catness(self, value):
"""
A simple filter to display owners of pets with high catness, canines excuse.
"""
catness = self.request.query_params.get('catness')
return Q(owner__pet__catness__gt=catness)
def filter_by_owner(self, value):
if value == 'me':
return Q(owner=self.request.user.profile)
elif value.isdigit():
try:
profile = PetOwnerProfile.objects.get(user__id=value)
except PetOwnerProfile.DoesNotExist:
raise ValidationError('Owner does not exist')
return Q(owner=profile)
else:
raise ValidationError('Wrong filter applied with owner')
More complex filters :
def filter_by_context(self, value):
"""
value = {"context_type" : "context_id or context_ids separated by comma"}
"""
import json
try:
context = json.loads(value)
except json.JSONDecodeError as e:
raise ValidationError(e)
context_type, context_ids = context.items()
context_ids = [int(i) for i in context_ids]
if context_type == 'default':
ids = context_ids
else:
ids = Context.get_ids_by_unsupported_contexts(context_type, context_ids)
else:
raise ValidationError('Wrong context type found')
return Q(context_id__in=ids)
To understand fully how this works, you can read up my detailed blogpost : http://iank.it/pluggable-filters-for-django-rest-framework/
All the code is there in a Gist as well : https://gist.github.com/ankitml/fc8f4cf30ff40e19eae6