mirror of
https://github.com/graphql-python/graphene.git
synced 2025-03-11 07:25:47 +03:00
Improved classtypes django support
This commit is contained in:
parent
5b3000f734
commit
f5837ac4f3
|
@ -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']
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user