mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-01-31 19:54:12 +03:00
Merge branch 'master' of https://github.com/graphql-python/graphene-django into fix/enhanced-proxy-model-support
This commit is contained in:
commit
a7ee042e9d
|
@ -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
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
0
graphene_django/tests/issues/__init__.py
Normal file
0
graphene_django/tests/issues/__init__.py
Normal file
44
graphene_django/tests/issues/test_520.py
Normal file
44
graphene_django/tests/issues/test_520.py
Normal 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)
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user