mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-18 12:30:37 +03:00
Improved querying
Conflicts: graphene/core/schema.py
This commit is contained in:
parent
dd5b26e6ed
commit
0aa251fd94
|
@ -2,7 +2,7 @@ from django.db import models
|
|||
from singledispatch import singledispatch
|
||||
|
||||
from ...core.types.scalars import ID, Boolean, Float, Int, String
|
||||
from .fields import ConnectionOrListField, DjangoModelField
|
||||
from .fields import DjangoField, ConnectionOrListField, DjangoModelField
|
||||
|
||||
try:
|
||||
UUIDField = models.UUIDField
|
||||
|
@ -19,6 +19,14 @@ def convert_django_field(field):
|
|||
(field, field.__class__))
|
||||
|
||||
|
||||
def fetch_field(f):
|
||||
def wrapped(field):
|
||||
_type = f(field)
|
||||
kwargs = dict(_type.kwargs, _field=field)
|
||||
return DjangoField(_type, *_type.args, **kwargs)
|
||||
return wrapped
|
||||
|
||||
|
||||
@convert_django_field.register(models.DateField)
|
||||
@convert_django_field.register(models.CharField)
|
||||
@convert_django_field.register(models.TextField)
|
||||
|
@ -26,11 +34,13 @@ def convert_django_field(field):
|
|||
@convert_django_field.register(models.SlugField)
|
||||
@convert_django_field.register(models.URLField)
|
||||
@convert_django_field.register(UUIDField)
|
||||
@fetch_field
|
||||
def convert_field_to_string(field):
|
||||
return String(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.AutoField)
|
||||
@fetch_field
|
||||
def convert_field_to_id(field):
|
||||
return ID(description=field.help_text)
|
||||
|
||||
|
@ -40,22 +50,26 @@ def convert_field_to_id(field):
|
|||
@convert_django_field.register(models.SmallIntegerField)
|
||||
@convert_django_field.register(models.BigIntegerField)
|
||||
@convert_django_field.register(models.IntegerField)
|
||||
@fetch_field
|
||||
def convert_field_to_int(field):
|
||||
return Int(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.BooleanField)
|
||||
@fetch_field
|
||||
def convert_field_to_boolean(field):
|
||||
return Boolean(description=field.help_text, required=True)
|
||||
|
||||
|
||||
@convert_django_field.register(models.NullBooleanField)
|
||||
@fetch_field
|
||||
def convert_field_to_nullboolean(field):
|
||||
return Boolean(description=field.help_text)
|
||||
|
||||
|
||||
@convert_django_field.register(models.DecimalField)
|
||||
@convert_django_field.register(models.FloatField)
|
||||
@fetch_field
|
||||
def convert_field_to_float(field):
|
||||
return Float(description=field.help_text)
|
||||
|
||||
|
@ -64,10 +78,10 @@ def convert_field_to_float(field):
|
|||
@convert_django_field.register(models.ManyToOneRel)
|
||||
def convert_field_to_list_or_connection(field):
|
||||
model_field = DjangoModelField(field.related_model)
|
||||
return ConnectionOrListField(model_field)
|
||||
return ConnectionOrListField(model_field, _field=field)
|
||||
|
||||
|
||||
@convert_django_field.register(models.OneToOneField)
|
||||
@convert_django_field.register(models.ForeignKey)
|
||||
def convert_field_to_djangomodel(field):
|
||||
return DjangoModelField(field.related_model, description=field.help_text)
|
||||
return DjangoField(DjangoModelField(field.related_model), description=field.help_text, _field=field)
|
||||
|
|
|
@ -9,7 +9,18 @@ from ...relay.utils import is_node
|
|||
from .utils import get_type_for_model
|
||||
|
||||
|
||||
class DjangoConnectionField(ConnectionField):
|
||||
class DjangoField(Field):
|
||||
def decorate_resolver(self, resolver):
|
||||
f = super(DjangoField, self).decorate_resolver(resolver)
|
||||
setattr(f, 'django_fetch_field', self.field.name)
|
||||
return f
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.field = kwargs.pop('_field')
|
||||
return super(DjangoField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class DjangoConnectionField(DjangoField, ConnectionField):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cls = self.__class__
|
||||
|
@ -19,7 +30,7 @@ class DjangoConnectionField(ConnectionField):
|
|||
return super(DjangoConnectionField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class ConnectionOrListField(Field):
|
||||
class ConnectionOrListField(DjangoField):
|
||||
|
||||
def internal_type(self, schema):
|
||||
model_field = self.type
|
||||
|
@ -27,9 +38,9 @@ class ConnectionOrListField(Field):
|
|||
if not field_object_type:
|
||||
raise SkipField()
|
||||
if is_node(field_object_type):
|
||||
field = ConnectionField(field_object_type)
|
||||
field = DjangoConnectionField(field_object_type, _field=self.field)
|
||||
else:
|
||||
field = Field(List(field_object_type))
|
||||
field = DjangoField(List(field_object_type), _field=self.field)
|
||||
field.contribute_to_class(self.object_type, self.attname)
|
||||
return schema.T(field)
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ from .models import Article, Reporter
|
|||
|
||||
def assert_conversion(django_field, graphene_field, *args):
|
||||
field = django_field(*args, help_text='Custom Help Text')
|
||||
graphene_type = convert_django_field(field)
|
||||
field = convert_django_field(field)
|
||||
graphene_type = field.type
|
||||
assert isinstance(graphene_type, graphene_field)
|
||||
field = graphene_type.as_field()
|
||||
assert field.description == 'Custom Help Text'
|
||||
return field
|
||||
|
||||
|
|
79
graphene/contrib/django/tests/test_optimizequery.py
Normal file
79
graphene/contrib/django/tests/test_optimizequery.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
from graphql.core.utils.get_field_def import get_field_def
|
||||
import pytest
|
||||
|
||||
import graphene
|
||||
from graphene.contrib.django import DjangoObjectType
|
||||
|
||||
from ..tests.models import Reporter
|
||||
from ..debug.plugin import DjangoDebugPlugin
|
||||
|
||||
# from examples.starwars_django.models import Character
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_should_query_well():
|
||||
r1 = Reporter(last_name='ABA')
|
||||
r1.save()
|
||||
r2 = Reporter(last_name='Griffin')
|
||||
r2.save()
|
||||
|
||||
class ReporterType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
reporter = graphene.Field(ReporterType)
|
||||
all_reporters = ReporterType.List()
|
||||
|
||||
def resolve_all_reporters(self, args, info):
|
||||
queryset = Reporter.objects.all()
|
||||
# from graphql.core.execution.base import collect_fields
|
||||
# print info.field_asts[0], info.parent_type, info.return_type.of_type
|
||||
# field_asts = collect_fields(info.context, info.parent_type, info.field_asts[0], {}, set())
|
||||
# field_asts = info.field_asts
|
||||
field_asts = info.field_asts[0].selection_set.selections
|
||||
only_args = []
|
||||
for field in field_asts:
|
||||
field_def = get_field_def(info.schema, info.return_type.of_type, field)
|
||||
f = field_def.resolver
|
||||
fetch_field = getattr(f, 'django_fetch_field')
|
||||
only_args.append(fetch_field)
|
||||
queryset = queryset.only(*only_args)
|
||||
return queryset
|
||||
|
||||
def resolve_reporter(self, *args, **kwargs):
|
||||
return Reporter.objects.first()
|
||||
|
||||
query = '''
|
||||
query ReporterQuery {
|
||||
allReporters {
|
||||
lastName
|
||||
email
|
||||
}
|
||||
__debug {
|
||||
sql {
|
||||
rawSql
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
expected = {
|
||||
'allReporters': [{
|
||||
'lastName': 'ABA',
|
||||
'email': '',
|
||||
}, {
|
||||
'lastName': 'Griffin',
|
||||
'email': '',
|
||||
}],
|
||||
'__debug': {
|
||||
'sql': [{
|
||||
'rawSql': str(Reporter.objects.all().only('last_name', 'email').query)
|
||||
}]
|
||||
}
|
||||
}
|
||||
schema = graphene.Schema(query=Query, plugins=[DjangoDebugPlugin()])
|
||||
result = schema.execute(query)
|
||||
assert not result.errors
|
||||
assert result.data == expected
|
|
@ -29,7 +29,15 @@ class Human(DjangoNode):
|
|||
def get_node(self, id):
|
||||
pass
|
||||
|
||||
schema = Schema(query=Human)
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
human = graphene.Field(Human)
|
||||
|
||||
def resolve_human(self, args, info):
|
||||
return Human()
|
||||
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -7,11 +7,13 @@ def format_response(response):
|
|||
|
||||
def test_client_get_good_query(settings, client):
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.get('/graphql', {'query': '{ headline }'})
|
||||
response = client.get('/graphql', {'query': '{ human { headline } }'})
|
||||
json_response = format_response(response)
|
||||
expected_json = {
|
||||
'data': {
|
||||
'headline': None
|
||||
'human': {
|
||||
'headline': None
|
||||
}
|
||||
}
|
||||
}
|
||||
assert json_response == expected_json
|
||||
|
@ -19,20 +21,22 @@ def test_client_get_good_query(settings, client):
|
|||
|
||||
def test_client_get_good_query_with_raise(settings, client):
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.get('/graphql', {'query': '{ raises }'})
|
||||
response = client.get('/graphql', {'query': '{ human { raises } }'})
|
||||
json_response = format_response(response)
|
||||
assert json_response['errors'][0]['message'] == 'This field should raise exception'
|
||||
assert json_response['data']['raises'] is None
|
||||
assert json_response['data']['human']['raises'] is None
|
||||
|
||||
|
||||
def test_client_post_good_query_json(settings, client):
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', json.dumps({'query': '{ headline }'}), 'application/json')
|
||||
'/graphql', json.dumps({'query': '{ human { headline } }'}), 'application/json')
|
||||
json_response = format_response(response)
|
||||
expected_json = {
|
||||
'data': {
|
||||
'headline': None
|
||||
'human': {
|
||||
'headline': None
|
||||
}
|
||||
}
|
||||
}
|
||||
assert json_response == expected_json
|
||||
|
@ -41,11 +45,13 @@ def test_client_post_good_query_json(settings, client):
|
|||
def test_client_post_good_query_graphql(settings, client):
|
||||
settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls'
|
||||
response = client.post(
|
||||
'/graphql', '{ headline }', 'application/graphql')
|
||||
'/graphql', '{ human { headline } }', 'application/graphql')
|
||||
json_response = format_response(response)
|
||||
expected_json = {
|
||||
'data': {
|
||||
'headline': None
|
||||
'human': {
|
||||
'headline': None
|
||||
}
|
||||
}
|
||||
}
|
||||
assert json_response == expected_json
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
from graphql_django_view import GraphQLView as BaseGraphQLView
|
||||
from django.http import HttpResponseNotAllowed
|
||||
from django.http.response import HttpResponseBadRequest
|
||||
from graphql.core import Source, parse, validate
|
||||
from graphql.core.execution import ExecutionResult
|
||||
from graphql.core.utils.get_operation_ast import get_operation_ast
|
||||
|
||||
from graphql_django_view import GraphQLView as BaseGraphQLView, HttpError
|
||||
|
||||
|
||||
class GraphQLView(BaseGraphQLView):
|
||||
|
@ -12,5 +18,38 @@ class GraphQLView(BaseGraphQLView):
|
|||
**kwargs
|
||||
)
|
||||
|
||||
def get_root_value(self, request):
|
||||
return self.graphene_schema.query(super(GraphQLView, self).get_root_value(request))
|
||||
def execute_graphql_request(self, request):
|
||||
query, variables, operation_name = self.get_graphql_params(request, self.parse_body(request))
|
||||
|
||||
if not query:
|
||||
raise HttpError(HttpResponseBadRequest('Must provide query string.'))
|
||||
|
||||
source = Source(query, name='GraphQL request')
|
||||
|
||||
try:
|
||||
document_ast = parse(source)
|
||||
except Exception as e:
|
||||
return ExecutionResult(errors=[e], invalid=True)
|
||||
|
||||
validation_errors = validate(self.schema, document_ast)
|
||||
if validation_errors:
|
||||
return ExecutionResult(invalid=True, errors=validation_errors)
|
||||
|
||||
if request.method.lower() == 'get':
|
||||
operation_ast = get_operation_ast(document_ast, operation_name)
|
||||
if operation_ast and operation_ast.operation != 'query':
|
||||
raise HttpError(HttpResponseNotAllowed(
|
||||
['POST'], 'Can only perform a {} operation from a POST request.'.format(operation_ast.operation)
|
||||
))
|
||||
|
||||
try:
|
||||
return self.graphene_schema.execute(
|
||||
document_ast,
|
||||
self.get_root_value(request),
|
||||
variables,
|
||||
operation_name=operation_name,
|
||||
validate_ast=False,
|
||||
request_context=request
|
||||
)
|
||||
except Exception as e:
|
||||
return ExecutionResult(errors=[e], invalid=True)
|
||||
|
|
Loading…
Reference in New Issue
Block a user