mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-31 07:57:26 +03:00 
			
		
		
		
	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:
		
							parent
							
								
									70024ed0eb
								
							
						
					
					
						commit
						3709f9450b
					
				|  | @ -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 |  | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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(): | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user