mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-29 04:53:43 +03:00
Moved django-graphql-view and django-graphiql into graphene-django 😎
This commit is contained in:
parent
a4306c890b
commit
3aa929fdc9
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -59,20 +59,6 @@ docs/_build/
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
|
|
||||||
|
|
||||||
/tests/django.sqlite
|
|
||||||
|
|
||||||
/graphene/index.json
|
|
||||||
/graphene/meta.json
|
|
||||||
|
|
||||||
/meta.json
|
|
||||||
/index.json
|
|
||||||
|
|
||||||
/docs/playground/graphene-js/pypyjs-release-nojit/
|
|
||||||
/docs/static/playground/lib
|
|
||||||
|
|
||||||
/docs/static/playground
|
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
|
27
README.md
27
README.md
|
@ -16,6 +16,33 @@ For instaling graphene, just run this command in your shell
|
||||||
pip install "graphene-django>=1.0.dev"
|
pip install "graphene-django>=1.0.dev"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
```python
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
# ...
|
||||||
|
'graphene_django',
|
||||||
|
)
|
||||||
|
|
||||||
|
GRAPHENE = {
|
||||||
|
'SCHEMA': 'app.schema.schema' # Where your Graphene schema lives
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Urls
|
||||||
|
|
||||||
|
We need to set up a `GraphQL` endpoint in our Django app, so we can serve the queries.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from django.conf.urls import url
|
||||||
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# ...
|
||||||
|
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Here is a simple Django model:
|
Here is a simple Django model:
|
||||||
|
|
30
README.rst
30
README.rst
|
@ -20,6 +20,36 @@ For instaling graphene, just run this command in your shell
|
||||||
|
|
||||||
pip install "graphene-django>=1.0.dev"
|
pip install "graphene-django>=1.0.dev"
|
||||||
|
|
||||||
|
Settings
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
# ...
|
||||||
|
'graphene_django',
|
||||||
|
)
|
||||||
|
|
||||||
|
GRAPHENE = {
|
||||||
|
'SCHEMA': 'app.schema.schema' # Where your Graphene schema lives
|
||||||
|
}
|
||||||
|
|
||||||
|
Urls
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
We need to set up a ``GraphQL`` endpoint in our Django app, so we can
|
||||||
|
serve the queries.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# ...
|
||||||
|
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
|
||||||
|
]
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
@ -18,3 +18,17 @@ DATABASES = {
|
||||||
'NAME': 'django_test.sqlite',
|
'NAME': 'django_test.sqlite',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
GRAPHENE = {
|
||||||
|
'SCHEMA': 'graphene_django.tests.schema_view.schema'
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'graphene_django.tests.urls'
|
||||||
|
|
|
@ -37,7 +37,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django_graphiql',
|
'graphene_django',
|
||||||
|
|
||||||
'cookbook.ingredients.apps.IngredientsConfig',
|
'cookbook.ingredients.apps.IngredientsConfig',
|
||||||
'cookbook.recipes.apps.RecipesConfig',
|
'cookbook.recipes.apps.RecipesConfig',
|
||||||
|
@ -54,6 +54,10 @@ MIDDLEWARE_CLASSES = [
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
GRAPHENE = {
|
||||||
|
'SCHEMA': 'cookbook.schema.schema'
|
||||||
|
}
|
||||||
|
|
||||||
ROOT_URLCONF = 'cookbook.urls'
|
ROOT_URLCONF = 'cookbook.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
|
|
||||||
from cookbook.schema import schema
|
from cookbook.schema import schema
|
||||||
from graphene_django.views import GraphQLView
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^graphql', csrf_exempt(GraphQLView.as_view(schema=schema))),
|
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
|
||||||
url(r'^graphiql', include('django_graphiql.urls')),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,6 +6,8 @@ from optparse import make_option
|
||||||
from django import get_version as get_django_version
|
from django import get_version as get_django_version
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
from graphene_django.settings import graphene_settings
|
||||||
|
|
||||||
LT_DJANGO_1_8 = StrictVersion(get_django_version()) < StrictVersion('1.8')
|
LT_DJANGO_1_8 = StrictVersion(get_django_version()) < StrictVersion('1.8')
|
||||||
|
|
||||||
if LT_DJANGO_1_8:
|
if LT_DJANGO_1_8:
|
||||||
|
@ -16,7 +18,7 @@ if LT_DJANGO_1_8:
|
||||||
type=str,
|
type=str,
|
||||||
dest='schema',
|
dest='schema',
|
||||||
default='',
|
default='',
|
||||||
help='Django app containing schema to dump, e.g. myproject.core.schema',
|
help='Django app containing schema to dump, e.g. myproject.core.schema.schema',
|
||||||
),
|
),
|
||||||
make_option(
|
make_option(
|
||||||
'--out',
|
'--out',
|
||||||
|
@ -35,14 +37,14 @@ else:
|
||||||
'--schema',
|
'--schema',
|
||||||
type=str,
|
type=str,
|
||||||
dest='schema',
|
dest='schema',
|
||||||
default=getattr(settings, 'GRAPHENE_SCHEMA', ''),
|
default=graphene_settings.SCHEMA,
|
||||||
help='Django app containing schema to dump, e.g. myproject.core.schema')
|
help='Django app containing schema to dump, e.g. myproject.core.schema.schema')
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--out',
|
'--out',
|
||||||
type=str,
|
type=str,
|
||||||
dest='out',
|
dest='out',
|
||||||
default=getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json'),
|
default=graphene_settings.SCHEMA_OUTPUT,
|
||||||
help='Output file (default: schema.json)')
|
help='Output file (default: schema.json)')
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,14 +58,18 @@ class Command(CommandArguments):
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
schema = options.get('schema') or getattr(settings, 'GRAPHENE_SCHEMA', '')
|
options_schema = options.get('schema')
|
||||||
out = options.get('out') or getattr(settings, 'GRAPHENE_SCHEMA_OUTPUT', 'schema.json')
|
if options_schema:
|
||||||
|
schema = importlib.import_module(options_schema)
|
||||||
|
else:
|
||||||
|
schema = graphene_settings.SCHEMA
|
||||||
|
|
||||||
if schema == '':
|
out = options.get('out') or graphene_settings.SCHEMA_OUTPUT
|
||||||
raise CommandError('Specify schema on GRAPHENE_SCHEMA setting or by using --schema')
|
|
||||||
i = importlib.import_module(schema)
|
|
||||||
|
|
||||||
schema_dict = {'data': i.schema.introspect()}
|
if not schema:
|
||||||
|
raise CommandError('Specify schema on GRAPHENE.SCHEMA setting or by using --schema')
|
||||||
|
|
||||||
|
schema_dict = {'data': schema.introspect()}
|
||||||
self.save_file(out, schema_dict)
|
self.save_file(out, schema_dict)
|
||||||
|
|
||||||
style = getattr(self, 'style', None)
|
style = getattr(self, 'style', None)
|
||||||
|
|
127
graphene_django/settings.py
Normal file
127
graphene_django/settings.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
"""
|
||||||
|
Settings for Graphene are all namespaced in the GRAPHENE setting.
|
||||||
|
For example your project's `settings.py` file might look like this:
|
||||||
|
GRAPHENE = {
|
||||||
|
'SCHEMA': 'my_app.schema.schema'
|
||||||
|
'MIDDLEWARE': (
|
||||||
|
'graphene_django.debug.DjangoDebugMiddleware',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
This module provides the `graphene_settings` object, that is used to access
|
||||||
|
Graphene settings, checking for user settings first, then falling
|
||||||
|
back to the defaults.
|
||||||
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test.signals import setting_changed
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
|
try:
|
||||||
|
import importlib # Available in Python 3.1+
|
||||||
|
except ImportError:
|
||||||
|
from django.utils import importlib # Will be removed in Django 1.9
|
||||||
|
|
||||||
|
|
||||||
|
# Copied shamelessly from Django REST Framework
|
||||||
|
|
||||||
|
DEFAULTS = {
|
||||||
|
'SCHEMA': None,
|
||||||
|
'SCHEMA_OUTPUT': 'schema.json',
|
||||||
|
'MIDDLEWARE': (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
DEFAULTS['MIDDLEWARE'] += (
|
||||||
|
'graphene_django.debug.DjangoDebugMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
# List of settings that may be in string import notation.
|
||||||
|
IMPORT_STRINGS = (
|
||||||
|
'MIDDLEWARE',
|
||||||
|
'SCHEMA',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def perform_import(val, setting_name):
|
||||||
|
"""
|
||||||
|
If the given setting is a string import notation,
|
||||||
|
then perform the necessary import or imports.
|
||||||
|
"""
|
||||||
|
if val is None:
|
||||||
|
return None
|
||||||
|
elif isinstance(val, six.string_types):
|
||||||
|
return import_from_string(val, setting_name)
|
||||||
|
elif isinstance(val, (list, tuple)):
|
||||||
|
return [import_from_string(item, setting_name) for item in val]
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def import_from_string(val, setting_name):
|
||||||
|
"""
|
||||||
|
Attempt to import a class from a string representation.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Nod to tastypie's use of importlib.
|
||||||
|
parts = val.split('.')
|
||||||
|
module_path, class_name = '.'.join(parts[:-1]), parts[-1]
|
||||||
|
module = importlib.import_module(module_path)
|
||||||
|
return getattr(module, class_name)
|
||||||
|
except (ImportError, AttributeError) as e:
|
||||||
|
msg = "Could not import '%s' for Graphene setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)
|
||||||
|
raise ImportError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class GrapheneSettings(object):
|
||||||
|
"""
|
||||||
|
A settings object, that allows API settings to be accessed as properties.
|
||||||
|
For example:
|
||||||
|
from graphene_django.settings import settings
|
||||||
|
print(settings.SCHEMA)
|
||||||
|
Any setting with string import paths will be automatically resolved
|
||||||
|
and return the class, rather than the string literal.
|
||||||
|
"""
|
||||||
|
def __init__(self, user_settings=None, defaults=None, import_strings=None):
|
||||||
|
if user_settings:
|
||||||
|
self._user_settings = user_settings
|
||||||
|
self.defaults = defaults or DEFAULTS
|
||||||
|
self.import_strings = import_strings or IMPORT_STRINGS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_settings(self):
|
||||||
|
if not hasattr(self, '_user_settings'):
|
||||||
|
self._user_settings = getattr(settings, 'GRAPHENE', {})
|
||||||
|
return self._user_settings
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
if attr not in self.defaults:
|
||||||
|
raise AttributeError("Invalid Graphene setting: '%s'" % attr)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check if present in user settings
|
||||||
|
val = self.user_settings[attr]
|
||||||
|
except KeyError:
|
||||||
|
# Fall back to defaults
|
||||||
|
val = self.defaults[attr]
|
||||||
|
|
||||||
|
# Coerce import strings into classes
|
||||||
|
if attr in self.import_strings:
|
||||||
|
val = perform_import(val, attr)
|
||||||
|
|
||||||
|
# Cache the result
|
||||||
|
setattr(self, attr, val)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
graphene_settings = GrapheneSettings(None, DEFAULTS, IMPORT_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
def reload_graphene_settings(*args, **kwargs):
|
||||||
|
global graphene_settings
|
||||||
|
setting, value = kwargs['setting'], kwargs['value']
|
||||||
|
if setting == 'GRAPHENE':
|
||||||
|
graphene_settings = GrapheneSettings(value, DEFAULTS, IMPORT_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
setting_changed.connect(reload_graphene_settings)
|
123
graphene_django/templates/graphene/graphiql.html
Normal file
123
graphene_django/templates/graphene/graphiql.html
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<!--
|
||||||
|
The request to this GraphQL server provided the header "Accept: text/html"
|
||||||
|
and as a result has been presented GraphiQL - an in-browser IDE for
|
||||||
|
exploring GraphQL.
|
||||||
|
If you wish to receive JSON, provide the header "Accept: application/json" or
|
||||||
|
add "&raw" to the end of the URL within a browser.
|
||||||
|
-->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link href="//cdn.jsdelivr.net/graphiql/{{graphiql_version}}/graphiql.css" rel="stylesheet" />
|
||||||
|
<script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
|
||||||
|
<script src="//cdn.jsdelivr.net/react/15.0.1/react.min.js"></script>
|
||||||
|
<script src="//cdn.jsdelivr.net/react/15.0.1/react-dom.min.js"></script>
|
||||||
|
<script src="//cdn.jsdelivr.net/graphiql/{{graphiql_version}}/graphiql.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
// Parse the cookie value for a CSRF token
|
||||||
|
var csrftoken;
|
||||||
|
var cookies = ('; ' + document.cookie).split('; csrftoken=');
|
||||||
|
if (cookies.length == 2)
|
||||||
|
csrftoken = cookies.pop().split(';').shift();
|
||||||
|
|
||||||
|
// Collect the URL parameters
|
||||||
|
var parameters = {};
|
||||||
|
window.location.search.substr(1).split('&').forEach(function (entry) {
|
||||||
|
var eq = entry.indexOf('=');
|
||||||
|
if (eq >= 0) {
|
||||||
|
parameters[decodeURIComponent(entry.slice(0, eq))] =
|
||||||
|
decodeURIComponent(entry.slice(eq + 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Produce a Location query string from a parameter object.
|
||||||
|
function locationQuery(params) {
|
||||||
|
return '?' + Object.keys(params).map(function (key) {
|
||||||
|
return encodeURIComponent(key) + '=' +
|
||||||
|
encodeURIComponent(params[key]);
|
||||||
|
}).join('&');
|
||||||
|
}
|
||||||
|
// Derive a fetch URL from the current URL, sans the GraphQL parameters.
|
||||||
|
var graphqlParamNames = {
|
||||||
|
query: true,
|
||||||
|
variables: true,
|
||||||
|
operationName: true
|
||||||
|
};
|
||||||
|
var otherParams = {};
|
||||||
|
for (var k in parameters) {
|
||||||
|
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
|
||||||
|
otherParams[k] = parameters[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var fetchURL = locationQuery(otherParams);
|
||||||
|
// Defines a GraphQL fetcher using the fetch API.
|
||||||
|
function graphQLFetcher(graphQLParams) {
|
||||||
|
var headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
if (csrftoken) {
|
||||||
|
headers['X-CSRFToken'] = csrftoken;
|
||||||
|
}
|
||||||
|
return fetch(fetchURL, {
|
||||||
|
method: 'post',
|
||||||
|
headers: headers,
|
||||||
|
body: JSON.stringify(graphQLParams),
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(function (response) {
|
||||||
|
return response.text();
|
||||||
|
}).then(function (responseBody) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(responseBody);
|
||||||
|
} catch (error) {
|
||||||
|
return responseBody;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// When the query and variables string is edited, update the URL bar so
|
||||||
|
// that it can be easily shared.
|
||||||
|
function onEditQuery(newQuery) {
|
||||||
|
parameters.query = newQuery;
|
||||||
|
updateURL();
|
||||||
|
}
|
||||||
|
function onEditVariables(newVariables) {
|
||||||
|
parameters.variables = newVariables;
|
||||||
|
updateURL();
|
||||||
|
}
|
||||||
|
function onEditOperationName(newOperationName) {
|
||||||
|
parameters.operationName = newOperationName;
|
||||||
|
updateURL();
|
||||||
|
}
|
||||||
|
function updateURL() {
|
||||||
|
history.replaceState(null, null, locationQuery(parameters));
|
||||||
|
}
|
||||||
|
// Render <GraphiQL /> into the body.
|
||||||
|
ReactDOM.render(
|
||||||
|
React.createElement(GraphiQL, {
|
||||||
|
fetcher: graphQLFetcher,
|
||||||
|
onEditQuery: onEditQuery,
|
||||||
|
onEditVariables: onEditVariables,
|
||||||
|
onEditOperationName: onEditOperationName,
|
||||||
|
query: '{{ query|escapejs }}',
|
||||||
|
response: '{{ result|escapejs }}',
|
||||||
|
{% if variables %}
|
||||||
|
variables: '{{ variables|escapejs }}',
|
||||||
|
{% endif %}
|
||||||
|
{% if operationName %}
|
||||||
|
operationName: '{{ operation_name|escapejs }}',
|
||||||
|
{% endif %}
|
||||||
|
}),
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
32
graphene_django/tests/schema_view.py
Normal file
32
graphene_django/tests/schema_view.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import graphene
|
||||||
|
from graphene import Schema, ObjectType, relay
|
||||||
|
|
||||||
|
from ..types import DjangoObjectType
|
||||||
|
from .models import Article, Reporter
|
||||||
|
|
||||||
|
|
||||||
|
class QueryRoot(ObjectType):
|
||||||
|
|
||||||
|
thrower = graphene.String(required=True)
|
||||||
|
request = graphene.String(required=True)
|
||||||
|
test = graphene.String(who=graphene.String())
|
||||||
|
|
||||||
|
def resolve_thrower(self, args, context, info):
|
||||||
|
raise Exception("Throws!")
|
||||||
|
|
||||||
|
def resolve_request(self, args, context, info):
|
||||||
|
request = context
|
||||||
|
return request.GET.get('q')
|
||||||
|
|
||||||
|
def resolve_test(self, args, context, info):
|
||||||
|
return 'Hello %s' % (args.get('who') or 'World')
|
||||||
|
|
||||||
|
|
||||||
|
class MutationRoot(ObjectType):
|
||||||
|
write_test = graphene.Field(QueryRoot)
|
||||||
|
|
||||||
|
def resolve_write_test(self, args, context, info):
|
||||||
|
return QueryRoot()
|
||||||
|
|
||||||
|
|
||||||
|
schema = Schema(query=QueryRoot, mutation=MutationRoot)
|
|
@ -5,7 +5,6 @@ from six import StringIO
|
||||||
|
|
||||||
@patch('graphene_django.management.commands.graphql_schema.Command.save_file')
|
@patch('graphene_django.management.commands.graphql_schema.Command.save_file')
|
||||||
def test_generate_file_on_call_graphql_schema(savefile_mock, settings):
|
def test_generate_file_on_call_graphql_schema(savefile_mock, settings):
|
||||||
settings.GRAPHENE_SCHEMA = 'graphene_django.tests.schema'
|
|
||||||
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()
|
||||||
|
|
|
@ -1,57 +1,421 @@
|
||||||
|
import pytest
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib import urlencode
|
||||||
|
except ImportError:
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
def format_response(response):
|
|
||||||
|
def url_string(**url_params):
|
||||||
|
string = '/graphql'
|
||||||
|
|
||||||
|
if url_params:
|
||||||
|
string += '?' + urlencode(url_params)
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def response_json(response):
|
||||||
return json.loads(response.content.decode())
|
return json.loads(response.content.decode())
|
||||||
|
|
||||||
|
|
||||||
def test_client_get_good_query(settings, client):
|
j = lambda **kwargs: json.dumps(kwargs)
|
||||||
settings.ROOT_URLCONF = 'graphene_django.tests.urls'
|
|
||||||
response = client.get('/graphql', {'query': '{ human { headline } }'})
|
|
||||||
json_response = format_response(response)
|
def test_graphiql_is_enabled(client):
|
||||||
expected_json = {
|
response = client.get(url_string(), HTTP_ACCEPT='text/html')
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_get_with_query_param(client):
|
||||||
|
response = client.get(url_string(query='{test}'))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello World"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_get_with_variable_values(client):
|
||||||
|
response = client.get(url_string(
|
||||||
|
query='query helloWho($who: String){ test(who: $who) }',
|
||||||
|
variables=json.dumps({'who': "Dolly"})
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello Dolly"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_get_with_operation_name(client):
|
||||||
|
response = client.get(url_string(
|
||||||
|
query='''
|
||||||
|
query helloYou { test(who: "You"), ...shared }
|
||||||
|
query helloWorld { test(who: "World"), ...shared }
|
||||||
|
query helloDolly { test(who: "Dolly"), ...shared }
|
||||||
|
fragment shared on QueryRoot {
|
||||||
|
shared: test(who: "Everyone")
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
operationName='helloWorld'
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
'data': {
|
'data': {
|
||||||
'human': {
|
'test': 'Hello World',
|
||||||
'headline': None
|
'shared': 'Hello Everyone'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_reports_validation_errors(client):
|
||||||
|
response = client.get(url_string(
|
||||||
|
query='{ test, unknownOne, unknownTwo }'
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [
|
||||||
|
{
|
||||||
|
'message': 'Cannot query field "unknownOne" on type "QueryRoot".',
|
||||||
|
'locations': [{'line': 1, 'column': 9}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'message': 'Cannot query field "unknownTwo" on type "QueryRoot".',
|
||||||
|
'locations': [{'line': 1, 'column': 21}]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
assert json_response == expected_json
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_get_good_query_with_raise(settings, client):
|
def test_errors_when_missing_operation_name(client):
|
||||||
settings.ROOT_URLCONF = 'graphene_django.tests.urls'
|
response = client.get(url_string(
|
||||||
response = client.get('/graphql', {'query': '{ human { raises } }'})
|
query='''
|
||||||
json_response = format_response(response)
|
query TestQuery { test }
|
||||||
assert json_response['errors'][0]['message'] == 'This field should raise exception'
|
mutation TestMutation { writeTest { test } }
|
||||||
assert json_response['data']['human']['raises'] is None
|
'''
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [
|
||||||
|
{
|
||||||
|
'message': 'Must provide operation name if query contains multiple operations.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_client_post_good_query_json(settings, client):
|
def test_errors_when_sending_a_mutation_via_get(client):
|
||||||
settings.ROOT_URLCONF = 'graphene_django.tests.urls'
|
response = client.get(url_string(
|
||||||
response = client.post(
|
query='''
|
||||||
'/graphql', json.dumps({'query': '{ human { headline } }'}), 'application/json')
|
mutation TestMutation { writeTest { test } }
|
||||||
json_response = format_response(response)
|
'''
|
||||||
expected_json = {
|
))
|
||||||
|
assert response.status_code == 405
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [
|
||||||
|
{
|
||||||
|
'message': 'Can only perform a mutation operation from a POST request.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_errors_when_selecting_a_mutation_within_a_get(client):
|
||||||
|
response = client.get(url_string(
|
||||||
|
query='''
|
||||||
|
query TestQuery { test }
|
||||||
|
mutation TestMutation { writeTest { test } }
|
||||||
|
''',
|
||||||
|
operationName='TestMutation'
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status_code == 405
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [
|
||||||
|
{
|
||||||
|
'message': 'Can only perform a mutation operation from a POST request.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_mutation_to_exist_within_a_get(client):
|
||||||
|
response = client.get(url_string(
|
||||||
|
query='''
|
||||||
|
query TestQuery { test }
|
||||||
|
mutation TestMutation { writeTest { test } }
|
||||||
|
''',
|
||||||
|
operationName='TestQuery'
|
||||||
|
))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello World"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_post_with_json_encoding(client):
|
||||||
|
response = client.post(url_string(), j(query='{test}'), 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello World"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_sending_a_mutation_via_post(client):
|
||||||
|
response = client.post(url_string(), j(query='mutation TestMutation { writeTest { test } }'), 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'writeTest': {'test': 'Hello World'}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_post_with_url_encoding(client):
|
||||||
|
response = client.post(url_string(), urlencode(dict(query='{test}')), 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello World"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_supports_post_json_query_with_string_variables(client):
|
||||||
|
response = client.post(url_string(), j(
|
||||||
|
query='query helloWho($who: String){ test(who: $who) }',
|
||||||
|
variables=json.dumps({'who': "Dolly"})
|
||||||
|
), 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello Dolly"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_supports_post_json_query_with_json_variables(client):
|
||||||
|
response = client.post(url_string(), j(
|
||||||
|
query='query helloWho($who: String){ test(who: $who) }',
|
||||||
|
variables={'who': "Dolly"}
|
||||||
|
), 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello Dolly"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_supports_post_url_encoded_query_with_string_variables(client):
|
||||||
|
response = client.post(url_string(), urlencode(dict(
|
||||||
|
query='query helloWho($who: String){ test(who: $who) }',
|
||||||
|
variables=json.dumps({'who': "Dolly"})
|
||||||
|
)), 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello Dolly"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_supports_post_json_quey_with_get_variable_values(client):
|
||||||
|
response = client.post(url_string(
|
||||||
|
variables=json.dumps({'who': "Dolly"})
|
||||||
|
), j(
|
||||||
|
query='query helloWho($who: String){ test(who: $who) }',
|
||||||
|
), 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello Dolly"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_url_encoded_query_with_get_variable_values(client):
|
||||||
|
response = client.post(url_string(
|
||||||
|
variables=json.dumps({'who': "Dolly"})
|
||||||
|
), urlencode(dict(
|
||||||
|
query='query helloWho($who: String){ test(who: $who) }',
|
||||||
|
)), 'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello Dolly"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_supports_post_raw_text_query_with_get_variable_values(client):
|
||||||
|
response = client.post(url_string(
|
||||||
|
variables=json.dumps({'who': "Dolly"})
|
||||||
|
),
|
||||||
|
'query helloWho($who: String){ test(who: $who) }',
|
||||||
|
'application/graphql'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {'test': "Hello Dolly"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allows_post_with_operation_name(client):
|
||||||
|
response = client.post(url_string(), j(
|
||||||
|
query='''
|
||||||
|
query helloYou { test(who: "You"), ...shared }
|
||||||
|
query helloWorld { test(who: "World"), ...shared }
|
||||||
|
query helloDolly { test(who: "Dolly"), ...shared }
|
||||||
|
fragment shared on QueryRoot {
|
||||||
|
shared: test(who: "Everyone")
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
operationName='helloWorld'
|
||||||
|
), 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
'data': {
|
'data': {
|
||||||
'human': {
|
'test': 'Hello World',
|
||||||
'headline': None
|
'shared': 'Hello Everyone'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
assert json_response == expected_json
|
|
||||||
|
|
||||||
|
|
||||||
def test_client_post_good_query_graphql(settings, client):
|
def test_allows_post_with_get_operation_name(client):
|
||||||
settings.ROOT_URLCONF = 'graphene_django.tests.urls'
|
response = client.post(url_string(
|
||||||
response = client.post(
|
operationName='helloWorld'
|
||||||
'/graphql', '{ human { headline } }', 'application/graphql')
|
), '''
|
||||||
json_response = format_response(response)
|
query helloYou { test(who: "You"), ...shared }
|
||||||
expected_json = {
|
query helloWorld { test(who: "World"), ...shared }
|
||||||
|
query helloDolly { test(who: "Dolly"), ...shared }
|
||||||
|
fragment shared on QueryRoot {
|
||||||
|
shared: test(who: "Everyone")
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
'application/graphql')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
'data': {
|
'data': {
|
||||||
'human': {
|
'test': 'Hello World',
|
||||||
'headline': None
|
'shared': 'Hello Everyone'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.urls('tests.urls_pretty')
|
||||||
|
def test_supports_pretty_printing(client):
|
||||||
|
response = client.get(url_string(query='{test}'))
|
||||||
|
|
||||||
|
assert response.content.decode() == (
|
||||||
|
'{\n'
|
||||||
|
' "data": {\n'
|
||||||
|
' "test": "Hello World"\n'
|
||||||
|
' }\n'
|
||||||
|
'}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_supports_pretty_printing_by_request(client):
|
||||||
|
response = client.get(url_string(query='{test}', pretty='1'))
|
||||||
|
|
||||||
|
assert response.content.decode() == (
|
||||||
|
'{\n'
|
||||||
|
' "data": {\n'
|
||||||
|
' "test": "Hello World"\n'
|
||||||
|
' }\n'
|
||||||
|
'}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_field_errors_caught_by_graphql(client):
|
||||||
|
response = client.get(url_string(query='{thrower}'))
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': None,
|
||||||
|
'errors': [{'locations': [{'column': 2, 'line': 1}], 'message': 'Throws!'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_syntax_errors_caught_by_graphql(client):
|
||||||
|
response = client.get(url_string(query='syntaxerror'))
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [{'locations': [{'column': 1, 'line': 1}],
|
||||||
|
'message': 'Syntax Error GraphQL request (1:1) '
|
||||||
|
'Unexpected Name "syntaxerror"\n\n1: syntaxerror\n ^\n'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_errors_caused_by_a_lack_of_query(client):
|
||||||
|
response = client.get(url_string())
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [{'message': 'Must provide query string.'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_invalid_json_bodies(client):
|
||||||
|
response = client.post(url_string(), '[]', 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [{'message': 'POST body sent invalid JSON.'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_incomplete_json_bodies(client):
|
||||||
|
response = client.post(url_string(), '{"query":', 'application/json')
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [{'message': 'POST body sent invalid JSON.'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_plain_post_text(client):
|
||||||
|
response = client.post(url_string(
|
||||||
|
variables=json.dumps({'who': "Dolly"})
|
||||||
|
),
|
||||||
|
'query helloWho($who: String){ test(who: $who) }',
|
||||||
|
'text/plain'
|
||||||
|
)
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [{'message': 'Must provide query string.'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_poorly_formed_variables(client):
|
||||||
|
response = client.get(url_string(
|
||||||
|
query='query helloWho($who: String){ test(who: $who) }',
|
||||||
|
variables='who:You'
|
||||||
|
))
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [{'message': 'Variables are invalid JSON.'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_unsupported_http_methods(client):
|
||||||
|
response = client.put(url_string(query='{test}'))
|
||||||
|
assert response.status_code == 405
|
||||||
|
assert response['Allow'] == 'GET, POST'
|
||||||
|
assert response_json(response) == {
|
||||||
|
'errors': [{'message': 'GraphQL only supports GET and POST requests.'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_passes_request_into_context_request(client):
|
||||||
|
response = client.get(url_string(query='{request}', q='testing'))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response_json(response) == {
|
||||||
|
'data': {
|
||||||
|
'request': 'testing'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert json_response == expected_json
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from ..views import GraphQLView
|
from ..views import GraphQLView
|
||||||
from .schema import schema
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^graphql', GraphQLView.as_view(schema=schema)),
|
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,252 @@
|
||||||
from graphql_django_view import GraphQLView as BaseGraphQLView
|
import re
|
||||||
|
import json
|
||||||
|
import six
|
||||||
|
|
||||||
|
from django.http import HttpResponse, HttpResponseNotAllowed
|
||||||
|
from django.http.response import HttpResponseBadRequest
|
||||||
|
from django.views.generic import View
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from graphql import Source, parse, execute, validate
|
||||||
|
from graphql.error import GraphQLError, format_error as format_graphql_error
|
||||||
|
from graphql.execution import ExecutionResult
|
||||||
|
from graphql.type.schema import GraphQLSchema
|
||||||
|
from graphql.execution.middleware import MiddlewareManager
|
||||||
|
from graphql.utils.get_operation_ast import get_operation_ast
|
||||||
|
|
||||||
|
from .settings import graphene_settings
|
||||||
|
|
||||||
|
|
||||||
class GraphQLView(BaseGraphQLView):
|
class HttpError(Exception):
|
||||||
|
def __init__(self, response, message=None, *args, **kwargs):
|
||||||
|
self.response = response
|
||||||
|
self.message = message = message or response.content.decode()
|
||||||
|
super(HttpError, self).__init__(message, *args, **kwargs)
|
||||||
|
|
||||||
def __init__(self, schema, **kwargs):
|
|
||||||
super(GraphQLView, self).__init__(
|
def get_accepted_content_types(request):
|
||||||
schema=schema,
|
def qualify(x):
|
||||||
**kwargs
|
parts = x.split(';', 1)
|
||||||
|
if len(parts) == 2:
|
||||||
|
match = re.match(r'(^|;)q=(0(\.\d{,3})?|1(\.0{,3})?)(;|$)',
|
||||||
|
parts[1])
|
||||||
|
if match:
|
||||||
|
return parts[0], float(match.group(2))
|
||||||
|
return parts[0], 1
|
||||||
|
|
||||||
|
raw_content_types = request.META.get('HTTP_ACCEPT', '*/*').split(',')
|
||||||
|
qualified_content_types = map(qualify, raw_content_types)
|
||||||
|
return list(x[0] for x in sorted(qualified_content_types,
|
||||||
|
key=lambda x: x[1], reverse=True))
|
||||||
|
|
||||||
|
|
||||||
|
class GraphQLView(View):
|
||||||
|
graphiql_version = '0.7.8'
|
||||||
|
graphiql_template = 'graphene/graphiql.html'
|
||||||
|
|
||||||
|
graphiql = False
|
||||||
|
executor = None
|
||||||
|
middleware = None
|
||||||
|
root_value = None
|
||||||
|
pretty = False
|
||||||
|
|
||||||
|
def __init__(self, schema=None, executor=None, middleware=None, root_value=None, graphiql=False, pretty=False):
|
||||||
|
if not schema:
|
||||||
|
schema = graphene_settings.SCHEMA
|
||||||
|
|
||||||
|
if middleware is None:
|
||||||
|
middleware = graphene_settings.MIDDLEWARE
|
||||||
|
|
||||||
|
self.schema = schema
|
||||||
|
self.middleware = MiddlewareManager(middleware)
|
||||||
|
self.executor = executor
|
||||||
|
self.root_value = root_value
|
||||||
|
self.pretty = pretty
|
||||||
|
self.graphiql = graphiql
|
||||||
|
|
||||||
|
assert isinstance(self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.'
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
|
def get_root_value(self, request):
|
||||||
|
return self.root_value
|
||||||
|
|
||||||
|
def get_middleware(self, request):
|
||||||
|
return self.middleware
|
||||||
|
|
||||||
|
def get_context(self, request):
|
||||||
|
return request
|
||||||
|
|
||||||
|
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.'))
|
||||||
|
|
||||||
|
data = self.parse_body(request)
|
||||||
|
show_graphiql = self.graphiql and self.can_display_graphiql(request, data)
|
||||||
|
|
||||||
|
query, variables, operation_name = self.get_graphql_params(request, data)
|
||||||
|
|
||||||
|
execution_result = self.execute_graphql_request(
|
||||||
|
request,
|
||||||
|
data,
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
operation_name,
|
||||||
|
show_graphiql
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if execution_result:
|
||||||
|
response = {}
|
||||||
|
|
||||||
|
if execution_result.errors:
|
||||||
|
response['errors'] = [self.format_error(e) for e in execution_result.errors]
|
||||||
|
|
||||||
|
if execution_result.invalid:
|
||||||
|
status_code = 400
|
||||||
|
else:
|
||||||
|
status_code = 200
|
||||||
|
response['data'] = execution_result.data
|
||||||
|
|
||||||
|
result = self.json_encode(request, response, pretty=show_graphiql)
|
||||||
|
else:
|
||||||
|
result = None
|
||||||
|
|
||||||
|
if show_graphiql:
|
||||||
|
return self.render_graphiql(
|
||||||
|
request,
|
||||||
|
graphiql_version=self.graphiql_version,
|
||||||
|
query=query or '',
|
||||||
|
variables=variables or '',
|
||||||
|
operation_name=operation_name or '',
|
||||||
|
result=result or ''
|
||||||
|
)
|
||||||
|
|
||||||
|
return HttpResponse(
|
||||||
|
status=status_code,
|
||||||
|
content=result,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
except HttpError as e:
|
||||||
|
response = e.response
|
||||||
|
response['Content-Type'] = 'application/json'
|
||||||
|
response.content = self.json_encode(request, {
|
||||||
|
'errors': [self.format_error(e)]
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
|
||||||
|
def render_graphiql(self, request, **data):
|
||||||
|
return render(request, self.graphiql_template, data)
|
||||||
|
|
||||||
|
def json_encode(self, request, d, pretty=False):
|
||||||
|
if not (self.pretty or pretty) and not request.GET.get('pretty'):
|
||||||
|
return json.dumps(d, separators=(',', ':'))
|
||||||
|
|
||||||
|
return json.dumps(d, sort_keys=True,
|
||||||
|
indent=2, separators=(',', ': '))
|
||||||
|
|
||||||
|
# noinspection PyBroadException
|
||||||
|
def parse_body(self, request):
|
||||||
|
content_type = self.get_content_type(request)
|
||||||
|
|
||||||
|
if content_type == 'application/graphql':
|
||||||
|
return {'query': request.body.decode()}
|
||||||
|
|
||||||
|
elif content_type == 'application/json':
|
||||||
|
try:
|
||||||
|
request_json = json.loads(request.body.decode('utf-8'))
|
||||||
|
assert isinstance(request_json, dict)
|
||||||
|
return request_json
|
||||||
|
except:
|
||||||
|
raise HttpError(HttpResponseBadRequest('POST body sent invalid JSON.'))
|
||||||
|
|
||||||
|
elif content_type in ['application/x-www-form-urlencoded', 'multipart/form-data']:
|
||||||
|
return request.POST
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def execute(self, *args, **kwargs):
|
||||||
|
return execute(self.schema, *args, **kwargs)
|
||||||
|
|
||||||
|
def execute_graphql_request(self, request, data, query, variables, operation_name, show_graphiql=False):
|
||||||
|
if not query:
|
||||||
|
if show_graphiql:
|
||||||
|
return None
|
||||||
|
raise HttpError(HttpResponseBadRequest('Must provide query string.'))
|
||||||
|
|
||||||
|
source = Source(query, name='GraphQL request')
|
||||||
|
|
||||||
|
try:
|
||||||
|
document_ast = parse(source)
|
||||||
|
validation_errors = validate(self.schema, document_ast)
|
||||||
|
if validation_errors:
|
||||||
|
return ExecutionResult(
|
||||||
|
errors=validation_errors,
|
||||||
|
invalid=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return ExecutionResult(errors=[e], invalid=True)
|
||||||
|
|
||||||
|
if request.method.lower() == 'get':
|
||||||
|
operation_ast = get_operation_ast(document_ast, operation_name)
|
||||||
|
if operation_ast and operation_ast.operation != 'query':
|
||||||
|
if show_graphiql:
|
||||||
|
return None
|
||||||
|
|
||||||
|
raise HttpError(HttpResponseNotAllowed(
|
||||||
|
['POST'], 'Can only perform a {} operation from a POST request.'.format(operation_ast.operation)
|
||||||
|
))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.execute(
|
||||||
|
document_ast,
|
||||||
|
root_value=self.get_root_value(request),
|
||||||
|
variable_values=variables,
|
||||||
|
operation_name=operation_name,
|
||||||
|
context_value=self.get_context(request),
|
||||||
|
middlewares=self.get_middleware(request),
|
||||||
|
executor=self.executor,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return ExecutionResult(errors=[e], invalid=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_display_graphiql(cls, request, data):
|
||||||
|
raw = 'raw' in request.GET or 'raw' in data
|
||||||
|
return not raw and cls.request_wants_html(request)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def request_wants_html(cls, request):
|
||||||
|
accepted = get_accepted_content_types(request)
|
||||||
|
html_index = accepted.count('text/html')
|
||||||
|
json_index = accepted.count('application/json')
|
||||||
|
|
||||||
|
return html_index > json_index
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_graphql_params(request, data):
|
||||||
|
query = request.GET.get('query') or data.get('query')
|
||||||
|
variables = request.GET.get('variables') or data.get('variables')
|
||||||
|
|
||||||
|
if variables and isinstance(variables, six.text_type):
|
||||||
|
try:
|
||||||
|
variables = json.loads(variables)
|
||||||
|
except:
|
||||||
|
raise HttpError(HttpResponseBadRequest('Variables are invalid JSON.'))
|
||||||
|
|
||||||
|
operation_name = request.GET.get('operationName') or data.get('operationName')
|
||||||
|
|
||||||
|
return query, variables, operation_name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_error(error):
|
||||||
|
if isinstance(error, GraphQLError):
|
||||||
|
return format_graphql_error(error)
|
||||||
|
|
||||||
|
return {'message': six.text_type(error)}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_content_type(request):
|
||||||
|
meta = request.META
|
||||||
|
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