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) 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 Filtering ID-based Node Access
------------------------------ ------------------------------

View File

@ -11,7 +11,7 @@ data to ``schema.json`` that is compatible with babel-relay-plugin.
Usage Usage
----- -----
Include ``graphene_django`` to ``INSTALLED_APPS`` in you project Include ``graphene_django`` to ``INSTALLED_APPS`` in your project
settings: settings:
.. code:: python .. 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 project root directory. Point ``babel-relay-plugin`` to this file and
you're ready to use Relay with Graphene GraphQL implementation. you're ready to use Relay with Graphene GraphQL implementation.
The schema file is sorted to create a reproducible canonical representation.
Advanced Usage Advanced Usage
-------------- --------------

View File

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

View File

@ -67,6 +67,10 @@ class DjangoConnectionField(ConnectionField):
else: else:
return self.model._default_manager return self.model._default_manager
@classmethod
def resolve_queryset(cls, connection, queryset, info, args):
return connection._meta.node.get_queryset(queryset, info)
@classmethod @classmethod
def merge_querysets(cls, default_queryset, queryset): def merge_querysets(cls, default_queryset, queryset):
if default_queryset.query.distinct and not queryset.query.distinct: if default_queryset.query.distinct and not queryset.query.distinct:
@ -135,7 +139,8 @@ class DjangoConnectionField(ConnectionField):
args["last"] = min(last, max_limit) args["last"] = min(last, max_limit)
iterable = resolver(root, info, **args) 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): if Promise.is_thenable(iterable):
return Promise.resolve(iterable).then(on_resolve) 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 graphene_django.registry import get_global_registry
from .converter import convert_form_field from .converter import convert_form_field
from .types import ErrorType from ..types import ErrorType
def fields_for_form(form, only_fields, exclude_fields): 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): def save_file(self, out, schema_dict, indent):
with open(out, "w") as outfile: 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): def handle(self, *args, **options):
options_schema = options.get("schema") options_schema = options.get("schema")
@ -65,7 +65,7 @@ class Command(CommandArguments):
indent = options.get("indent") indent = options.get("indent")
schema_dict = {"data": schema.introspect()} schema_dict = {"data": schema.introspect()}
if out == '-': if out == '-':
self.stdout.write(json.dumps(schema_dict, indent=indent)) self.stdout.write(json.dumps(schema_dict, indent=indent, sort_keys=True))
else: else:
self.save_file(out, schema_dict, indent) 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 graphene.types.objecttype import yank_fields_from_attrs
from .serializer_converter import convert_serializer_field from .serializer_converter import convert_serializer_field
from .types import ErrorType from ..types import ErrorType
class SerializerMutationOptions(MutationOptions): class SerializerMutationOptions(MutationOptions):

View File

@ -2,11 +2,6 @@ import graphene
from graphene.types.unmountedtype import UnmountedType 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): class DictType(UnmountedType):
key = graphene.String() key = graphene.String()
value = graphene.String() value = graphene.String()

View File

@ -28,7 +28,7 @@ except ImportError:
DEFAULTS = { DEFAULTS = {
"SCHEMA": None, "SCHEMA": None,
"SCHEMA_OUTPUT": "schema.json", "SCHEMA_OUTPUT": "schema.json",
"SCHEMA_INDENT": None, "SCHEMA_INDENT": 2,
"MIDDLEWARE": (), "MIDDLEWARE": (),
# Set to True if the connection fields must have # Set to True if the connection fields must have
# either the first or last argument # 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 django.core import management
from mock import patch from mock import patch, mock_open
from six import StringIO from six import StringIO
@ -8,3 +8,16 @@ def test_generate_file_on_call_graphql_schema(savefile_mock, settings):
out = StringIO() out = StringIO()
management.call_command("graphql_schema", schema="", stdout=out) management.call_command("graphql_schema", schema="", stdout=out)
assert "Successfully dumped GraphQL schema to schema.json" in out.getvalue() 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) 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) 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) 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) assert_conversion(models.UUIDField, graphene.UUID)

View File

@ -965,3 +965,47 @@ def test_proxy_model_support():
result = schema.execute(query) result = schema.execute(query)
assert not result.errors assert not result.errors
assert result.data == expected 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.db.models import Model
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
import graphene
from graphene import Field from graphene import Field
from graphene.relay import Connection, Node from graphene.relay import Connection, Node
from graphene.types.objecttype import ObjectType, ObjectTypeOptions from graphene.types.objecttype import ObjectType, ObjectTypeOptions
@ -137,9 +138,19 @@ class DjangoObjectType(ObjectType):
return model == cls._meta.model return model == cls._meta.model
@classmethod
def get_queryset(cls, queryset, info):
return queryset
@classmethod @classmethod
def get_node(cls, info, id): def get_node(cls, info, id):
queryset = cls.get_queryset(cls._meta.model.objects, info)
try: try:
return cls._meta.model.objects.get(pk=id) return queryset.get(pk=id)
except cls._meta.model.DoesNotExist: except cls._meta.model.DoesNotExist:
return None return None
class ErrorType(ObjectType):
field = graphene.String(required=True)
messages = graphene.List(graphene.NonNull(graphene.String), required=True)