Skip to content

Commit 3709f94

Browse files
committed
Refactoring filterset creation logic
I have moved it from `DjangoFilterConnectionField` and pushed it down into `FilterConnectionResolver` where I think it makes more sense for it to live. I have also pulled out `get_filtering_args_from_filterset()` as a utility method.
1 parent 70024ed commit 3709f94

File tree

5 files changed

+71
-46
lines changed

5 files changed

+71
-46
lines changed

graphene/contrib/django/fields.py

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import warnings
22

3-
import six
4-
5-
from graphene.contrib.django.filterset import setup_filterset
3+
from graphene.contrib.django.utils import get_filtering_args_from_filterset
4+
from .resolvers import FilterConnectionResolver
5+
from .utils import get_type_for_model
66
from ...core.exceptions import SkipField
77
from ...core.fields import Field
8-
from ...core.types import Argument, String
98
from ...core.types.base import FieldType
109
from ...core.types.definitions import List
1110
from ...relay import ConnectionField
1211
from ...relay.utils import is_node
13-
from .form_converter import convert_form_field
14-
from .resolvers import FilterConnectionResolver
15-
from .utils import get_type_for_model
16-
from .filterset import custom_filterset_factory
1712

1813

1914
class DjangoConnectionField(ConnectionField):
@@ -69,39 +64,21 @@ def get_object_type(self, schema):
6964

7065
class DjangoFilterConnectionField(DjangoConnectionField):
7166

72-
def __init__(self, type, filterset_class=None, resolver=None, on=None,
73-
fields=None, order_by=None, extra_filter_meta=None,
67+
def __init__(self, type, on=None, fields=None, order_by=None,
68+
extra_filter_meta=None, filterset_class=None, resolver=None,
7469
*args, **kwargs):
7570

76-
if not filterset_class:
77-
# If no filter class is specified then create one given the
78-
# information provided
79-
meta = dict(
80-
model=type._meta.model,
71+
if not resolver:
72+
resolver = FilterConnectionResolver(
73+
node=type,
74+
on=on,
75+
filterset_class=filterset_class,
8176
fields=fields,
8277
order_by=order_by,
78+
extra_filter_meta=extra_filter_meta,
8379
)
84-
if extra_filter_meta:
85-
meta.update(extra_filter_meta)
86-
filterset_class = custom_filterset_factory(**meta)
87-
else:
88-
filterset_class = setup_filterset(filterset_class)
89-
90-
if not resolver:
91-
resolver = FilterConnectionResolver(type, on, filterset_class)
9280

81+
filtering_args = get_filtering_args_from_filterset(resolver.get_filterset_class(), type)
9382
kwargs.setdefault('args', {})
94-
kwargs['args'].update(**self.get_filtering_args(type, filterset_class))
83+
kwargs['args'].update(**filtering_args)
9584
super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs)
96-
97-
def get_filtering_args(self, type, filterset_class):
98-
args = {}
99-
for name, filter_field in six.iteritems(filterset_class.base_filters):
100-
field_type = Argument(convert_form_field(filter_field.field))
101-
# Is this correct? I don't quite grok the 'parent' system yet
102-
field_type.mount(type)
103-
args[name] = field_type
104-
105-
# Also add the 'order_by' field
106-
args[filterset_class.order_by_field] = Argument(String)
107-
return args

graphene/contrib/django/resolvers.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.core.exceptions import ImproperlyConfigured
2-
from django_filters.filterset import filterset_factory
2+
3+
from graphene.contrib.django.filterset import setup_filterset, custom_filterset_factory
34

45

56
class BaseQuerySetConnectionResolver(object):
@@ -50,8 +51,13 @@ def get_order(self):
5051
class FilterConnectionResolver(BaseQuerySetConnectionResolver):
5152
# Querying using django-filter
5253

53-
def __init__(self, node, on=None, filterset_class=None):
54+
def __init__(self, node, on=None, filterset_class=None,
55+
fields=None, order_by=None, extra_filter_meta=None):
5456
self.filterset_class = filterset_class
57+
self.fields = fields
58+
self.order_by = order_by
59+
self.extra_filter_meta = extra_filter_meta or {}
60+
self._filterset_class = None
5561
super(FilterConnectionResolver, self).__init__(node, on)
5662

5763
def make_query(self):
@@ -60,19 +66,40 @@ def make_query(self):
6066
return filterset.qs
6167

6268
def get_filterset_class(self):
69+
"""Get the class to be used as the FilterSet"""
70+
if self._filterset_class:
71+
return self._filterset_class
72+
6373
if self.filterset_class:
64-
return self.filterset_class
74+
# If were given a FilterSet class, then set it up and
75+
# return it
76+
self._filterset_class = setup_filterset(self.filterset_class)
6577
elif self.model:
66-
return filterset_factory(self.model)
78+
# If no filter class was specified then create one given the
79+
# other information provided
80+
meta = dict(
81+
model=self.model,
82+
fields=self.fields,
83+
order_by=self.order_by,
84+
)
85+
meta.update(self.extra_filter_meta)
86+
self._filterset_class = custom_filterset_factory(**meta)
6787
else:
68-
msg = "'%s' must define 'filterset_class' or 'model'"
88+
msg = "Neither 'filterset_class' or 'model' available in '%s'. " \
89+
"Either pass in 'filterset_class' or 'model' when " \
90+
"initialising, or extend this class and override " \
91+
"get_filterset() or get_filterset_class()"
6992
raise ImproperlyConfigured(msg % self.__class__.__name__)
7093

94+
return self._filterset_class
95+
7196
def get_filterset(self, filterset_class):
97+
"""Get an instance of the FilterSet"""
7298
kwargs = self.get_filterset_kwargs(filterset_class)
7399
return filterset_class(**kwargs)
74100

75101
def get_filterset_kwargs(self, filterset_class):
102+
"""Get the kwargs to use when initialising the FilterSet class"""
76103
kwargs = {
77104
'data': self.args or None,
78105
'queryset': self.get_manager()

graphene/contrib/django/tests/test_fields.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def test_filter_shortcut_filterset_extra_meta():
9898

9999
def test_global_id_field_implicit():
100100
field = DjangoFilterConnectionField(ArticleNode, fields=['id'])
101-
filterset_class = field.resolver_fn.filterset_class
101+
filterset_class = field.resolver_fn.get_filterset_class()
102102
id_filter = filterset_class.base_filters['id']
103103
assert isinstance(id_filter, GlobalIDFilter)
104104
assert id_filter.field_class == GlobalIDFormField
@@ -111,15 +111,15 @@ class Meta:
111111
fields = ['id']
112112

113113
field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter)
114-
filterset_class = field.resolver_fn.filterset_class
114+
filterset_class = field.resolver_fn.get_filterset_class()
115115
id_filter = filterset_class.base_filters['id']
116116
assert isinstance(id_filter, GlobalIDFilter)
117117
assert id_filter.field_class == GlobalIDFormField
118118

119119

120120
def test_global_id_field_relation():
121121
field = DjangoFilterConnectionField(ArticleNode, fields=['reporter'])
122-
filterset_class = field.resolver_fn.filterset_class
122+
filterset_class = field.resolver_fn.get_filterset_class()
123123
id_filter = filterset_class.base_filters['reporter']
124124
assert isinstance(id_filter, GlobalIDFilter)
125125
assert id_filter.field_class == GlobalIDFormField

graphene/contrib/django/tests/test_resolvers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def test_filter_get_filterset_class_explicit():
6666
resolver = FilterConnectionResolver(ReporterNode,
6767
filterset_class=ReporterFilter)
6868
resolver(inst=reporter, args={}, info=None)
69-
assert resolver.get_filterset_class() == ReporterFilter, \
69+
assert issubclass(resolver.get_filterset_class(), ReporterFilter), \
7070
'ReporterFilter not returned'
7171

7272

@@ -83,7 +83,7 @@ def test_filter_get_filterset_class_error():
8383
resolver.model = None
8484
with raises(ImproperlyConfigured) as excinfo:
8585
resolver(inst=reporter, args={}, info=None)
86-
assert "must define 'filterset_class' or 'model'" in str(excinfo.value)
86+
assert "Neither 'filterset_class' or 'model' available" in str(excinfo.value)
8787

8888

8989
def test_filter_filter():

graphene/contrib/django/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import six
12
from django.db import models
23
from django.db.models.manager import Manager
34

5+
from graphene import Argument, String
6+
from graphene.contrib.django.form_converter import convert_form_field
7+
48

59
def get_type_for_model(schema, model):
610
schema = schema
@@ -23,3 +27,20 @@ def maybe_queryset(value):
2327
if isinstance(value, Manager):
2428
value = value.get_queryset()
2529
return value
30+
31+
32+
def get_filtering_args_from_filterset(filterset_class, type):
33+
""" Inspect a FilterSet and produce the arguments to pass to
34+
a Graphene Field. These arguments will be available to
35+
filter against in the GraphQL
36+
"""
37+
args = {}
38+
for name, filter_field in six.iteritems(filterset_class.base_filters):
39+
field_type = Argument(convert_form_field(filter_field.field))
40+
# Is this correct? I don't quite grok the 'parent' system yet
41+
field_type.mount(type)
42+
args[name] = field_type
43+
44+
# Also add the 'order_by' field
45+
args[filterset_class.order_by_field] = Argument(String)
46+
return args

0 commit comments

Comments
 (0)