Skip to content

Commit fea9b5b

Browse files
jkimbomvanlonden
authored andcommitted
Extend DjangoListField to use model queryset if none defined (#732)
* Fix model property * Only allow DjangoObjectTypes to DjangoListField * Resolve model queryset by default * Add some more tests to check behaviour
1 parent 4bbc082 commit fea9b5b

File tree

2 files changed

+230
-8
lines changed

2 files changed

+230
-8
lines changed

graphene_django/fields.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,56 @@
11
from functools import partial
22

33
from django.db.models.query import QuerySet
4-
from graphene import NonNull
5-
4+
from graphql_relay.connection.arrayconnection import connection_from_list_slice
65
from promise import Promise
76

8-
from graphene.types import Field, List
7+
from graphene import NonNull
98
from graphene.relay import ConnectionField, PageInfo
10-
from graphql_relay.connection.arrayconnection import connection_from_list_slice
9+
from graphene.types import Field, List
1110

1211
from .settings import graphene_settings
1312
from .utils import maybe_queryset
1413

1514

1615
class DjangoListField(Field):
1716
def __init__(self, _type, *args, **kwargs):
17+
from .types import DjangoObjectType
18+
19+
if isinstance(_type, NonNull):
20+
_type = _type.of_type
21+
22+
assert issubclass(
23+
_type, DjangoObjectType
24+
), "DjangoListField only accepts DjangoObjectType types"
25+
1826
# Django would never return a Set of None vvvvvvv
1927
super(DjangoListField, self).__init__(List(NonNull(_type)), *args, **kwargs)
2028

2129
@property
2230
def model(self):
23-
return self.type.of_type._meta.node._meta.model
31+
_type = self.type.of_type
32+
if isinstance(_type, NonNull):
33+
_type = _type.of_type
34+
return _type._meta.model
2435

2536
@staticmethod
26-
def list_resolver(resolver, root, info, **args):
27-
return maybe_queryset(resolver(root, info, **args))
37+
def list_resolver(django_object_type, resolver, root, info, **args):
38+
queryset = maybe_queryset(resolver(root, info, **args))
39+
if queryset is None:
40+
# Default to Django Model queryset
41+
# N.B. This happens if DjangoListField is used in the top level Query object
42+
model = django_object_type._meta.model
43+
queryset = maybe_queryset(
44+
django_object_type.get_queryset(model.objects, info)
45+
)
46+
return queryset
2847

2948
def get_resolver(self, parent_resolver):
30-
return partial(self.list_resolver, parent_resolver)
49+
_type = self.type
50+
if isinstance(_type, NonNull):
51+
_type = _type.of_type
52+
django_object_type = _type.of_type.of_type
53+
return partial(self.list_resolver, django_object_type, parent_resolver)
3154

3255

3356
class DjangoConnectionField(ConnectionField):

graphene_django/tests/test_fields.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import datetime
2+
3+
import pytest
4+
5+
from graphene import List, NonNull, ObjectType, Schema, String
6+
7+
from ..fields import DjangoListField
8+
from ..types import DjangoObjectType
9+
from .models import Article as ArticleModel
10+
from .models import Reporter as ReporterModel
11+
12+
13+
@pytest.mark.django_db
14+
class TestDjangoListField:
15+
def test_only_django_object_types(self):
16+
class TestType(ObjectType):
17+
foo = String()
18+
19+
with pytest.raises(AssertionError):
20+
list_field = DjangoListField(TestType)
21+
22+
def test_non_null_type(self):
23+
class Reporter(DjangoObjectType):
24+
class Meta:
25+
model = ReporterModel
26+
fields = ("first_name",)
27+
28+
list_field = DjangoListField(NonNull(Reporter))
29+
30+
assert isinstance(list_field.type, List)
31+
assert isinstance(list_field.type.of_type, NonNull)
32+
assert list_field.type.of_type.of_type is Reporter
33+
34+
def test_get_django_model(self):
35+
class Reporter(DjangoObjectType):
36+
class Meta:
37+
model = ReporterModel
38+
fields = ("first_name",)
39+
40+
list_field = DjangoListField(Reporter)
41+
assert list_field.model is ReporterModel
42+
43+
def test_list_field_default_queryset(self):
44+
class Reporter(DjangoObjectType):
45+
class Meta:
46+
model = ReporterModel
47+
fields = ("first_name",)
48+
49+
class Query(ObjectType):
50+
reporters = DjangoListField(Reporter)
51+
52+
schema = Schema(query=Query)
53+
54+
query = """
55+
query {
56+
reporters {
57+
firstName
58+
}
59+
}
60+
"""
61+
62+
ReporterModel.objects.create(first_name="Tara", last_name="West")
63+
ReporterModel.objects.create(first_name="Debra", last_name="Payne")
64+
65+
result = schema.execute(query)
66+
67+
assert not result.errors
68+
assert result.data == {
69+
"reporters": [{"firstName": "Tara"}, {"firstName": "Debra"}]
70+
}
71+
72+
def test_override_resolver(self):
73+
class Reporter(DjangoObjectType):
74+
class Meta:
75+
model = ReporterModel
76+
fields = ("first_name",)
77+
78+
class Query(ObjectType):
79+
reporters = DjangoListField(Reporter)
80+
81+
def resolve_reporters(_, info):
82+
return ReporterModel.objects.filter(first_name="Tara")
83+
84+
schema = Schema(query=Query)
85+
86+
query = """
87+
query {
88+
reporters {
89+
firstName
90+
}
91+
}
92+
"""
93+
94+
ReporterModel.objects.create(first_name="Tara", last_name="West")
95+
ReporterModel.objects.create(first_name="Debra", last_name="Payne")
96+
97+
result = schema.execute(query)
98+
99+
assert not result.errors
100+
assert result.data == {"reporters": [{"firstName": "Tara"}]}
101+
102+
def test_nested_list_field(self):
103+
class Article(DjangoObjectType):
104+
class Meta:
105+
model = ArticleModel
106+
fields = ("headline",)
107+
108+
class Reporter(DjangoObjectType):
109+
class Meta:
110+
model = ReporterModel
111+
fields = ("first_name", "articles")
112+
113+
class Query(ObjectType):
114+
reporters = DjangoListField(Reporter)
115+
116+
schema = Schema(query=Query)
117+
118+
query = """
119+
query {
120+
reporters {
121+
firstName
122+
articles {
123+
headline
124+
}
125+
}
126+
}
127+
"""
128+
129+
r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
130+
ReporterModel.objects.create(first_name="Debra", last_name="Payne")
131+
132+
ArticleModel.objects.create(
133+
headline="Amazing news",
134+
reporter=r1,
135+
pub_date=datetime.date.today(),
136+
pub_date_time=datetime.datetime.now(),
137+
editor=r1,
138+
)
139+
140+
result = schema.execute(query)
141+
142+
assert not result.errors
143+
assert result.data == {
144+
"reporters": [
145+
{"firstName": "Tara", "articles": [{"headline": "Amazing news"}]},
146+
{"firstName": "Debra", "articles": []},
147+
]
148+
}
149+
150+
def test_override_resolver_nested_list_field(self):
151+
class Article(DjangoObjectType):
152+
class Meta:
153+
model = ArticleModel
154+
fields = ("headline",)
155+
156+
class Reporter(DjangoObjectType):
157+
class Meta:
158+
model = ReporterModel
159+
fields = ("first_name", "articles")
160+
161+
def resolve_reporters(reporter, info):
162+
return reporter.articles.all()
163+
164+
class Query(ObjectType):
165+
reporters = DjangoListField(Reporter)
166+
167+
schema = Schema(query=Query)
168+
169+
query = """
170+
query {
171+
reporters {
172+
firstName
173+
articles {
174+
headline
175+
}
176+
}
177+
}
178+
"""
179+
180+
r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
181+
ReporterModel.objects.create(first_name="Debra", last_name="Payne")
182+
183+
ArticleModel.objects.create(
184+
headline="Amazing news",
185+
reporter=r1,
186+
pub_date=datetime.date.today(),
187+
pub_date_time=datetime.datetime.now(),
188+
editor=r1,
189+
)
190+
191+
result = schema.execute(query)
192+
193+
assert not result.errors
194+
assert result.data == {
195+
"reporters": [
196+
{"firstName": "Tara", "articles": [{"headline": "Amazing news"}]},
197+
{"firstName": "Debra", "articles": []},
198+
]
199+
}

0 commit comments

Comments
 (0)