Merge branch 'master' of https://github.com/graphql-python/graphene-django into fix/enhanced-proxy-model-support

This commit is contained in:
Andrew Bettke 2019-04-01 22:15:16 +13:00
commit a7ee042e9d
15 changed files with 154 additions and 17 deletions

View File

@ -96,6 +96,29 @@ schema is simple.
result = schema.execute(query, context_value=request)
Global Filtering
----------------
If you are using ``DjangoObjectType`` you can define a custom `get_queryset`.
.. code:: python
from graphene import relay
from graphene_django.types import DjangoObjectType
from .models import Post
class PostNode(DjangoObjectType):
class Meta:
model = Post
@classmethod
def get_queryset(cls, queryset, info):
if info.context.user.is_anonymous:
return queryset.filter(published=True)
return queryset
Filtering ID-based Node Access
------------------------------

View File

@ -11,7 +11,7 @@ data to ``schema.json`` that is compatible with babel-relay-plugin.
Usage
-----
Include ``graphene_django`` to ``INSTALLED_APPS`` in you project
Include ``graphene_django`` to ``INSTALLED_APPS`` in your project
settings:
.. code:: python
@ -29,6 +29,8 @@ It dumps your full introspection schema to ``schema.json`` inside your
project root directory. Point ``babel-relay-plugin`` to this file and
you're ready to use Relay with Graphene GraphQL implementation.
The schema file is sorted to create a reproducible canonical representation.
Advanced Usage
--------------

View File

@ -158,7 +158,7 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
The filtering functionality is provided by
`django-filter <https://django-filter.readthedocs.org>`__. See the
`usage
documentation <https://django-filter.readthedocs.org/en/latest/usage.html#the-filter>`__
documentation <https://django-filter.readthedocs.org/en/latest/guide/usage.html#the-filter>`__
for details on the format for ``filter_fields``. While optional, this
tutorial makes use of this functionality so you will need to install
``django-filter`` for this tutorial to work:

View File

@ -67,6 +67,10 @@ class DjangoConnectionField(ConnectionField):
else:
return self.model._default_manager
@classmethod
def resolve_queryset(cls, connection, queryset, info, args):
return connection._meta.node.get_queryset(queryset, info)
@classmethod
def merge_querysets(cls, default_queryset, queryset):
if default_queryset.query.distinct and not queryset.query.distinct:
@ -135,7 +139,8 @@ class DjangoConnectionField(ConnectionField):
args["last"] = min(last, max_limit)
iterable = resolver(root, info, **args)
on_resolve = partial(cls.resolve_connection, connection, default_manager, args)
queryset = cls.resolve_queryset(connection, default_manager, info, args)
on_resolve = partial(cls.resolve_connection, connection, queryset, args)
if Promise.is_thenable(iterable):
return Promise.resolve(iterable).then(on_resolve)

View File

@ -14,7 +14,7 @@ from graphene.types.utils import yank_fields_from_attrs
from graphene_django.registry import get_global_registry
from .converter import convert_form_field
from .types import ErrorType
from ..types import ErrorType
def fields_for_form(form, only_fields, exclude_fields):

View File

@ -39,7 +39,7 @@ class Command(CommandArguments):
def save_file(self, out, schema_dict, indent):
with open(out, "w") as outfile:
json.dump(schema_dict, outfile, indent=indent)
json.dump(schema_dict, outfile, indent=indent, sort_keys=True)
def handle(self, *args, **options):
options_schema = options.get("schema")
@ -65,7 +65,7 @@ class Command(CommandArguments):
indent = options.get("indent")
schema_dict = {"data": schema.introspect()}
if out == '-':
self.stdout.write(json.dumps(schema_dict, indent=indent))
self.stdout.write(json.dumps(schema_dict, indent=indent, sort_keys=True))
else:
self.save_file(out, schema_dict, indent)

View File

@ -9,7 +9,7 @@ from graphene.relay.mutation import ClientIDMutation
from graphene.types.objecttype import yank_fields_from_attrs
from .serializer_converter import convert_serializer_field
from .types import ErrorType
from ..types import ErrorType
class SerializerMutationOptions(MutationOptions):

View File

@ -2,11 +2,6 @@ import graphene
from graphene.types.unmountedtype import UnmountedType
class ErrorType(graphene.ObjectType):
field = graphene.String(required=True)
messages = graphene.List(graphene.NonNull(graphene.String), required=True)
class DictType(UnmountedType):
key = graphene.String()
value = graphene.String()

View File

@ -28,7 +28,7 @@ except ImportError:
DEFAULTS = {
"SCHEMA": None,
"SCHEMA_OUTPUT": "schema.json",
"SCHEMA_INDENT": None,
"SCHEMA_INDENT": 2,
"MIDDLEWARE": (),
# Set to True if the connection fields must have
# either the first or last argument

View File

View File

@ -0,0 +1,44 @@
# https://github.com/graphql-python/graphene-django/issues/520
import datetime
from django import forms
import graphene
from graphene import Field, ResolveInfo
from graphene.types.inputobjecttype import InputObjectType
from py.test import raises
from py.test import mark
from rest_framework import serializers
from ...types import DjangoObjectType
from ...rest_framework.models import MyFakeModel
from ...rest_framework.mutation import SerializerMutation
from ...forms.mutation import DjangoFormMutation
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyFakeModel
fields = "__all__"
class MyForm(forms.Form):
text = forms.CharField()
def test_can_use_form_and_serializer_mutations():
class MyMutation(SerializerMutation):
class Meta:
serializer_class = MyModelSerializer
class MyFormMutation(DjangoFormMutation):
class Meta:
form_class = MyForm
class Mutation(graphene.ObjectType):
my_mutation = MyMutation.Field()
my_form_mutation = MyFormMutation.Field()
graphene.Schema(mutation=Mutation)

View File

@ -1,5 +1,5 @@
from django.core import management
from mock import patch
from mock import patch, mock_open
from six import StringIO
@ -8,3 +8,16 @@ def test_generate_file_on_call_graphql_schema(savefile_mock, settings):
out = StringIO()
management.call_command("graphql_schema", schema="", stdout=out)
assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue()
@patch('json.dump')
def test_files_are_canonical(dump_mock):
open_mock = mock_open()
with patch('graphene_django.management.commands.graphql_schema.open', open_mock):
management.call_command('graphql_schema', schema='')
open_mock.assert_called_once()
dump_mock.assert_called_once()
assert dump_mock.call_args[1]["sort_keys"], "json.mock() should be used to sort the output"
assert dump_mock.call_args[1]["indent"] > 0, "output should be pretty-printed by default"

View File

@ -83,7 +83,7 @@ def test_should_image_convert_string():
assert_conversion(models.ImageField, graphene.String)
def test_should_url_convert_string():
def test_should_file_path_field_convert_string():
assert_conversion(models.FilePathField, graphene.String)
@ -91,7 +91,7 @@ def test_should_auto_convert_id():
assert_conversion(models.AutoField, graphene.ID, primary_key=True)
def test_should_auto_convert_id():
def test_should_uuid_convert_id():
assert_conversion(models.UUIDField, graphene.UUID)

View File

@ -965,3 +965,47 @@ def test_proxy_model_support():
result = schema.execute(query)
assert not result.errors
assert result.data == expected
def test_should_resolve_get_queryset_connectionfields():
reporter_1 = Reporter.objects.create(
first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
)
reporter_2 = CNNReporter.objects.create(
first_name="Some",
last_name="Guy",
email="someguy@cnn.com",
a_choice=1,
reporter_type=2, # set this guy to be CNN
)
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)
@classmethod
def get_queryset(cls, queryset, info):
return queryset.filter(reporter_type=2)
class Query(graphene.ObjectType):
all_reporters = DjangoConnectionField(ReporterType)
schema = graphene.Schema(query=Query)
query = """
query ReporterPromiseConnectionQuery {
allReporters(first: 1) {
edges {
node {
id
}
}
}
}
"""
expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjI="}}]}}
result = schema.execute(query)
assert not result.errors
assert result.data == expected

View File

@ -3,6 +3,7 @@ from collections import OrderedDict
from django.db.models import Model
from django.utils.functional import SimpleLazyObject
import graphene
from graphene import Field
from graphene.relay import Connection, Node
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
@ -137,9 +138,19 @@ class DjangoObjectType(ObjectType):
return model == cls._meta.model
@classmethod
def get_queryset(cls, queryset, info):
return queryset
@classmethod
def get_node(cls, info, id):
queryset = cls.get_queryset(cls._meta.model.objects, info)
try:
return cls._meta.model.objects.get(pk=id)
return queryset.get(pk=id)
except cls._meta.model.DoesNotExist:
return None
class ErrorType(ObjectType):
field = graphene.String(required=True)
messages = graphene.List(graphene.NonNull(graphene.String), required=True)