From 25eca8776a58425e2bec11952c0cc64a1f41d2c2 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 3 Oct 2015 16:48:18 -0700 Subject: [PATCH] Added ability to skip fields in runtime --- graphene/contrib/django/fields.py | 22 +++++++++++++--------- graphene/contrib/django/types.py | 5 ++++- graphene/core/fields.py | 7 +++++-- graphene/core/scalars.py | 10 ++++++++++ graphene/core/schema.py | 4 ++++ graphene/core/types.py | 4 ++-- graphene/relay/types.py | 2 ++ tests/contrib_django/test_schema.py | 3 --- 8 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 graphene/core/scalars.py diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index 4d70b9b6..f92096cd 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -65,14 +65,18 @@ class DjangoModelField(Field): @memoize def internal_type(self, schema): _type = self.get_object_type(schema) - return _type and _type.internal_type(schema) + if not _type and self.object_type._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 %s in %s" % ( + self.model, + self.field_name, + self.object_type + ) + ) + return _type and _type.internal_type(schema) or Field.SKIP def get_object_type(self, schema): - _type = get_type_for_model(schema, self.model) - if not _type and self.object_type._meta.only_fields: - # We will only raise the exception if the related field is - # specified in only_fields - raise Exception("Field %s (%s) model not mapped in current schema" % ( - self, self.model._meta.object_name)) - - return _type + return get_type_for_model(schema, self.model) diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index 0c5026dc..8e6a3f57 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -46,4 +46,7 @@ class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)): class DjangoNode(BaseNode, DjangoInterface): - pass + @classmethod + def get_node(cls, id): + instance = cls._meta.model.objects.filter(id=id).first() + return cls(instance) diff --git a/graphene/core/fields.py b/graphene/core/fields.py index c74b2ac4..49f52338 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -12,9 +12,11 @@ from graphql.core.type import ( ) from graphene.utils import memoize, to_camel_case from graphene.core.types import BaseObjectType +from graphene.core.scalars import GraphQLSkipField class Field(object): + SKIP = GraphQLSkipField def __init__(self, field_type, name=None, resolve=None, null=True, args=None, description='', **extra_args): self.field_type = field_type @@ -95,7 +97,8 @@ class Field(object): )) internal_type = self.internal_type(schema) - + if not internal_type: + raise Exception("Internal type for field %s is None" % self) return GraphQLField( internal_type, description=self.description, @@ -111,7 +114,7 @@ class Field(object): """ Displays the module, class and name of the field. """ - path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) + path = '%r.%s' % (self.object_type, self.__class__.__name__) name = getattr(self, 'field_name', None) if name is not None: return '<%s: %s>' % (path, name) diff --git a/graphene/core/scalars.py b/graphene/core/scalars.py new file mode 100644 index 00000000..2b6a9494 --- /dev/null +++ b/graphene/core/scalars.py @@ -0,0 +1,10 @@ +from graphql.core.type.definition import GraphQLScalarType + + +def skip(value): + return None + +GraphQLSkipField = GraphQLScalarType(name='SkipField', + serialize=skip, + parse_value=skip, + parse_literal=skip) diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 6d979f76..7b3cd640 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -45,6 +45,10 @@ class Schema(object): def associate_internal_type(self, internal_type, object_type): self._internal_types[internal_type.name] = object_type + def register(self, object_type): + self._internal_types[object_type._meta.type_name] = object_type + return object_type + def get_type(self, type_name): # print 'get_type' # _type = self.schema.get_type(type_name) diff --git a/graphene/core/types.py b/graphene/core/types.py index 59c7221c..a646f37b 100644 --- a/graphene/core/types.py +++ b/graphene/core/types.py @@ -49,7 +49,7 @@ class ObjectTypeMeta(type): new_class.add_extra_fields() new_fields = new_class._meta.local_fields - field_names = {f.name for f in new_fields} + field_names = {f.name:f for f in new_fields} for base in parents: original_base = base @@ -65,7 +65,7 @@ class ObjectTypeMeta(type): # on the base classes (we cannot handle shadowed fields at the # moment). for field in parent_fields: - if field.name in field_names: + if field.name in field_names and field.__class__ != field_names[field].__class__: raise Exception( 'Local field %r in class %r clashes ' 'with field of similar name from ' diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 4898c78d..a971447f 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -19,6 +19,8 @@ def get_node(schema, globalId, *args): resolvedGlobalId = fromGlobalId(globalId) _type, _id = resolvedGlobalId.type, resolvedGlobalId.id object_type = schema.get_type(_type) + if not object_type or not issubclass(object_type, BaseNode): + raise Exception("The type %s is not a Node" % _type) return object_type.get_node(_id) diff --git a/tests/contrib_django/test_schema.py b/tests/contrib_django/test_schema.py index 6fc51074..6469913b 100644 --- a/tests/contrib_django/test_schema.py +++ b/tests/contrib_django/test_schema.py @@ -43,9 +43,6 @@ def test_should_raise_if_model_is_invalid(): result = schema.execute(query) assert not result.errors - assert 'articles (Article) model not mapped in current schema' in str( - excinfo.value) - def test_should_map_fields_correctly(): class ReporterType2(DjangoObjectType):