Source code for django_ufilter.backends.django

from django.core.exceptions import FieldDoesNotExist
from django.db.models.constants import LOOKUP_SEP

from ..utils import suppress
from .base import BaseFilterBackend


[docs]class DjangoFilterBackend(BaseFilterBackend): """ Filter backend for filtering Django querysets. .. warning:: The filter backend can **ONLY** filter Django's ``QuerySet``. Passing any other datatype for filtering will kill happy bunnies under rainbow. """ name = "django" supported_lookups = { "contains", "date", "day", "endswith", "exact", "gt", "gte", "hour", "icontains", "iendswith", "iexact", "in", "iregex", "isnull", "istartswith", "len", "lt", "lte", "minute", "month", "range", "regex", "second", "startswith", "week_day", "year", }
[docs] def empty(self): """ Get empty queryset """ return self.queryset.none()
[docs] def get_model(self): """ Get the model from the given queryset """ return self.queryset.model
@property def includes(self): """ Property which gets list of non-negated filters By combining all non-negated filters we can optimize filtering by calling ``QuerySet.filter`` once rather then calling it for each filter specification. """ return filter(lambda i: not i.is_negated, self.regular_specs) @property def excludes(self): """ Property which gets list of negated filters By combining all negated filters we can optimize filtering by calling ``QuerySet.exclude`` once rather then calling it for each filter specification. """ return filter(lambda i: i.is_negated, self.regular_specs) def _prepare_spec(self, spec): return "{}{}{}".format( LOOKUP_SEP.join(spec.components), LOOKUP_SEP, spec.lookup )
[docs] def filter_by_specs(self, queryset): """ Filter queryset by applying all filter specifications The filtering is done by calling ``QuerySet.filter`` and ``QuerySet.exclude`` as appropriate. """ include = {self._prepare_spec(i): i.value for i in self.includes} exclude = {self._prepare_spec(i): i.value for i in self.excludes} if include: queryset = queryset.filter(**include) # Plain queryset.exclude(**exclude) would cause exclusion of ALL # negative-matching objects. I.e. x!=1&y!=2 is equivalent # to "NOT (x = 1 AND y = 2)" SQL, which is not an intuitive behavior. # We chain .exclude to achieve "NOT (x = 1) AND NOT (y = 2)" instead. for lookup, value in exclude.items(): queryset = queryset.exclude(**{lookup: value}) to_many = self._is_any_to_many() return queryset.distinct() if to_many and (include or exclude) else queryset
def _is_any_to_many(self): return any( self._is_to_many(self.model, i.components) for i in self.regular_specs ) def _is_to_many(self, model, components): if not components: return False with suppress(FieldDoesNotExist): f = model._meta.get_field(components[0]) return ( f.one_to_many or f.many_to_many or self._is_to_many(f.related_model, components[1:]) )