Created
April 22, 2019 12:14
-
-
Save arkanister/2b6655aa0ac8420879a2062aac97d94b to your computer and use it in GitHub Desktop.
Django filter interface to use in any kind of view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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