Skip to content

Commit b8025b0

Browse files
author
Thomas Leonard
committed
Issue #1111: foreign key should also call get_queryset method
1 parent 8ae5763 commit b8025b0

File tree

4 files changed

+450
-4
lines changed

4 files changed

+450
-4
lines changed

graphene_django/converter.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,24 @@ def dynamic_type():
301301
if not _type:
302302
return
303303

304-
return Field(
304+
class CustomField(Field):
305+
def wrap_resolve(self, parent_resolver):
306+
"""
307+
Implements a custom resolver which go through the `get_node` method to insure that
308+
it goes through the `get_queryset` method of the DjangoObjectType.
309+
"""
310+
resolver = super().wrap_resolve(parent_resolver)
311+
312+
def custom_resolver(root, info, **args):
313+
fk_obj = resolver(root, info, **args)
314+
if fk_obj is None:
315+
return None
316+
else:
317+
return _type.get_node(info, fk_obj.pk)
318+
319+
return custom_resolver
320+
321+
return CustomField(
305322
_type,
306323
description=get_django_field_description(field),
307324
required=not field.null,

graphene_django/tests/models.py

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class Person(models.Model):
1313
class Pet(models.Model):
1414
name = models.CharField(max_length=30)
1515
age = models.PositiveIntegerField()
16+
owner = models.ForeignKey(
17+
"Person", on_delete=models.CASCADE, null=True, blank=True, related_name="pets"
18+
)
1619

1720

1821
class FilmDetails(models.Model):
+361
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
import pytest
2+
3+
import graphene
4+
from graphene.relay import Node
5+
6+
from graphql_relay import to_global_id
7+
8+
from ..fields import DjangoConnectionField
9+
from ..types import DjangoObjectType
10+
11+
from .models import Article, Reporter
12+
13+
14+
class TestShouldCallGetQuerySetOnForeignKey:
15+
"""
16+
Check that the get_queryset method is called in both forward and reversed direction
17+
of a foreignkey on types.
18+
(see issue #1111)
19+
"""
20+
21+
@pytest.fixture(autouse=True)
22+
def setup_schema(self):
23+
class ReporterType(DjangoObjectType):
24+
class Meta:
25+
model = Reporter
26+
27+
@classmethod
28+
def get_queryset(cls, queryset, info):
29+
if info.context and info.context.get("admin"):
30+
return queryset
31+
raise Exception("Not authorized to access reporters.")
32+
33+
class ArticleType(DjangoObjectType):
34+
class Meta:
35+
model = Article
36+
37+
@classmethod
38+
def get_queryset(cls, queryset, info):
39+
return queryset.exclude(headline__startswith="Draft")
40+
41+
class Query(graphene.ObjectType):
42+
reporter = graphene.Field(ReporterType, id=graphene.ID(required=True))
43+
article = graphene.Field(ArticleType, id=graphene.ID(required=True))
44+
45+
def resolve_reporter(self, info, id):
46+
return (
47+
ReporterType.get_queryset(Reporter.objects, info)
48+
.filter(id=id)
49+
.last()
50+
)
51+
52+
def resolve_article(self, info, id):
53+
return (
54+
ArticleType.get_queryset(Article.objects, info).filter(id=id).last()
55+
)
56+
57+
self.schema = graphene.Schema(query=Query)
58+
59+
self.reporter = Reporter.objects.create(first_name="Jane", last_name="Doe")
60+
61+
self.articles = [
62+
Article.objects.create(
63+
headline="A fantastic article",
64+
reporter=self.reporter,
65+
editor=self.reporter,
66+
),
67+
Article.objects.create(
68+
headline="Draft: My next best seller",
69+
reporter=self.reporter,
70+
editor=self.reporter,
71+
),
72+
]
73+
74+
def test_get_queryset_called_on_field(self):
75+
# If a user tries to access an article it is fine as long as it's not a draft one
76+
query = """
77+
query getArticle($id: ID!) {
78+
article(id: $id) {
79+
headline
80+
}
81+
}
82+
"""
83+
# Non-draft
84+
result = self.schema.execute(query, variables={"id": self.articles[0].id})
85+
assert not result.errors
86+
assert result.data["article"] == {
87+
"headline": "A fantastic article",
88+
}
89+
# Draft
90+
result = self.schema.execute(query, variables={"id": self.articles[1].id})
91+
assert not result.errors
92+
assert result.data["article"] is None
93+
94+
# If a non admin user tries to access a reporter they should get our authorization error
95+
query = """
96+
query getReporter($id: ID!) {
97+
reporter(id: $id) {
98+
firstName
99+
}
100+
}
101+
"""
102+
103+
result = self.schema.execute(query, variables={"id": self.reporter.id})
104+
assert len(result.errors) == 1
105+
assert result.errors[0].message == "Not authorized to access reporters."
106+
107+
# An admin user should be able to get reporters
108+
query = """
109+
query getReporter($id: ID!) {
110+
reporter(id: $id) {
111+
firstName
112+
}
113+
}
114+
"""
115+
116+
result = self.schema.execute(
117+
query,
118+
variables={"id": self.reporter.id},
119+
context_value={"admin": True},
120+
)
121+
assert not result.errors
122+
assert result.data == {"reporter": {"firstName": "Jane"}}
123+
124+
def test_get_queryset_called_on_foreignkey(self):
125+
# If a user tries to access a reporter through an article they should get our authorization error
126+
query = """
127+
query getArticle($id: ID!) {
128+
article(id: $id) {
129+
headline
130+
reporter {
131+
firstName
132+
}
133+
}
134+
}
135+
"""
136+
137+
result = self.schema.execute(query, variables={"id": self.articles[0].id})
138+
assert len(result.errors) == 1
139+
assert result.errors[0].message == "Not authorized to access reporters."
140+
141+
# An admin user should be able to get reporters through an article
142+
query = """
143+
query getArticle($id: ID!) {
144+
article(id: $id) {
145+
headline
146+
reporter {
147+
firstName
148+
}
149+
}
150+
}
151+
"""
152+
153+
result = self.schema.execute(
154+
query,
155+
variables={"id": self.articles[0].id},
156+
context_value={"admin": True},
157+
)
158+
assert not result.errors
159+
assert result.data["article"] == {
160+
"headline": "A fantastic article",
161+
"reporter": {"firstName": "Jane"},
162+
}
163+
164+
# An admin user should not be able to access draft article through a reporter
165+
query = """
166+
query getReporter($id: ID!) {
167+
reporter(id: $id) {
168+
firstName
169+
articles {
170+
headline
171+
}
172+
}
173+
}
174+
"""
175+
176+
result = self.schema.execute(
177+
query,
178+
variables={"id": self.reporter.id},
179+
context_value={"admin": True},
180+
)
181+
assert not result.errors
182+
assert result.data["reporter"] == {
183+
"firstName": "Jane",
184+
"articles": [{"headline": "A fantastic article"}],
185+
}
186+
187+
188+
class TestShouldCallGetQuerySetOnForeignKeyNode:
189+
"""
190+
Check that the get_queryset method is called in both forward and reversed direction
191+
of a foreignkey on types using a node interface.
192+
(see issue #1111)
193+
"""
194+
195+
@pytest.fixture(autouse=True)
196+
def setup_schema(self):
197+
class ReporterType(DjangoObjectType):
198+
class Meta:
199+
model = Reporter
200+
interfaces = (Node,)
201+
202+
@classmethod
203+
def get_queryset(cls, queryset, info):
204+
if info.context and info.context.get("admin"):
205+
return queryset
206+
raise Exception("Not authorized to access reporters.")
207+
208+
class ArticleType(DjangoObjectType):
209+
class Meta:
210+
model = Article
211+
interfaces = (Node,)
212+
213+
@classmethod
214+
def get_queryset(cls, queryset, info):
215+
return queryset.exclude(headline__startswith="Draft")
216+
217+
class Query(graphene.ObjectType):
218+
reporter = Node.Field(ReporterType)
219+
article = Node.Field(ArticleType)
220+
221+
self.schema = graphene.Schema(query=Query)
222+
223+
self.reporter = Reporter.objects.create(first_name="Jane", last_name="Doe")
224+
225+
self.articles = [
226+
Article.objects.create(
227+
headline="A fantastic article",
228+
reporter=self.reporter,
229+
editor=self.reporter,
230+
),
231+
Article.objects.create(
232+
headline="Draft: My next best seller",
233+
reporter=self.reporter,
234+
editor=self.reporter,
235+
),
236+
]
237+
238+
def test_get_queryset_called_on_node(self):
239+
# If a user tries to access an article it is fine as long as it's not a draft one
240+
query = """
241+
query getArticle($id: ID!) {
242+
article(id: $id) {
243+
headline
244+
}
245+
}
246+
"""
247+
# Non-draft
248+
result = self.schema.execute(
249+
query, variables={"id": to_global_id("ArticleType", self.articles[0].id)}
250+
)
251+
assert not result.errors
252+
assert result.data["article"] == {
253+
"headline": "A fantastic article",
254+
}
255+
# Draft
256+
result = self.schema.execute(
257+
query, variables={"id": to_global_id("ArticleType", self.articles[1].id)}
258+
)
259+
assert not result.errors
260+
assert result.data["article"] is None
261+
262+
# If a non admin user tries to access a reporter they should get our authorization error
263+
query = """
264+
query getReporter($id: ID!) {
265+
reporter(id: $id) {
266+
firstName
267+
}
268+
}
269+
"""
270+
271+
result = self.schema.execute(
272+
query, variables={"id": to_global_id("ReporterType", self.reporter.id)}
273+
)
274+
assert len(result.errors) == 1
275+
assert result.errors[0].message == "Not authorized to access reporters."
276+
277+
# An admin user should be able to get reporters
278+
query = """
279+
query getReporter($id: ID!) {
280+
reporter(id: $id) {
281+
firstName
282+
}
283+
}
284+
"""
285+
286+
result = self.schema.execute(
287+
query,
288+
variables={"id": to_global_id("ReporterType", self.reporter.id)},
289+
context_value={"admin": True},
290+
)
291+
assert not result.errors
292+
assert result.data == {"reporter": {"firstName": "Jane"}}
293+
294+
def test_get_queryset_called_on_foreignkey(self):
295+
# If a user tries to access a reporter through an article they should get our authorization error
296+
query = """
297+
query getArticle($id: ID!) {
298+
article(id: $id) {
299+
headline
300+
reporter {
301+
firstName
302+
}
303+
}
304+
}
305+
"""
306+
307+
result = self.schema.execute(
308+
query, variables={"id": to_global_id("ArticleType", self.articles[0].id)}
309+
)
310+
assert len(result.errors) == 1
311+
assert result.errors[0].message == "Not authorized to access reporters."
312+
313+
# An admin user should be able to get reporters through an article
314+
query = """
315+
query getArticle($id: ID!) {
316+
article(id: $id) {
317+
headline
318+
reporter {
319+
firstName
320+
}
321+
}
322+
}
323+
"""
324+
325+
result = self.schema.execute(
326+
query,
327+
variables={"id": to_global_id("ArticleType", self.articles[0].id)},
328+
context_value={"admin": True},
329+
)
330+
assert not result.errors
331+
assert result.data["article"] == {
332+
"headline": "A fantastic article",
333+
"reporter": {"firstName": "Jane"},
334+
}
335+
336+
# An admin user should not be able to access draft article through a reporter
337+
query = """
338+
query getReporter($id: ID!) {
339+
reporter(id: $id) {
340+
firstName
341+
articles {
342+
edges {
343+
node {
344+
headline
345+
}
346+
}
347+
}
348+
}
349+
}
350+
"""
351+
352+
result = self.schema.execute(
353+
query,
354+
variables={"id": to_global_id("ReporterType", self.reporter.id)},
355+
context_value={"admin": True},
356+
)
357+
assert not result.errors
358+
assert result.data["reporter"] == {
359+
"firstName": "Jane",
360+
"articles": {"edges": [{"node": {"headline": "A fantastic article"}}]},
361+
}

0 commit comments

Comments
 (0)