Skip to content

Instantly share code, notes, and snippets.

@arkanister
Created April 22, 2019 12:14
Show Gist options
  • Select an option

  • Save arkanister/2b6655aa0ac8420879a2062aac97d94b to your computer and use it in GitHub Desktop.

Select an option

Save arkanister/2b6655aa0ac8420879a2062aac97d94b to your computer and use it in GitHub Desktop.
Django filter interface to use in any kind of view
# coding: utf-8
"""
This is an interface for simplify the django queryset handler
when we need to apply a filter on it.
Usage:
>>> from core import models
>>> from filters import FilterableQuerysetMixin, SimpleFilter, SimpleSearchFilter
>>>
>>> class ListView(FilterableQuerysetMixin, ListView):
>>> model = models.Post
>>> template_name = 'posts.html'
>>> filters = [
>>> SimpleFilter('tag'),
>>> SimpleSearchFilter('q', fields=['title', 'content'])
>>> ]
>>>
"""
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
class BaseFilter:
"""
Define the filter object with expected features.
"""
def __init__(self, lookup_url_kwarg):
if not lookup_url_kwarg:
raise ImproperlyConfigured(
'"lookup_url_kwarg" is required on class %s.' %
self.__class__.__name__)
self.lookup_url_kwarg = lookup_url_kwarg
def filter(self, request, queryset, view):
"""
This method must implement a way to handle the queryset
and apply the filter.
Args:
request: (view.Request, required) - The view request.
queryset: (models.Queryset, required) - Queryset to be filtered.
view: (View, required) - The view that is handling the queryset.
Returns:
Queryset object.
"""
raise NotImplementedError('The "filter" method must be implemented.')
def __call__(self, request, queryset, view):
return self.filter(request, queryset, view)
class SimpleFilter(BaseFilter):
"""
Simple filter based on a single value.
"""
def __init__(self, lookup_url_kwarg, lookup_filter=None):
super().__init__(lookup_url_kwarg)
self.lookup_field = lookup_filter or lookup_url_kwarg
def filter(self, request, queryset, view):
"""
Filter the queryset based on a single value from url.
"""
value = request.GET.get(self.lookup_url_kwarg, None)
if value is not None:
queryset = queryset.filter(**{self.lookup_field: value})
return queryset
class SimpleSearchFilter(BaseFilter):
"""
Activate the search feature in the view.
"""
def __init__(self, lookup_url_kwarg, fields=None, lookup_suffix='icontains'):
super().__init__(lookup_url_kwarg)
if not fields or not isinstance(fields, (list, tuple)):
raise ImproperlyConfigured(
'The \'fields\' argument is required and must be a list of names.'
)
self.fields = fields
self.lookup_suffix = lookup_suffix
def get_lookup_fields(self):
"""
Walk into fields and grant that the field will
be on the correct django lookup format.
Possible values for lookups:
- contains, icontains
- startswith, istartswith
- endswith, iendswith
- exact
- None
"""
if not self.lookup_suffix:
# if there is no suffix to lookups just
# returns the fields.
return self.fields
return ['{field}__{lookup}'.format(
field=field,
lookup=self.lookup_suffix
) for field in self.fields]
def filter(self, request, queryset, view):
"""
Apply the search using all defined fields.
"""
value = request.GET.get(self.lookup_url_kwarg)
if not value:
# if there is no value to be searched
# just ignore the filter.
return queryset
lookups = Q()
for lookup in self.get_lookup_fields():
# concat all filters using OR condition
# and returns a valid lookup
# for queryset.
lookups |= Q(**{lookup: value})
# returns filtered queryset.
return queryset.filter(lookups)
class FilterableQuerysetMixin:
"""
Mixin for handling a queryset, which will return a
filtered queryset based on the filters specifications.
"""
filters = None
def get_filters(self):
"""
Returns all filter instances for this view.
"""
return self.filters or []
def apply_filters(self, request, queryset):
"""
Apply any defined filter in the
view to queryset.
"""
for _filter in self.get_filters():
queryset = _filter(request, queryset, view=self)
# returns filtered queryset.
return queryset
def get_queryset(self):
"""
After the base handle of the view into queryset, apply
view custom filters to modify the queryset.
"""
queryset = super().get_queryset()
# returns filtered queryset.
return self.apply_filters(request=self.request, queryset=queryset)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment