diff --git a/graphene-django/examples/starwars/schema.py b/graphene-django/examples/starwars/schema.py index b30ebcc0..f708b6fc 100644 --- a/graphene-django/examples/starwars/schema.py +++ b/graphene-django/examples/starwars/schema.py @@ -49,9 +49,9 @@ class IntroduceShip(relay.ClientIDMutation): faction = graphene.Field(Faction) @classmethod - def mutate_and_get_payload(cls, input, info): - ship_name = input.get('ship_name') - faction_id = input.get('faction_id') + def mutate_and_get_payload(cls, input, context, info): + ship_name = input.get('shipName') + faction_id = input.get('factionId') ship = create_ship(ship_name, faction_id) faction = get_faction(faction_id) return IntroduceShip(ship=ship, faction=faction) @@ -60,7 +60,7 @@ class IntroduceShip(relay.ClientIDMutation): class Query(graphene.ObjectType): rebels = graphene.Field(Faction) empire = graphene.Field(Faction) - node = relay.Node.Field() + node = DjangoNode.Field() ships = relay.ConnectionField(Ship, description='All the ships.') @resolve_only_args @@ -77,9 +77,9 @@ class Query(graphene.ObjectType): class Mutation(graphene.ObjectType): - introduce_ship = graphene.Field(IntroduceShip) + introduce_ship = IntroduceShip.Field() # We register the Character Model because if not would be # inaccessible for the schema -schema = Schema(query=Query, mutation=Mutation, types=[]) +schema = Schema(query=Query, mutation=Mutation, types=[Ship, Character]) diff --git a/graphene-django/graphene_django/fields.py b/graphene-django/graphene_django/fields.py index 839cdf20..087bd7c6 100644 --- a/graphene-django/graphene_django/fields.py +++ b/graphene-django/graphene_django/fields.py @@ -1,19 +1,18 @@ -# from ...core.exceptions import SkipField -from graphene import Field, List +from django.db.models.query import QuerySet from graphene.relay import ConnectionField -from .utils import DJANGO_FILTER_INSTALLED, get_type_for_model, maybe_queryset +from graphql_relay.connection.arrayconnection import connection_from_list_slice +from .utils import maybe_queryset class DjangoConnectionField(ConnectionField): def __init__(self, *args, **kwargs): self.on = kwargs.pop('on', False) - # kwargs['default'] = kwargs.pop('default', self.get_manager) return super(DjangoConnectionField, self).__init__(*args, **kwargs) @property def model(self): - return self.type._meta.model + return self.connection._meta.node._meta.model def get_manager(self): if self.on: @@ -21,44 +20,22 @@ class DjangoConnectionField(ConnectionField): else: return self.model._default_manager - def get_queryset(self, resolved_qs, args, info): - return resolved_qs + def default_resolver(self, root, args, context, info): + return getattr(root, self.source or self.attname, self.get_manager()) - def from_list(self, connection_type, resolved, args, context, info): - resolved_qs = maybe_queryset(resolved) - qs = self.get_queryset(resolved_qs, args, info) - return super(DjangoConnectionField, self).from_list(connection_type, qs, args, context, info) - - -def get_list_or_connection_type_for_model(model): - pass - # field_object_type = model_field.get_object_type(schema) - # if not field_object_type: - # raise SkipField() - # if isinstance(: - # if field_object_type._meta.filter_fields: - # field = DjangoFilterConnectionField(field_object_type) - # else: - # field = DjangoConnectionField(field_object_type) - # else: - # field = List(field_object_type) - # field.contribute_to_class(self.object_type, self.attname) - # return schema.T(field) - - -def get_graphene_type_from_model(model): - pass - # _type = self.get_object_type(schema) - # if not _type and self.parent._meta.only_fields: - # raise Exception( - # "Model %r is not accessible by the schema. " - # "You can either register the type manually " - # "using @schema.register. " - # "Or disable the field in %s" % ( - # self.model, - # self.parent, - # ) - # ) - # if not _type: - # raise SkipField() - # return schema.T(_type) + def connection_resolver(self, root, args, context, info): + iterable = super(ConnectionField, self).resolver(root, args, context, info) + iterable = maybe_queryset(iterable) + if isinstance(iterable, QuerySet): + _len = iterable.count() + else: + _len = len(iterable) + return connection_from_list_slice( + iterable, + args, + slice_start=0, + list_length=_len, + list_slice_length=_len, + connection_type=self.connection, + edge_type=self.connection.Edge, + ) diff --git a/graphene-django/graphene_django/tests/test_command.py b/graphene-django/graphene_django/tests/test_command.py index fac4f7ac..ec887b5b 100644 --- a/graphene-django/graphene_django/tests/test_command.py +++ b/graphene-django/graphene_django/tests/test_command.py @@ -5,7 +5,7 @@ from six import StringIO @patch('graphene_django.management.commands.graphql_schema.Command.save_file') def test_generate_file_on_call_graphql_schema(savefile_mock, settings): - settings.GRAPHENE_SCHEMA = 'graphene_django.tests.test_urls' + settings.GRAPHENE_SCHEMA = 'graphene_django.tests.urls' out = StringIO() management.call_command('graphql_schema', schema='', stdout=out) assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue() diff --git a/graphene-django/graphene_django/tests/test_converter.py b/graphene-django/graphene_django/tests/test_converter.py index 59beff2f..7057f37f 100644 --- a/graphene-django/graphene_django/tests/test_converter.py +++ b/graphene-django/graphene_django/tests/test_converter.py @@ -115,9 +115,8 @@ def test_field_with_choices_convert_enum(): app_label = 'test' graphene_type = convert_django_field_with_choices(field) - assert issubclass(graphene_type, graphene.Enum) - assert graphene_type._meta.graphql_type.name == 'TEST_TRANSLATEDMODEL_LANGUAGE' - assert graphene_type._meta.graphql_type.description == 'Language' + assert isinstance(graphene_type, graphene.Enum) + assert graphene_type._meta.graphql_type.name == 'TranslatedModelLanguage' assert graphene_type._meta.enum.__members__['SPANISH'].value == 'es' assert graphene_type._meta.enum.__members__['ENGLISH'].value == 'en' diff --git a/graphene-django/graphene_django/tests/test_query.py b/graphene-django/graphene_django/tests/test_query.py index c8fd6439..14817a00 100644 --- a/graphene-django/graphene_django/tests/test_query.py +++ b/graphene-django/graphene_django/tests/test_query.py @@ -5,11 +5,10 @@ from django.db import models from py.test import raises import graphene -from graphene import relay from ..compat import MissingType, RangeField from ..types import DjangoNode, DjangoObjectType -from ..registry import reset_global_registry +from ..registry import reset_global_registry, get_global_registry from .models import Article, Reporter pytestmark = pytest.mark.django_db @@ -43,7 +42,7 @@ def test_should_query_well(): reporter = graphene.Field(ReporterType) def resolve_reporter(self, *args, **kwargs): - return ReporterType(Reporter(first_name='ABA', last_name='X')) + return Reporter(first_name='ABA', last_name='X') query = ''' query ReporterQuery { @@ -119,37 +118,37 @@ def test_should_query_postgres_fields(): def test_should_node(): - reset_global_registry() + # reset_global_registry() + # DjangoNode._meta.registry = get_global_registry() - class ReporterNode(DjangoNode): + class ReporterNode(DjangoNode, DjangoObjectType): class Meta: model = Reporter @classmethod - def get_node(cls, id, info): - return ReporterNode(Reporter(id=2, first_name='Cookie Monster')) + def get_node(cls, id, context, info): + return Reporter(id=2, first_name='Cookie Monster') def resolve_articles(self, *args, **kwargs): return [Article(headline='Hi!')] - class ArticleNode(DjangoNode): + class ArticleNode(DjangoNode, DjangoObjectType): class Meta: model = Article @classmethod - def get_node(cls, id, info): + def get_node(cls, id, context, info): return Article(id=1, headline='Article node', pub_date=datetime.date(2002, 3, 11)) class Query(graphene.ObjectType): - node = relay.Node.Field() + node = DjangoNode.Field() reporter = graphene.Field(ReporterNode) article = graphene.Field(ArticleNode) def resolve_reporter(self, *args, **kwargs): - return ReporterNode( - Reporter(id=1, first_name='ABA', last_name='X')) + return Reporter(id=1, first_name='ABA', last_name='X') query = ''' query ReporterQuery { diff --git a/graphene-django/graphene_django/tests/test_schema.py b/graphene-django/graphene_django/tests/test_schema.py index f63788f2..85f0fbb9 100644 --- a/graphene-django/graphene_django/tests/test_schema.py +++ b/graphene-django/graphene_django/tests/test_schema.py @@ -1,7 +1,7 @@ from py.test import raises from ..types import DjangoObjectType -from tests.utils import assert_equal_lists +from ..registry import Registry from .models import Reporter @@ -10,7 +10,7 @@ def test_should_raise_if_no_model(): with raises(Exception) as excinfo: class Character1(DjangoObjectType): pass - assert 'model in the Meta' in str(excinfo.value) + assert 'valid Django Model' in str(excinfo.value) def test_should_raise_if_model_is_invalid(): @@ -19,18 +19,15 @@ def test_should_raise_if_model_is_invalid(): class Meta: model = 1 - assert 'not a Django model' in str(excinfo.value) + assert 'valid Django Model' in str(excinfo.value) def test_should_map_fields_correctly(): class ReporterType2(DjangoObjectType): - class Meta: model = Reporter - assert_equal_lists( - ReporterType2._meta.fields_map.keys(), - ['articles', 'first_name', 'last_name', 'email', 'pets', 'id', 'films'] - ) + registry = Registry() + assert list(ReporterType2._meta.graphql_type.get_fields().keys()) == ['id', 'firstName', 'lastName', 'email', 'pets', 'aChoice'] def test_should_map_only_few_fields(): @@ -38,8 +35,6 @@ def test_should_map_only_few_fields(): class Meta: model = Reporter - only_fields = ('id', 'email') - assert_equal_lists( - Reporter2._meta.fields_map.keys(), - ['id', 'email'] - ) + fields = ('id', 'email') + + assert list(Reporter2._meta.graphql_type.get_fields().keys()) == ['id', 'email'] diff --git a/graphene-django/graphene_django/tests/urls.py b/graphene-django/graphene_django/tests/urls.py index 5caa4a82..644a1125 100644 --- a/graphene-django/graphene_django/tests/urls.py +++ b/graphene-django/graphene_django/tests/urls.py @@ -2,13 +2,13 @@ from django.conf.urls import url import graphene from graphene import Schema -from ..types import DjangoNode +from ..types import DjangoNode, DjangoObjectType from ..views import GraphQLView from .models import Article, Reporter -class Character(DjangoNode): +class Character(DjangoNode, DjangoObjectType): class Meta: model = Reporter @@ -17,7 +17,7 @@ class Character(DjangoNode): pass -class Human(DjangoNode): +class Human(DjangoNode, DjangoObjectType): raises = graphene.String() class Meta: diff --git a/graphene-django/graphene_django/types.py b/graphene-django/graphene_django/types.py index 8b02c8b1..437393eb 100644 --- a/graphene-django/graphene_django/types.py +++ b/graphene-django/graphene_django/types.py @@ -1,21 +1,18 @@ -import inspect -from collections import OrderedDict from functools import partial import six -from django.db import models from graphene import Field, Interface from graphene.types.objecttype import ObjectType, ObjectTypeMeta, attrs_without_fields, GrapheneObjectType, get_interfaces -from graphene.types.interface import InterfaceTypeMeta -from graphene.relay import Connection, Node +from graphene.relay import Node from graphene.relay.node import NodeMeta from .converter import convert_django_field_with_choices from graphene.types.options import Options -from .utils import get_model_fields +from .utils import get_model_fields, is_valid_django_model from .registry import Registry, get_global_registry from graphene.utils.is_base_type import is_base_type from graphene.utils.copy_fields import copy_fields +from graphene.utils.get_graphql_type import get_graphql_type from graphene.utils.get_fields import get_fields from graphene.utils.as_field import as_field @@ -63,8 +60,8 @@ class DjangoObjectTypeMeta(ObjectTypeMeta): ) if not options.registry: options.registry = get_global_registry() - assert isinstance(options.registry, Registry), 'The attribute registry in {}.Meta needs to be an instance of Registry.'.format(name) - assert options.model, 'You need to pass a valid Django Model in {}.Meta'.format(name) + assert isinstance(options.registry, Registry), 'The attribute registry in {}.Meta needs to be an instance of Registry, received "{}".'.format(name, options.registry) + assert is_valid_django_model(options.model), 'You need to pass a valid Django Model in {}.Meta, received "{}".'.format(name, options.model) interfaces = tuple(options.interfaces) fields = get_fields(ObjectType, attrs, bases, interfaces) @@ -80,7 +77,7 @@ class DjangoObjectTypeMeta(ObjectTypeMeta): interfaces=tuple(get_interfaces(interfaces + base_interfaces)) ) - if issubclass(cls, DjangoObjectType): + if issubclass(cls, DjangoObjectType): options.registry.register(cls) return cls @@ -90,8 +87,24 @@ class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)): pass -class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta): - pass +class DjangoNodeMeta(NodeMeta, DjangoObjectTypeMeta): + + @staticmethod + def _get_interface_options(meta): + return Options( + meta, + name=None, + description=None, + graphql_type=None, + registry=False + ) + + def __new__(cls, name, bases, attrs): + cls = super(DjangoNodeMeta, cls).__new__(cls, name, bases, attrs) + if not cls._meta.registry: + cls._meta.registry = get_global_registry() + assert isinstance(cls._meta.registry, Registry), 'The attribute registry in {}.Meta needs to be an instance of Registry.'.format(name) + return cls class DjangoNode(six.with_metaclass(DjangoNodeMeta, Node)): @@ -101,3 +114,13 @@ class DjangoNode(six.with_metaclass(DjangoNodeMeta, Node)): return cls._meta.model.objects.get(id=id) except cls._meta.model.DoesNotExist: return None + + @classmethod + def resolve_type(cls, type, context, info): + # We get the model from the _meta in the Django class/instance + model = type._meta.model + graphene_type = cls._meta.registry.get_type_for_model(model) + if graphene_type: + return get_graphql_type(graphene_type) + + raise Exception("Type not found for model \"{}\"".format(model)) diff --git a/graphene-django/graphene_django/utils.py b/graphene-django/graphene_django/utils.py index 0cf57a47..cf0f5894 100644 --- a/graphene-django/graphene_django/utils.py +++ b/graphene-django/graphene_django/utils.py @@ -1,6 +1,6 @@ +import inspect from django.db import models from django.db.models.manager import Manager -from django.db.models.query import QuerySet # from graphene.utils import LazyList class LazyList(object): @@ -54,8 +54,6 @@ class WrappedQueryset(LazyList): def maybe_queryset(value): if isinstance(value, Manager): value = value.get_queryset() - if isinstance(value, QuerySet): - return WrappedQueryset(value) return value @@ -75,6 +73,10 @@ def get_related_model(field): return field.related_model +def is_valid_django_model(model): + return inspect.isclass(model) and issubclass(model, models.Model) + + def import_single_dispatch(): try: from functools import singledispatch diff --git a/graphene-django/graphene_django/views.py b/graphene-django/graphene_django/views.py index c7a0692a..cc8b2b67 100644 --- a/graphene-django/graphene_django/views.py +++ b/graphene-django/graphene_django/views.py @@ -2,11 +2,8 @@ from graphql_django_view import GraphQLView as BaseGraphQLView class GraphQLView(BaseGraphQLView): - graphene_schema = None - def __init__(self, schema, **kwargs): super(GraphQLView, self).__init__( - graphene_schema=schema, schema=schema, **kwargs )