Improved classtypes django support

This commit is contained in:
Syrus Akbary 2015-12-02 23:36:51 -08:00
parent 5b3000f734
commit f5837ac4f3
5 changed files with 53 additions and 74 deletions

View File

@ -1,7 +1,6 @@
from graphene.contrib.django.types import ( from graphene.contrib.django.types import (
DjangoConnection, DjangoConnection,
DjangoObjectType, DjangoObjectType,
DjangoInterface,
DjangoNode DjangoNode
) )
from graphene.contrib.django.fields import ( from graphene.contrib.django.fields import (
@ -9,5 +8,5 @@ from graphene.contrib.django.fields import (
DjangoModelField DjangoModelField
) )
__all__ = ['DjangoObjectType', 'DjangoInterface', 'DjangoNode', __all__ = ['DjangoObjectType', 'DjangoNode', 'DjangoConnection',
'DjangoConnection', 'DjangoConnectionField', 'DjangoModelField'] 'DjangoConnectionField', 'DjangoModelField']

View File

@ -1,24 +1,15 @@
import inspect from ...core.classtypes.objecttype import ObjectTypeOptions
from django.db import models
from ...core.options import Options
from ...relay.types import Node from ...relay.types import Node
from ...relay.utils import is_node from ...relay.utils import is_node
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields') VALID_ATTRS = ('model', 'only_fields', 'exclude_fields')
def is_base(cls): class DjangoOptions(ObjectTypeOptions):
from graphene.contrib.django.types import DjangoObjectType
return DjangoObjectType in cls.__bases__
class DjangoOptions(Options):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.model = None
super(DjangoOptions, self).__init__(*args, **kwargs) super(DjangoOptions, self).__init__(*args, **kwargs)
self.model = None
self.valid_attrs += VALID_ATTRS self.valid_attrs += VALID_ATTRS
self.only_fields = None self.only_fields = None
self.exclude_fields = [] self.exclude_fields = []
@ -28,11 +19,3 @@ class DjangoOptions(Options):
if is_node(cls): if is_node(cls):
self.exclude_fields = list(self.exclude_fields) + ['id'] self.exclude_fields = list(self.exclude_fields) + ['id']
self.interfaces.append(Node) self.interfaces.append(Node)
if not is_node(cls) and not is_base(cls):
return
if not self.model:
raise Exception(
'Django ObjectType %s must have a model in the Meta class attr' %
cls)
elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model):
raise Exception('Provided model in %s is not a Django model' % cls)

View File

@ -3,7 +3,7 @@ from mock import patch
from pytest import raises from pytest import raises
from graphene import Schema from graphene import Schema
from graphene.contrib.django.types import DjangoInterface, DjangoNode from graphene.contrib.django.types import DjangoNode, DjangoObjectType
from graphene.core.fields import Field from graphene.core.fields import Field
from graphene.core.types.scalars import Int from graphene.core.types.scalars import Int
from graphene.relay.fields import GlobalIDField from graphene.relay.fields import GlobalIDField
@ -14,7 +14,8 @@ from .models import Article, Reporter
schema = Schema() schema = Schema()
class Character(DjangoInterface): @schema.register
class Character(DjangoObjectType):
'''Character description''' '''Character description'''
class Meta: class Meta:
model = Reporter model = Reporter
@ -31,7 +32,7 @@ class Human(DjangoNode):
def test_django_interface(): def test_django_interface():
assert DjangoNode._meta.is_interface is True assert DjangoNode._meta.interface is True
@patch('graphene.contrib.django.tests.models.Article.objects.get', return_value=Article(id=1)) @patch('graphene.contrib.django.tests.models.Article.objects.get', return_value=Article(id=1))
@ -41,17 +42,6 @@ def test_django_get_node(get):
assert human.id == 1 assert human.id == 1
def test_pseudo_interface_registered():
object_type = schema.T(Character)
assert Character._meta.is_interface is True
assert isinstance(object_type, GraphQLInterfaceType)
assert Character._meta.model == Reporter
assert_equal_lists(
object_type.get_fields().keys(),
['articles', 'firstName', 'lastName', 'email', 'pets', 'id']
)
def test_djangonode_idfield(): def test_djangonode_idfield():
idfield = DjangoNode._meta.fields_map['id'] idfield = DjangoNode._meta.fields_map['id']
assert isinstance(idfield, GlobalIDField) assert isinstance(idfield, GlobalIDField)
@ -68,32 +58,21 @@ def test_node_replacedfield():
assert schema.T(idfield).type == schema.T(Int()) assert schema.T(idfield).type == schema.T(Int())
def test_interface_resolve_type(): def test_objecttype_init_none():
resolve_type = Character._resolve_type(schema, Human())
assert isinstance(resolve_type, GraphQLObjectType)
def test_interface_objecttype_init_none():
h = Human() h = Human()
assert h._root is None assert h._root is None
def test_interface_objecttype_init_good(): def test_objecttype_init_good():
instance = Article() instance = Article()
h = Human(instance) h = Human(instance)
assert h._root == instance assert h._root == instance
def test_interface_objecttype_init_unexpected():
with raises(AssertionError) as excinfo:
Human(object())
assert str(excinfo.value) == "Human received a non-compatible instance (object) when expecting Article"
def test_object_type(): def test_object_type():
object_type = schema.T(Human) object_type = schema.T(Human)
Human._meta.fields_map Human._meta.fields_map
assert Human._meta.is_interface is False assert Human._meta.interface is False
assert isinstance(object_type, GraphQLObjectType) assert isinstance(object_type, GraphQLObjectType)
assert_equal_lists( assert_equal_lists(
object_type.get_fields().keys(), object_type.get_fields().keys(),
@ -103,5 +82,5 @@ def test_object_type():
def test_node_notinterface(): def test_node_notinterface():
assert Human._meta.is_interface is False assert Human._meta.interface is False
assert DjangoNode in Human._meta.interfaces assert DjangoNode in Human._meta.interfaces

View File

@ -1,22 +1,19 @@
import six import six
import inspect
from ...core.types import BaseObjectType, ObjectTypeMeta from django.db import models
from ...relay.fields import GlobalIDField
from ...relay.types import BaseNode, Connection from ...core.classtypes.objecttype import ObjectTypeMeta, ObjectType
from ...relay.types import Node, NodeMeta, Connection
from .converter import convert_django_field from .converter import convert_django_field
from .options import DjangoOptions from .options import DjangoOptions
from .utils import get_reverse_fields, maybe_queryset from .utils import get_reverse_fields, maybe_queryset
class DjangoObjectTypeMeta(ObjectTypeMeta): class DjangoObjectTypeMeta(ObjectTypeMeta):
options_cls = DjangoOptions options_class = DjangoOptions
def is_interface(cls, parents): def construct_fields(cls):
return DjangoInterface in parents
def add_extra_fields(cls):
if not cls._meta.model:
return
only_fields = cls._meta.only_fields only_fields = cls._meta.only_fields
reverse_fields = get_reverse_fields(cls._meta.model) reverse_fields = get_reverse_fields(cls._meta.model)
all_fields = sorted(list(cls._meta.model._meta.fields) + all_fields = sorted(list(cls._meta.model._meta.fields) +
@ -35,8 +32,23 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
converted_field = convert_django_field(field) converted_field = convert_django_field(field)
cls.add_to_class(field.name, converted_field) cls.add_to_class(field.name, converted_field)
def construct(cls, *args, **kwargs):
cls = super(DjangoObjectTypeMeta, cls).construct(*args, **kwargs)
if not cls._meta.abstract:
if not cls._meta.model:
raise Exception(
'Django ObjectType %s must have a model in the Meta class attr' %
cls)
elif not inspect.isclass(cls._meta.model) or not issubclass(cls._meta.model, models.Model):
raise Exception('Provided model in %s is not a Django model' % cls)
class InstanceObjectType(BaseObjectType): cls.construct_fields()
return cls
class InstanceObjectType(ObjectType):
class Meta:
abstract = True
def __init__(self, _root=None): def __init__(self, _root=None):
if _root: if _root:
@ -63,12 +75,8 @@ class InstanceObjectType(BaseObjectType):
class DjangoObjectType(six.with_metaclass( class DjangoObjectType(six.with_metaclass(
DjangoObjectTypeMeta, InstanceObjectType)): DjangoObjectTypeMeta, InstanceObjectType)):
pass class Meta:
abstract = True
class DjangoInterface(six.with_metaclass(
DjangoObjectTypeMeta, InstanceObjectType)):
pass
class DjangoConnection(Connection): class DjangoConnection(Connection):
@ -79,8 +87,19 @@ class DjangoConnection(Connection):
return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs) return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs)
class DjangoNode(BaseNode, DjangoInterface): class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta):
id = GlobalIDField() pass
class NodeInstance(Node, InstanceObjectType):
class Meta:
abstract = True
class DjangoNode(six.with_metaclass(
DjangoNodeMeta, NodeInstance)):
class Meta:
abstract = True
@classmethod @classmethod
def get_node(cls, id, info=None): def get_node(cls, id, info=None):

View File

@ -87,7 +87,7 @@ class FieldsClassTypeMeta(ClassTypeMeta):
field_names = {f.name: f for f in new_fields} field_names = {f.name: f for f in new_fields}
for base in bases: for base in bases:
if not issubclass(base, FieldsClassType): if not isinstance(base, FieldsClassTypeMeta):
continue continue
parent_fields = base._meta.local_fields parent_fields = base._meta.local_fields
@ -110,8 +110,7 @@ class FieldsClassTypeMeta(ClassTypeMeta):
def construct(cls, bases, attrs): def construct(cls, bases, attrs):
cls = super(FieldsClassTypeMeta, cls).construct(bases, attrs) cls = super(FieldsClassTypeMeta, cls).construct(bases, attrs)
if not cls._meta.abstract: cls.extend_fields(bases)
cls.extend_fields(bases)
return cls return cls