Skip to content

Commit 9d564a0

Browse files
author
Thomas Leonard
committed
Issue #1111: foreign key should also call get_queryset method
1 parent f6ec068 commit 9d564a0

File tree

4 files changed

+443
-4
lines changed

4 files changed

+443
-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):
+355
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
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, variables={"id": self.reporter.id}, context_value={"admin": True},
118+
)
119+
assert not result.errors
120+
assert result.data == {"reporter": {"firstName": "Jane"}}
121+
122+
def test_get_queryset_called_on_foreignkey(self):
123+
# If a user tries to access a reporter through an article they should get our authorization error
124+
query = """
125+
query getArticle($id: ID!) {
126+
article(id: $id) {
127+
headline
128+
reporter {
129+
firstName
130+
}
131+
}
132+
}
133+
"""
134+
135+
result = self.schema.execute(query, variables={"id": self.articles[0].id})
136+
assert len(result.errors) == 1
137+
assert result.errors[0].message == "Not authorized to access reporters."
138+
139+
# An admin user should be able to get reporters through an article
140+
query = """
141+
query getArticle($id: ID!) {
142+
article(id: $id) {
143+
headline
144+
reporter {
145+
firstName
146+
}
147+
}
148+
}
149+
"""
150+
151+
result = self.schema.execute(
152+
query, variables={"id": self.articles[0].id}, context_value={"admin": True},
153+
)
154+
assert not result.errors
155+
assert result.data["article"] == {
156+
"headline": "A fantastic article",
157+
"reporter": {"firstName": "Jane"},
158+
}
159+
160+
# An admin user should not be able to access draft article through a reporter
161+
query = """
162+
query getReporter($id: ID!) {
163+
reporter(id: $id) {
164+
firstName
165+
articles {
166+
headline
167+
}
168+
}
169+
}
170+
"""
171+
172+
result = self.schema.execute(
173+
query, variables={"id": self.reporter.id}, context_value={"admin": True},
174+
)
175+
assert not result.errors
176+
assert result.data["reporter"] == {
177+
"firstName": "Jane",
178+
"articles": [{"headline": "A fantastic article"}],
179+
}
180+
181+
182+
class TestShouldCallGetQuerySetOnForeignKeyNode:
183+
"""
184+
Check that the get_queryset method is called in both forward and reversed direction
185+
of a foreignkey on types using a node interface.
186+
(see issue #1111)
187+
"""
188+
189+
@pytest.fixture(autouse=True)
190+
def setup_schema(self):
191+
class ReporterType(DjangoObjectType):
192+
class Meta:
193+
model = Reporter
194+
interfaces = (Node,)
195+
196+
@classmethod
197+
def get_queryset(cls, queryset, info):
198+
if info.context and info.context.get("admin"):
199+
return queryset
200+
raise Exception("Not authorized to access reporters.")
201+
202+
class ArticleType(DjangoObjectType):
203+
class Meta:
204+
model = Article
205+
interfaces = (Node,)
206+
207+
@classmethod
208+
def get_queryset(cls, queryset, info):
209+
return queryset.exclude(headline__startswith="Draft")
210+
211+
class Query(graphene.ObjectType):
212+
reporter = Node.Field(ReporterType)
213+
article = Node.Field(ArticleType)
214+
215+
self.schema = graphene.Schema(query=Query)
216+
217+
self.reporter = Reporter.objects.create(first_name="Jane", last_name="Doe")
218+
219+
self.articles = [
220+
Article.objects.create(
221+
headline="A fantastic article",
222+
reporter=self.reporter,
223+
editor=self.reporter,
224+
),
225+
Article.objects.create(
226+
headline="Draft: My next best seller",
227+
reporter=self.reporter,
228+
editor=self.reporter,
229+
),
230+
]
231+
232+
def test_get_queryset_called_on_node(self):
233+
# If a user tries to access an article it is fine as long as it's not a draft one
234+
query = """
235+
query getArticle($id: ID!) {
236+
article(id: $id) {
237+
headline
238+
}
239+
}
240+
"""
241+
# Non-draft
242+
result = self.schema.execute(
243+
query, variables={"id": to_global_id("ArticleType", self.articles[0].id)}
244+
)
245+
assert not result.errors
246+
assert result.data["article"] == {
247+
"headline": "A fantastic article",
248+
}
249+
# Draft
250+
result = self.schema.execute(
251+
query, variables={"id": to_global_id("ArticleType", self.articles[1].id)}
252+
)
253+
assert not result.errors
254+
assert result.data["article"] is None
255+
256+
# If a non admin user tries to access a reporter they should get our authorization error
257+
query = """
258+
query getReporter($id: ID!) {
259+
reporter(id: $id) {
260+
firstName
261+
}
262+
}
263+
"""
264+
265+
result = self.schema.execute(
266+
query, variables={"id": to_global_id("ReporterType", self.reporter.id)}
267+
)
268+
assert len(result.errors) == 1
269+
assert result.errors[0].message == "Not authorized to access reporters."
270+
271+
# An admin user should be able to get reporters
272+
query = """
273+
query getReporter($id: ID!) {
274+
reporter(id: $id) {
275+
firstName
276+
}
277+
}
278+
"""
279+
280+
result = self.schema.execute(
281+
query,
282+
variables={"id": to_global_id("ReporterType", self.reporter.id)},
283+
context_value={"admin": True},
284+
)
285+
assert not result.errors
286+
assert result.data == {"reporter": {"firstName": "Jane"}}
287+
288+
def test_get_queryset_called_on_foreignkey(self):
289+
# If a user tries to access a reporter through an article they should get our authorization error
290+
query = """
291+
query getArticle($id: ID!) {
292+
article(id: $id) {
293+
headline
294+
reporter {
295+
firstName
296+
}
297+
}
298+
}
299+
"""
300+
301+
result = self.schema.execute(
302+
query, variables={"id": to_global_id("ArticleType", self.articles[0].id)}
303+
)
304+
assert len(result.errors) == 1
305+
assert result.errors[0].message == "Not authorized to access reporters."
306+
307+
# An admin user should be able to get reporters through an article
308+
query = """
309+
query getArticle($id: ID!) {
310+
article(id: $id) {
311+
headline
312+
reporter {
313+
firstName
314+
}
315+
}
316+
}
317+
"""
318+
319+
result = self.schema.execute(
320+
query,
321+
variables={"id": to_global_id("ArticleType", self.articles[0].id)},
322+
context_value={"admin": True},
323+
)
324+
assert not result.errors
325+
assert result.data["article"] == {
326+
"headline": "A fantastic article",
327+
"reporter": {"firstName": "Jane"},
328+
}
329+
330+
# An admin user should not be able to access draft article through a reporter
331+
query = """
332+
query getReporter($id: ID!) {
333+
reporter(id: $id) {
334+
firstName
335+
articles {
336+
edges {
337+
node {
338+
headline
339+
}
340+
}
341+
}
342+
}
343+
}
344+
"""
345+
346+
result = self.schema.execute(
347+
query,
348+
variables={"id": to_global_id("ReporterType", self.reporter.id)},
349+
context_value={"admin": True},
350+
)
351+
assert not result.errors
352+
assert result.data["reporter"] == {
353+
"firstName": "Jane",
354+
"articles": {"edges": [{"node": {"headline": "A fantastic article"}}]},
355+
}

0 commit comments

Comments
 (0)