Skip to content

Update django-filter to 1.0 #144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions rest_framework_filters/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,30 @@
from django.utils import six

from django_filters.rest_framework.filters import *
from rest_framework_filters.utils import import_class


ALL_LOOKUPS = '__all__'


def _import_class(path):
module_path, class_name = path.rsplit('.', 1)
class_name = str(class_name) # Ensure not unicode on py2.x
module = __import__(module_path, fromlist=[class_name], level=0)
return getattr(module, class_name)
class AutoFilter(Filter):
def __init__(self, *args, **kwargs):
self.lookups = kwargs.pop('lookups', [])

super(AutoFilter, self).__init__(*args, **kwargs)

class RelatedFilter(ModelChoiceFilter):
def __init__(self, filterset, lookups=None, *args, **kwargs):

class RelatedFilter(AutoFilter, ModelChoiceFilter):
def __init__(self, filterset, *args, **kwargs):
self.filterset = filterset
self.lookups = lookups
return super(RelatedFilter, self).__init__(*args, **kwargs)
kwargs.setdefault('lookups', None)

super(RelatedFilter, self).__init__(*args, **kwargs)

def filterset():
def fget(self):
if isinstance(self._filterset, six.string_types):
self._filterset = _import_class(self._filterset)
self._filterset = import_class(self._filterset)
return self._filterset

def fset(self, value):
Expand All @@ -34,12 +36,15 @@ def fset(self, value):
return locals()
filterset = property(**filterset())

@property
def field(self):
# if no queryset is provided, default to the filterset's default queryset
self.extra.setdefault('queryset', self.filterset._meta.model._default_manager.all())
return super(RelatedFilter, self).field
def get_queryset(self, request):
queryset = super(RelatedFilter, self).get_queryset(request)
if queryset is not None:
return queryset
return self.filterset._meta.model._default_manager.all()


class AllLookupsFilter(AutoFilter):
def __init__(self, *args, **kwargs):
kwargs.setdefault('lookups', ALL_LOOKUPS)

class AllLookupsFilter(Filter):
lookups = ALL_LOOKUPS
super(AllLookupsFilter, self).__init__(*args, **kwargs)
70 changes: 34 additions & 36 deletions rest_framework_filters/filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,45 @@
class FilterSetMetaclass(filterset.FilterSetMetaclass):
def __new__(cls, name, bases, attrs):
new_class = super(FilterSetMetaclass, cls).__new__(cls, name, bases, attrs)

opts = copy.deepcopy(new_class._meta)
orig_meta = new_class._meta

declared_filters = new_class.declared_filters.copy()
orig_declared = new_class.declared_filters

# If no model is defined, skip all lookups processing
# If no model is defined, skip auto filter processing
if not opts.model:
return new_class

# Determine declared filters and filters to generate lookups from. Declared
# filters have precedence over generated filters and should not be overwritten.
declared_filters, lookups_filters = OrderedDict(), OrderedDict()
for name, f in six.iteritems(new_class.declared_filters):
if isinstance(f, (filters.AllLookupsFilter, filters.RelatedFilter)):
lookups_filters[name] = f
# Generate filters for auto filters
auto_filters = OrderedDict([
(param, f) for param, f in six.iteritems(new_class.declared_filters)
if isinstance(f, filters.AutoFilter)
])

# `AllLookupsFilter` is an exception, as it should be overwritten
if not isinstance(f, filters.AllLookupsFilter):
declared_filters[name] = f
# Remove auto filters from declared_filters so that they *are* overwritten
# RelatedFilter is an exception, and should *not* be overwritten
for param, f in six.iteritems(auto_filters):
if not isinstance(f, filters.RelatedFilter):
del declared_filters[param]

# generate filters for AllLookups/Related filters
# name is the parameter name on the filterset, f.name is the model field's name
for name, f in six.iteritems(lookups_filters):
for param, f in six.iteritems(auto_filters):
opts.fields = {f.name: f.lookups or []}
new_filters = new_class.filters_for_model(opts.model, opts)

# filters_for_model generate param names from the model field name
# replace model field name with the parameter name from the filerset
# patch, generate auto filters
new_class._meta, new_class.declared_filters = opts, declared_filters
generated_filters = new_class.get_filters()

# get_filters() generates param names from the model field name
# Replace the field name with the parameter name from the filerset
new_class.base_filters.update(OrderedDict(
(param.replace(f.name, name, 1), v)
for param, v in six.iteritems(new_filters)
(gen_param.replace(f.name, param, 1), gen_f)
for gen_param, gen_f in six.iteritems(generated_filters)
))

# re-apply declared filters (sans `AllLookupsFilter`s)
new_class.base_filters.update(declared_filters)
new_class._meta, new_class.declared_filters = orig_meta, orig_declared

return new_class

@property
Expand All @@ -68,26 +75,17 @@ class FilterSet(six.with_metaclass(FilterSetMetaclass, rest_framework.FilterSet)
_subset_cache = {}

@classmethod
def filters_for_model(cls, model, opts):
fields = opts.fields

if not isinstance(fields, dict):
return super(FilterSet, cls).filters_for_model(model, opts)
def get_fields(cls):
fields = super(FilterSet, cls).get_fields()

# replace all '__all__' values by the resolved list of all lookups
fields = fields.copy()
for name, lookups in six.iteritems(fields):
if lookups == filters.ALL_LOOKUPS:
field = get_model_field(model, name)
field = get_model_field(cls._meta.model, name)
fields[name] = utils.lookups_for_field(field)

return filterset.filters_for_model(
model, fields, opts.exclude,
cls.filter_for_field,
cls.filter_for_reverse_field
)
return fields

def get_filters(self):
def expand_filters(self):
"""
Build a set of filters based on the requested data. The resulting set
will walk `RelatedFilter`s to recursively build the set of filters.
Expand Down Expand Up @@ -128,7 +126,7 @@ def get_filters(self):
filterset = subset_class(data=subset_data)

# modify filter names to account for relationship
for related_name, related_f in six.iteritems(filterset.get_filters()):
for related_name, related_f in six.iteritems(filterset.expand_filters()):
related_name = LOOKUP_SEP.join([filter_name, related_name])
related_f.name = LOOKUP_SEP.join([f.name, related_f.name])
requested_filters[related_name] = related_f
Expand Down Expand Up @@ -258,7 +256,7 @@ def cache_set(cls, key, value):
@property
def qs(self):
available_filters = self.filters
requested_filters = self.get_filters()
requested_filters = self.expand_filters()

self.filters = requested_filters
qs = super(FilterSet, self).qs
Expand Down
7 changes: 7 additions & 0 deletions rest_framework_filters/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
from django.utils import six


def import_class(path):
module_path, class_name = path.rsplit('.', 1)
class_name = str(class_name) # Ensure not unicode on py2.x
module = __import__(module_path, fromlist=[class_name], level=0)
return getattr(module, class_name)


def lookups_for_field(model_field):
"""
Generates a list of all possible lookup expressions for a model field.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def get_package_data(package):
zip_safe=False,
install_requires=[
'djangorestframework',
'django-filter>=0.15.0',
'django-filter>=1.0.0',
],
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down
1 change: 0 additions & 1 deletion tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ class SimpleViewSet(views.FilterFieldsUserViewSet):
<p>
<label for="id_username">Username:</label>
<input id="id_username" name="username" type="text" />
<span class="helptext">Filter</span>
</p>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Expand Down
6 changes: 3 additions & 3 deletions tests/test_filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ def test_exclude_property(self):
}

filterset = TagFilter(GET, queryset=Tag.objects.all())
requested_filters = filterset.get_filters()
requested_filters = filterset.expand_filters()

self.assertTrue(requested_filters['name__contains!'].exclude)

Expand All @@ -357,7 +357,7 @@ def test_filter_and_exclude(self):
}

filterset = TagFilter(GET, queryset=Tag.objects.all())
requested_filters = filterset.get_filters()
requested_filters = filterset.expand_filters()

self.assertFalse(requested_filters['name__contains'].exclude)
self.assertTrue(requested_filters['name__contains!'].exclude)
Expand All @@ -368,7 +368,7 @@ def test_related_exclude(self):
}

filterset = BlogPostFilter(GET, queryset=BlogPost.objects.all())
requested_filters = filterset.get_filters()
requested_filters = filterset.expand_filters()

self.assertTrue(requested_filters['tags__name__contains!'].exclude)

Expand Down