From 55769e814f3fc3da6c6d39696d6d1460fd8c9c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kowalski?= Date: Fri, 7 Aug 2020 11:13:26 +0200 Subject: [PATCH 1/4] Add headers support to GraphiQL (#1016) Co-authored-by: Jonathan Kim --- docs/settings.rst | 21 +++++++++++++++++++ graphene_django/settings.py | 4 ++++ .../static/graphene_django/graphiql.js | 19 ++++++++++------- .../templates/graphene/graphiql.html | 1 + graphene_django/views.py | 2 ++ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index c2f8600..1e82e70 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -186,3 +186,24 @@ Default: ``None`` GRAPHENE = { '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, + } diff --git a/graphene_django/settings.py b/graphene_django/settings.py index 52cca89..71b791c 100644 --- a/graphene_django/settings.py +++ b/graphene_django/settings.py @@ -41,6 +41,10 @@ DEFAULTS = { "DJANGO_CHOICE_FIELD_ENUM_CUSTOM_NAME": None, # Use a separate path for handling subscriptions. "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: diff --git a/graphene_django/static/graphene_django/graphiql.js b/graphene_django/static/graphene_django/graphiql.js index 45f8ad7..8c3b5ce 100644 --- a/graphene_django/static/graphene_django/graphiql.js +++ b/graphene_django/static/graphene_django/graphiql.js @@ -61,13 +61,15 @@ var fetchURL = locationQuery(otherParams); // Defines a GraphQL fetcher using the fetch API. - function httpClient(graphQLParams) { - var headers = { - Accept: "application/json", - "Content-Type": "application/json", - }; + function httpClient(graphQLParams, opts) { + if (typeof opts === 'undefined') { + opts = {}; + } + var headers = opts.headers || {}; + headers['Accept'] = headers['Accept'] || 'application/json'; + headers['Content-Type'] = headers['Content-Type'] || 'application/json'; if (csrftoken) { - headers["X-CSRFToken"] = csrftoken; + headers['X-CSRFToken'] = csrftoken } return fetch(fetchURL, { method: "post", @@ -108,7 +110,7 @@ var activeSubscription = null; // 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); // If we're about to execute a new operation, and we have an active subscription, @@ -126,7 +128,7 @@ }, }; } else { - return httpClient(graphQLParams); + return httpClient(graphQLParams, opts); } } @@ -173,6 +175,7 @@ onEditQuery: onEditQuery, onEditVariables: onEditVariables, onEditOperationName: onEditOperationName, + headerEditorEnabled: GRAPHENE_SETTINGS.graphiqlHeaderEditorEnabled, query: parameters.query, }; if (parameters.variables) { diff --git a/graphene_django/templates/graphene/graphiql.html b/graphene_django/templates/graphene/graphiql.html index abc4b52..cec4893 100644 --- a/graphene_django/templates/graphene/graphiql.html +++ b/graphene_django/templates/graphene/graphiql.html @@ -45,6 +45,7 @@ add "&raw" to the end of the URL within a browser. {% if subscription_path %} subscriptionPath: "{{subscription_path}}", {% endif %} + graphiqlHeaderEditorEnabled: {{ graphiql_header_editor_enabled|yesno:"true,false" }}, }; diff --git a/graphene_django/views.py b/graphene_django/views.py index 59084e8..5ee0297 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -167,6 +167,8 @@ class GraphQLView(View): subscriptions_transport_ws_sri=self.subscriptions_transport_ws_sri, # The SUBSCRIPTION_PATH setting. subscription_path=self.subscription_path, + # GraphiQL headers tab, + graphiql_header_editor_enabled=graphene_settings.GRAPHIQL_HEADER_EDITOR_ENABLED, ) if self.batch: From 11dbde3beaa9882277082ec108b993c36be62f4e Mon Sep 17 00:00:00 2001 From: Thomas Leonard <64223923+tcleonard@users.noreply.github.com> Date: Fri, 7 Aug 2020 10:15:35 +0100 Subject: [PATCH 2/4] Fix Connection/Edge naming and add unit test (#1012) Co-authored-by: Thomas Leonard --- graphene_django/tests/test_types.py | 26 ++++++++++++++++++++++++++ graphene_django/types.py | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index 4d14749..fb95820 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -9,6 +9,7 @@ from graphene import Connection, Field, Interface, ObjectType, Schema, String from graphene.relay import Node from .. import registry +from ..filter import DjangoFilterConnectionField from ..types import DjangoObjectType, DjangoObjectTypeOptions from .models import Article as ArticleModel from .models import Reporter as ReporterModel @@ -580,3 +581,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 diff --git a/graphene_django/types.py b/graphene_django/types.py index b31fd0f..e38ae1f 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -239,7 +239,7 @@ class DjangoObjectType(ObjectType): connection_class = Connection 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: From 67a0492c124587a98435621565c00b3f9e9053f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolai=20R=C3=B8ed=20Kristiansen?= Date: Fri, 7 Aug 2020 11:22:15 +0200 Subject: [PATCH 3/4] Add converter for django 3.1 JSONField (#1017) --- .github/workflows/tests.yml | 2 +- graphene_django/compat.py | 10 ++++++++-- graphene_django/converter.py | 5 +++-- graphene_django/tests/test_converter.py | 16 ++++++++++++++-- tox.ini | 4 +++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 37453f0..b9e57b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 4 matrix: - django: ["1.11", "2.2", "3.0"] + django: ["1.11", "2.2", "3.0", "3.1"] python-version: ["3.6", "3.7", "3.8"] include: - django: "1.11" diff --git a/graphene_django/compat.py b/graphene_django/compat.py index 59fab30..6e5e769 100644 --- a/graphene_django/compat.py +++ b/graphene_django/compat.py @@ -8,8 +8,14 @@ try: from django.contrib.postgres.fields import ( ArrayField, HStoreField, - JSONField, + JSONField as PGJSONField, RangeField, ) 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.contrib.fields import JSONField +except ImportError: + JSONField = MissingType diff --git a/graphene_django/converter.py b/graphene_django/converter.py index ca524ff..0de6964 100644 --- a/graphene_django/converter.py +++ b/graphene_django/converter.py @@ -24,7 +24,7 @@ from graphene.utils.str_converters import to_camel_case from graphql import assert_valid_name from .settings import graphene_settings -from .compat import ArrayField, HStoreField, JSONField, RangeField +from .compat import ArrayField, HStoreField, JSONField, PGJSONField, RangeField from .fields import DjangoListField, DjangoConnectionField from .utils import import_single_dispatch from .utils.str_converters import to_const @@ -267,8 +267,9 @@ def convert_postgres_array_to_list(field, registry=None): @convert_django_field.register(HStoreField) +@convert_django_field.register(PGJSONField) @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(description=field.help_text, required=not field.null) diff --git a/graphene_django/tests/test_converter.py b/graphene_django/tests/test_converter.py index f6e3606..7d8e669 100644 --- a/graphene_django/tests/test_converter.py +++ b/graphene_django/tests/test_converter.py @@ -11,7 +11,14 @@ from graphene.relay import ConnectionField, Node from graphene.types.datetime import Date, DateTime, Time 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 ( convert_django_field, convert_django_field_with_choices, @@ -348,8 +355,13 @@ def test_should_postgres_hstore_convert_string(): 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(): + assert_conversion(PGJSONField, JSONString) + + +@pytest.mark.skipif(JSONField is MissingType, reason="JSONField should exist") +def test_should_json_convert_string(): assert_conversion(JSONField, JSONString) diff --git a/tox.ini b/tox.ini index 6744c5b..9086a55 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = py{27,35,36,37,38}-django{111,20,21,22,master}, - py{36,37,38}-django30, + py{36,37,38}-django{30,31}, black,flake8 [gh-actions] @@ -18,6 +18,7 @@ DJANGO = 2.1: django21 2.2: django22 3.0: django30 + 3.1: django31 master: djangomaster [testenv] @@ -33,6 +34,7 @@ deps = django21: Django>=2.1,<2.2 django22: Django>=2.2,<3.0 django30: Django>=3.0a1,<3.1 + django31: Django>=3.1,<3.2 djangomaster: https://github.com/django/django/archive/master.zip commands = {posargs:py.test --cov=graphene_django graphene_django examples} From bd553be10e6200c6558cc7dd91d7bc3743325d6e Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Wed, 12 Aug 2020 07:03:23 +0100 Subject: [PATCH 4/4] Fix JSONField import (#1021) --- graphene_django/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/compat.py b/graphene_django/compat.py index 6e5e769..8a2b933 100644 --- a/graphene_django/compat.py +++ b/graphene_django/compat.py @@ -16,6 +16,6 @@ except ImportError: try: # JSONField is only available from Django 3.1 - from django.contrib.fields import JSONField + from django.db.models import JSONField except ImportError: JSONField = MissingType