diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index d9d6f3da..5c4f034e 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -1,28 +1,44 @@ -import warnings - from ...core.exceptions import SkipField from ...core.fields import Field from ...core.types.base import FieldType from ...core.types.definitions import List from ...relay import ConnectionField from ...relay.utils import is_node -from .filter.fields import DjangoFilterConnectionField -from .utils import get_type_for_model +from .utils import get_type_for_model, maybe_queryset class DjangoConnectionField(ConnectionField): def __init__(self, *args, **kwargs): - cls = self.__class__ - warnings.warn("Using {} will be not longer supported." - " Use relay.ConnectionField instead".format(cls.__name__), - FutureWarning) + self.on = kwargs.pop('on', False) return super(DjangoConnectionField, self).__init__(*args, **kwargs) + @property + def model(self): + return self.type._meta.model + + def get_manager(self): + if self.on: + return getattr(self.model, self.on) + else: + return self.model._default_manager + + def get_queryset(self, resolved_qs, args, info): + return resolved_qs + + def from_list(self, connection_type, resolved, args, info): + if not resolved: + resolved = self.get_manager() + resolved_qs = maybe_queryset(resolved) + qs = self.get_queryset(resolved_qs, args, info) + return super(DjangoConnectionField, self).from_list(connection_type, qs, args, info) + class ConnectionOrListField(Field): def internal_type(self, schema): + from .filter.fields import DjangoFilterConnectionField + model_field = self.type field_object_type = model_field.get_object_type(schema) if not field_object_type: @@ -31,7 +47,7 @@ class ConnectionOrListField(Field): if field_object_type._meta.filter_fields: field = DjangoFilterConnectionField(field_object_type) else: - field = ConnectionField(field_object_type) + field = DjangoConnectionField(field_object_type) else: field = Field(List(field_object_type)) field.contribute_to_class(self.object_type, self.attname) diff --git a/graphene/contrib/django/filter/fields.py b/graphene/contrib/django/filter/fields.py index 43196f6e..f974a4ee 100644 --- a/graphene/contrib/django/filter/fields.py +++ b/graphene/contrib/django/filter/fields.py @@ -1,25 +1,36 @@ -from graphene.contrib.django.filter.resolvers import FilterConnectionResolver -from graphene.contrib.django.utils import get_filtering_args_from_filterset -from graphene.relay import ConnectionField +from .utils import get_filterset_class, get_filtering_args_from_filterset +from ..fields import DjangoConnectionField -class DjangoFilterConnectionField(ConnectionField): +class DjangoFilterConnectionField(DjangoConnectionField): - def __init__(self, type, on=None, fields=None, order_by=None, - extra_filter_meta=None, filterset_class=None, resolver=None, + def __init__(self, type, fields=None, order_by=None, + extra_filter_meta=None, filterset_class=None, *args, **kwargs): - if not resolver: - resolver = FilterConnectionResolver( - node=type, - on=on, - filterset_class=filterset_class, - fields=fields, - order_by=order_by, - extra_filter_meta=extra_filter_meta, - ) - - filtering_args = get_filtering_args_from_filterset(resolver.get_filterset_class(), type) + self.order_by = order_by or type._meta.filter_order_by + self.fields = fields or type._meta.filter_fields + meta = dict(model=type._meta.model, + fields=self.fields, + order_by=self.order_by) + if extra_filter_meta: + meta.update(extra_filter_meta) + self.filterset_class = get_filterset_class(filterset_class, **meta) + self.filtering_args = get_filtering_args_from_filterset(self.filterset_class, type) kwargs.setdefault('args', {}) - kwargs['args'].update(**filtering_args) - super(DjangoFilterConnectionField, self).__init__(type, resolver, *args, **kwargs) + kwargs['args'].update(**self.filtering_args) + super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs) + + def get_queryset(self, qs, args, info): + filterset_class = self.filterset_class + filter_kwargs = self.get_filter_kwargs(args) + order = self.get_order(args) + if order: + qs = qs.order_by(order) + return filterset_class(data=filter_kwargs, queryset=qs) + + def get_filter_kwargs(self, args): + return {k: v for k, v in args.items() if k in self.filtering_args} + + def get_order(self, args): + return args.get('order_by', None) diff --git a/graphene/contrib/django/filter/resolvers.py b/graphene/contrib/django/filter/resolvers.py deleted file mode 100644 index 76b3e7ad..00000000 --- a/graphene/contrib/django/filter/resolvers.py +++ /dev/null @@ -1,64 +0,0 @@ -from django.core.exceptions import ImproperlyConfigured - -from graphene.contrib.django.filter.filterset import (custom_filterset_factory, - setup_filterset) -from graphene.contrib.django.resolvers import BaseQuerySetConnectionResolver - - -class FilterConnectionResolver(BaseQuerySetConnectionResolver): - # Querying using django-filter - - def __init__(self, node, on=None, filterset_class=None, - fields=None, order_by=None, extra_filter_meta=None): - self.filterset_class = filterset_class - self.fields = fields or node._meta.filter_fields - self.order_by = order_by or node._meta.filter_order_by - self.extra_filter_meta = extra_filter_meta or {} - self._filterset_class = None - super(FilterConnectionResolver, self).__init__(node, on) - - def make_query(self): - filterset_class = self.get_filterset_class() - filterset = self.get_filterset(filterset_class) - return filterset.qs - - 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 were given a FilterSet class, then set it up and - # return it - self._filterset_class = setup_filterset(self.filterset_class) - elif 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: - 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__) - - return self._filterset_class - - def get_filterset(self, filterset_class): - """Get an instance of the FilterSet""" - kwargs = self.get_filterset_kwargs(filterset_class) - return filterset_class(**kwargs) - - def get_filterset_kwargs(self, filterset_class): - """Get the kwargs to use when initialising the FilterSet class""" - kwargs = { - 'data': self.args or None, - 'queryset': self.get_manager() - } - return kwargs diff --git a/graphene/contrib/django/filter/tests/test_fields.py b/graphene/contrib/django/filter/tests/test_fields.py index b2591d1e..90d6b0f5 100644 --- a/graphene/contrib/django/filter/tests/test_fields.py +++ b/graphene/contrib/django/filter/tests/test_fields.py @@ -9,7 +9,7 @@ from graphene.contrib.django.forms import (GlobalIDFormField, GlobalIDMultipleChoiceField) from graphene.contrib.django.tests.models import Article, Pet, Reporter from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED -from graphene.relay import NodeField +from graphene.relay import NodeField, ConnectionField from graphene.utils import ProxySnakeDict pytestmark = [] @@ -217,7 +217,7 @@ def test_filter_filterset_related_results(): def test_global_id_field_implicit(): field = DjangoFilterConnectionField(ArticleNode, fields=['id']) - filterset_class = field.resolver_fn.get_filterset_class() + filterset_class = field.filterset_class id_filter = filterset_class.base_filters['id'] assert isinstance(id_filter, GlobalIDFilter) assert id_filter.field_class == GlobalIDFormField @@ -231,7 +231,7 @@ def test_global_id_field_explicit(): fields = ['id'] field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter) - filterset_class = field.resolver_fn.get_filterset_class() + filterset_class = field.filterset_class id_filter = filterset_class.base_filters['id'] assert isinstance(id_filter, GlobalIDFilter) assert id_filter.field_class == GlobalIDFormField @@ -239,7 +239,7 @@ def test_global_id_field_explicit(): def test_global_id_field_relation(): field = DjangoFilterConnectionField(ArticleNode, fields=['reporter']) - filterset_class = field.resolver_fn.get_filterset_class() + filterset_class = field.filterset_class id_filter = filterset_class.base_filters['reporter'] assert isinstance(id_filter, GlobalIDFilter) assert id_filter.field_class == GlobalIDFormField @@ -247,7 +247,7 @@ def test_global_id_field_relation(): def test_global_id_multiple_field_implicit(): field = DjangoFilterConnectionField(ReporterNode, fields=['pets']) - filterset_class = field.resolver_fn.get_filterset_class() + filterset_class = field.filterset_class multiple_filter = filterset_class.base_filters['pets'] assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert multiple_filter.field_class == GlobalIDMultipleChoiceField @@ -261,7 +261,7 @@ def test_global_id_multiple_field_explicit(): fields = ['pets'] field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) - filterset_class = field.resolver_fn.get_filterset_class() + filterset_class = field.filterset_class multiple_filter = filterset_class.base_filters['pets'] assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert multiple_filter.field_class == GlobalIDMultipleChoiceField @@ -269,7 +269,7 @@ def test_global_id_multiple_field_explicit(): def test_global_id_multiple_field_implicit_reverse(): field = DjangoFilterConnectionField(ReporterNode, fields=['articles']) - filterset_class = field.resolver_fn.get_filterset_class() + filterset_class = field.filterset_class multiple_filter = filterset_class.base_filters['articles'] assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert multiple_filter.field_class == GlobalIDMultipleChoiceField @@ -283,7 +283,7 @@ def test_global_id_multiple_field_explicit_reverse(): fields = ['articles'] field = DjangoFilterConnectionField(ReporterNode, filterset_class=ReporterPetsFilter) - filterset_class = field.resolver_fn.get_filterset_class() + filterset_class = field.filterset_class multiple_filter = filterset_class.base_filters['articles'] assert isinstance(multiple_filter, GlobalIDMultipleChoiceFilter) assert multiple_filter.field_class == GlobalIDMultipleChoiceField diff --git a/graphene/contrib/django/filter/tests/test_resolvers.py b/graphene/contrib/django/filter/tests/test_resolvers.py deleted file mode 100644 index 670e87c8..00000000 --- a/graphene/contrib/django/filter/tests/test_resolvers.py +++ /dev/null @@ -1,82 +0,0 @@ -import pytest -from django.core.exceptions import ImproperlyConfigured - -from graphene.contrib.django.tests.models import Article, Reporter -from graphene.contrib.django.tests.test_resolvers import (ArticleNode, - ReporterNode) -from graphene.contrib.django.utils import DJANGO_FILTER_INSTALLED - -if DJANGO_FILTER_INSTALLED: - from graphene.contrib.django.filter.resolvers import FilterConnectionResolver - from graphene.contrib.django.filter.tests.filters import ArticleFilter, ReporterFilter -else: - pytestmark = pytest.mark.skipif(True, reason='django_filters not installed') - - -def test_filter_get_filterset_class_explicit(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = FilterConnectionResolver(ReporterNode, - filterset_class=ReporterFilter) - resolver(inst=reporter, args={}, info=None) - assert issubclass(resolver.get_filterset_class(), ReporterFilter), \ - 'ReporterFilter not returned' - - -def test_filter_get_filterset_class_implicit(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = FilterConnectionResolver(ReporterNode) - resolver(inst=reporter, args={}, info=None) - assert resolver.get_filterset_class().__name__ == 'ReporterFilterSet' - - -def test_filter_get_filterset_class_error(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = FilterConnectionResolver(ReporterNode) - resolver.model = None - with pytest.raises(ImproperlyConfigured) as excinfo: - resolver(inst=reporter, args={}, info=None) - assert "Neither 'filterset_class' or 'model' available" in str(excinfo.value) - - -def test_filter_filter(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = FilterConnectionResolver(ReporterNode, - filterset_class=ReporterFilter) - resolved = resolver(inst=reporter, args={ - 'first_name': 'Elmo' - }, info=None) - assert '"first_name" = Elmo' in str(resolved.query) - assert 'ORDER BY' not in str(resolved.query) - - -def test_filter_filter_contains(): - article = Article(id=1, headline='Cookie Monster eats fruit') - resolver = FilterConnectionResolver(ArticleNode, - filterset_class=ArticleFilter) - resolved = resolver(inst=article, args={ - 'headline__icontains': 'Elmo' - }, info=None) - assert '"headline" LIKE %Elmo%' in str(resolved.query) - - -def test_filter_order(): - article = Article(id=1, headline='Cookie Monster eats fruit') - resolver = FilterConnectionResolver(ArticleNode, - filterset_class=ArticleFilter) - resolved = resolver(inst=article, args={ - 'order_by': 'headline' - }, info=None) - assert 'WHERE' not in str(resolved.query) - assert 'ORDER BY' in str(resolved.query) - assert '"headline" ASC' in str(resolved.query) - - -def test_filter_order_not_available(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = FilterConnectionResolver(ReporterNode, - filterset_class=ReporterFilter) - resolved = resolver(inst=reporter, args={ - 'order_by': 'last_name' - }, info=None) - assert 'WHERE' not in str(resolved.query) - assert 'ORDER BY' not in str(resolved.query) diff --git a/graphene/contrib/django/filter/utils.py b/graphene/contrib/django/filter/utils.py new file mode 100644 index 00000000..29aade96 --- /dev/null +++ b/graphene/contrib/django/filter/utils.py @@ -0,0 +1,31 @@ +import six + +from .filterset import custom_filterset_factory, setup_filterset +from ....core.types import Argument, String + + +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 + """ + from graphene.contrib.django.form_converter import convert_form_field + + args = {} + for name, filter_field in six.iteritems(filterset_class.base_filters): + field_type = Argument(convert_form_field(filter_field.field)) + args[name] = field_type + + # Also add the 'order_by' field + if filterset_class._meta.order_by: + args[filterset_class.order_by_field] = Argument(String()) + return args + + +def get_filterset_class(filterset_class, **meta): + """Get the class to be used as the FilterSet""" + if filterset_class: + # If were given a FilterSet class, then set it up and + # return it + return setup_filterset(filterset_class) + return custom_filterset_factory(**meta) diff --git a/graphene/contrib/django/resolvers.py b/graphene/contrib/django/resolvers.py deleted file mode 100644 index a5494bfb..00000000 --- a/graphene/contrib/django/resolvers.py +++ /dev/null @@ -1,43 +0,0 @@ -class BaseQuerySetConnectionResolver(object): - - def __init__(self, node, on=None): - self.node = node - self.model = node._meta.model - # The name of the field on the model which contains the - # manager upon which to perform the query. Optional. - # If omitted the model's default manager will be used. - self.on = on - - def __call__(self, inst, args, info): - self.inst = inst - self.args = args - self.info = info - return self.make_query() - - def get_manager(self): - if self.on: - return getattr(self.inst, self.on) - else: - return self.model._default_manager - - def make_query(self): - raise NotImplemented() - - -class SimpleQuerySetConnectionResolver(BaseQuerySetConnectionResolver): - # Simple querying without using django-filter (ported from previous gist) - - def make_query(self): - filter_kwargs = self.get_filter_kwargs() - query = self.get_manager().filter(**filter_kwargs) - order = self.get_order() - if order: - query = query.order_by(order) - return query - - def get_filter_kwargs(self): - ignore = ['first', 'last', 'before', 'after', 'order_by'] - return {k: v for k, v in self.args.items() if k not in ignore} - - def get_order(self): - return self.args.get('order_by', None) diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index 5b68ebbb..0c3bf69f 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -7,7 +7,7 @@ from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta from ...relay.types import Connection, Node, NodeMeta from .converter import convert_django_field from .options import DjangoOptions -from .utils import get_reverse_fields, maybe_queryset +from .utils import get_reverse_fields class DjangoObjectTypeMeta(ObjectTypeMeta): @@ -82,11 +82,7 @@ class DjangoObjectType(six.with_metaclass( class DjangoConnection(Connection): - - @classmethod - def from_list(cls, iterable, *args, **kwargs): - iterable = maybe_queryset(iterable) - return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs) + pass class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta): @@ -112,5 +108,3 @@ class DjangoNode(six.with_metaclass( return cls(instance) except cls._meta.model.DoesNotExist: return None - - connection_type = DjangoConnection diff --git a/graphene/contrib/django/utils.py b/graphene/contrib/django/utils.py index 76f4477c..b03c2fc8 100644 --- a/graphene/contrib/django/utils.py +++ b/graphene/contrib/django/utils.py @@ -1,9 +1,7 @@ -import six from django.db import models from django.db.models.manager import Manager from django.db.models.query import QuerySet -from graphene import Argument, String from graphene.utils import LazyList from .compat import RelatedObject @@ -56,26 +54,6 @@ def maybe_queryset(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 - """ - from graphene.contrib.django.form_converter import convert_form_field - - 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 - if filterset_class._meta.order_by: - args[filterset_class.order_by_field] = Argument(String()) - return args - - def get_related_model(field): if hasattr(field, 'rel'): # Django 1.6, 1.7