Merge branch 'master' into v3

This commit is contained in:
Jonathan Kim 2020-08-12 07:06:35 +01:00
commit 33c6a54414
12 changed files with 97 additions and 18 deletions

View File

@ -8,7 +8,7 @@ jobs:
strategy: strategy:
max-parallel: 4 max-parallel: 4
matrix: matrix:
django: ["2.2", "3.0"] django: ["2.2", "3.0", "3.1"]
python-version: ["3.6", "3.7", "3.8"] python-version: ["3.6", "3.7", "3.8"]
steps: steps:

View File

@ -186,3 +186,24 @@ Default: ``None``
GRAPHENE = { GRAPHENE = {
'SUBSCRIPTION_PATH': "/ws/graphql" 'SUBSCRIPTION_PATH': "/ws/graphql"
} }
``GRAPHIQL_HEADER_EDITOR_ENABLED``
---------------------
GraphiQL starting from version 1.0.0 allows setting custom headers in similar fashion to query variables.
Set to ``False`` if you want to disable GraphiQL headers editor tab for some reason.
This setting is passed to ``headerEditorEnabled`` GraphiQL options, for details refer to GraphiQLDocs_.
.. _GraphiQLDocs: https://github.com/graphql/graphiql/tree/main/packages/graphiql#options
Default: ``True``
.. code:: python
GRAPHENE = {
'GRAPHIQL_HEADER_EDITOR_ENABLED': True,
}

View File

@ -8,8 +8,14 @@ try:
from django.contrib.postgres.fields import ( from django.contrib.postgres.fields import (
ArrayField, ArrayField,
HStoreField, HStoreField,
JSONField, JSONField as PGJSONField,
RangeField, RangeField,
) )
except ImportError: except ImportError:
ArrayField, HStoreField, JSONField, RangeField = (MissingType,) * 4 ArrayField, HStoreField, PGJSONField, RangeField = (MissingType,) * 4
try:
# JSONField is only available from Django 3.1
from django.db.models import JSONField
except ImportError:
JSONField = MissingType

View File

@ -26,9 +26,10 @@ from graphene.utils.str_converters import to_camel_case
from graphql import GraphQLError, assert_valid_name from graphql import GraphQLError, assert_valid_name
from graphql.pyutils import register_description from graphql.pyutils import register_description
from .compat import ArrayField, HStoreField, JSONField, RangeField from .compat import ArrayField, HStoreField, JSONField, PGJSONField, RangeField
from .fields import DjangoConnectionField, DjangoListField from .fields import DjangoListField, DjangoConnectionField
from .settings import graphene_settings from .settings import graphene_settings
from .utils import import_single_dispatch
from .utils.str_converters import to_const from .utils.str_converters import to_const
@ -296,8 +297,9 @@ def convert_postgres_array_to_list(field, registry=None):
@convert_django_field.register(HStoreField) @convert_django_field.register(HStoreField)
@convert_django_field.register(PGJSONField)
@convert_django_field.register(JSONField) @convert_django_field.register(JSONField)
def convert_postgres_field_to_string(field, registry=None): def convert_pg_and_json_field_to_string(field, registry=None):
return JSONString( return JSONString(
description=get_django_field_description(field), required=not field.null description=get_django_field_description(field), required=not field.null
) )

View File

@ -40,6 +40,10 @@ DEFAULTS = {
"DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME": None, "DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME": None,
# Use a separate path for handling subscriptions. # Use a separate path for handling subscriptions.
"SUBSCRIPTION_PATH": None, "SUBSCRIPTION_PATH": None,
# By default GraphiQL headers editor tab is enabled, set to False to hide it
# This sets headerEditorEnabled GraphiQL option, for details go to
# https://github.com/graphql/graphiql/tree/main/packages/graphiql#options
"GRAPHIQL_HEADER_EDITOR_ENABLED": True,
} }
if settings.DEBUG: if settings.DEBUG:

View File

@ -61,13 +61,15 @@
var fetchURL = locationQuery(otherParams); var fetchURL = locationQuery(otherParams);
// Defines a GraphQL fetcher using the fetch API. // Defines a GraphQL fetcher using the fetch API.
function httpClient(graphQLParams) { function httpClient(graphQLParams, opts) {
var headers = { if (typeof opts === 'undefined') {
Accept: "application/json", opts = {};
"Content-Type": "application/json", }
}; var headers = opts.headers || {};
headers['Accept'] = headers['Accept'] || 'application/json';
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
if (csrftoken) { if (csrftoken) {
headers["X-CSRFToken"] = csrftoken; headers['X-CSRFToken'] = csrftoken
} }
return fetch(fetchURL, { return fetch(fetchURL, {
method: "post", method: "post",
@ -108,7 +110,7 @@
var activeSubscription = null; var activeSubscription = null;
// Define a GraphQL fetcher that can intelligently route queries based on the operation type. // Define a GraphQL fetcher that can intelligently route queries based on the operation type.
function graphQLFetcher(graphQLParams) { function graphQLFetcher(graphQLParams, opts) {
var operationType = getOperationType(graphQLParams); var operationType = getOperationType(graphQLParams);
// If we're about to execute a new operation, and we have an active subscription, // If we're about to execute a new operation, and we have an active subscription,
@ -126,7 +128,7 @@
}, },
}; };
} else { } else {
return httpClient(graphQLParams); return httpClient(graphQLParams, opts);
} }
} }
@ -173,6 +175,7 @@
onEditQuery: onEditQuery, onEditQuery: onEditQuery,
onEditVariables: onEditVariables, onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName, onEditOperationName: onEditOperationName,
headerEditorEnabled: GRAPHENE_SETTINGS.graphiqlHeaderEditorEnabled,
query: parameters.query, query: parameters.query,
}; };
if (parameters.variables) { if (parameters.variables) {

View File

@ -45,6 +45,7 @@ add "&raw" to the end of the URL within a browser.
{% if subscription_path %} {% if subscription_path %}
subscriptionPath: "{{subscription_path}}", subscriptionPath: "{{subscription_path}}",
{% endif %} {% endif %}
graphiqlHeaderEditorEnabled: {{ graphiql_header_editor_enabled|yesno:"true,false" }},
}; };
</script> </script>
<script src="{% static 'graphene_django/graphiql.js' %}"></script> <script src="{% static 'graphene_django/graphiql.js' %}"></script>

View File

@ -11,7 +11,14 @@ from graphene.relay import ConnectionField, Node
from graphene.types.datetime import Date, DateTime, Time from graphene.types.datetime import Date, DateTime, Time
from graphene.types.json import JSONString from graphene.types.json import JSONString
from ..compat import ArrayField, HStoreField, JSONField, MissingType, RangeField from ..compat import (
ArrayField,
HStoreField,
JSONField,
PGJSONField,
MissingType,
RangeField,
)
from ..converter import ( from ..converter import (
convert_django_field, convert_django_field,
convert_django_field_with_choices, convert_django_field_with_choices,
@ -352,8 +359,13 @@ def test_should_postgres_hstore_convert_string():
assert_conversion(HStoreField, JSONString) assert_conversion(HStoreField, JSONString)
@pytest.mark.skipif(JSONField is MissingType, reason="JSONField should exist") @pytest.mark.skipif(PGJSONField is MissingType, reason="PGJSONField should exist")
def test_should_postgres_json_convert_string(): def test_should_postgres_json_convert_string():
assert_conversion(PGJSONField, JSONString)
@pytest.mark.skipif(JSONField is MissingType, reason="JSONField should exist")
def test_should_json_convert_string():
assert_conversion(JSONField, JSONString) assert_conversion(JSONField, JSONString)

View File

@ -9,6 +9,7 @@ from graphene import Connection, Field, Interface, ObjectType, Schema, String
from graphene.relay import Node from graphene.relay import Node
from .. import registry from .. import registry
from ..filter import DjangoFilterConnectionField
from ..types import DjangoObjectType, DjangoObjectTypeOptions from ..types import DjangoObjectType, DjangoObjectTypeOptions
from .models import Article as ArticleModel from .models import Article as ArticleModel
from .models import Reporter as ReporterModel from .models import Reporter as ReporterModel
@ -662,3 +663,28 @@ class TestDjangoObjectType:
} }
""" """
) )
@with_local_registry
def test_django_objecttype_name_connection_propagation():
class Reporter(DjangoObjectType):
class Meta:
model = ReporterModel
name = "CustomReporterName"
filter_fields = ["email"]
interfaces = (Node,)
class Query(ObjectType):
reporter = Node.Field(Reporter)
reporters = DjangoFilterConnectionField(Reporter)
assert Reporter._meta.name == "CustomReporterName"
schema = str(Schema(query=Query))
assert "type CustomReporterName implements Node {" in schema
assert "type CustomReporterNameConnection {" in schema
assert "type CustomReporterNameEdge {" in schema
assert "type Reporter implements Node {" not in schema
assert "type ReporterConnection {" not in schema
assert "type ReporterEdge {" not in schema

View File

@ -246,7 +246,7 @@ class DjangoObjectType(ObjectType):
connection_class = Connection connection_class = Connection
connection = connection_class.create_type( connection = connection_class.create_type(
"{}Connection".format(cls.__name__), node=cls "{}Connection".format(options.get("name") or cls.__name__), node=cls
) )
if connection is not None: if connection is not None:

View File

@ -154,6 +154,8 @@ class GraphQLView(View):
subscriptions_transport_ws_sri=self.subscriptions_transport_ws_sri, subscriptions_transport_ws_sri=self.subscriptions_transport_ws_sri,
# The SUBSCRIPTION_PATH setting. # The SUBSCRIPTION_PATH setting.
subscription_path=self.subscription_path, subscription_path=self.subscription_path,
# GraphiQL headers tab,
graphiql_header_editor_enabled=graphene_settings.GRAPHIQL_HEADER_EDITOR_ENABLED,
) )
if self.batch: if self.batch:

View File

@ -1,6 +1,6 @@
[tox] [tox]
envlist = envlist =
py{36,37,38}-django{22,30,master}, py{36,37,38}-django{22,30,31,master},
black,flake8 black,flake8
[gh-actions] [gh-actions]
@ -13,6 +13,7 @@ python =
DJANGO = DJANGO =
2.2: django22 2.2: django22
3.0: django30 3.0: django30
3.1: django31
master: djangomaster master: djangomaster
[testenv] [testenv]
@ -28,6 +29,7 @@ deps =
django21: Django>=2.1,<2.2 django21: Django>=2.1,<2.2
django22: Django>=2.2,<3.0 django22: Django>=2.2,<3.0
django30: Django>=3.0a1,<3.1 django30: Django>=3.0a1,<3.1
django31: Django>=3.1,<3.2
djangomaster: https://github.com/django/django/archive/master.zip djangomaster: https://github.com/django/django/archive/master.zip
commands = {posargs:py.test --cov=graphene_django graphene_django examples} commands = {posargs:py.test --cov=graphene_django graphene_django examples}