Skip to content
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

Fix filterset multiple inheritance bug #1131

Merged
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
26 changes: 16 additions & 10 deletions django_filters/filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,22 @@ def get_declared_filters(cls, bases, attrs):

filters.sort(key=lambda x: x[1].creation_counter)

# merge declared filters from base classes
for base in reversed(bases):
if hasattr(base, 'declared_filters'):
filters = [
(name, f) for name, f
in base.declared_filters.items()
if name not in attrs
] + filters

return OrderedDict(filters)
# Ensures a base class field doesn't override cls attrs, and maintains
# field precedence when inheriting multiple parents. e.g. if there is a
# class C(A, B), and A and B both define 'field', use 'field' from A.
known = set(attrs)

def visit(name):
known.add(name)
return name

base_filters = [
(visit(name), f)
for base in bases if hasattr(base, 'declared_filters')
for name, f in base.declared_filters.items() if name not in known
]

return OrderedDict(base_filters + filters)


FILTER_FOR_DBFIELD_DEFAULTS = {
Expand Down
42 changes: 42 additions & 0 deletions tests/test_filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,48 @@ class Grandchild(Child):
self.assertEqual(len(Child.base_filters), 1)
self.assertEqual(len(Grandchild.base_filters), 1)

def test_declared_filter_multiple_inheritance(self):
class A(FilterSet):
f = CharFilter()

class B(FilterSet):
f = NumberFilter()

class F(A, B):
pass

filters = {name: type(f) for name, f in F.declared_filters.items()}
self.assertEqual(filters, {'f': CharFilter})

def test_declared_filter_multiple_inheritance_field_ordering(self):
class Base(FilterSet):
f1 = CharFilter()
f2 = CharFilter()

class A(Base):
f3 = NumberFilter()

class B(FilterSet):
f3 = CharFilter()
f4 = CharFilter()

class F(A, B):
f2 = NumberFilter()
f5 = CharFilter()

fields = {name: type(f) for name, f in F.declared_filters.items()}

# `NumberFilter`s should be the 'winners' in filter name conflicts
# - `F.f2` should override `Base.F2`
# - `A.f3` should override `B.f3`
assert fields == {
'f1': CharFilter,
'f2': NumberFilter,
'f3': NumberFilter,
'f4': CharFilter,
'f5': CharFilter,
}


class FilterSetInstantiationTests(TestCase):

Expand Down