Skip to content

Commit a7ee042

Browse files
author
Andrew Bettke
committed
Merge branch 'master' of https://github.com/graphql-python/graphene-django into fix/enhanced-proxy-model-support
2 parents 959e98e + 090ce6e commit a7ee042

File tree

15 files changed

+154
-17
lines changed

15 files changed

+154
-17
lines changed

docs/authorization.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,29 @@ schema is simple.
9696
9797
result = schema.execute(query, context_value=request)
9898
99+
100+
Global Filtering
101+
----------------
102+
103+
If you are using ``DjangoObjectType`` you can define a custom `get_queryset`.
104+
105+
.. code:: python
106+
107+
from graphene import relay
108+
from graphene_django.types import DjangoObjectType
109+
from .models import Post
110+
111+
class PostNode(DjangoObjectType):
112+
class Meta:
113+
model = Post
114+
115+
@classmethod
116+
def get_queryset(cls, queryset, info):
117+
if info.context.user.is_anonymous:
118+
return queryset.filter(published=True)
119+
return queryset
120+
121+
99122
Filtering ID-based Node Access
100123
------------------------------
101124

docs/introspection.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ data to ``schema.json`` that is compatible with babel-relay-plugin.
1111
Usage
1212
-----
1313

14-
Include ``graphene_django`` to ``INSTALLED_APPS`` in you project
14+
Include ``graphene_django`` to ``INSTALLED_APPS`` in your project
1515
settings:
1616

1717
.. code:: python
@@ -29,6 +29,8 @@ It dumps your full introspection schema to ``schema.json`` inside your
2929
project root directory. Point ``babel-relay-plugin`` to this file and
3030
you're ready to use Relay with Graphene GraphQL implementation.
3131

32+
The schema file is sorted to create a reproducible canonical representation.
33+
3234
Advanced Usage
3335
--------------
3436

docs/tutorial-relay.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
158158
The filtering functionality is provided by
159159
`django-filter <https://django-filter.readthedocs.org>`__. See the
160160
`usage
161-
documentation <https://django-filter.readthedocs.org/en/latest/usage.html#the-filter>`__
161+
documentation <https://django-filter.readthedocs.org/en/latest/guide/usage.html#the-filter>`__
162162
for details on the format for ``filter_fields``. While optional, this
163163
tutorial makes use of this functionality so you will need to install
164164
``django-filter`` for this tutorial to work:

graphene_django/fields.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ def get_manager(self):
6767
else:
6868
return self.model._default_manager
6969

70+
@classmethod
71+
def resolve_queryset(cls, connection, queryset, info, args):
72+
return connection._meta.node.get_queryset(queryset, info)
73+
7074
@classmethod
7175
def merge_querysets(cls, default_queryset, queryset):
7276
if default_queryset.query.distinct and not queryset.query.distinct:
@@ -135,7 +139,8 @@ def connection_resolver(
135139
args["last"] = min(last, max_limit)
136140

137141
iterable = resolver(root, info, **args)
138-
on_resolve = partial(cls.resolve_connection, connection, default_manager, args)
142+
queryset = cls.resolve_queryset(connection, default_manager, info, args)
143+
on_resolve = partial(cls.resolve_connection, connection, queryset, args)
139144

140145
if Promise.is_thenable(iterable):
141146
return Promise.resolve(iterable).then(on_resolve)

graphene_django/forms/mutation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from graphene_django.registry import get_global_registry
1515

1616
from .converter import convert_form_field
17-
from .types import ErrorType
17+
from ..types import ErrorType
1818

1919

2020
def fields_for_form(form, only_fields, exclude_fields):

graphene_django/management/commands/graphql_schema.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class Command(CommandArguments):
3939

4040
def save_file(self, out, schema_dict, indent):
4141
with open(out, "w") as outfile:
42-
json.dump(schema_dict, outfile, indent=indent)
42+
json.dump(schema_dict, outfile, indent=indent, sort_keys=True)
4343

4444
def handle(self, *args, **options):
4545
options_schema = options.get("schema")
@@ -65,7 +65,7 @@ def handle(self, *args, **options):
6565
indent = options.get("indent")
6666
schema_dict = {"data": schema.introspect()}
6767
if out == '-':
68-
self.stdout.write(json.dumps(schema_dict, indent=indent))
68+
self.stdout.write(json.dumps(schema_dict, indent=indent, sort_keys=True))
6969
else:
7070
self.save_file(out, schema_dict, indent)
7171

graphene_django/rest_framework/mutation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from graphene.types.objecttype import yank_fields_from_attrs
1010

1111
from .serializer_converter import convert_serializer_field
12-
from .types import ErrorType
12+
from ..types import ErrorType
1313

1414

1515
class SerializerMutationOptions(MutationOptions):

graphene_django/rest_framework/types.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22
from graphene.types.unmountedtype import UnmountedType
33

44

5-
class ErrorType(graphene.ObjectType):
6-
field = graphene.String(required=True)
7-
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
8-
9-
105
class DictType(UnmountedType):
116
key = graphene.String()
127
value = graphene.String()

graphene_django/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
DEFAULTS = {
2929
"SCHEMA": None,
3030
"SCHEMA_OUTPUT": "schema.json",
31-
"SCHEMA_INDENT": None,
31+
"SCHEMA_INDENT": 2,
3232
"MIDDLEWARE": (),
3333
# Set to True if the connection fields must have
3434
# either the first or last argument

graphene_django/tests/issues/__init__.py

Whitespace-only changes.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# https://github.com/graphql-python/graphene-django/issues/520
2+
3+
import datetime
4+
5+
from django import forms
6+
7+
import graphene
8+
9+
from graphene import Field, ResolveInfo
10+
from graphene.types.inputobjecttype import InputObjectType
11+
from py.test import raises
12+
from py.test import mark
13+
from rest_framework import serializers
14+
15+
from ...types import DjangoObjectType
16+
from ...rest_framework.models import MyFakeModel
17+
from ...rest_framework.mutation import SerializerMutation
18+
from ...forms.mutation import DjangoFormMutation
19+
20+
21+
class MyModelSerializer(serializers.ModelSerializer):
22+
class Meta:
23+
model = MyFakeModel
24+
fields = "__all__"
25+
26+
27+
class MyForm(forms.Form):
28+
text = forms.CharField()
29+
30+
31+
def test_can_use_form_and_serializer_mutations():
32+
class MyMutation(SerializerMutation):
33+
class Meta:
34+
serializer_class = MyModelSerializer
35+
36+
class MyFormMutation(DjangoFormMutation):
37+
class Meta:
38+
form_class = MyForm
39+
40+
class Mutation(graphene.ObjectType):
41+
my_mutation = MyMutation.Field()
42+
my_form_mutation = MyFormMutation.Field()
43+
44+
graphene.Schema(mutation=Mutation)

graphene_django/tests/test_command.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.core import management
2-
from mock import patch
2+
from mock import patch, mock_open
33
from six import StringIO
44

55

@@ -8,3 +8,16 @@ def test_generate_file_on_call_graphql_schema(savefile_mock, settings):
88
out = StringIO()
99
management.call_command("graphql_schema", schema="", stdout=out)
1010
assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue()
11+
12+
13+
@patch('json.dump')
14+
def test_files_are_canonical(dump_mock):
15+
open_mock = mock_open()
16+
with patch('graphene_django.management.commands.graphql_schema.open', open_mock):
17+
management.call_command('graphql_schema', schema='')
18+
19+
open_mock.assert_called_once()
20+
21+
dump_mock.assert_called_once()
22+
assert dump_mock.call_args[1]["sort_keys"], "json.mock() should be used to sort the output"
23+
assert dump_mock.call_args[1]["indent"] > 0, "output should be pretty-printed by default"

graphene_django/tests/test_converter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,15 @@ def test_should_image_convert_string():
8383
assert_conversion(models.ImageField, graphene.String)
8484

8585

86-
def test_should_url_convert_string():
86+
def test_should_file_path_field_convert_string():
8787
assert_conversion(models.FilePathField, graphene.String)
8888

8989

9090
def test_should_auto_convert_id():
9191
assert_conversion(models.AutoField, graphene.ID, primary_key=True)
9292

9393

94-
def test_should_auto_convert_id():
94+
def test_should_uuid_convert_id():
9595
assert_conversion(models.UUIDField, graphene.UUID)
9696

9797

graphene_django/tests/test_query.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,3 +965,47 @@ class Query(graphene.ObjectType):
965965
result = schema.execute(query)
966966
assert not result.errors
967967
assert result.data == expected
968+
969+
970+
def test_should_resolve_get_queryset_connectionfields():
971+
reporter_1 = Reporter.objects.create(
972+
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
973+
)
974+
reporter_2 = CNNReporter.objects.create(
975+
first_name="Some",
976+
last_name="Guy",
977+
email="someguy@cnn.com",
978+
a_choice=1,
979+
reporter_type=2, # set this guy to be CNN
980+
)
981+
982+
class ReporterType(DjangoObjectType):
983+
class Meta:
984+
model = Reporter
985+
interfaces = (Node,)
986+
987+
@classmethod
988+
def get_queryset(cls, queryset, info):
989+
return queryset.filter(reporter_type=2)
990+
991+
class Query(graphene.ObjectType):
992+
all_reporters = DjangoConnectionField(ReporterType)
993+
994+
schema = graphene.Schema(query=Query)
995+
query = """
996+
query ReporterPromiseConnectionQuery {
997+
allReporters(first: 1) {
998+
edges {
999+
node {
1000+
id
1001+
}
1002+
}
1003+
}
1004+
}
1005+
"""
1006+
1007+
expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjI="}}]}}
1008+
1009+
result = schema.execute(query)
1010+
assert not result.errors
1011+
assert result.data == expected

graphene_django/types.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from django.db.models import Model
55
from django.utils.functional import SimpleLazyObject
6+
import graphene
67
from graphene import Field
78
from graphene.relay import Connection, Node
89
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
@@ -137,9 +138,19 @@ def is_type_of(cls, root, info):
137138

138139
return model == cls._meta.model
139140

141+
@classmethod
142+
def get_queryset(cls, queryset, info):
143+
return queryset
144+
140145
@classmethod
141146
def get_node(cls, info, id):
147+
queryset = cls.get_queryset(cls._meta.model.objects, info)
142148
try:
143-
return cls._meta.model.objects.get(pk=id)
149+
return queryset.get(pk=id)
144150
except cls._meta.model.DoesNotExist:
145151
return None
152+
153+
154+
class ErrorType(ObjectType):
155+
field = graphene.String(required=True)
156+
messages = graphene.List(graphene.NonNull(graphene.String), required=True)

0 commit comments

Comments
 (0)