mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-02-07 15:10:35 +03:00
Merge branch 'master' into fix-select-related
This commit is contained in:
commit
b8fb64d893
|
@ -12,7 +12,7 @@ A [Django](https://www.djangoproject.com/) integration for [Graphene](http://gra
|
|||
For instaling graphene, just run this command in your shell
|
||||
|
||||
```bash
|
||||
pip install "graphene-django>=2.0.dev"
|
||||
pip install "graphene-django>=2.0"
|
||||
```
|
||||
|
||||
### Settings
|
||||
|
@ -67,7 +67,6 @@ class User(DjangoObjectType):
|
|||
class Query(graphene.ObjectType):
|
||||
users = graphene.List(User)
|
||||
|
||||
@graphene.resolve_only_args
|
||||
def resolve_users(self):
|
||||
return UserModel.objects.all()
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ For instaling graphene, just run this command in your shell
|
|||
|
||||
.. code:: bash
|
||||
|
||||
pip install "graphene-django>=2.0.dev"
|
||||
pip install "graphene-django>=2.0"
|
||||
|
||||
Settings
|
||||
~~~~~~~~
|
||||
|
|
|
@ -34,7 +34,7 @@ This is easy, simply use the ``only_fields`` meta attribute.
|
|||
only_fields = ('title', 'content')
|
||||
interfaces = (relay.Node, )
|
||||
|
||||
conversely you can use ``exclude_fields`` meta atrribute.
|
||||
conversely you can use ``exclude_fields`` meta attribute.
|
||||
|
||||
.. code:: python
|
||||
|
||||
|
|
|
@ -445,8 +445,8 @@ We can update our schema to support that, by adding new query for ``ingredient``
|
|||
return Ingredient.objects.all()
|
||||
|
||||
def resolve_category(self, info, **kwargs):
|
||||
id = kargs.get('id')
|
||||
name = kargs.get('name')
|
||||
id = kwargs.get('id')
|
||||
name = kwargs.get('name')
|
||||
|
||||
if id is not None:
|
||||
return Category.objects.get(pk=id)
|
||||
|
@ -457,8 +457,8 @@ We can update our schema to support that, by adding new query for ``ingredient``
|
|||
return None
|
||||
|
||||
def resolve_ingredient(self, info, **kwargs):
|
||||
id = kargs.get('id')
|
||||
name = kargs.get('name')
|
||||
id = kwargs.get('id')
|
||||
name = kwargs.get('name')
|
||||
|
||||
if id is not None:
|
||||
return Ingredient.objects.get(pk=id)
|
||||
|
|
|
@ -5,7 +5,7 @@ from .fields import (
|
|||
DjangoConnectionField,
|
||||
)
|
||||
|
||||
__version__ = '2.0.dev2017083101'
|
||||
__version__ = '2.0.0'
|
||||
|
||||
__all__ = [
|
||||
'__version__',
|
||||
|
|
|
@ -61,9 +61,7 @@ class DjangoFilterConnectionField(DjangoConnectionField):
|
|||
low = default_queryset.query.low_mark or queryset.query.low_mark
|
||||
high = default_queryset.query.high_mark or queryset.query.high_mark
|
||||
default_queryset.query.clear_limits()
|
||||
|
||||
queryset = super(cls, cls).merge_querysets(default_queryset, queryset)
|
||||
|
||||
queryset.query.set_limits(low, high)
|
||||
return queryset
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from datetime import datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from graphene import Field, ObjectType, Schema, Argument, Float
|
||||
from graphene import Field, ObjectType, Schema, Argument, Float, Boolean, String
|
||||
from graphene.relay import Node
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphene_django.forms import (GlobalIDFormField,
|
||||
|
@ -10,6 +10,10 @@ from graphene_django.forms import (GlobalIDFormField,
|
|||
from graphene_django.tests.models import Article, Pet, Reporter
|
||||
from graphene_django.utils import DJANGO_FILTER_INSTALLED
|
||||
|
||||
# for annotation test
|
||||
from django.db.models import TextField, Value
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
pytestmark = []
|
||||
|
||||
if DJANGO_FILTER_INSTALLED:
|
||||
|
@ -534,3 +538,135 @@ def test_should_query_filter_node_double_limit_raises():
|
|||
assert str(result.errors[0]) == (
|
||||
'Received two sliced querysets (high mark) in the connection, please slice only in one.'
|
||||
)
|
||||
|
||||
def test_order_by_is_perserved():
|
||||
class ReporterType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Reporter
|
||||
interfaces = (Node, )
|
||||
filter_fields = ()
|
||||
|
||||
class Query(ObjectType):
|
||||
all_reporters = DjangoFilterConnectionField(ReporterType, reverse_order=Boolean())
|
||||
|
||||
def resolve_all_reporters(self, info, reverse_order=False, **args):
|
||||
reporters = Reporter.objects.order_by('first_name')
|
||||
|
||||
if reverse_order:
|
||||
return reporters.reverse()
|
||||
|
||||
return reporters
|
||||
|
||||
Reporter.objects.create(
|
||||
first_name='b',
|
||||
)
|
||||
r = Reporter.objects.create(
|
||||
first_name='a',
|
||||
)
|
||||
|
||||
schema = Schema(query=Query)
|
||||
query = '''
|
||||
query NodeFilteringQuery {
|
||||
allReporters(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
firstName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
expected = {
|
||||
'allReporters': {
|
||||
'edges': [{
|
||||
'node': {
|
||||
'firstName': 'a',
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
result = schema.execute(query)
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
||||
|
||||
|
||||
reverse_query = '''
|
||||
query NodeFilteringQuery {
|
||||
allReporters(first: 1, reverseOrder: true) {
|
||||
edges {
|
||||
node {
|
||||
firstName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
reverse_expected = {
|
||||
'allReporters': {
|
||||
'edges': [{
|
||||
'node': {
|
||||
'firstName': 'b',
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
reverse_result = schema.execute(reverse_query)
|
||||
|
||||
assert not reverse_result.errors
|
||||
assert reverse_result.data == reverse_expected
|
||||
|
||||
def test_annotation_is_perserved():
|
||||
class ReporterType(DjangoObjectType):
|
||||
full_name = String()
|
||||
|
||||
def resolve_full_name(instance, info, **args):
|
||||
return instance.full_name
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
interfaces = (Node, )
|
||||
filter_fields = ()
|
||||
|
||||
class Query(ObjectType):
|
||||
all_reporters = DjangoFilterConnectionField(ReporterType)
|
||||
|
||||
def resolve_all_reporters(self, info, **args):
|
||||
return Reporter.objects.annotate(
|
||||
full_name=Concat('first_name', Value(' '), 'last_name', output_field=TextField())
|
||||
)
|
||||
|
||||
Reporter.objects.create(
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
)
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
query = '''
|
||||
query NodeFilteringQuery {
|
||||
allReporters(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
expected = {
|
||||
'allReporters': {
|
||||
'edges': [{
|
||||
'node': {
|
||||
'fullName': 'John Doe',
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
result = schema.execute(query)
|
||||
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from mock import patch
|
||||
|
||||
from graphene import Interface, ObjectType, Schema
|
||||
from graphene import Interface, ObjectType, Schema, Connection, String
|
||||
from graphene.relay import Node
|
||||
|
||||
from .. import registry
|
||||
|
@ -17,11 +17,23 @@ class Reporter(DjangoObjectType):
|
|||
model = ReporterModel
|
||||
|
||||
|
||||
class ArticleConnection(Connection):
|
||||
'''Article Connection'''
|
||||
test = String()
|
||||
|
||||
def resolve_test():
|
||||
return 'test'
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Article(DjangoObjectType):
|
||||
'''Article description'''
|
||||
class Meta:
|
||||
model = ArticleModel
|
||||
interfaces = (Node, )
|
||||
connection_class = ArticleConnection
|
||||
|
||||
|
||||
class RootQuery(ObjectType):
|
||||
|
@ -74,6 +86,7 @@ type Article implements Node {
|
|||
type ArticleConnection {
|
||||
pageInfo: PageInfo!
|
||||
edges: [ArticleEdge]!
|
||||
test: String
|
||||
}
|
||||
|
||||
type ArticleEdge {
|
||||
|
|
|
@ -45,7 +45,7 @@ class DjangoObjectType(ObjectType):
|
|||
@classmethod
|
||||
def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False,
|
||||
only_fields=(), exclude_fields=(), filter_fields=None, connection=None,
|
||||
use_connection=None, interfaces=(), **options):
|
||||
connection_class=None, use_connection=None, interfaces=(), **options):
|
||||
assert is_valid_django_model(model), (
|
||||
'You need to pass a valid Django Model in {}.Meta, received "{}".'
|
||||
).format(cls.__name__, model)
|
||||
|
@ -71,7 +71,11 @@ class DjangoObjectType(ObjectType):
|
|||
|
||||
if use_connection and not connection:
|
||||
# We create the connection automatically
|
||||
connection = Connection.create_type('{}Connection'.format(cls.__name__), node=cls)
|
||||
if not connection_class:
|
||||
connection_class = Connection
|
||||
|
||||
connection = connection_class.create_type(
|
||||
'{}Connection'.format(cls.__name__), node=cls)
|
||||
|
||||
if connection is not None:
|
||||
assert issubclass(connection, Connection), (
|
||||
|
|
|
@ -81,8 +81,10 @@ class GraphQLView(View):
|
|||
self.graphiql = graphiql
|
||||
self.batch = batch
|
||||
|
||||
assert isinstance(self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.'
|
||||
assert not all((graphiql, batch)), 'Use either graphiql or batch processing'
|
||||
assert isinstance(
|
||||
self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.'
|
||||
assert not all((graphiql, batch)
|
||||
), 'Use either graphiql or batch processing'
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def get_root_value(self, request):
|
||||
|
@ -98,20 +100,27 @@ class GraphQLView(View):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
if request.method.lower() not in ('get', 'post'):
|
||||
raise HttpError(HttpResponseNotAllowed(['GET', 'POST'], 'GraphQL only supports GET and POST requests.'))
|
||||
raise HttpError(HttpResponseNotAllowed(
|
||||
['GET', 'POST'], 'GraphQL only supports GET and POST requests.'))
|
||||
|
||||
data = self.parse_body(request)
|
||||
show_graphiql = self.graphiql and self.can_display_graphiql(request, data)
|
||||
show_graphiql = self.graphiql and self.can_display_graphiql(
|
||||
request, data)
|
||||
|
||||
if self.batch:
|
||||
responses = [self.get_response(request, entry) for entry in data]
|
||||
result = '[{}]'.format(','.join([response[0] for response in responses]))
|
||||
status_code = max(responses, key=lambda response: response[1])[1]
|
||||
responses = [self.get_response(
|
||||
request, entry) for entry in data]
|
||||
result = '[{}]'.format(
|
||||
','.join([response[0] for response in responses]))
|
||||
status_code = max(
|
||||
responses, key=lambda response: response[1])[1]
|
||||
else:
|
||||
result, status_code = self.get_response(request, data, show_graphiql)
|
||||
result, status_code = self.get_response(
|
||||
request, data, show_graphiql)
|
||||
|
||||
if show_graphiql:
|
||||
query, variables, operation_name, id = self.get_graphql_params(request, data)
|
||||
query, variables, operation_name, id = self.get_graphql_params(
|
||||
request, data)
|
||||
return self.render_graphiql(
|
||||
request,
|
||||
graphiql_version=self.graphiql_version,
|
||||
|
@ -136,7 +145,8 @@ class GraphQLView(View):
|
|||
return response
|
||||
|
||||
def get_response(self, request, data, show_graphiql=False):
|
||||
query, variables, operation_name, id = self.get_graphql_params(request, data)
|
||||
query, variables, operation_name, id = self.get_graphql_params(
|
||||
request, data)
|
||||
|
||||
execution_result = self.execute_graphql_request(
|
||||
request,
|
||||
|
@ -152,7 +162,8 @@ class GraphQLView(View):
|
|||
response = {}
|
||||
|
||||
if execution_result.errors:
|
||||
response['errors'] = [self.format_error(e) for e in execution_result.errors]
|
||||
response['errors'] = [self.format_error(
|
||||
e) for e in execution_result.errors]
|
||||
|
||||
if execution_result.invalid:
|
||||
status_code = 400
|
||||
|
@ -209,7 +220,8 @@ class GraphQLView(View):
|
|||
except AssertionError as e:
|
||||
raise HttpError(HttpResponseBadRequest(str(e)))
|
||||
except (TypeError, ValueError):
|
||||
raise HttpError(HttpResponseBadRequest('POST body sent invalid JSON.'))
|
||||
raise HttpError(HttpResponseBadRequest(
|
||||
'POST body sent invalid JSON.'))
|
||||
|
||||
elif content_type in ['application/x-www-form-urlencoded', 'multipart/form-data']:
|
||||
return request.POST
|
||||
|
@ -223,7 +235,8 @@ class GraphQLView(View):
|
|||
if not query:
|
||||
if show_graphiql:
|
||||
return None
|
||||
raise HttpError(HttpResponseBadRequest('Must provide query string.'))
|
||||
raise HttpError(HttpResponseBadRequest(
|
||||
'Must provide query string.'))
|
||||
|
||||
source = Source(query, name='GraphQL request')
|
||||
|
||||
|
@ -245,7 +258,8 @@ class GraphQLView(View):
|
|||
return None
|
||||
|
||||
raise HttpError(HttpResponseNotAllowed(
|
||||
['POST'], 'Can only perform a {} operation from a POST request.'.format(operation_ast.operation)
|
||||
['POST'], 'Can only perform a {} operation from a POST request.'.format(
|
||||
operation_ast.operation)
|
||||
))
|
||||
|
||||
try:
|
||||
|
@ -283,10 +297,12 @@ class GraphQLView(View):
|
|||
if variables and isinstance(variables, six.text_type):
|
||||
try:
|
||||
variables = json.loads(variables)
|
||||
except:
|
||||
raise HttpError(HttpResponseBadRequest('Variables are invalid JSON.'))
|
||||
except Exception:
|
||||
raise HttpError(HttpResponseBadRequest(
|
||||
'Variables are invalid JSON.'))
|
||||
|
||||
operation_name = request.GET.get('operationName') or data.get('operationName')
|
||||
operation_name = request.GET.get(
|
||||
'operationName') or data.get('operationName')
|
||||
if operation_name == "null":
|
||||
operation_name = None
|
||||
|
||||
|
@ -302,5 +318,6 @@ class GraphQLView(View):
|
|||
@staticmethod
|
||||
def get_content_type(request):
|
||||
meta = request.META
|
||||
content_type = meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
|
||||
content_type = meta.get(
|
||||
'CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
|
||||
return content_type.split(';', 1)[0].lower()
|
||||
|
|
Loading…
Reference in New Issue
Block a user