Skip to content

Commit a818ec9

Browse files
zbyte64jkimbo
authored andcommitted
replace merge_queryset with resolve_queryset pattern (#796)
* replace merge_queryset with resolve_queryset pattern * skip double limit test * Update graphene_django/fields.py Co-Authored-By: Jonathan Kim <jkimbo@gmail.com> * yank skipped test * fix bad variable ref * add test for annotations * add test for using queryset with django filters * document ththat one should use defer instead of values with queysets and DjangoObjectTypes
1 parent 3ce4490 commit a818ec9

File tree

5 files changed

+132
-132
lines changed

5 files changed

+132
-132
lines changed

docs/queries.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,13 @@ of Django's ``HTTPRequest`` in your resolve methods, such as checking for authen
282282
return Question.objects.none()
283283
284284
285+
DjangoObjectTypes
286+
~~~~~~~~~~~~~~~~~
287+
288+
A Resolver that maps to a defined `DjangoObjectType` should only use methods that return a queryset.
289+
Queryset methods like `values` will return dictionaries, use `defer` instead.
290+
291+
285292
Plain ObjectTypes
286293
-----------------
287294

graphene_django/fields.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ def list_resolver(django_object_type, resolver, root, info, **args):
3939
if queryset is None:
4040
# Default to Django Model queryset
4141
# N.B. This happens if DjangoListField is used in the top level Query object
42-
model = django_object_type._meta.model
42+
model_manager = django_object_type._meta.model.objects
4343
queryset = maybe_queryset(
44-
django_object_type.get_queryset(model.objects, info)
44+
django_object_type.get_queryset(model_manager, info)
4545
)
4646
return queryset
4747

@@ -108,25 +108,13 @@ def get_manager(self):
108108

109109
@classmethod
110110
def resolve_queryset(cls, connection, queryset, info, args):
111+
# queryset is the resolved iterable from ObjectType
111112
return connection._meta.node.get_queryset(queryset, info)
112113

113114
@classmethod
114-
def merge_querysets(cls, default_queryset, queryset):
115-
if default_queryset.query.distinct and not queryset.query.distinct:
116-
queryset = queryset.distinct()
117-
elif queryset.query.distinct and not default_queryset.query.distinct:
118-
default_queryset = default_queryset.distinct()
119-
return queryset & default_queryset
120-
121-
@classmethod
122-
def resolve_connection(cls, connection, default_manager, args, iterable):
123-
if iterable is None:
124-
iterable = default_manager
115+
def resolve_connection(cls, connection, args, iterable):
125116
iterable = maybe_queryset(iterable)
126117
if isinstance(iterable, QuerySet):
127-
if iterable.model.objects is not default_manager:
128-
default_queryset = maybe_queryset(default_manager)
129-
iterable = cls.merge_querysets(default_queryset, iterable)
130118
_len = iterable.count()
131119
else:
132120
_len = len(iterable)
@@ -150,6 +138,7 @@ def connection_resolver(
150138
resolver,
151139
connection,
152140
default_manager,
141+
queryset_resolver,
153142
max_limit,
154143
enforce_first_or_last,
155144
root,
@@ -177,9 +166,15 @@ def connection_resolver(
177166
).format(last, info.field_name, max_limit)
178167
args["last"] = min(last, max_limit)
179168

169+
# eventually leads to DjangoObjectType's get_queryset (accepts queryset)
170+
# or a resolve_foo (does not accept queryset)
180171
iterable = resolver(root, info, **args)
181-
queryset = cls.resolve_queryset(connection, default_manager, info, args)
182-
on_resolve = partial(cls.resolve_connection, connection, queryset, args)
172+
if iterable is None:
173+
iterable = default_manager
174+
# thus the iterable gets refiltered by resolve_queryset
175+
# but iterable might be promise
176+
iterable = queryset_resolver(connection, iterable, info, args)
177+
on_resolve = partial(cls.resolve_connection, connection, args)
183178

184179
if Promise.is_thenable(iterable):
185180
return Promise.resolve(iterable).then(on_resolve)
@@ -192,6 +187,10 @@ def get_resolver(self, parent_resolver):
192187
parent_resolver,
193188
self.connection_type,
194189
self.get_manager(),
190+
self.get_queryset_resolver(),
195191
self.max_limit,
196192
self.enforce_first_or_last,
197193
)
194+
195+
def get_queryset_resolver(self):
196+
return self.resolve_queryset

graphene_django/filter/fields.py

Lines changed: 8 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -52,69 +52,17 @@ def filtering_args(self):
5252
return get_filtering_args_from_filterset(self.filterset_class, self.node_type)
5353

5454
@classmethod
55-
def merge_querysets(cls, default_queryset, queryset):
56-
# There could be the case where the default queryset (returned from the filterclass)
57-
# and the resolver queryset have some limits on it.
58-
# We only would be able to apply one of those, but not both
59-
# at the same time.
60-
61-
# See related PR: https://github.com/graphql-python/graphene-django/pull/126
62-
63-
assert not (
64-
default_queryset.query.low_mark and queryset.query.low_mark
65-
), "Received two sliced querysets (low mark) in the connection, please slice only in one."
66-
assert not (
67-
default_queryset.query.high_mark and queryset.query.high_mark
68-
), "Received two sliced querysets (high mark) in the connection, please slice only in one."
69-
low = default_queryset.query.low_mark or queryset.query.low_mark
70-
high = default_queryset.query.high_mark or queryset.query.high_mark
71-
default_queryset.query.clear_limits()
72-
queryset = super(DjangoFilterConnectionField, cls).merge_querysets(
73-
default_queryset, queryset
74-
)
75-
queryset.query.set_limits(low, high)
76-
return queryset
77-
78-
@classmethod
79-
def connection_resolver(
80-
cls,
81-
resolver,
82-
connection,
83-
default_manager,
84-
max_limit,
85-
enforce_first_or_last,
86-
filterset_class,
87-
filtering_args,
88-
root,
89-
info,
90-
**args
55+
def resolve_queryset(
56+
cls, connection, iterable, info, args, filtering_args, filterset_class
9157
):
9258
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
93-
qs = filterset_class(
94-
data=filter_kwargs,
95-
queryset=default_manager.get_queryset(),
96-
request=info.context,
59+
return filterset_class(
60+
data=filter_kwargs, queryset=iterable, request=info.context
9761
).qs
9862

99-
return super(DjangoFilterConnectionField, cls).connection_resolver(
100-
resolver,
101-
connection,
102-
qs,
103-
max_limit,
104-
enforce_first_or_last,
105-
root,
106-
info,
107-
**args
108-
)
109-
110-
def get_resolver(self, parent_resolver):
63+
def get_queryset_resolver(self):
11164
return partial(
112-
self.connection_resolver,
113-
parent_resolver,
114-
self.connection_type,
115-
self.get_manager(),
116-
self.max_limit,
117-
self.enforce_first_or_last,
118-
self.filterset_class,
119-
self.filtering_args,
65+
self.resolve_queryset,
66+
filterset_class=self.filterset_class,
67+
filtering_args=self.filtering_args,
12068
)

graphene_django/filter/tests/test_fields.py

Lines changed: 43 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -608,58 +608,6 @@ def resolve_all_reporters(self, info, **args):
608608
assert result.data == expected
609609

610610

611-
def test_should_query_filter_node_double_limit_raises():
612-
class ReporterFilter(FilterSet):
613-
limit = NumberFilter(method="filter_limit")
614-
615-
def filter_limit(self, queryset, name, value):
616-
return queryset[:value]
617-
618-
class Meta:
619-
model = Reporter
620-
fields = ["first_name"]
621-
622-
class ReporterType(DjangoObjectType):
623-
class Meta:
624-
model = Reporter
625-
interfaces = (Node,)
626-
627-
class Query(ObjectType):
628-
all_reporters = DjangoFilterConnectionField(
629-
ReporterType, filterset_class=ReporterFilter
630-
)
631-
632-
def resolve_all_reporters(self, info, **args):
633-
return Reporter.objects.order_by("a_choice")[:2]
634-
635-
Reporter.objects.create(
636-
first_name="Bob", last_name="Doe", email="bobdoe@example.com", a_choice=2
637-
)
638-
Reporter.objects.create(
639-
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
640-
)
641-
642-
schema = Schema(query=Query)
643-
query = """
644-
query NodeFilteringQuery {
645-
allReporters(limit: 1) {
646-
edges {
647-
node {
648-
id
649-
firstName
650-
}
651-
}
652-
}
653-
}
654-
"""
655-
656-
result = schema.execute(query)
657-
assert len(result.errors) == 1
658-
assert str(result.errors[0]) == (
659-
"Received two sliced querysets (high mark) in the connection, please slice only in one."
660-
)
661-
662-
663611
def test_order_by_is_perserved():
664612
class ReporterType(DjangoObjectType):
665613
class Meta:
@@ -721,7 +669,7 @@ def resolve_all_reporters(self, info, reverse_order=False, **args):
721669
assert reverse_result.data == reverse_expected
722670

723671

724-
def test_annotation_is_perserved():
672+
def test_annotation_is_preserved():
725673
class ReporterType(DjangoObjectType):
726674
full_name = String()
727675

@@ -766,6 +714,48 @@ def resolve_all_reporters(self, info, **args):
766714
assert result.data == expected
767715

768716

717+
def test_annotation_with_only():
718+
class ReporterType(DjangoObjectType):
719+
full_name = String()
720+
721+
class Meta:
722+
model = Reporter
723+
interfaces = (Node,)
724+
filter_fields = ()
725+
726+
class Query(ObjectType):
727+
all_reporters = DjangoFilterConnectionField(ReporterType)
728+
729+
def resolve_all_reporters(self, info, **args):
730+
return Reporter.objects.only("first_name", "last_name").annotate(
731+
full_name=Concat(
732+
"first_name", Value(" "), "last_name", output_field=TextField()
733+
)
734+
)
735+
736+
Reporter.objects.create(first_name="John", last_name="Doe")
737+
738+
schema = Schema(query=Query)
739+
740+
query = """
741+
query NodeFilteringQuery {
742+
allReporters(first: 1) {
743+
edges {
744+
node {
745+
fullName
746+
}
747+
}
748+
}
749+
}
750+
"""
751+
expected = {"allReporters": {"edges": [{"node": {"fullName": "John Doe"}}]}}
752+
753+
result = schema.execute(query)
754+
755+
assert not result.errors
756+
assert result.data == expected
757+
758+
769759
def test_integer_field_filter_type():
770760
class PetType(DjangoObjectType):
771761
class Meta:

graphene_django/tests/test_query.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,8 @@ class Meta:
638638
class Query(graphene.ObjectType):
639639
all_reporters = DjangoConnectionField(ReporterType)
640640

641+
assert Query.all_reporters.max_limit == 100
642+
641643
r = Reporter.objects.create(
642644
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
643645
)
@@ -679,6 +681,8 @@ class Meta:
679681
class Query(graphene.ObjectType):
680682
all_reporters = DjangoConnectionField(ReporterType)
681683

684+
assert Query.all_reporters.max_limit == 100
685+
682686
r = Reporter.objects.create(
683687
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
684688
)
@@ -804,7 +808,7 @@ def resolve_all_reporters(self, info, **args):
804808
schema = graphene.Schema(query=Query)
805809
query = """
806810
query ReporterLastQuery {
807-
allReporters(first: 2) {
811+
allReporters(first: 1) {
808812
edges {
809813
node {
810814
id
@@ -1116,3 +1120,55 @@ def resolve_films(root, info):
11161120
with django_assert_num_queries(3) as captured:
11171121
result = schema.execute(query)
11181122
assert not result.errors
1123+
1124+
1125+
def test_should_preserve_annotations():
1126+
class ReporterType(DjangoObjectType):
1127+
class Meta:
1128+
model = Reporter
1129+
interfaces = (graphene.relay.Node,)
1130+
1131+
class FilmType(DjangoObjectType):
1132+
reporters = DjangoConnectionField(ReporterType)
1133+
reporters_count = graphene.Int()
1134+
1135+
class Meta:
1136+
model = Film
1137+
interfaces = (graphene.relay.Node,)
1138+
1139+
class Query(graphene.ObjectType):
1140+
films = DjangoConnectionField(FilmType)
1141+
1142+
def resolve_films(root, info):
1143+
qs = Film.objects.prefetch_related("reporters")
1144+
return qs.annotate(reporters_count=models.Count("reporters"))
1145+
1146+
r1 = Reporter.objects.create(first_name="Dave", last_name="Smith")
1147+
r2 = Reporter.objects.create(first_name="Jane", last_name="Doe")
1148+
1149+
f1 = Film.objects.create()
1150+
f1.reporters.set([r1, r2])
1151+
f2 = Film.objects.create()
1152+
f2.reporters.set([r2])
1153+
1154+
query = """
1155+
query {
1156+
films {
1157+
edges {
1158+
node {
1159+
reportersCount
1160+
}
1161+
}
1162+
}
1163+
}
1164+
"""
1165+
schema = graphene.Schema(query=Query)
1166+
result = schema.execute(query)
1167+
assert not result.errors, str(result)
1168+
1169+
expected = {
1170+
"films": {
1171+
"edges": [{"node": {"reportersCount": 2}}, {"node": {"reportersCount": 1}}]
1172+
}
1173+
}
1174+
assert result.data == expected, str(result.data)

0 commit comments

Comments
 (0)