mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-02 12:44:15 +03:00
First working version with Django.
This commit is contained in:
parent
c79097879d
commit
2e8707aee6
0
graphene/contrib/__init__.py
Normal file
0
graphene/contrib/__init__.py
Normal file
4
graphene/contrib/django/__init__.py
Normal file
4
graphene/contrib/django/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from graphene.contrib.django.types import (
|
||||
DjangoObjectType,
|
||||
DjangoNode
|
||||
)
|
40
graphene/contrib/django/converter.py
Normal file
40
graphene/contrib/django/converter.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from singledispatch import singledispatch
|
||||
from django.db import models
|
||||
|
||||
from graphene.core.fields import (
|
||||
StringField,
|
||||
IDField,
|
||||
IntField,
|
||||
BooleanField,
|
||||
FloatField,
|
||||
)
|
||||
|
||||
@singledispatch
|
||||
def convert_django_field(field):
|
||||
raise Exception("Don't know how to convert the Django field %s"%field)
|
||||
|
||||
|
||||
@convert_django_field.register(models.CharField)
|
||||
def _(field):
|
||||
return StringField(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.AutoField)
|
||||
def _(field):
|
||||
return IDField(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.BigIntegerField)
|
||||
@convert_django_field.register(models.IntegerField)
|
||||
def _(field):
|
||||
return IntField(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.BooleanField)
|
||||
def _(field):
|
||||
return BooleanField(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.FloatField)
|
||||
def _(field):
|
||||
return FloatField(description=field.help_text)
|
22
graphene/contrib/django/options.py
Normal file
22
graphene/contrib/django/options.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import inspect
|
||||
from django.db import models
|
||||
|
||||
from graphene.core.options import Options
|
||||
|
||||
VALID_ATTRS = ('model', 'only_fields')
|
||||
|
||||
class DjangoOptions(Options):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.model = None
|
||||
super(DjangoOptions, self).__init__(*args, **kwargs)
|
||||
self.valid_attrs += VALID_ATTRS
|
||||
self.only_fields = None
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
super(DjangoOptions, self).contribute_to_class(cls, name)
|
||||
if self.proxy:
|
||||
return
|
||||
if not self.model:
|
||||
raise Exception('Django ObjectType %s must have a model in the Meta 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)
|
31
graphene/contrib/django/types.py
Normal file
31
graphene/contrib/django/types.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
import six
|
||||
|
||||
from graphene.core.types import ObjectTypeMeta, ObjectType
|
||||
from graphene.contrib.django.options import DjangoOptions
|
||||
from graphene.contrib.django.converter import convert_django_field
|
||||
|
||||
from graphene.relay import Node
|
||||
|
||||
class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||
options_cls = DjangoOptions
|
||||
def add_extra_fields(cls):
|
||||
if not cls._meta.model:
|
||||
return
|
||||
|
||||
only_fields = cls._meta.only_fields
|
||||
# print cls._meta.model._meta._get_fields(forward=False, reverse=True, include_hidden=True)
|
||||
for field in cls._meta.model._meta.fields:
|
||||
if only_fields and field.name not in only_fields:
|
||||
continue
|
||||
converted_field = convert_django_field(field)
|
||||
cls.add_to_class(field.name, converted_field)
|
||||
|
||||
|
||||
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class DjangoNode(six.with_metaclass(DjangoObjectTypeMeta, Node)):
|
||||
class Meta:
|
||||
proxy = True
|
|
@ -8,8 +8,10 @@ from graphql.core.type import (
|
|||
GraphQLBoolean,
|
||||
GraphQLID,
|
||||
GraphQLArgument,
|
||||
GraphQLFloat,
|
||||
)
|
||||
from graphene.utils import cached_property
|
||||
from graphene.core.types import ObjectType
|
||||
|
||||
class Field(object):
|
||||
def __init__(self, field_type, resolve=None, null=True, args=None, description='', **extra_args):
|
||||
|
@ -44,7 +46,6 @@ class Field(object):
|
|||
return resolve_fn(instance, args, info)
|
||||
|
||||
def get_object_type(self):
|
||||
from graphene.core.types import ObjectType
|
||||
field_type = self.field_type
|
||||
_is_class = inspect.isclass(field_type)
|
||||
if _is_class and issubclass(field_type, ObjectType):
|
||||
|
@ -143,6 +144,10 @@ class IDField(TypeField):
|
|||
field_type = GraphQLID
|
||||
|
||||
|
||||
class FloatField(TypeField):
|
||||
field_type = GraphQLFloat
|
||||
|
||||
|
||||
class ListField(Field):
|
||||
def type_wrapper(self, field_type):
|
||||
return GraphQLList(field_type)
|
||||
|
|
|
@ -14,6 +14,7 @@ class Options(object):
|
|||
self.schema = schema
|
||||
self.interfaces = []
|
||||
self.parents = []
|
||||
self.valid_attrs = DEFAULT_NAMES
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
cls._meta = self
|
||||
|
@ -36,7 +37,7 @@ class Options(object):
|
|||
# over it, so we loop over the *original* dictionary instead.
|
||||
if name.startswith('_'):
|
||||
del meta_attrs[name]
|
||||
for attr_name in DEFAULT_NAMES:
|
||||
for attr_name in self.valid_attrs:
|
||||
if attr_name in meta_attrs:
|
||||
setattr(self, attr_name, meta_attrs.pop(attr_name))
|
||||
self.original_attrs[attr_name] = getattr(self, attr_name)
|
||||
|
@ -44,9 +45,13 @@ class Options(object):
|
|||
setattr(self, attr_name, getattr(self.meta, attr_name))
|
||||
self.original_attrs[attr_name] = getattr(self, attr_name)
|
||||
|
||||
del self.valid_attrs
|
||||
|
||||
# Any leftover attributes must be invalid.
|
||||
if meta_attrs != {}:
|
||||
raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
|
||||
else:
|
||||
self.proxy = False
|
||||
|
||||
if self.interfaces != [] and self.interface:
|
||||
raise Exception("A interface cannot inherit from interfaces")
|
||||
|
|
|
@ -11,9 +11,11 @@ from graphene.core.options import Options
|
|||
|
||||
|
||||
class ObjectTypeMeta(type):
|
||||
options_cls = Options
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
super_new = super(ObjectTypeMeta, cls).__new__
|
||||
parents = [b for b in bases if isinstance(b, ObjectTypeMeta)]
|
||||
parents = [b for b in bases if isinstance(b, cls)]
|
||||
if not parents:
|
||||
# If this isn't a subclass of Model, don't do anything special.
|
||||
return super_new(cls, name, bases, attrs)
|
||||
|
@ -25,20 +27,26 @@ class ObjectTypeMeta(type):
|
|||
'__doc__': doc
|
||||
})
|
||||
attr_meta = attrs.pop('Meta', None)
|
||||
proxy = None
|
||||
if not attr_meta:
|
||||
meta = getattr(new_class, 'Meta', None)
|
||||
meta = None
|
||||
# meta = getattr(new_class, 'Meta', None)
|
||||
else:
|
||||
meta = attr_meta
|
||||
|
||||
base_meta = getattr(new_class, '_meta', None)
|
||||
|
||||
schema = (base_meta and base_meta.schema)
|
||||
|
||||
new_class.add_to_class('_meta', Options(meta, schema))
|
||||
new_class.add_to_class('_meta', new_class.options_cls(meta, schema))
|
||||
|
||||
if base_meta and base_meta.proxy:
|
||||
new_class._meta.interface = base_meta.interface
|
||||
|
||||
# Add all attributes to the class.
|
||||
for obj_name, obj in attrs.items():
|
||||
new_class.add_to_class(obj_name, obj)
|
||||
new_class.add_extra_fields()
|
||||
|
||||
new_fields = new_class._meta.local_fields
|
||||
field_names = {f.field_name for f in new_fields}
|
||||
|
@ -71,6 +79,9 @@ class ObjectTypeMeta(type):
|
|||
new_class._prepare()
|
||||
return new_class
|
||||
|
||||
def add_extra_fields(cls):
|
||||
pass
|
||||
|
||||
def _prepare(cls):
|
||||
signals.class_prepared.send(cls)
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import collections
|
||||
|
||||
from graphql_relay.node.node import (
|
||||
globalIdField
|
||||
)
|
||||
|
@ -8,7 +6,6 @@ from graphql_relay.connection.connection import (
|
|||
)
|
||||
|
||||
from graphene import signals
|
||||
|
||||
from graphene.core.fields import NativeField
|
||||
from graphene.relay.utils import get_relay
|
||||
from graphene.relay.relay import Relay
|
||||
|
@ -18,10 +15,9 @@ from graphene.relay.relay import Relay
|
|||
def object_type_created(object_type):
|
||||
relay = get_relay(object_type._meta.schema)
|
||||
if relay and issubclass(object_type, relay.Node):
|
||||
if object_type._meta.proxy:
|
||||
return
|
||||
type_name = object_type._meta.type_name
|
||||
# def getId(*args, **kwargs):
|
||||
# print '**GET ID', args, kwargs
|
||||
# return 2
|
||||
field = NativeField(globalIdField(type_name))
|
||||
object_type.add_to_class('id', field)
|
||||
assert hasattr(object_type, 'get_node'), 'get_node classmethod not found in %s Node' % type_name
|
||||
|
|
0
tests/contrib_django/__init__.py
Normal file
0
tests/contrib_django/__init__.py
Normal file
17
tests/contrib_django/data.py
Normal file
17
tests/contrib_django/data.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from datetime import date
|
||||
|
||||
from .models import Reporter, Article
|
||||
|
||||
r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
|
||||
r.save()
|
||||
|
||||
r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com')
|
||||
r2.save()
|
||||
|
||||
a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r)
|
||||
a.save()
|
||||
|
||||
new_article = r.articles.create(headline="John's second story", pub_date=date(2005, 7, 29))
|
||||
|
||||
new_article2 = Article(headline="Paul's story", pub_date=date(2006, 1, 17))
|
||||
r.articles.add(new_article2)
|
43
tests/contrib_django/models.py
Normal file
43
tests/contrib_django/models.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from __future__ import absolute_import
|
||||
import django
|
||||
from django.conf import settings
|
||||
|
||||
settings.configure(
|
||||
DATABASES={
|
||||
'INSTALLED_APPS': [
|
||||
'graphql.contrib.django',
|
||||
],
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'db_test.sqlite',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
from django.db import models
|
||||
|
||||
class Reporter(models.Model):
|
||||
first_name = models.CharField(max_length=30)
|
||||
last_name = models.CharField(max_length=30)
|
||||
email = models.EmailField()
|
||||
|
||||
def __str__(self): # __unicode__ on Python 2
|
||||
return "%s %s" % (self.first_name, self.last_name)
|
||||
|
||||
class Meta:
|
||||
app_label = 'graphql'
|
||||
|
||||
class Article(models.Model):
|
||||
headline = models.CharField(max_length=100)
|
||||
pub_date = models.DateField()
|
||||
reporter = models.ForeignKey(Reporter, related_name='articles')
|
||||
|
||||
def __str__(self): # __unicode__ on Python 2
|
||||
return self.headline
|
||||
|
||||
class Meta:
|
||||
ordering = ('headline',)
|
||||
app_label = 'graphql'
|
||||
|
||||
|
||||
django.setup()
|
115
tests/contrib_django/test_schema.py
Normal file
115
tests/contrib_django/test_schema.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
from py.test import raises
|
||||
from collections import namedtuple
|
||||
from pytest import raises
|
||||
import graphene
|
||||
from graphene import relay
|
||||
from graphene.contrib.django import (
|
||||
DjangoObjectType,
|
||||
DjangoNode
|
||||
)
|
||||
from .models import Reporter, Article
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_should_raise_if_model_is_invalid():
|
||||
with raises(Exception) as excinfo:
|
||||
class Character2(DjangoObjectType):
|
||||
class Meta:
|
||||
model = 1
|
||||
assert 'not a Django model' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_should_map_fields():
|
||||
class ReporterType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
reporter = graphene.Field(ReporterType)
|
||||
|
||||
def resolve_reporter(self, *args, **kwargs):
|
||||
return ReporterType(Reporter(first_name='ABA', last_name='X'))
|
||||
|
||||
query = '''
|
||||
query ReporterQuery {
|
||||
reporter {
|
||||
first_name,
|
||||
last_name,
|
||||
email
|
||||
}
|
||||
}
|
||||
'''
|
||||
expected = {
|
||||
'reporter': {
|
||||
'first_name': 'ABA',
|
||||
'last_name': 'X',
|
||||
'email': ''
|
||||
}
|
||||
}
|
||||
Schema = graphene.Schema(query=Query)
|
||||
result = Schema.execute(query)
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
||||
|
||||
|
||||
def test_should_map_only_few_fields():
|
||||
class Reporter2(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
only_fields = ('id', 'email')
|
||||
assert Reporter2._meta.fields_map.keys() == ['id', 'email']
|
||||
|
||||
def test_should_node():
|
||||
class ReporterNodeType(DjangoNode):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id):
|
||||
return ReporterNodeType(Reporter(id=2, first_name='Cookie Monster'))
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
node = relay.NodeField()
|
||||
reporter = graphene.Field(ReporterNodeType)
|
||||
|
||||
def resolve_reporter(self, *args, **kwargs):
|
||||
return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X'))
|
||||
|
||||
query = '''
|
||||
query ReporterQuery {
|
||||
reporter {
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
email
|
||||
}
|
||||
aCustomNode: node(id:"UmVwb3J0ZXJOb2RlVHlwZToy") {
|
||||
id
|
||||
... on ReporterNodeType {
|
||||
first_name
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
expected = {
|
||||
'reporter': {
|
||||
'id': 'UmVwb3J0ZXJOb2RlVHlwZTox',
|
||||
'first_name': 'ABA',
|
||||
'last_name': 'X',
|
||||
'email': ''
|
||||
},
|
||||
'aCustomNode': {
|
||||
'id': 'UmVwb3J0ZXJOb2RlVHlwZToy',
|
||||
'first_name': 'Cookie Monster'
|
||||
}
|
||||
}
|
||||
Schema = graphene.Schema(query=Query)
|
||||
result = Schema.execute(query)
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
Loading…
Reference in New Issue
Block a user