Source code for url_filter.utils

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


[docs]class FilterSpec(object): """ Class for describing filter specification. The main job of the ``FilterSet`` is to parse the submitted lookups into a list of filter specs. A list of these specs is then used by the filter backend to actually filter given queryset. The reason why filtering is decoupled from the ``FilterSet`` is because this allows to implement filter backends not related to Django. Attributes ---------- components : list A list of strings which are names of the keys/attributes to be used in filtering of the queryset. For example lookup config with key ``user__profile__email`` will be components of ``['user', 'profile', 'email']. lookup : str Name of the lookup how final key/attribute from ``components`` should be compared. For example lookup config with key ``user__profile__email__contains`` will have a lookup ``contains``. value Value of the filter. is_negated : bool, optional Whether this filter should be negated. By default its ``False``. """ def __init__(self, components, lookup, value, is_negated=False): self.components = components self.lookup = lookup self.value = value self.is_negated = is_negated def __repr__(self): return '<{} {} {}{} {}>'.format( self.__class__.__name__, '.'.join(self.components), 'NOT ' if self.is_negated else '', self.lookup, repr(self.value), ) def __eq__(self, other): return hash(self) == hash(other) def __hash__(self): return hash(repr(self))
[docs]class LookupConfig(object): """ Lookup configuration which is used by ``FilterSet`` to create a ``FilterSpec``. The main purpose of this config is to allow the use if recursion in ``FilterSet``. Each lookup key (the keys in the querystring) is parsed into a nested one-key dictionary which lookup config stores. For example the querystring:: ?user__profile__email__endswith=gmail.com is parsed into the following config:: { 'user': { 'profile': { 'email': { 'endswith': 'gmail.com' } } } } Attributes ---------- key : str Full lookup key from the querystring. For example ``user__profile__email__endswith`` data : dict, str Either: * nested dictionary where the key is the next key within the lookup chain and value is another ``LookupConfig`` * the filtering value as provided in the querystring value Parameters ---------- key : str Full lookup key from the querystring. data : dict, str A regular vanilla Python dictionary. This class automatically converts nested dictionaries to instances of LookupConfig. Alternatively a filtering value as provided in the querystring. """ def __init__(self, key, data): if isinstance(data, dict): data = {k: self.__class__(key, v) for k, v in data.items()} self.key = key self.data = data
[docs] def is_key_value(self): return len(self.data) == 1 and not isinstance(self.value, dict)
@property def name(self): """ If the ``data`` is nested ``LookupConfig``, this gets its first lookup key. """ return next(iter(self.data.keys())) @property def value(self): """ If the ``data`` is nested ``LookupConfig``, this gets its first lookup value which could either be another ``LookupConfig`` or actual filtering value. """ return next(iter(self.data.values()))
[docs] def as_dict(self): """ Converts the nested ``LookupConfig``s to a regular ``dict``. """ if isinstance(self.data, dict): return {k: v.as_dict() for k, v in self.data.items()} return self.data
def __repr__(self): return '<{} {}=>{}>'.format( self.__class__.__name__, self.key, repr(self.as_dict()), )
[docs]class SubClassDict(dict): """ Special-purpose ``dict`` with special getter for looking up values by finding matching subclasses. This is better illustrated in an example:: >>> class Klass(object): pass >>> class Foo(object): pass >>> class Bar(Foo): pass >>> mapping = SubClassDict({ ... Foo: 'foo', ... Klass: 'klass', ... }) >>> print(mapping.get(Klass)) klass >>> print(mapping.get(Foo)) foo >>> print(mapping.get(Bar)) foo """
[docs] def get(self, k, d=None): """ If no value is found by using Python's default implementation, try to find the value where the key is a base class of the provided search class. """ value = super(SubClassDict, self).get(k, d) # try to match by value if value is d and inspect.isclass(k): for klass, v in self.items(): if not inspect.isclass(klass): continue if issubclass(k, klass): return v return value