Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-graphene-django for
openSUSE:Factory checked in at 2023-01-26 13:58:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-graphene-django (Old)
and /work/SRC/openSUSE:Factory/.python-graphene-django.new.32243 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-graphene-django"
Thu Jan 26 13:58:39 2023 rev:8 rq:1061085 version:3.0.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-graphene-django/python-graphene-django.changes
2022-09-25 15:36:12.391753490 +0200
+++
/work/SRC/openSUSE:Factory/.python-graphene-django.new.32243/python-graphene-django.changes
2023-01-26 14:08:02.923480949 +0100
@@ -1,0 +2,17 @@
+Thu Jan 26 01:05:11 UTC 2023 - John Vandenberg <[email protected]>
+
+- Update to v3.0.0
+ * See https://github.com/graphql-python/graphene-django/releases/tag/v3.0.0
+- from v3.0.0b9
+ * fix: unit test for https://github.com/graphql-python/graphene/pull/1412
+ * Make errors in form mutation non nullable
+ * Fixes related to https://github.com/graphql-python/graphene/pull/1412
+ * Delay assignment of csrftoken in Graphiql
+ * Fix type hint for DjangoObjectTypeOptions.model
+ * Fix code examples in queries.rst
+ * Fixed graphql_relay deprecation warning
+ * Make instructions runnable without tweaking
+ * Update tutorial-relay.rst
+ * Add support to persist GraphQL headers in GraphiQL
+
+-------------------------------------------------------------------
Old:
----
graphene-django-3.0.0b8.tar.gz
New:
----
graphene-django-3.0.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-graphene-django.spec ++++++
--- /var/tmp/diff_new_pack.BGVAjc/_old 2023-01-26 14:08:03.351483470 +0100
+++ /var/tmp/diff_new_pack.BGVAjc/_new 2023-01-26 14:08:03.355483493 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-graphene-django
#
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-graphene-django
-Version: 3.0.0b8
+Version: 3.0.0
Release: 0
Summary: Graphene Django integration
License: MIT
@@ -51,6 +51,7 @@
BuildRequires: %{python_module graphql-relay}
BuildRequires: %{python_module promise >= 2.1}
BuildRequires: %{python_module psycopg2}
+BuildRequires: %{python_module pytest}
BuildRequires: %{python_module pytest-django >= 3.3.2}
BuildRequires: %{python_module pytz}
BuildRequires: %{python_module text-unidecode}
@@ -65,6 +66,8 @@
%patch0 -p1
sed -i 's/from mock import MagicMock/from unittest.mock import MagicMock/'
graphene_django/filter/tests/conftest.py
+sed -i 's/py\.test/pytest/g' graphene_django/tests/*.py
graphene_django/tests/issues/*.py graphene_django/*/tests/*.py
+
rm setup.cfg
sed -i '/pytest-runner/d' setup.py
@@ -90,6 +93,6 @@
%files %{python_files}
%doc README.rst README.md
%license LICENSE
-%{python_sitelib}/graphene[_-]django*
+%{python_sitelib}/graphene[_-]django*/
%changelog
++++++ graphene-django-3.0.0b8.tar.gz -> graphene-django-3.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/docs/queries.rst
new/graphene-django-3.0.0/docs/queries.rst
--- old/graphene-django-3.0.0b8/docs/queries.rst 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/docs/queries.rst 2022-09-26 14:08:32.000000000
+0200
@@ -151,7 +151,7 @@
Results in the following GraphQL schema definition:
-.. code::
+.. code:: graphql
type Pet {
id: ID!
@@ -178,7 +178,7 @@
fields = ("id", "kind",)
convert_choices_to_enum = False
-.. code::
+.. code:: graphql
type Pet {
id: ID!
@@ -313,7 +313,7 @@
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 @@
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 @@
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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/docs/settings.rst
new/graphene-django-3.0.0/docs/settings.rst
--- old/graphene-django-3.0.0b8/docs/settings.rst 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/docs/settings.rst 2022-09-26 14:08:32.000000000
+0200
@@ -189,7 +189,7 @@
``GRAPHIQL_HEADER_EDITOR_ENABLED``
----------------------
+----------------------------------
GraphiQL starting from version 1.0.0 allows setting custom headers in similar
fashion to query variables.
@@ -207,3 +207,36 @@
GRAPHENE = {
'GRAPHIQL_HEADER_EDITOR_ENABLED': True,
}
+
+
+``TESTING_ENDPOINT``
+--------------------
+
+Define the graphql endpoint url used for the `GraphQLTestCase` class.
+
+Default: ``/graphql``
+
+.. code:: python
+
+ GRAPHENE = {
+ 'TESTING_ENDPOINT': '/customEndpoint'
+ }
+
+
+``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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/docs/testing.rst
new/graphene-django-3.0.0/docs/testing.rst
--- old/graphene-django-3.0.0b8/docs/testing.rst 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/docs/testing.rst 2022-09-26 14:08:32.000000000
+0200
@@ -6,7 +6,8 @@
If you want to unittest your API calls derive your test case from the class
`GraphQLTestCase`.
-Your endpoint is set through the `GRAPHQL_URL` attribute on `GraphQLTestCase`.
The default endpoint is `GRAPHQL_URL = "/graphql/"`.
+The default endpoint for testing is `/graphql`. You can override this in the
`settings
<https://docs.graphene-python.org/projects/django/en/latest/settings/#testing-endpoint>`__.
+
Usage:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/docs/tutorial-plain.rst
new/graphene-django-3.0.0/docs/tutorial-plain.rst
--- old/graphene-django-3.0.0b8/docs/tutorial-plain.rst 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/docs/tutorial-plain.rst 2022-09-26
14:08:32.000000000 +0200
@@ -35,6 +35,7 @@
.. code:: bash
+ cd ..
python manage.py migrate
Let's create a few simple models...
@@ -77,6 +78,18 @@
"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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/graphene_django/__init__.py
new/graphene-django-3.0.0/graphene_django/__init__.py
--- old/graphene-django-3.0.0b8/graphene_django/__init__.py 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/__init__.py 2022-09-26
14:08:32.000000000 +0200
@@ -1,7 +1,7 @@
from .fields import DjangoConnectionField, DjangoListField
from .types import DjangoObjectType
-__version__ = "3.0.0b8"
+__version__ = "3.0.0"
__all__ = [
"__version__",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/graphene_django/converter.py
new/graphene-django-3.0.0/graphene_django/converter.py
--- old/graphene-django-3.0.0b8/graphene_django/converter.py 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/converter.py 2022-09-26
14:08:32.000000000 +0200
@@ -308,7 +308,24 @@
if not _type:
return
- return Field(
+ class CustomField(Field):
+ def wrap_resolve(self, parent_resolver):
+ """
+ Implements a custom resolver which go through the `get_node`
method to insure that
+ it goes through the `get_queryset` method of the
DjangoObjectType.
+ """
+ resolver = super().wrap_resolve(parent_resolver)
+
+ def custom_resolver(root, info, **args):
+ fk_obj = resolver(root, info, **args)
+ if fk_obj is None:
+ return None
+ else:
+ return _type.get_node(info, fk_obj.pk)
+
+ return custom_resolver
+
+ return CustomField(
_type,
description=get_django_field_description(field),
required=not field.null,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/graphene_django/fields.py
new/graphene-django-3.0.0/graphene_django/fields.py
--- old/graphene-django-3.0.0b8/graphene_django/fields.py 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/fields.py 2022-09-26
14:08:32.000000000 +0200
@@ -2,7 +2,7 @@
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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/forms/mutation.py
new/graphene-django-3.0.0/graphene_django/forms/mutation.py
--- old/graphene-django-3.0.0b8/graphene_django/forms/mutation.py
2022-09-23 10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/forms/mutation.py 2022-09-26
14:08:32.000000000 +0200
@@ -117,7 +117,7 @@
class Meta:
abstract = True
- errors = graphene.List(ErrorType)
+ errors = graphene.List(graphene.NonNull(ErrorType), required=True)
@classmethod
def __init_subclass_with_meta__(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/graphene_django/settings.py
new/graphene-django-3.0.0/graphene_django/settings.py
--- old/graphene-django-3.0.0b8/graphene_django/settings.py 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/settings.py 2022-09-26
14:08:32.000000000 +0200
@@ -41,7 +41,9 @@
# 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,
+ "TESTING_ENDPOINT": "/graphql",
}
if settings.DEBUG:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/static/graphene_django/graphiql.js
new/graphene-django-3.0.0/graphene_django/static/graphene_django/graphiql.js
---
old/graphene-django-3.0.0b8/graphene_django/static/graphene_django/graphiql.js
2022-09-23 10:38:11.000000000 +0200
+++
new/graphene-django-3.0.0/graphene_django/static/graphene_django/graphiql.js
2022-09-26 14:08:32.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/templates/graphene/graphiql.html
new/graphene-django-3.0.0/graphene_django/templates/graphene/graphiql.html
---
old/graphene-django-3.0.0b8/graphene_django/templates/graphene/graphiql.html
2022-09-23 10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/templates/graphene/graphiql.html
2022-09-26 14:08:32.000000000 +0200
@@ -46,6 +46,7 @@
subscriptionPath: "{{subscription_path}}",
{% endif %}
graphiqlHeaderEditorEnabled: {{
graphiql_header_editor_enabled|yesno:"true,false" }},
+ graphiqlShouldPersistHeaders: {{
graphiql_should_persist_headers|yesno:"true,false" }},
};
</script>
<script src="{% static 'graphene_django/graphiql.js' %}"></script>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/tests/models.py
new/graphene-django-3.0.0/graphene_django/tests/models.py
--- old/graphene-django-3.0.0b8/graphene_django/tests/models.py 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/tests/models.py 2022-09-26
14:08:32.000000000 +0200
@@ -13,6 +13,9 @@
class Pet(models.Model):
name = models.CharField(max_length=30)
age = models.PositiveIntegerField()
+ owner = models.ForeignKey(
+ "Person", on_delete=models.CASCADE, null=True, blank=True,
related_name="pets"
+ )
class FilmDetails(models.Model):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/tests/test_get_queryset.py
new/graphene-django-3.0.0/graphene_django/tests/test_get_queryset.py
--- old/graphene-django-3.0.0b8/graphene_django/tests/test_get_queryset.py
1970-01-01 01:00:00.000000000 +0100
+++ new/graphene-django-3.0.0/graphene_django/tests/test_get_queryset.py
2022-09-26 14:08:32.000000000 +0200
@@ -0,0 +1,361 @@
+import pytest
+
+import graphene
+from graphene.relay import Node
+
+from graphql_relay import to_global_id
+
+from ..fields import DjangoConnectionField
+from ..types import DjangoObjectType
+
+from .models import Article, Reporter
+
+
+class TestShouldCallGetQuerySetOnForeignKey:
+ """
+ Check that the get_queryset method is called in both forward and reversed
direction
+ of a foreignkey on types.
+ (see issue #1111)
+ """
+
+ @pytest.fixture(autouse=True)
+ def setup_schema(self):
+ class ReporterType(DjangoObjectType):
+ class Meta:
+ model = Reporter
+
+ @classmethod
+ def get_queryset(cls, queryset, info):
+ if info.context and info.context.get("admin"):
+ return queryset
+ raise Exception("Not authorized to access reporters.")
+
+ class ArticleType(DjangoObjectType):
+ class Meta:
+ model = Article
+
+ @classmethod
+ def get_queryset(cls, queryset, info):
+ return queryset.exclude(headline__startswith="Draft")
+
+ class Query(graphene.ObjectType):
+ reporter = graphene.Field(ReporterType,
id=graphene.ID(required=True))
+ article = graphene.Field(ArticleType,
id=graphene.ID(required=True))
+
+ def resolve_reporter(self, info, id):
+ return (
+ ReporterType.get_queryset(Reporter.objects, info)
+ .filter(id=id)
+ .last()
+ )
+
+ def resolve_article(self, info, id):
+ return (
+ ArticleType.get_queryset(Article.objects,
info).filter(id=id).last()
+ )
+
+ self.schema = graphene.Schema(query=Query)
+
+ self.reporter = Reporter.objects.create(first_name="Jane",
last_name="Doe")
+
+ self.articles = [
+ Article.objects.create(
+ headline="A fantastic article",
+ reporter=self.reporter,
+ editor=self.reporter,
+ ),
+ Article.objects.create(
+ headline="Draft: My next best seller",
+ reporter=self.reporter,
+ editor=self.reporter,
+ ),
+ ]
+
+ def test_get_queryset_called_on_field(self):
+ # If a user tries to access an article it is fine as long as it's not
a draft one
+ query = """
+ query getArticle($id: ID!) {
+ article(id: $id) {
+ headline
+ }
+ }
+ """
+ # Non-draft
+ result = self.schema.execute(query, variables={"id":
self.articles[0].id})
+ assert not result.errors
+ assert result.data["article"] == {
+ "headline": "A fantastic article",
+ }
+ # Draft
+ result = self.schema.execute(query, variables={"id":
self.articles[1].id})
+ assert not result.errors
+ assert result.data["article"] is None
+
+ # If a non admin user tries to access a reporter they should get our
authorization error
+ query = """
+ query getReporter($id: ID!) {
+ reporter(id: $id) {
+ firstName
+ }
+ }
+ """
+
+ result = self.schema.execute(query, variables={"id": self.reporter.id})
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Not authorized to access
reporters."
+
+ # An admin user should be able to get reporters
+ query = """
+ query getReporter($id: ID!) {
+ reporter(id: $id) {
+ firstName
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query,
+ variables={"id": self.reporter.id},
+ context_value={"admin": True},
+ )
+ assert not result.errors
+ assert result.data == {"reporter": {"firstName": "Jane"}}
+
+ def test_get_queryset_called_on_foreignkey(self):
+ # If a user tries to access a reporter through an article they should
get our authorization error
+ query = """
+ query getArticle($id: ID!) {
+ article(id: $id) {
+ headline
+ reporter {
+ firstName
+ }
+ }
+ }
+ """
+
+ result = self.schema.execute(query, variables={"id":
self.articles[0].id})
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Not authorized to access
reporters."
+
+ # An admin user should be able to get reporters through an article
+ query = """
+ query getArticle($id: ID!) {
+ article(id: $id) {
+ headline
+ reporter {
+ firstName
+ }
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query,
+ variables={"id": self.articles[0].id},
+ context_value={"admin": True},
+ )
+ assert not result.errors
+ assert result.data["article"] == {
+ "headline": "A fantastic article",
+ "reporter": {"firstName": "Jane"},
+ }
+
+ # An admin user should not be able to access draft article through a
reporter
+ query = """
+ query getReporter($id: ID!) {
+ reporter(id: $id) {
+ firstName
+ articles {
+ headline
+ }
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query,
+ variables={"id": self.reporter.id},
+ context_value={"admin": True},
+ )
+ assert not result.errors
+ assert result.data["reporter"] == {
+ "firstName": "Jane",
+ "articles": [{"headline": "A fantastic article"}],
+ }
+
+
+class TestShouldCallGetQuerySetOnForeignKeyNode:
+ """
+ Check that the get_queryset method is called in both forward and reversed
direction
+ of a foreignkey on types using a node interface.
+ (see issue #1111)
+ """
+
+ @pytest.fixture(autouse=True)
+ def setup_schema(self):
+ class ReporterType(DjangoObjectType):
+ class Meta:
+ model = Reporter
+ interfaces = (Node,)
+
+ @classmethod
+ def get_queryset(cls, queryset, info):
+ if info.context and info.context.get("admin"):
+ return queryset
+ raise Exception("Not authorized to access reporters.")
+
+ class ArticleType(DjangoObjectType):
+ class Meta:
+ model = Article
+ interfaces = (Node,)
+
+ @classmethod
+ def get_queryset(cls, queryset, info):
+ return queryset.exclude(headline__startswith="Draft")
+
+ class Query(graphene.ObjectType):
+ reporter = Node.Field(ReporterType)
+ article = Node.Field(ArticleType)
+
+ self.schema = graphene.Schema(query=Query)
+
+ self.reporter = Reporter.objects.create(first_name="Jane",
last_name="Doe")
+
+ self.articles = [
+ Article.objects.create(
+ headline="A fantastic article",
+ reporter=self.reporter,
+ editor=self.reporter,
+ ),
+ Article.objects.create(
+ headline="Draft: My next best seller",
+ reporter=self.reporter,
+ editor=self.reporter,
+ ),
+ ]
+
+ def test_get_queryset_called_on_node(self):
+ # If a user tries to access an article it is fine as long as it's not
a draft one
+ query = """
+ query getArticle($id: ID!) {
+ article(id: $id) {
+ headline
+ }
+ }
+ """
+ # Non-draft
+ result = self.schema.execute(
+ query, variables={"id": to_global_id("ArticleType",
self.articles[0].id)}
+ )
+ assert not result.errors
+ assert result.data["article"] == {
+ "headline": "A fantastic article",
+ }
+ # Draft
+ result = self.schema.execute(
+ query, variables={"id": to_global_id("ArticleType",
self.articles[1].id)}
+ )
+ assert not result.errors
+ assert result.data["article"] is None
+
+ # If a non admin user tries to access a reporter they should get our
authorization error
+ query = """
+ query getReporter($id: ID!) {
+ reporter(id: $id) {
+ firstName
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query, variables={"id": to_global_id("ReporterType",
self.reporter.id)}
+ )
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Not authorized to access
reporters."
+
+ # An admin user should be able to get reporters
+ query = """
+ query getReporter($id: ID!) {
+ reporter(id: $id) {
+ firstName
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query,
+ variables={"id": to_global_id("ReporterType", self.reporter.id)},
+ context_value={"admin": True},
+ )
+ assert not result.errors
+ assert result.data == {"reporter": {"firstName": "Jane"}}
+
+ def test_get_queryset_called_on_foreignkey(self):
+ # If a user tries to access a reporter through an article they should
get our authorization error
+ query = """
+ query getArticle($id: ID!) {
+ article(id: $id) {
+ headline
+ reporter {
+ firstName
+ }
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query, variables={"id": to_global_id("ArticleType",
self.articles[0].id)}
+ )
+ assert len(result.errors) == 1
+ assert result.errors[0].message == "Not authorized to access
reporters."
+
+ # An admin user should be able to get reporters through an article
+ query = """
+ query getArticle($id: ID!) {
+ article(id: $id) {
+ headline
+ reporter {
+ firstName
+ }
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query,
+ variables={"id": to_global_id("ArticleType", self.articles[0].id)},
+ context_value={"admin": True},
+ )
+ assert not result.errors
+ assert result.data["article"] == {
+ "headline": "A fantastic article",
+ "reporter": {"firstName": "Jane"},
+ }
+
+ # An admin user should not be able to access draft article through a
reporter
+ query = """
+ query getReporter($id: ID!) {
+ reporter(id: $id) {
+ firstName
+ articles {
+ edges {
+ node {
+ headline
+ }
+ }
+ }
+ }
+ }
+ """
+
+ result = self.schema.execute(
+ query,
+ variables={"id": to_global_id("ReporterType", self.reporter.id)},
+ context_value={"admin": True},
+ )
+ assert not result.errors
+ assert result.data["reporter"] == {
+ "firstName": "Jane",
+ "articles": {"edges": [{"node": {"headline": "A fantastic
article"}}]},
+ }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/tests/test_query.py
new/graphene-django-3.0.0/graphene_django/tests/test_query.py
--- old/graphene-django-3.0.0b8/graphene_django/tests/test_query.py
2022-09-23 10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/tests/test_query.py
2022-09-26 14:08:32.000000000 +0200
@@ -15,7 +15,7 @@
from ..fields import DjangoConnectionField
from ..types import DjangoObjectType
from ..utils import DJANGO_FILTER_INSTALLED
-from .models import Article, CNNReporter, Film, FilmDetails, Reporter
+from .models import Article, CNNReporter, Film, FilmDetails, Person, Pet,
Reporter
def test_should_query_only_fields():
@@ -251,8 +251,8 @@
def test_should_query_onetoone_fields():
- film = Film(id=1)
- film_details = FilmDetails(id=1, film=film)
+ film = Film.objects.create(id=1)
+ film_details = FilmDetails.objects.create(id=1, film=film)
class FilmNode(DjangoObjectType):
class Meta:
@@ -1697,3 +1697,67 @@
}
}
assert result.data == expected
+
+
+def test_should_query_nullable_foreign_key():
+ class PetType(DjangoObjectType):
+ class Meta:
+ model = Pet
+
+ class PersonType(DjangoObjectType):
+ class Meta:
+ model = Person
+
+ class Query(graphene.ObjectType):
+ pet = graphene.Field(PetType, name=graphene.String(required=True))
+ person = graphene.Field(PersonType,
name=graphene.String(required=True))
+
+ def resolve_pet(self, info, name):
+ return Pet.objects.filter(name=name).first()
+
+ def resolve_person(self, info, name):
+ return Person.objects.filter(name=name).first()
+
+ schema = graphene.Schema(query=Query)
+
+ person = Person.objects.create(name="Jane")
+ pets = [
+ Pet.objects.create(name="Stray dog", age=1),
+ Pet.objects.create(name="Jane's dog", owner=person, age=1),
+ ]
+
+ query_pet = """
+ query getPet($name: String!) {
+ pet(name: $name) {
+ owner {
+ name
+ }
+ }
+ }
+ """
+ result = schema.execute(query_pet, variables={"name": "Stray dog"})
+ assert not result.errors
+ assert result.data["pet"] == {
+ "owner": None,
+ }
+
+ result = schema.execute(query_pet, variables={"name": "Jane's dog"})
+ assert not result.errors
+ assert result.data["pet"] == {
+ "owner": {"name": "Jane"},
+ }
+
+ query_owner = """
+ query getOwner($name: String!) {
+ person(name: $name) {
+ pets {
+ name
+ }
+ }
+ }
+ """
+ result = schema.execute(query_owner, variables={"name": "Jane"})
+ assert not result.errors
+ assert result.data["person"] == {
+ "pets": [{"name": "Jane's dog"}],
+ }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/graphene_django/types.py
new/graphene-django-3.0.0/graphene_django/types.py
--- old/graphene-django-3.0.0b8/graphene_django/types.py 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/types.py 2022-09-26
14:08:32.000000000 +0200
@@ -122,7 +122,7 @@
class DjangoObjectTypeOptions(ObjectTypeOptions):
- model = None # type: Model
+ model = None # type: Type[Model]
registry = None # type: Registry
connection = None # type: Type[Connection]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/utils/testing.py
new/graphene-django-3.0.0/graphene_django/utils/testing.py
--- old/graphene-django-3.0.0b8/graphene_django/utils/testing.py
2022-09-23 10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/utils/testing.py 2022-09-26
14:08:32.000000000 +0200
@@ -3,6 +3,8 @@
from django.test import Client, TestCase, TransactionTestCase
+from graphene_django.settings import graphene_settings
+
DEFAULT_GRAPHQL_URL = "/graphql"
@@ -40,7 +42,7 @@
if client is None:
client = Client()
if not graphql_url:
- graphql_url = DEFAULT_GRAPHQL_URL
+ graphql_url = graphene_settings.TESTING_ENDPOINT
body = {"query": query}
if operation_name:
@@ -69,7 +71,7 @@
"""
# URL to graphql endpoint
- GRAPHQL_URL = DEFAULT_GRAPHQL_URL
+ GRAPHQL_URL = graphene_settings.TESTING_ENDPOINT
def query(
self, query, operation_name=None, input_data=None, variables=None,
headers=None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/graphene-django-3.0.0b8/graphene_django/utils/tests/test_testing.py
new/graphene-django-3.0.0/graphene_django/utils/tests/test_testing.py
--- old/graphene-django-3.0.0b8/graphene_django/utils/tests/test_testing.py
2022-09-23 10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/utils/tests/test_testing.py
2022-09-26 14:08:32.000000000 +0200
@@ -2,6 +2,7 @@
from .. import GraphQLTestCase
from ...tests.test_types import with_local_registry
+from ...settings import graphene_settings
from django.test import Client
@@ -43,3 +44,11 @@
with pytest.warns(PendingDeprecationWarning):
tc._client = Client()
+
+
+def test_graphql_test_case_imports_endpoint():
+ """
+ GraphQLTestCase class should import the default endpoint from settings file
+ """
+
+ assert GraphQLTestCase.GRAPHQL_URL == graphene_settings.TESTING_ENDPOINT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/graphene-django-3.0.0b8/graphene_django/views.py
new/graphene-django-3.0.0/graphene_django/views.py
--- old/graphene-django-3.0.0b8/graphene_django/views.py 2022-09-23
10:38:11.000000000 +0200
+++ new/graphene-django-3.0.0/graphene_django/views.py 2022-09-26
14:08:32.000000000 +0200
@@ -162,6 +162,7 @@
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: