Source code for url_filter.backends.base

# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
import abc

import six
from cached_property import cached_property


[docs]class BaseFilterBackend(six.with_metaclass(abc.ABCMeta, object)): """ Base filter backend from which all other backends must subclass. Parameters ---------- queryset Iterable which this filter backend will eventually filter. The type of the iterable depends on the filter backend. For example for :class:`.DjangoFilterBackend`, Django's ``QuerySet`` needs to be passed. context: dict Context dictionary. It could contain any information which potentially could be useful to filter given queryset. That can include, request, view, view kwargs, etc. The idea is similar to DRF serializers. By passing the context, it allows custom filters to reference all the information they need to be able to effectively filter data. """ name = None """ Name of the filter backend. This is used by custom callable filters to define callables for each supported backend. More at :class:`.CallableFilter` """ supported_lookups = set() """ Set of supported lookups this filter backend supports. This is used by leaf :class:`.Filter` to determine whether it should construct :class:`.FilterSpec` for a particular key-value pair from querystring since it if constructs specification but then filter backend will not be able to filter it, things will blow up. By explicitly checking if filter backend supports particular lookup it can short-circuit the logic and avoid errors down the road. This is pretty much the only coupling between filters and filter backends. """ enforce_same_models = True """ Whether same models should be enforced when trying to use this filter backend. More can be found in :meth:`BaseFilterBackend.model` """ def __init__(self, queryset, context=None): self.queryset = queryset self.context = context or {} self.specs = [] @cached_property def model(self): """ Property for getting model on which this filter backend operates. This is meant to be used by the integrations directly shipped with django-url-filter which need to be able to validate that the filterset will be able to filter given queryset. They can do that by comparing the model they are trying to filter matches the model the filterbackend got. This primarily will have misconfigurations such as using SQLAlchemy filterset to filter Django's ``QuerySet``. """ return self.get_model()
[docs] def bind(self, specs): """ Bind the given specs to the filter backend. This allows the filter backend to be instantiated first before filter specs are constructed and later, specs can be binded to the backend. Parameters ---------- specs : list List of :class:`.FilterSpec` to be binded for the filter backend for filtering """ self.specs = specs
@cached_property def regular_specs(self): """ Property for getting standard filter specifications which can be used directly by the filter backend to filter queryset. See Also -------- callable_specs """ return [i for i in self.specs if not i.is_callable] @cached_property def callable_specs(self): """ Property for getting custom filter specifications which have a filter callable for filtering querysets. These specifications cannot be directly used by filter backend and have to be called manually to filter data. See Also -------- regular_specs """ return [i for i in self.specs if i.is_callable]
[docs] @abc.abstractmethod def get_model(self): """ Get the queryset model. .. note:: **MUST** be implemented by subclasses. :meth:`.model` property uses this method to get the model. See Also -------- model """
[docs] def filter(self): """ Main public method for filtering querysets. """ qs = self.filter_by_specs(self.queryset) qs = self.filter_by_callables(qs) return qs
[docs] @abc.abstractmethod def filter_by_specs(self, queryset): """ Method for filtering queryset by using standard filter specs. .. note:: **MUST** be implemented by subclasses """
[docs] @abc.abstractmethod def empty(self): """ Method for returning empty queryset when any validations failed. """
[docs] def filter_by_callables(self, queryset): """ Method for filtering queryset by using custom filter callables as given in the :class:`.Filter` definition. This is really meant to accommodate filtering with simple filter keys having complex filtering logic behind them. More about custom callables can be found at :class:`.CallableFilter` """ if not self.callable_specs: return queryset for spec in self.callable_specs: queryset = spec.filter_callable( queryset=queryset, spec=spec, ) return queryset