Source code for vstutils.api.filters

import typing as _t
import operator
from functools import reduce

from django.conf import settings
from django.db.models import Q
from django.utils.functional import SimpleLazyObject
from django_filters import rest_framework as filters
from django_filters import CharFilter
from vstutils.utils import lazy_translate as __

from .. import utils

id_help = __('A unique integer value (or comma separated list) identifying this instance.')
name_help = __('A name string value (or comma separated list) of instance.')
name_filter_method = "contains" if settings.CASE_SENSITIVE_API_FILTER else 'icontains'


def _extra_query_search(field, value, stype):
    vals = field.rsplit("__", 1)
    field_name, tp = vals[0], (list(vals)[1:2] + [""])[0]
    negate = tp.upper() == "NOT"
    if negate:
        field = field_name
    field += f"__{stype}"
    value = value.split(",") if stype == "in" else value
    return {field: value}, negate


def _extra_search(queryset, field, value, stype):
    query_filter, negate = _extra_query_search(field, value, stype)
    if negate:
        return queryset.exclude(**query_filter)
    return queryset.filter(**query_filter)


[docs]def extra_filter(queryset, field, value): """ Method for searching values in a comma-separated list. :param queryset: model queryset for filtration. :type queryset: django.db.models.query.QuerySet :param field: field name in FilterSet. Also supports `__not` suffix. :type field: str :param value: comma separated list of searching values. :type value: str :return: filtered queryset. :rtype: django.db.models.query.QuerySet """ return _extra_search(queryset, field, value, "in")
[docs]def name_filter(queryset, field, value): """ Method for searching by part of name. Uses `LIKE` DB condition or `contains` qs-expression. :param queryset: model queryset for filtration. :type queryset: django.db.models.query.QuerySet :param field: field name in FilterSet. Also supports `__not` suffix. :type field: str :param value: searching part of name. :type value: str :return: filtered queryset. :rtype: django.db.models.query.QuerySet """ return _extra_search(queryset, field, value, name_filter_method)
[docs]class FkFilterHandler: """ Simple handler for filtering by relational fields. :param related_pk: Field name of related model's primary key. Default is 'id'. :param related_name: Field name of related model's charfield. Default is 'name'. :param pk_handler: Changes handler for checking value before search. Sends "0" in handler falls. Default is 'int()'. Example: .. sourcecode:: python class CustomFilterSet(filters.FilterSet): author = CharFilter(method=vst_filters.FkFilterHandler(related_pk='pk', related_name='email')) Where ``author`` is ForeignKey to `User` and you want to search by primary key and email. """ __slots__ = ('related_pk', 'related_name', 'pk_handler') def __init__(self, related_pk: str = 'id', related_name: str = 'name', pk_handler: _t.Callable = int): self.related_pk = related_pk self.related_name = related_name self.pk_handler = pk_handler def __get_q(self, kw, negate): query = Q(**kw) if negate: query.negate() return query def list_of_q(self, field, value): field_split = field.rsplit("__", 1) if len(field_split) > 1 and field_split[-1] == 'not': field, suffix = field_split[0], '__not' else: suffix = '' if self.related_pk: pk_value = SimpleLazyObject( utils.raise_context_decorator_with_default(default='0')( lambda: str(self.pk_handler(value)) or "0" ) ) yield self.__get_q(*_extra_query_search(f'{field}__{self.related_pk}{suffix}', pk_value, "in")) if self.related_name: yield self.__get_q(*_extra_query_search(f'{field}__{self.related_name}{suffix}', value, name_filter_method)) def __call__(self, queryset, field, value): return queryset.filter( reduce( operator.or_, self.list_of_q(field, value) ) )
[docs]class DefaultIDFilter(filters.FilterSet): """ Basic filterset to search by id. Provides a search for multiple values separated by commas. Uses :func:`.extra_filter` in fields. """ id = CharFilter(method=extra_filter, help_text=id_help, label="Primary keys") id__not = CharFilter(method=extra_filter, help_text=id_help, label="Exclude primary keys")
[docs]class DefaultNameFilter(filters.FilterSet): """ Basic filterset to search by part of name. Uses `LIKE` DB condition by :func:`.name_filter`. """ name = CharFilter(method=name_filter, help_text=name_help, label="Name") name__not = CharFilter(method=name_filter, help_text=name_help, label='Exclude name')