diff --git a/.travis.yml b/.travis.yml index 3dbb00e0..b6996d24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,13 +80,13 @@ matrix: fast_finish: true include: - python: '2.7' - env: DJANGO_VERSION=1.6 + env: TEST_TYPE=build DJANGO_VERSION=1.6 - python: '2.7' - env: DJANGO_VERSION=1.7 + env: TEST_TYPE=build DJANGO_VERSION=1.7 - python: '2.7' - env: DJANGO_VERSION=1.8 + env: TEST_TYPE=build DJANGO_VERSION=1.8 - python: '2.7' - env: DJANGO_VERSION=1.9 + env: TEST_TYPE=build DJANGO_VERSION=1.9 - python: '2.7' env: TEST_TYPE=build_website - python: '2.7' diff --git a/bin/autolinter b/bin/autolinter index 7f749242..0fc3ccae 100755 --- a/bin/autolinter +++ b/bin/autolinter @@ -1,5 +1,7 @@ #!/bin/bash +# Install the required scripts with +# pip install autoflake autopep8 isort autoflake ./examples/ ./graphene/ -r --remove-unused-variables --remove-all-unused-imports --in-place autopep8 ./examples/ ./graphene/ -r --in-place --experimental --aggressive --max-line-length 120 isort -rc ./examples/ ./graphene/ diff --git a/examples/cookbook/README.md b/examples/cookbook_django/README.md similarity index 100% rename from examples/cookbook/README.md rename to examples/cookbook_django/README.md diff --git a/examples/cookbook/cookbook/__init__.py b/examples/cookbook_django/cookbook/__init__.py similarity index 100% rename from examples/cookbook/cookbook/__init__.py rename to examples/cookbook_django/cookbook/__init__.py diff --git a/examples/cookbook/cookbook/ingredients/__init__.py b/examples/cookbook_django/cookbook/ingredients/__init__.py similarity index 100% rename from examples/cookbook/cookbook/ingredients/__init__.py rename to examples/cookbook_django/cookbook/ingredients/__init__.py diff --git a/examples/cookbook/cookbook/ingredients/admin.py b/examples/cookbook_django/cookbook/ingredients/admin.py similarity index 61% rename from examples/cookbook/cookbook/ingredients/admin.py rename to examples/cookbook_django/cookbook/ingredients/admin.py index b3d92366..766b23fb 100644 --- a/examples/cookbook/cookbook/ingredients/admin.py +++ b/examples/cookbook_django/cookbook/ingredients/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from cookbook.ingredients.models import Ingredient, Category +from cookbook.ingredients.models import Category, Ingredient admin.site.register(Ingredient) admin.site.register(Category) diff --git a/examples/cookbook/cookbook/ingredients/apps.py b/examples/cookbook_django/cookbook/ingredients/apps.py similarity index 100% rename from examples/cookbook/cookbook/ingredients/apps.py rename to examples/cookbook_django/cookbook/ingredients/apps.py diff --git a/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json b/examples/cookbook_django/cookbook/ingredients/fixtures/ingredients.json similarity index 100% rename from examples/cookbook/cookbook/ingredients/fixtures/ingredients.json rename to examples/cookbook_django/cookbook/ingredients/fixtures/ingredients.json diff --git a/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py b/examples/cookbook_django/cookbook/ingredients/migrations/0001_initial.py similarity index 100% rename from examples/cookbook/cookbook/ingredients/migrations/0001_initial.py rename to examples/cookbook_django/cookbook/ingredients/migrations/0001_initial.py index 3249c5a1..04949239 100644 --- a/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py +++ b/examples/cookbook_django/cookbook/ingredients/migrations/0001_initial.py @@ -2,8 +2,8 @@ # Generated by Django 1.9 on 2015-12-04 18:15 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/examples/cookbook/cookbook/ingredients/migrations/__init__.py b/examples/cookbook_django/cookbook/ingredients/migrations/__init__.py similarity index 100% rename from examples/cookbook/cookbook/ingredients/migrations/__init__.py rename to examples/cookbook_django/cookbook/ingredients/migrations/__init__.py diff --git a/examples/cookbook/cookbook/ingredients/models.py b/examples/cookbook_django/cookbook/ingredients/models.py similarity index 100% rename from examples/cookbook/cookbook/ingredients/models.py rename to examples/cookbook_django/cookbook/ingredients/models.py diff --git a/examples/cookbook/cookbook/ingredients/schema.py b/examples/cookbook_django/cookbook/ingredients/schema.py similarity index 96% rename from examples/cookbook/cookbook/ingredients/schema.py rename to examples/cookbook_django/cookbook/ingredients/schema.py index 6c640dff..13825267 100644 --- a/examples/cookbook/cookbook/ingredients/schema.py +++ b/examples/cookbook_django/cookbook/ingredients/schema.py @@ -1,13 +1,13 @@ -from graphene import relay, ObjectType +from cookbook.ingredients.models import Category, Ingredient +from graphene import ObjectType, relay from graphene.contrib.django.filter import DjangoFilterConnectionField from graphene.contrib.django.types import DjangoNode -from cookbook.ingredients.models import Category, Ingredient - # Graphene will automatically map the User model's fields onto the UserType. # This is configured in the UserType's Meta class (as you can see below) class CategoryNode(DjangoNode): + class Meta: model = Category filter_fields = ['name', 'ingredients'] @@ -15,6 +15,7 @@ class CategoryNode(DjangoNode): class IngredientNode(DjangoNode): + class Meta: model = Ingredient # Allow for some more advanced filtering here diff --git a/examples/cookbook/cookbook/ingredients/tests.py b/examples/cookbook_django/cookbook/ingredients/tests.py similarity index 100% rename from examples/cookbook/cookbook/ingredients/tests.py rename to examples/cookbook_django/cookbook/ingredients/tests.py diff --git a/examples/cookbook/cookbook/ingredients/views.py b/examples/cookbook_django/cookbook/ingredients/views.py similarity index 100% rename from examples/cookbook/cookbook/ingredients/views.py rename to examples/cookbook_django/cookbook/ingredients/views.py diff --git a/examples/cookbook/cookbook/recipes/__init__.py b/examples/cookbook_django/cookbook/recipes/__init__.py similarity index 100% rename from examples/cookbook/cookbook/recipes/__init__.py rename to examples/cookbook_django/cookbook/recipes/__init__.py diff --git a/examples/cookbook/cookbook/recipes/admin.py b/examples/cookbook_django/cookbook/recipes/admin.py similarity index 100% rename from examples/cookbook/cookbook/recipes/admin.py rename to examples/cookbook_django/cookbook/recipes/admin.py diff --git a/examples/cookbook/cookbook/recipes/apps.py b/examples/cookbook_django/cookbook/recipes/apps.py similarity index 100% rename from examples/cookbook/cookbook/recipes/apps.py rename to examples/cookbook_django/cookbook/recipes/apps.py diff --git a/examples/cookbook/cookbook/recipes/migrations/0001_initial.py b/examples/cookbook_django/cookbook/recipes/migrations/0001_initial.py similarity index 100% rename from examples/cookbook/cookbook/recipes/migrations/0001_initial.py rename to examples/cookbook_django/cookbook/recipes/migrations/0001_initial.py diff --git a/examples/cookbook/cookbook/recipes/migrations/__init__.py b/examples/cookbook_django/cookbook/recipes/migrations/__init__.py similarity index 100% rename from examples/cookbook/cookbook/recipes/migrations/__init__.py rename to examples/cookbook_django/cookbook/recipes/migrations/__init__.py diff --git a/examples/cookbook/cookbook/recipes/models.py b/examples/cookbook_django/cookbook/recipes/models.py similarity index 100% rename from examples/cookbook/cookbook/recipes/models.py rename to examples/cookbook_django/cookbook/recipes/models.py diff --git a/examples/cookbook/cookbook/recipes/tests.py b/examples/cookbook_django/cookbook/recipes/tests.py similarity index 100% rename from examples/cookbook/cookbook/recipes/tests.py rename to examples/cookbook_django/cookbook/recipes/tests.py diff --git a/examples/cookbook/cookbook/recipes/views.py b/examples/cookbook_django/cookbook/recipes/views.py similarity index 100% rename from examples/cookbook/cookbook/recipes/views.py rename to examples/cookbook_django/cookbook/recipes/views.py diff --git a/examples/cookbook/cookbook/schema.py b/examples/cookbook_django/cookbook/schema.py similarity index 99% rename from examples/cookbook/cookbook/schema.py rename to examples/cookbook_django/cookbook/schema.py index d417ce8e..acb53666 100644 --- a/examples/cookbook/cookbook/schema.py +++ b/examples/cookbook_django/cookbook/schema.py @@ -1,6 +1,5 @@ -import graphene - import cookbook.ingredients.schema +import graphene class Query(cookbook.ingredients.schema.Query): diff --git a/examples/cookbook/cookbook/settings.py b/examples/cookbook_django/cookbook/settings.py similarity index 100% rename from examples/cookbook/cookbook/settings.py rename to examples/cookbook_django/cookbook/settings.py diff --git a/examples/cookbook/cookbook/urls.py b/examples/cookbook_django/cookbook/urls.py similarity index 89% rename from examples/cookbook/cookbook/urls.py rename to examples/cookbook_django/cookbook/urls.py index f0ad66f3..e8bc0aa5 100644 --- a/examples/cookbook/cookbook/urls.py +++ b/examples/cookbook_django/cookbook/urls.py @@ -1,10 +1,9 @@ -from django.conf.urls import url, include +from django.conf.urls import include, url from django.contrib import admin from django.views.decorators.csrf import csrf_exempt -from graphene.contrib.django.views import GraphQLView - from cookbook.schema import schema +from graphene.contrib.django.views import GraphQLView urlpatterns = [ url(r'^admin/', admin.site.urls), diff --git a/examples/cookbook/cookbook/wsgi.py b/examples/cookbook_django/cookbook/wsgi.py similarity index 100% rename from examples/cookbook/cookbook/wsgi.py rename to examples/cookbook_django/cookbook/wsgi.py diff --git a/examples/cookbook/manage.py b/examples/cookbook_django/manage.py similarity index 100% rename from examples/cookbook/manage.py rename to examples/cookbook_django/manage.py diff --git a/examples/cookbook/requirements.txt b/examples/cookbook_django/requirements.txt similarity index 100% rename from examples/cookbook/requirements.txt rename to examples/cookbook_django/requirements.txt 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/__init__.py b/graphene/contrib/django/filter/__init__.py index 95b28aff..51a04b7e 100644 --- a/graphene/contrib/django/filter/__init__.py +++ b/graphene/contrib/django/filter/__init__.py @@ -8,8 +8,6 @@ if not DJANGO_FILTER_INSTALLED: from .fields import DjangoFilterConnectionField from .filterset import GrapheneFilterSet, GlobalIDFilter, GlobalIDMultipleChoiceFilter -from .resolvers import FilterConnectionResolver __all__ = ['DjangoFilterConnectionField', 'GrapheneFilterSet', - 'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter', - 'FilterConnectionResolver'] + 'GlobalIDFilter', 'GlobalIDMultipleChoiceFilter'] diff --git a/graphene/contrib/django/filter/fields.py b/graphene/contrib/django/filter/fields.py index 43196f6e..d8457fa8 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 ..fields import DjangoConnectionField +from .utils import get_filtering_args_from_filterset, get_filterset_class -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/filterset.py b/graphene/contrib/django/filter/filterset.py index 3ecd9680..70f776be 100644 --- a/graphene/contrib/django/filter/filterset.py +++ b/graphene/contrib/django/filter/filterset.py @@ -2,12 +2,12 @@ import six from django.conf import settings from django.db import models from django.utils.text import capfirst - from django_filters import Filter, MultipleChoiceFilter from django_filters.filterset import FilterSet, FilterSetMetaclass +from graphql_relay.node.node import from_global_id + from graphene.contrib.django.forms import (GlobalIDFormField, GlobalIDMultipleChoiceField) -from graphql_relay.node.node import from_global_id class GlobalIDFilter(Filter): 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/filters.py b/graphene/contrib/django/filter/tests/filters.py index 94c0dffe..bccd72d5 100644 --- a/graphene/contrib/django/filter/tests/filters.py +++ b/graphene/contrib/django/filter/tests/filters.py @@ -1,4 +1,5 @@ import django_filters + from graphene.contrib.django.tests.models import Article, Pet, Reporter diff --git a/graphene/contrib/django/filter/tests/test_fields.py b/graphene/contrib/django/filter/tests/test_fields.py index b2591d1e..56d69dc8 100644 --- a/graphene/contrib/django/filter/tests/test_fields.py +++ b/graphene/contrib/django/filter/tests/test_fields.py @@ -1,7 +1,6 @@ from datetime import datetime import pytest -from graphql.core.execution.base import ResolveInfo, ExecutionContext from graphene import ObjectType, Schema from graphene.contrib.django import DjangoNode @@ -10,7 +9,6 @@ from graphene.contrib.django.forms import (GlobalIDFormField, 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.utils import ProxySnakeDict pytestmark = [] if DJANGO_FILTER_INSTALLED: @@ -187,8 +185,8 @@ def test_filter_filterset_related_results(): r1 = Reporter.objects.create(first_name='r1', last_name='r1', email='r1@test.com') r2 = Reporter.objects.create(first_name='r2', last_name='r2', email='r2@test.com') - a1 = Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1) - a2 = Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2) + Article.objects.create(headline='a1', pub_date=datetime.now(), reporter=r1) + Article.objects.create(headline='a2', pub_date=datetime.now(), reporter=r2) query = ''' query { @@ -217,7 +215,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 +229,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 +237,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 +245,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 +259,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 +267,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 +281,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..5071ddc4 --- /dev/null +++ b/graphene/contrib/django/filter/utils.py @@ -0,0 +1,31 @@ +import six + +from ....core.types import Argument, String +from .filterset import custom_filterset_factory, setup_filterset + + +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/forms.py b/graphene/contrib/django/forms.py index 88f1665e..d8062e39 100644 --- a/graphene/contrib/django/forms.py +++ b/graphene/contrib/django/forms.py @@ -3,7 +3,6 @@ import binascii from django.core.exceptions import ValidationError from django.forms import CharField, Field, IntegerField, MultipleChoiceField from django.utils.translation import ugettext_lazy as _ - from graphql_relay import from_global_id diff --git a/graphene/contrib/django/management/commands/graphql_schema.py b/graphene/contrib/django/management/commands/graphql_schema.py index 35eb2772..57174e01 100644 --- a/graphene/contrib/django/management/commands/graphql_schema.py +++ b/graphene/contrib/django/management/commands/graphql_schema.py @@ -1,8 +1,8 @@ -from django.core.management.base import BaseCommand, CommandError - import importlib import json +from django.core.management.base import BaseCommand, CommandError + class Command(BaseCommand): help = 'Dump Graphene schema JSON to file' 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/tests/test_resolvers.py b/graphene/contrib/django/tests/test_resolvers.py deleted file mode 100644 index db1610c9..00000000 --- a/graphene/contrib/django/tests/test_resolvers.py +++ /dev/null @@ -1,60 +0,0 @@ -from django.db.models import Manager -from django.db.models.query import QuerySet - -from graphene.contrib.django import DjangoNode -from graphene.contrib.django.resolvers import SimpleQuerySetConnectionResolver -from graphene.contrib.django.tests.models import Article, Reporter - - -class ReporterNode(DjangoNode): - - class Meta: - model = Reporter - - -class ArticleNode(DjangoNode): - - class Meta: - model = Article - - -def test_simple_resolve(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = SimpleQuerySetConnectionResolver(ReporterNode, on='articles') - resolved = resolver(inst=reporter, args={}, info=None) - assert isinstance(resolved, QuerySet), 'Did not resolve to a queryset' - - -def test_simple_get_manager_related(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = SimpleQuerySetConnectionResolver(ReporterNode, on='articles') - resolver(inst=reporter, args={}, info=None) - assert resolver.get_manager().instance == reporter, 'Resolver did not return a RelatedManager' - - -def test_simple_get_manager_all(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = SimpleQuerySetConnectionResolver(ReporterNode) - resolver(inst=reporter, args={}, info=None) - assert isinstance(resolver.get_manager(), Manager), 'Resolver did not return a Manager' - - -def test_simple_filter(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = SimpleQuerySetConnectionResolver(ReporterNode) - 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_simple_order(): - reporter = Reporter(id=1, first_name='Cookie Monster') - resolver = SimpleQuerySetConnectionResolver(ReporterNode) - resolved = resolver(inst=reporter, args={ - 'order_by': 'last_name' - }, info=None) - assert 'WHERE' not in str(resolved.query) - assert 'ORDER BY' in str(resolved.query) - assert '"last_name" ASC' in str(resolved.query) diff --git a/graphene/contrib/django/tests/test_schema.py b/graphene/contrib/django/tests/test_schema.py index e474121f..07a9a84b 100644 --- a/graphene/contrib/django/tests/test_schema.py +++ b/graphene/contrib/django/tests/test_schema.py @@ -1,7 +1,7 @@ from py.test import raises +from tests.utils import assert_equal_lists from graphene.contrib.django import DjangoObjectType -from tests.utils import assert_equal_lists from .models import Reporter diff --git a/graphene/contrib/django/tests/test_types.py b/graphene/contrib/django/tests/test_types.py index 42028a5e..c3583fe6 100644 --- a/graphene/contrib/django/tests/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -1,12 +1,12 @@ from graphql.core.type import GraphQLObjectType from mock import patch +from tests.utils import assert_equal_lists from graphene import Schema from graphene.contrib.django.types import DjangoNode, DjangoObjectType from graphene.core.fields import Field from graphene.core.types.scalars import Int from graphene.relay.fields import GlobalIDField -from tests.utils import assert_equal_lists from .models import Article, Reporter 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 diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py index 189111a1..8fb7e836 100644 --- a/graphene/core/tests/test_schema.py +++ b/graphene/core/tests/test_schema.py @@ -1,10 +1,10 @@ from graphql.core import graphql from py.test import raises +from tests.utils import assert_equal_lists from graphene import Interface, List, ObjectType, Schema, String from graphene.core.fields import Field from graphene.core.types.base import LazyType -from tests.utils import assert_equal_lists schema = Schema(name='My own schema') diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 6cbfff96..2d96f1d3 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -51,6 +51,16 @@ class Field(NamedType, OrderedType): def resolver(self): return self.resolver_fn or self.get_resolver_fn() + @property + def default(self): + if callable(self._default): + return self._default() + return self._default + + @default.setter + def default(self, value): + self._default = value + def get_resolver_fn(self): resolve_fn_name = 'resolve_%s' % self.attname if hasattr(self.object_type, resolve_fn_name): diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index dc8c4973..aa446083 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -23,15 +23,15 @@ class ConnectionField(Field): self.connection_type = connection_type self.edge_type = edge_type - def wrap_resolved(self, value, instance, args, info): - return value - def resolver(self, instance, args, info): schema = info.schema.graphene_schema connection_type = self.get_type(schema) resolved = super(ConnectionField, self).resolver(instance, args, info) if isinstance(resolved, connection_type): return resolved + return self.from_list(connection_type, resolved, args, info) + + def from_list(self, connection_type, resolved, args, info): return connection_type.from_list(resolved, args, info) def get_connection_type(self, node): diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 425e3038..672042e7 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -4,7 +4,6 @@ from collections import Iterable from functools import wraps import six - from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import to_global_id diff --git a/setup.cfg b/setup.cfg index d9e6b06f..2cd61786 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [flake8] -exclude = setup.py,docs/* +exclude = setup.py,docs/*,examples/cookbook_django/* max-line-length = 120 [coverage:run] diff --git a/setup.py b/setup.py index ac0e3d0f..90388c63 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ setup( install_requires=[ 'six>=1.10.0', - 'graphql-core==0.4.9', + 'graphql-core>=0.4.9', 'graphql-relay==0.3.3', ], tests_require=[