From 3ece0ed160dc9c8b8326bb12ea533f6bc003f4bd Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Fri, 8 Mar 2024 16:37:47 +0100 Subject: [PATCH] Allowed passing a FilterSet class to the filterset_factory(). (#1644) Co-authored-by: Birger Schacht --- django_filters/filterset.py | 16 ++++--- docs/ref/filterset.txt | 19 +++++++++ tests/test_filterset.py | 83 +++++++++++++++++++++++++++++++++---- 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/django_filters/filterset.py b/django_filters/filterset.py index 779b55bd..6c7019e2 100644 --- a/django_filters/filterset.py +++ b/django_filters/filterset.py @@ -459,9 +459,15 @@ class FilterSet(BaseFilterSet, metaclass=FilterSetMetaclass): pass -def filterset_factory(model, fields=ALL_FIELDS): - meta = type(str("Meta"), (object,), {"model": model, "fields": fields}) - filterset = type( - str("%sFilterSet" % model._meta.object_name), (FilterSet,), {"Meta": meta} +def filterset_factory(model, filterset=FilterSet, fields=None): + attrs = {"model": model} + if fields is None: + if getattr(getattr(filterset, "Meta", {}), "fields", None) is None: + attrs["fields"] = ALL_FIELDS + else: + attrs["fields"] = fields + bases = (filterset.Meta,) if hasattr(filterset, "Meta") else () + Meta = type("Meta", bases, attrs) + return type(filterset)( + str("%sFilterSet" % model._meta.object_name), (filterset,), {"Meta": Meta} ) - return filterset diff --git a/docs/ref/filterset.txt b/docs/ref/filterset.txt index 139f4190..ecc9e558 100644 --- a/docs/ref/filterset.txt +++ b/docs/ref/filterset.txt @@ -199,3 +199,22 @@ filters for a model field, you can override ``filter_for_lookup()``. Ex:: # use default behavior otherwise return super().filter_for_lookup(f, lookup_type) + + +.. _filterset_factory: + +Using ``filterset_factory`` +--------------------------- + +A ``FilterSet`` for a ``model`` can also be created by the +``filterset_factory``, which creats a ``FilterSet`` with the ``model`` set in +the FilterSets Meta. You can pass a customized ``FilterSet`` class to the +``filterset_factory``, which then uses this class a a base for the created +``FilterSet``. Ex:: + + class CustomFilterSet(django_filters.FilterSet): + class Meta: + form = CustomFilterSetForm + + + filterset = filterset_factory(Product, filterset=CustomFilterSet) diff --git a/tests/test_filterset.py b/tests/test_filterset.py index 1a2526db..706eda25 100644 --- a/tests/test_filterset.py +++ b/tests/test_filterset.py @@ -20,7 +20,11 @@ NumberFilter, UUIDFilter, ) -from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS, FilterSet +from django_filters.filterset import ( + FILTER_FOR_DBFIELD_DEFAULTS, + FilterSet, + filterset_factory, +) from django_filters.widgets import BooleanWidget from .models import ( @@ -53,10 +57,6 @@ def test_get_declared_filters(self): def test_filters_for_model(self): pass - @unittest.skip("todo") - def test_filterset_factory(self): - pass - class DbFieldDefaultFiltersTests(TestCase): def test_expected_db_fields_get_filters(self): @@ -521,7 +521,7 @@ class Meta: model = User fields = {"username": ["flub"]} - def test_meta_exlude_with_declared_and_declared_wins(self): + def test_meta_exclude_with_declared_and_declared_wins(self): class F(FilterSet): username = CharFilter() @@ -535,7 +535,7 @@ class Meta: list(F.base_filters), ["title", "average_rating", "username"] ) - def test_meta_fields_and_exlude_and_exclude_wins(self): + def test_meta_fields_and_exclude_and_exclude_wins(self): class F(FilterSet): username = CharFilter() @@ -548,7 +548,7 @@ class Meta: self.assertEqual(len(F.base_filters), 2) self.assertListEqual(list(F.base_filters), ["username", "price"]) - def test_meta_exlude_with_no_fields(self): + def test_meta_exclude_with_no_fields(self): class F(FilterSet): class Meta: model = Book @@ -716,6 +716,73 @@ class F(A, B): "f5": CharFilter, } + def test_filterset_factory(self): + filterset = filterset_factory(Article) + self.assertEqual(list(filterset.base_filters), ["name", "published", "author"]) + + def test_filterset_factory_fields(self): + filterset = filterset_factory(Article, fields=["name"]) + self.assertEqual(list(filterset.base_filters), ["name"]) + + def test_filterset_factory_base_filter(self): + class FilterSetBase(FilterSet): + f1 = CharFilter() + f2 = CharFilter() + + filterset = filterset_factory(Article, filterset=FilterSetBase) + self.assertEqual(list(filterset.base_filters), ["name", "published", "author", "f1", "f2"]) + + def test_filterset_factory_base_filter_fields(self): + class FilterSetBase(FilterSet): + f1 = CharFilter() + f2 = CharFilter() + + filterset = filterset_factory(Article, filterset=FilterSetBase, fields=["name"]) + self.assertEqual(list(filterset.base_filters), ["name", "f1", "f2"]) + + def test_filterset_factory_base_filter_meta_fields(self): + class FilterSetBase(FilterSet): + class Meta: + fields = ["name"] + f1 = CharFilter() + f2 = CharFilter() + + filterset = filterset_factory(Article, filterset=FilterSetBase) + self.assertEqual(list(filterset.base_filters), ["name", "f1", "f2"]) + + def test_filterset_factory_base_filter_fields_and_meta_fields(self): + class FilterSetBase(FilterSet): + class Meta: + fields = ["name"] + f1 = CharFilter() + f2 = CharFilter() + + filterset = filterset_factory(Article, filterset=FilterSetBase, fields=["author"]) + self.assertEqual(list(filterset.base_filters), ["author", "f1", "f2"]) + + def test_filterset_factory_base_filter_meta_inheritance_filter_overrides(self): + class FilterSetBase(FilterSet): + class Meta: + filter_overrides = { + models.CharField: { + "filter_class": BooleanFilter, + }, + } + + filterset = filterset_factory(Article, FilterSetBase) + + f = Article._meta.get_field("author") + result, params = filterset.filter_for_lookup(f, "isnull") + self.assertEqual(result, BooleanFilter) + + def test_filterset_factory_base_filter_meta_inheritance_exclude(self): + class FilterSetBase(FilterSet): + class Meta: + exclude = ["published"] + + filterset = filterset_factory(Article, FilterSetBase) + self.assertEqual(list(filterset.base_filters), ["name", "author"]) + class FilterSetInstantiationTests(TestCase): class F(FilterSet):