Skip to content

Commit

Permalink
Allowed passing a FilterSet class to the filterset_factory(). (#1644)
Browse files Browse the repository at this point in the history
Co-authored-by: Birger Schacht <birger@rantanplan.org>
  • Loading branch information
carltongibson and b1rger committed Mar 8, 2024
1 parent a5f3f1a commit 3ece0ed
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 13 deletions.
16 changes: 11 additions & 5 deletions django_filters/filterset.py
Expand Up @@ -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
19 changes: 19 additions & 0 deletions docs/ref/filterset.txt
Expand Up @@ -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)
83 changes: 75 additions & 8 deletions tests/test_filterset.py
Expand Up @@ -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 (
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()

Expand All @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 3ece0ed

Please sign in to comment.