Microfilters
Microfilters introduces two minimalist skeleton classes to build filters and filter sets in Python. I use them
mainly to implement filtering QuerySet
objects in Django, but they can be safely used wherever the pattern fits.
Act of purpose
This was born out of me not quite feeling any of the currently available Django filtering apps, like
django-filter or
django-easyfilters. Don't get me wrong, both apps are well
crafted and useful in almost any case where generic and/or introspective QuerySet
filtering is the feature you're
looking for. However, whenever I wanted to make customisations that go a little beyond the usual get_choices
override or lookup_type
adjustment, at some point I always felt like imposing my requirements onto something, that
is not ment to provide that kind of flexibility.
What's shipping?
The suggestion of an approach. That's it. No magic out-of-the-box s**t involved here.
Requirements
- Python 2.6, 2.7, 3.2, 3.3
- six >= 1.3
Installation
With pip
:
pip install microfilters
With setuptools
:
git clone https://github.com/lukasbuenger/microfilters.git cd microfilters/ python setup.py
Filter
and FilterSet
API
Microfilters literally consists of two classes that do actually nothing but providing a simple interface that
IMHO covers most filtering purposes. Both classes only implement two methods (FilterSet
being a subclass of
Filter
):
sanitize
- Gets passed a
value
. Should raise an error ifvalue
is not valid. Should return a evaluated and / or modified version ofvalue
that will be used for filtering. filter
- The actual filter process. Accepts a
item_list
and avalue
(preferably sanitized) and should return a filtered version ofitem_list
according tovalue
.
The main difference between Filter
and FilterSet
is, that Filter
objects should indeed filter lists, while
FilterSet
objects represent a set of Filter
objects and should delegate the filter operation to the
correspondent Filter
if present. This leads to the following:
-
FilterSet.sanitize
should accept and validate / modify a set of filter values instead of a single value. -
FilterSet.filter
should delegate the actual filtering of a givenitem_list
to registeredFilter
objects based on the givenvalue
set.
As mentioned before, a FilterSet
represents a collection of Filter
objects. We register
Filter
objects to a FilterSet
object by passing a dictionary to the constructor of the FilterSet
. It may
look something like this:
filter_set = FilterSet({ 'name': MyNameFilter(), 'city': MyCityFilter() })
Now if we pass a dictionary with correspondent keys to this FilterSet
, it delegates the handling of each value to
the registered Filter
that got registered with the correspondent key:
filter_set.filter({ 'name': 'Lukas', 'city': 'Zurich' })
Examples
Both Filter
and FilterSet
literally cry for getting subclassed. Here are two simple implementation examples:
Filtering a list of strings:
class StringListFilter(Filter): def sanitize(self, value): if not isinstance(value, six.text_type): raise Exception return value def filter(self, item_list, value): return list(filter(lambda x: value in x, item_list)) filter_set = FilterSet({ 'param1': StringListFilter(), 'param2': StringListFilter() }) item_list = ['Lukas', 'Peter' , 'Barbara', 'Stuart'] print filter_set.filter(item_list, { 'param1': 'u' }) # ['Lukas', 'Stuart'] print filter_set.filter(item_list, { 'param1': 'ar', 'param2': 'u' }) # ['Stuart']
Filtering of Django QuerySet
objects, based on fields:
class CharFilter(Filter): def __init__(self, field_name): self.field_name = field_name def sanitize(self, value): if not isinstance(value, six.text_type): raise Exception return value def filter(self, item_list, value): return item_list.filter(Q(**{'%s__contains' % self.field_name: value}) filter_set = FilterSet({ 'name': CharFilter('username'), 'email': CharFilter('email') }) queryset = User.objects.all() print filter_set.filter(queryset, { 'name': 'lukasbuenger', 'email': 'lukasbuenger@gmail.com' })
Running the tests
To run the unit tests, simply execute the runtests.py
in the microfilter/runtests
subdirectory in an environment
that has six installed:
python microfilter/runtests/runtests.py