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.
This commit is contained in:
Adam Charnock 2015-12-03 19:22:23 +00:00
parent 70024ed0eb
commit 3709f9450b
5 changed files with 71 additions and 46 deletions

View File

@ -1,19 +1,14 @@
import warnings import warnings
import six from graphene.contrib.django.utils import get_filtering_args_from_filterset
from .resolvers import FilterConnectionResolver
from graphene.contrib.django.filterset import setup_filterset from .utils import get_type_for_model
from ...core.exceptions import SkipField from ...core.exceptions import SkipField
from ...core.fields import Field from ...core.fields import Field
from ...core.types import Argument, String
from ...core.types.base import FieldType from ...core.types.base import FieldType
from ...core.types.definitions import List from ...core.types.definitions import List
from ...relay import ConnectionField from ...relay import ConnectionField
from ...relay.utils import is_node from ...relay.utils import is_node
from .form_converter import convert_form_field
from .resolvers import FilterConnectionResolver
from .utils import get_type_for_model
from .filterset import custom_filterset_factory
class DjangoConnectionField(ConnectionField): class DjangoConnectionField(ConnectionField):
@ -69,39 +64,21 @@ class DjangoModelField(FieldType):
class DjangoFilterConnectionField(DjangoConnectionField): class DjangoFilterConnectionField(DjangoConnectionField):
def __init__(self, type, filterset_class=None, resolver=None, on=None, def __init__(self, type, on=None, fields=None, order_by=None,
fields=None, order_by=None, extra_filter_meta=None, extra_filter_meta=None, filterset_class=None, resolver=None,
*args, **kwargs): *args, **kwargs):
if not filterset_class: if not resolver:
# If no filter class is specified then create one given the resolver = FilterConnectionResolver(
# information provided node=type,
meta = dict( on=on,
model=type._meta.model, filterset_class=filterset_class,
fields=fields, fields=fields,
order_by=order_by, order_by=order_by,
extra_filter_meta=extra_filter_meta,
) )
if extra_filter_meta:
meta.update(extra_filter_meta)
filterset_class = custom_filterset_factory(**meta)
else:
filterset_class = setup_filterset(filterset_class)
if not resolver:
resolver = FilterConnectionResolver(type, on, filterset_class)
filtering_args = get_filtering_args_from_filterset(resolver.get_filterset_class(), type)
kwargs.setdefault('args', {}) kwargs.setdefault('args', {})
kwargs['args'].update(**self.get_filtering_args(type, filterset_class)) kwargs['args'].update(**filtering_args)
super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs) super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs)
def get_filtering_args(self, type, filterset_class):
args = {}
for name, filter_field in six.iteritems(filterset_class.base_filters):
field_type = Argument(convert_form_field(filter_field.field))
# Is this correct? I don't quite grok the 'parent' system yet
field_type.mount(type)
args[name] = field_type
# Also add the 'order_by' field
args[filterset_class.order_by_field] = Argument(String)
return args

View File

@ -1,5 +1,6 @@
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django_filters.filterset import filterset_factory
from graphene.contrib.django.filterset import setup_filterset, custom_filterset_factory
class BaseQuerySetConnectionResolver(object): class BaseQuerySetConnectionResolver(object):
@ -50,8 +51,13 @@ class SimpleQuerySetConnectionResolver(BaseQuerySetConnectionResolver):
class FilterConnectionResolver(BaseQuerySetConnectionResolver): class FilterConnectionResolver(BaseQuerySetConnectionResolver):
# Querying using django-filter # Querying using django-filter
def __init__(self, node, on=None, filterset_class=None): def __init__(self, node, on=None, filterset_class=None,
fields=None, order_by=None, extra_filter_meta=None):
self.filterset_class = filterset_class self.filterset_class = filterset_class
self.fields = fields
self.order_by = order_by
self.extra_filter_meta = extra_filter_meta or {}
self._filterset_class = None
super(FilterConnectionResolver, self).__init__(node, on) super(FilterConnectionResolver, self).__init__(node, on)
def make_query(self): def make_query(self):
@ -60,19 +66,40 @@ class FilterConnectionResolver(BaseQuerySetConnectionResolver):
return filterset.qs return filterset.qs
def get_filterset_class(self): def get_filterset_class(self):
"""Get the class to be used as the FilterSet"""
if self._filterset_class:
return self._filterset_class
if self.filterset_class: if self.filterset_class:
return self.filterset_class # If were given a FilterSet class, then set it up and
# return it
self._filterset_class = setup_filterset(self.filterset_class)
elif self.model: elif self.model:
return filterset_factory(self.model) # If no filter class was specified then create one given the
# other information provided
meta = dict(
model=self.model,
fields=self.fields,
order_by=self.order_by,
)
meta.update(self.extra_filter_meta)
self._filterset_class = custom_filterset_factory(**meta)
else: else:
msg = "'%s' must define 'filterset_class' or 'model'" msg = "Neither 'filterset_class' or 'model' available in '%s'. " \
"Either pass in 'filterset_class' or 'model' when " \
"initialising, or extend this class and override " \
"get_filterset() or get_filterset_class()"
raise ImproperlyConfigured(msg % self.__class__.__name__) raise ImproperlyConfigured(msg % self.__class__.__name__)
return self._filterset_class
def get_filterset(self, filterset_class): def get_filterset(self, filterset_class):
"""Get an instance of the FilterSet"""
kwargs = self.get_filterset_kwargs(filterset_class) kwargs = self.get_filterset_kwargs(filterset_class)
return filterset_class(**kwargs) return filterset_class(**kwargs)
def get_filterset_kwargs(self, filterset_class): def get_filterset_kwargs(self, filterset_class):
"""Get the kwargs to use when initialising the FilterSet class"""
kwargs = { kwargs = {
'data': self.args or None, 'data': self.args or None,
'queryset': self.get_manager() 'queryset': self.get_manager()

View File

@ -98,7 +98,7 @@ def test_filter_shortcut_filterset_extra_meta():
def test_global_id_field_implicit(): def test_global_id_field_implicit():
field = DjangoFilterConnectionField(ArticleNode, fields=['id']) field = DjangoFilterConnectionField(ArticleNode, fields=['id'])
filterset_class = field.resolver_fn.filterset_class filterset_class = field.resolver_fn.get_filterset_class()
id_filter = filterset_class.base_filters['id'] id_filter = filterset_class.base_filters['id']
assert isinstance(id_filter, GlobalIDFilter) assert isinstance(id_filter, GlobalIDFilter)
assert id_filter.field_class == GlobalIDFormField assert id_filter.field_class == GlobalIDFormField
@ -111,7 +111,7 @@ def test_global_id_field_explicit():
fields = ['id'] fields = ['id']
field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter) field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter)
filterset_class = field.resolver_fn.filterset_class filterset_class = field.resolver_fn.get_filterset_class()
id_filter = filterset_class.base_filters['id'] id_filter = filterset_class.base_filters['id']
assert isinstance(id_filter, GlobalIDFilter) assert isinstance(id_filter, GlobalIDFilter)
assert id_filter.field_class == GlobalIDFormField assert id_filter.field_class == GlobalIDFormField
@ -119,7 +119,7 @@ def test_global_id_field_explicit():
def test_global_id_field_relation(): def test_global_id_field_relation():
field = DjangoFilterConnectionField(ArticleNode, fields=['reporter']) field = DjangoFilterConnectionField(ArticleNode, fields=['reporter'])
filterset_class = field.resolver_fn.filterset_class filterset_class = field.resolver_fn.get_filterset_class()
id_filter = filterset_class.base_filters['reporter'] id_filter = filterset_class.base_filters['reporter']
assert isinstance(id_filter, GlobalIDFilter) assert isinstance(id_filter, GlobalIDFilter)
assert id_filter.field_class == GlobalIDFormField assert id_filter.field_class == GlobalIDFormField

View File

@ -66,7 +66,7 @@ def test_filter_get_filterset_class_explicit():
resolver = FilterConnectionResolver(ReporterNode, resolver = FilterConnectionResolver(ReporterNode,
filterset_class=ReporterFilter) filterset_class=ReporterFilter)
resolver(inst=reporter, args={}, info=None) resolver(inst=reporter, args={}, info=None)
assert resolver.get_filterset_class() == ReporterFilter, \ assert issubclass(resolver.get_filterset_class(), ReporterFilter), \
'ReporterFilter not returned' 'ReporterFilter not returned'
@ -83,7 +83,7 @@ def test_filter_get_filterset_class_error():
resolver.model = None resolver.model = None
with raises(ImproperlyConfigured) as excinfo: with raises(ImproperlyConfigured) as excinfo:
resolver(inst=reporter, args={}, info=None) resolver(inst=reporter, args={}, info=None)
assert "must define 'filterset_class' or 'model'" in str(excinfo.value) assert "Neither 'filterset_class' or 'model' available" in str(excinfo.value)
def test_filter_filter(): def test_filter_filter():

View File

@ -1,6 +1,10 @@
import six
from django.db import models from django.db import models
from django.db.models.manager import Manager from django.db.models.manager import Manager
from graphene import Argument, String
from graphene.contrib.django.form_converter import convert_form_field
def get_type_for_model(schema, model): def get_type_for_model(schema, model):
schema = schema schema = schema
@ -23,3 +27,20 @@ def maybe_queryset(value):
if isinstance(value, Manager): if isinstance(value, Manager):
value = value.get_queryset() value = value.get_queryset()
return value return value
def get_filtering_args_from_filterset(filterset_class, type):
""" Inspect a FilterSet and produce the arguments to pass to
a Graphene Field. These arguments will be available to
filter against in the GraphQL
"""
args = {}
for name, filter_field in six.iteritems(filterset_class.base_filters):
field_type = Argument(convert_form_field(filter_field.field))
# Is this correct? I don't quite grok the 'parent' system yet
field_type.mount(type)
args[name] = field_type
# Also add the 'order_by' field
args[filterset_class.order_by_field] = Argument(String)
return args