Skip to content

Commit 48bcccd

Browse files
committed
Improved integration with Graphene 2.0
1 parent 18db46e commit 48bcccd

File tree

10 files changed

+85
-114
lines changed

10 files changed

+85
-114
lines changed

graphene_django/converter.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from graphql import assert_valid_name
1111

1212
from .compat import ArrayField, HStoreField, JSONField, RangeField
13-
from .fields import get_connection_field, DjangoListField
13+
from .fields import DjangoListField, DjangoConnectionField
1414
from .utils import get_related_model, import_single_dispatch
1515

1616
singledispatch = import_single_dispatch()
@@ -148,8 +148,16 @@ def dynamic_type():
148148
if not _type:
149149
return
150150

151-
if is_node(_type):
152-
return get_connection_field(_type)
151+
# If there is a connection, we should transform the field
152+
# into a DjangoConnectionField
153+
if _type._meta.connection:
154+
# Use a DjangoFilterConnectionField if there are
155+
# defined filter_fields in the DjangoObjectType Meta
156+
if _type._meta.filter_fields:
157+
from .filter.fields import DjangoFilterConnectionField
158+
return DjangoFilterConnectionField(_type)
159+
160+
return DjangoConnectionField(_type)
153161

154162
return DjangoListField(_type)
155163

graphene_django/fields.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ def __init__(self, *args, **kwargs):
4343
)
4444
super(DjangoConnectionField, self).__init__(*args, **kwargs)
4545

46+
@property
47+
def type(self):
48+
from .types import DjangoObjectType
49+
_type = super(ConnectionField, self).type
50+
assert issubclass(_type, DjangoObjectType), "DjangoConnectionField only accepts DjangoObjectType types"
51+
return _type._meta.connection
52+
4653
@property
4754
def node_type(self):
4855
return self.type._meta.node
@@ -128,10 +135,3 @@ def get_resolver(self, parent_resolver):
128135
self.max_limit,
129136
self.enforce_first_or_last
130137
)
131-
132-
133-
def get_connection_field(*args, **kwargs):
134-
if DJANGO_FILTER_INSTALLED:
135-
from .filter.fields import DjangoFilterConnectionField
136-
return DjangoFilterConnectionField(*args, **kwargs)
137-
return DjangoConnectionField(*args, **kwargs)

graphene_django/filter/tests/test_fields.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ def test_recursive_filter_connection():
356356
class ReporterFilterNode(DjangoObjectType):
357357
child_reporters = DjangoFilterConnectionField(lambda: ReporterFilterNode)
358358

359-
def resolve_child_reporters(self, args, context, info):
359+
def resolve_child_reporters(self, **args):
360360
return []
361361

362362
class Meta:
@@ -399,7 +399,7 @@ class Query(ObjectType):
399399
filterset_class=ReporterFilter
400400
)
401401

402-
def resolve_all_reporters(self, args, context, info):
402+
def resolve_all_reporters(self, **args):
403403
return Reporter.objects.order_by('a_choice')
404404

405405
Reporter.objects.create(
@@ -499,7 +499,7 @@ class Query(ObjectType):
499499
filterset_class=ReporterFilter
500500
)
501501

502-
def resolve_all_reporters(self, args, context, info):
502+
def resolve_all_reporters(self, **args):
503503
return Reporter.objects.order_by('a_choice')[:2]
504504

505505
Reporter.objects.create(

graphene_django/registry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
12
class Registry(object):
23

34
def __init__(self):
45
self._registry = {}
56
self._registry_models = {}
7+
self._connection_types = {}
68

79
def register(self, cls):
810
from .types import DjangoObjectType

graphene_django/rest_framework/mutation.py

Lines changed: 37 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33

44
import six
55
import graphene
6-
from graphene.types import Argument, Field
7-
from graphene.types.mutation import Mutation, MutationMeta
6+
from graphene import relay
7+
from graphene.types import Argument, Field, InputField
8+
from graphene.types.mutation import Mutation, MutationOptions
89
from graphene.types.objecttype import (
9-
ObjectTypeMeta,
10-
merge,
1110
yank_fields_from_attrs
1211
)
1312
from graphene.types.options import Options
1413
from graphene.types.utils import get_field_as
15-
from graphene.utils.is_base_type import is_base_type
1614

1715
from .serializer_converter import (
1816
convert_serializer_to_input_type,
@@ -21,90 +19,52 @@
2119
from .types import ErrorType
2220

2321

24-
class SerializerMutationOptions(Options):
25-
def __init__(self, *args, **kwargs):
26-
super().__init__(*args, serializer_class=None, **kwargs)
22+
class SerializerMutationOptions(MutationOptions):
23+
serializer_class = None
2724

2825

29-
class SerializerMutationMeta(MutationMeta):
30-
def __new__(cls, name, bases, attrs):
31-
if not is_base_type(bases, SerializerMutationMeta):
32-
return type.__new__(cls, name, bases, attrs)
33-
34-
options = Options(
35-
attrs.pop('Meta', None),
36-
name=name,
37-
description=attrs.pop('__doc__', None),
38-
serializer_class=None,
39-
local_fields=None,
40-
only_fields=(),
41-
exclude_fields=(),
42-
interfaces=(),
43-
registry=None
44-
)
45-
46-
if not options.serializer_class:
47-
raise Exception('Missing serializer_class')
48-
49-
cls = ObjectTypeMeta.__new__(
50-
cls, name, bases, dict(attrs, _meta=options)
51-
)
52-
53-
serializer_fields = cls.fields_for_serializer(options)
54-
options.serializer_fields = yank_fields_from_attrs(
55-
serializer_fields,
56-
_as=Field,
57-
)
58-
59-
options.fields = merge(
60-
options.interface_fields, options.serializer_fields,
61-
options.base_fields, options.local_fields,
62-
{'errors': get_field_as(cls.errors, Field)}
63-
)
64-
65-
cls.Input = convert_serializer_to_input_type(options.serializer_class)
66-
67-
cls.Field = partial(
68-
Field,
69-
cls,
70-
resolver=cls.mutate,
71-
input=Argument(cls.Input, required=True)
26+
def fields_for_serializer(serializer, only_fields, exclude_fields):
27+
fields = OrderedDict()
28+
for name, field in serializer.fields.items():
29+
is_not_in_only = only_fields and name not in only_fields
30+
is_excluded = (
31+
name in exclude_fields # or
32+
# name in already_created_fields
7233
)
7334

74-
return cls
35+
if is_not_in_only or is_excluded:
36+
continue
7537

76-
@staticmethod
77-
def fields_for_serializer(options):
78-
serializer = options.serializer_class()
38+
fields[name] = convert_serializer_field(field, is_input=False)
39+
return fields
7940

80-
only_fields = options.only_fields
8141

82-
already_created_fields = {
83-
name
84-
for name, _ in options.local_fields.items()
85-
}
42+
class SerializerMutation(relay.ClientIDMutation):
43+
errors = graphene.List(
44+
ErrorType,
45+
description='May contain more than one error for same field.'
46+
)
8647

87-
fields = OrderedDict()
88-
for name, field in serializer.fields.items():
89-
is_not_in_only = only_fields and name not in only_fields
90-
is_excluded = (
91-
name in options.exclude_fields or
92-
name in already_created_fields
93-
)
48+
@classmethod
49+
def __init_subclass_with_meta__(cls, serializer_class,
50+
only_fields=(), exclude_fields=(), **options):
9451

95-
if is_not_in_only or is_excluded:
96-
continue
52+
if not serializer_class:
53+
raise Exception('serializer_class is required for the SerializerMutation')
9754

98-
fields[name] = convert_serializer_field(field, is_input=False)
99-
return fields
55+
serializer = serializer_class()
56+
serializer_fields = fields_for_serializer(serializer, only_fields, exclude_fields)
10057

58+
_meta = SerializerMutationOptions(cls)
59+
_meta.fields = yank_fields_from_attrs(
60+
serializer_fields,
61+
_as=Field,
62+
)
10163

102-
class SerializerMutation(six.with_metaclass(SerializerMutationMeta, Mutation)):
103-
errors = graphene.List(
104-
ErrorType,
105-
description='May contain more than one error for '
106-
'same field.'
107-
)
64+
_meta.input_fields = yank_fields_from_attrs(
65+
serializer_fields,
66+
_as=InputField,
67+
)
10868

10969
@classmethod
11070
def mutate(cls, instance, args, request, info):

graphene_django/tests/schema.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Meta:
2222
model = Article
2323
interfaces = (relay.Node, )
2424

25-
def resolve_raises(self, *args):
25+
def resolve_raises(self):
2626
raise Exception("This field should raise exception")
2727

2828
def get_node(self, id):
@@ -32,7 +32,7 @@ def get_node(self, id):
3232
class Query(graphene.ObjectType):
3333
human = graphene.Field(Human)
3434

35-
def resolve_human(self, args, context, info):
35+
def resolve_human(self):
3636
return Human()
3737

3838

graphene_django/tests/schema_view.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import graphene
2-
from graphene import ObjectType, Schema
2+
from graphene import ObjectType, Schema, annotate, Context
33

44

55
class QueryRoot(ObjectType):
@@ -8,21 +8,21 @@ class QueryRoot(ObjectType):
88
request = graphene.String(required=True)
99
test = graphene.String(who=graphene.String())
1010

11-
def resolve_thrower(self, args, context, info):
11+
def resolve_thrower(self):
1212
raise Exception("Throws!")
1313

14-
def resolve_request(self, args, context, info):
15-
request = context
14+
@annotate(request=Context)
15+
def resolve_request(self, request):
1616
return request.GET.get('q')
1717

18-
def resolve_test(self, args, context, info):
19-
return 'Hello %s' % (args.get('who') or 'World')
18+
def resolve_test(self, who=None):
19+
return 'Hello %s' % (who or 'World')
2020

2121

2222
class MutationRoot(ObjectType):
2323
write_test = graphene.Field(QueryRoot)
2424

25-
def resolve_write_test(self, args, context, info):
25+
def resolve_write_test(self):
2626
return QueryRoot()
2727

2828

graphene_django/tests/test_converter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class Meta:
224224
assert isinstance(graphene_field, graphene.Dynamic)
225225
dynamic_field = graphene_field.get_type()
226226
assert isinstance(dynamic_field, ConnectionField)
227-
assert dynamic_field.type == A.Connection
227+
assert dynamic_field.type == A._meta.connection
228228

229229

230230
def test_should_manytoone_convert_connectionorlist():

graphene_django/tests/test_query.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Meta:
4646
class Query(graphene.ObjectType):
4747
reporter = graphene.Field(ReporterType)
4848

49-
def resolve_reporter(self, args, context, info):
49+
def resolve_reporter(self):
5050
return SimpleLazyObject(lambda: Reporter(id=1))
5151

5252
schema = graphene.Schema(query=Query)
@@ -75,7 +75,7 @@ class Meta:
7575
class Query(graphene.ObjectType):
7676
reporter = graphene.Field(ReporterType)
7777

78-
def resolve_reporter(self, *args, **kwargs):
78+
def resolve_reporter(self):
7979
return Reporter(first_name='ABA', last_name='X')
8080

8181
query = '''
@@ -119,7 +119,7 @@ class Meta:
119119
class Query(graphene.ObjectType):
120120
event = graphene.Field(EventType)
121121

122-
def resolve_event(self, *args, **kwargs):
122+
def resolve_event(self):
123123
return Event(
124124
ages=(0, 10),
125125
data={'angry_babies': True},
@@ -165,7 +165,7 @@ class Meta:
165165
def get_node(cls, id, context, info):
166166
return Reporter(id=2, first_name='Cookie Monster')
167167

168-
def resolve_articles(self, *args, **kwargs):
168+
def resolve_articles(self, **args):
169169
return [Article(headline='Hi!')]
170170

171171
class ArticleNode(DjangoObjectType):
@@ -183,7 +183,7 @@ class Query(graphene.ObjectType):
183183
reporter = graphene.Field(ReporterNode)
184184
article = graphene.Field(ArticleNode)
185185

186-
def resolve_reporter(self, *args, **kwargs):
186+
def resolve_reporter(self):
187187
return Reporter(id=1, first_name='ABA', last_name='X')
188188

189189
query = '''
@@ -250,7 +250,7 @@ class Meta:
250250
class Query(graphene.ObjectType):
251251
all_reporters = DjangoConnectionField(ReporterType)
252252

253-
def resolve_all_reporters(self, args, context, info):
253+
def resolve_all_reporters(self, **args):
254254
return [Reporter(id=1)]
255255

256256
schema = graphene.Schema(query=Query)
@@ -308,10 +308,10 @@ class Query(graphene.ObjectType):
308308
all_reporters = DjangoConnectionField(ReporterType)
309309
all_articles = DjangoConnectionField(ArticleType)
310310

311-
def resolve_all_reporters(self, args, context, info):
311+
def resolve_all_reporters(self, **args):
312312
return Reporter.objects.annotate(articles_c=Count('articles')).order_by('articles_c')
313313

314-
def resolve_all_articles(self, args, context, info):
314+
def resolve_all_articles(self, **args):
315315
return Article.objects.annotate(import_avg=Avg('importance')).order_by('import_avg')
316316

317317
schema = graphene.Schema(query=Query)
@@ -618,7 +618,7 @@ class Meta:
618618
class Query(graphene.ObjectType):
619619
all_reporters = DjangoConnectionField(ReporterType)
620620

621-
def resolve_all_reporters(self, *args, **kwargs):
621+
def resolve_all_reporters(self, **args):
622622
return Promise.resolve([Reporter(id=1)])
623623

624624
schema = graphene.Schema(query=Query)
@@ -673,10 +673,11 @@ class ReporterType(DjangoObjectType):
673673
class Meta:
674674
model = Reporter
675675
interfaces = (Node, )
676+
use_connection = True
676677

677678
articles = DjangoConnectionField(ArticleType)
678679

679-
def resolve_articles(self, *args, **kwargs):
680+
def resolve_articles(self, **args):
680681
return article_loader.load(self.id)
681682

682683
class Query(graphene.ObjectType):

0 commit comments

Comments
 (0)