diff --git a/docs/queries.rst b/docs/queries.rst index 1e1ba82..8b85d45 100644 --- a/docs/queries.rst +++ b/docs/queries.rst @@ -151,7 +151,7 @@ For example the following ``Model`` and ``DjangoObjectType``: Results in the following GraphQL schema definition: -.. code:: +.. code:: graphql type Pet { id: ID! @@ -178,7 +178,7 @@ You can disable this automatic conversion by setting fields = ("id", "kind",) convert_choices_to_enum = False -.. code:: +.. code:: graphql type Pet { id: ID! @@ -313,7 +313,7 @@ Additionally, Resolvers will receive **any arguments declared in the field defin bar=graphene.Int() ) - def resolve_question(root, info, foo, bar): + def resolve_question(root, info, foo=None, bar=None): # If `foo` or `bar` are declared in the GraphQL query they will be here, else None. return Question.objects.filter(foo=foo, bar=bar).first() @@ -336,12 +336,12 @@ of Django's ``HTTPRequest`` in your resolve methods, such as checking for authen class Query(graphene.ObjectType): questions = graphene.List(QuestionType) - def resolve_questions(root, info): - # See if a user is authenticated - if info.context.user.is_authenticated(): - return Question.objects.all() - else: - return Question.objects.none() + def resolve_questions(root, info): + # See if a user is authenticated + if info.context.user.is_authenticated(): + return Question.objects.all() + else: + return Question.objects.none() DjangoObjectTypes @@ -418,29 +418,29 @@ the core graphene pages for more information on customizing the Relay experience You can now execute queries like: -.. code:: python +.. code:: graphql { questions (first: 2, after: "YXJyYXljb25uZWN0aW9uOjEwNQ==") { pageInfo { - startCursor - endCursor - hasNextPage - hasPreviousPage + startCursor + endCursor + hasNextPage + hasPreviousPage } edges { - cursor - node { - id - question_text - } + cursor + node { + id + question_text + } } } } Which returns: -.. code:: python +.. code:: json { "data": { diff --git a/docs/settings.rst b/docs/settings.rst index ff1c05e..1984a15 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -207,3 +207,22 @@ Default: ``True`` GRAPHENE = { 'GRAPHIQL_HEADER_EDITOR_ENABLED': True, } + + +``GRAPHIQL_SHOULD_PERSIST_HEADERS`` +--------------------- + +Set to ``True`` if you want to persist GraphiQL headers after refreshing the page. + +This setting is passed to ``shouldPersistHeaders`` GraphiQL options, for details refer to GraphiQLDocs_. + +.. _GraphiQLDocs: https://github.com/graphql/graphiql/tree/main/packages/graphiql#options + + +Default: ``False`` + +.. code:: python + + GRAPHENE = { + 'GRAPHIQL_SHOULD_PERSIST_HEADERS': False, + } diff --git a/docs/tutorial-plain.rst b/docs/tutorial-plain.rst index 45927a5..43b6da9 100644 --- a/docs/tutorial-plain.rst +++ b/docs/tutorial-plain.rst @@ -35,6 +35,7 @@ Now sync your database for the first time: .. code:: bash + cd .. python manage.py migrate Let's create a few simple models... @@ -77,6 +78,18 @@ Add ingredients as INSTALLED_APPS: "cookbook.ingredients", ] +Make sure the app name in ``cookbook.ingredients.apps.IngredientsConfig`` is set to ``cookbook.ingredients``. + +.. code:: python + + # cookbook/ingredients/apps.py + + from django.apps import AppConfig + + + class IngredientsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'cookbook.ingredients' Don't forget to create & run migrations: diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 3c48595..05a7010 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -2,7 +2,7 @@ from functools import partial from django.db.models.query import QuerySet -from graphql_relay.connection.array_connection import ( +from graphql_relay import ( connection_from_array_slice, cursor_to_offset, get_offset_with_default, diff --git a/graphene_django/settings.py b/graphene_django/settings.py index 467c6a3..0fd70a7 100644 --- a/graphene_django/settings.py +++ b/graphene_django/settings.py @@ -41,6 +41,7 @@ DEFAULTS = { # This sets headerEditorEnabled GraphiQL option, for details go to # https://github.com/graphql/graphiql/tree/main/packages/graphiql#options "GRAPHIQL_HEADER_EDITOR_ENABLED": True, + "GRAPHIQL_SHOULD_PERSIST_HEADERS": False, "ATOMIC_MUTATIONS": False, } diff --git a/graphene_django/static/graphene_django/graphiql.js b/graphene_django/static/graphene_django/graphiql.js index ac010e8..f6be32c 100644 --- a/graphene_django/static/graphene_django/graphiql.js +++ b/graphene_django/static/graphene_django/graphiql.js @@ -10,14 +10,6 @@ history, location, ) { - // 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(); - } else { - csrftoken = document.querySelector("[name=csrfmiddlewaretoken]").value; - } // Collect the URL parameters var parameters = {}; @@ -68,9 +60,19 @@ var headers = opts.headers || {}; headers['Accept'] = headers['Accept'] || 'application/json'; headers['Content-Type'] = headers['Content-Type'] || 'application/json'; + + // 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(); + } else { + csrftoken = document.querySelector("[name=csrfmiddlewaretoken]").value; + } if (csrftoken) { headers['X-CSRFToken'] = csrftoken } + return fetch(fetchURL, { method: "post", headers: headers, @@ -176,6 +178,7 @@ onEditVariables: onEditVariables, onEditOperationName: onEditOperationName, headerEditorEnabled: GRAPHENE_SETTINGS.graphiqlHeaderEditorEnabled, + shouldPersistHeaders: GRAPHENE_SETTINGS.graphiqlShouldPersistHeaders, query: parameters.query, }; if (parameters.variables) { diff --git a/graphene_django/templates/graphene/graphiql.html b/graphene_django/templates/graphene/graphiql.html index cec4893..3685692 100644 --- a/graphene_django/templates/graphene/graphiql.html +++ b/graphene_django/templates/graphene/graphiql.html @@ -46,6 +46,7 @@ add "&raw" to the end of the URL within a browser. subscriptionPath: "{{subscription_path}}", {% endif %} graphiqlHeaderEditorEnabled: {{ graphiql_header_editor_enabled|yesno:"true,false" }}, + graphiqlShouldPersistHeaders: {{ graphiql_should_persist_headers|yesno:"true,false" }}, }; diff --git a/graphene_django/tests/test_get_queryset.py b/graphene_django/tests/test_get_queryset.py index b2647c3..91bdc70 100644 --- a/graphene_django/tests/test_get_queryset.py +++ b/graphene_django/tests/test_get_queryset.py @@ -114,7 +114,9 @@ class TestShouldCallGetQuerySetOnForeignKey: """ result = self.schema.execute( - query, variables={"id": self.reporter.id}, context_value={"admin": True}, + query, + variables={"id": self.reporter.id}, + context_value={"admin": True}, ) assert not result.errors assert result.data == {"reporter": {"firstName": "Jane"}} @@ -149,7 +151,9 @@ class TestShouldCallGetQuerySetOnForeignKey: """ result = self.schema.execute( - query, variables={"id": self.articles[0].id}, context_value={"admin": True}, + query, + variables={"id": self.articles[0].id}, + context_value={"admin": True}, ) assert not result.errors assert result.data["article"] == { @@ -170,7 +174,9 @@ class TestShouldCallGetQuerySetOnForeignKey: """ result = self.schema.execute( - query, variables={"id": self.reporter.id}, context_value={"admin": True}, + query, + variables={"id": self.reporter.id}, + context_value={"admin": True}, ) assert not result.errors assert result.data["reporter"] == { diff --git a/graphene_django/types.py b/graphene_django/types.py index c256f1d..0ebb7d3 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -122,7 +122,7 @@ def validate_fields(type_, model, fields, only_fields, exclude_fields): class DjangoObjectTypeOptions(ObjectTypeOptions): - model = None # type: Model + model = None # type: Type[Model] registry = None # type: Registry connection = None # type: Type[Connection] diff --git a/graphene_django/views.py b/graphene_django/views.py index f533f70..bf333a9 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -162,6 +162,7 @@ class GraphQLView(View): subscription_path=self.subscription_path, # GraphiQL headers tab, graphiql_header_editor_enabled=graphene_settings.GRAPHIQL_HEADER_EDITOR_ENABLED, + graphiql_should_persist_headers=graphene_settings.GRAPHIQL_SHOULD_PERSIST_HEADERS, ) if self.batch: diff --git a/setup.py b/setup.py index 3a46d24..d9aefef 100644 --- a/setup.py +++ b/setup.py @@ -59,8 +59,7 @@ setup( keywords="api graphql protocol rest relay graphene", packages=find_packages(exclude=["tests", "examples", "examples.*"]), install_requires=[ - # "graphene>=3.0,<4", - "graphene @ git+https://github.com/loft-orbital/graphene.git@loft-v3-1.0#egg=graphene", + "graphene>=3.0,<4", "graphql-core>=3.1.0,<4", "graphql-relay>=3.1.1,<4", "Django>=3.2",