From 9a1a954e35afce09e03856e823ed2d1ab4dfc2e6 Mon Sep 17 00:00:00 2001
From: Wei Lu <luwei.here@gmail.com>
Date: Wed, 9 Jun 2021 13:18:45 -0400
Subject: [PATCH 1/3] Only catch GraphQLException

which allows generic Exception through in case
UnforgivingExecutionContext is used
---
 graphene_django/views.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/graphene_django/views.py b/graphene_django/views.py
index c23b02081..e90d94fa3 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -339,7 +339,7 @@ def execute_graphql_request(
                 return result
 
             return self.schema.execute(**options)
-        except Exception as e:
+        except GraphQLError as e:
             return ExecutionResult(errors=[e])
 
     @classmethod

From 6928b3fb6c58655b23efd4d524ccfc9ce0bb695b Mon Sep 17 00:00:00 2001
From: Wei Lu <luwei.here@gmail.com>
Date: Tue, 4 Jan 2022 22:24:01 -0500
Subject: [PATCH 2/3] Convert multiple choice fields to list

---
 graphene_django/filter/utils.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/graphene_django/filter/utils.py b/graphene_django/filter/utils.py
index cd05a87c2..05256f967 100644
--- a/graphene_django/filter/utils.py
+++ b/graphene_django/filter/utils.py
@@ -86,8 +86,11 @@ def get_filtering_args_from_filterset(filterset_class, type):
                 form_field = form_field or filter_field.field
                 field_type = convert_form_field(form_field).get_type()
 
-            if isinstance(filter_field, ListFilter) or isinstance(
-                filter_field, RangeFilter
+            if (
+                isinstance(filter_field, ListFilter)
+                or isinstance(filter_field, RangeFilter)
+                or isinstance(form_field, forms.ModelMultipleChoiceField)
+                or isinstance(form_field, GlobalIDMultipleChoiceField)
             ):
                 # Replace InFilter/RangeFilter filters (`in`, `range`) argument type to be a list of
                 # the same type as the field. See comments in `replace_csv_filters` method for more details.

From 0b144e6ef86424ebeedaef8cb1ea5e0b98ea20ef Mon Sep 17 00:00:00 2001
From: Wei Lu <luwei.here@gmail.com>
Date: Thu, 7 Nov 2024 16:13:22 -0500
Subject: [PATCH 3/3] Revert "Merge branch 'main' into main"

This reverts commit eeaa2234b4a48a72381aaa4dc4496d3d33c3b23f, reversing
changes made to 6928b3fb6c58655b23efd4d524ccfc9ce0bb695b.
---
 .github/ISSUE_TEMPLATE/bug_report.md          |   6 +-
 .github/workflows/deploy.yml                  |  10 +-
 .github/workflows/lint.yml                    |  12 +-
 .github/workflows/tests.yml                   |  14 +-
 .pre-commit-config.yaml                       |  30 --
 CONTRIBUTING.md                               |   2 +-
 MANIFEST.in                                   |   2 +-
 Makefile                                      |  20 +-
 README.md                                     |   2 +-
 docs/authorization.rst                        |   2 +-
 docs/filtering.rst                            |  10 +-
 docs/queries.rst                              |  40 +--
 docs/schema.py                                |  79 ++---
 docs/settings.rst                             |  35 +-
 docs/testing.rst                              |  13 +-
 docs/tutorial-plain.rst                       |  13 -
 docs/tutorial-relay.rst                       |   2 +-
 examples/cookbook-plain/README.md             |   2 +-
 .../ingredients/fixtures/ingredients.json     |  53 +--
 .../ingredients/migrations/0001_initial.py    |  45 +--
 .../migrations/0002_auto_20161104_0050.py     |   9 +-
 .../migrations/0003_auto_20181018_1746.py     |   7 +-
 .../cookbook/ingredients/schema.py            |   2 +-
 .../recipes/migrations/0001_initial.py        |  61 +---
 .../migrations/0002_auto_20161104_0106.py     |  25 +-
 .../migrations/0003_auto_20181018_1728.py     |  17 +-
 .../cookbook-plain/cookbook/recipes/schema.py |   2 +-
 examples/cookbook/README.md                   |   4 +-
 .../ingredients/fixtures/ingredients.json     |  53 +--
 .../ingredients/migrations/0001_initial.py    |  45 +--
 .../migrations/0002_auto_20161104_0050.py     |   9 +-
 .../cookbook/cookbook/ingredients/schema.py   |   2 +-
 .../recipes/migrations/0001_initial.py        |  61 +---
 .../migrations/0002_auto_20161104_0106.py     |  25 +-
 examples/cookbook/cookbook/recipes/schema.py  |   2 +-
 examples/cookbook/dummy_data.json             | 303 +-----------------
 examples/starwars/models.py                   |   2 +
 graphene_django/__init__.py                   |   2 +-
 graphene_django/compat.py                     |   2 +-
 graphene_django/converter.py                  |  39 +--
 graphene_django/debug/exception/formating.py  |   2 +-
 graphene_django/debug/middleware.py           |  42 +--
 graphene_django/debug/sql/tracking.py         |   7 +-
 graphene_django/debug/tests/test_query.py     |   2 +-
 graphene_django/fields.py                     |  53 ++-
 graphene_django/filter/fields.py              |   8 +-
 .../filter/filters/array_filter.py            |   2 +-
 .../filter/filters/global_id_filter.py        |   6 +-
 graphene_django/filter/filters/list_filter.py |   2 +-
 .../filter/filters/typed_filter.py            |   2 +-
 graphene_django/filter/filterset.py           |  14 +-
 graphene_django/filter/tests/conftest.py      |  11 +-
 .../tests/test_array_field_exact_filter.py    |   5 +-
 .../filter/tests/test_enum_filtering.py       |  19 +-
 graphene_django/filter/tests/test_fields.py   |  36 +--
 .../filter/tests/test_in_filter.py            |  22 +-
 .../filter/tests/test_typed_filter.py         |  14 +-
 graphene_django/filter/utils.py               |   4 +-
 graphene_django/forms/converter.py            |  22 +-
 graphene_django/forms/mutation.py             |  12 +-
 graphene_django/forms/tests/test_converter.py |   7 +-
 graphene_django/forms/tests/test_mutation.py  |   2 +-
 .../management/commands/graphql_schema.py     |  12 +-
 graphene_django/registry.py                   |   2 +-
 graphene_django/rest_framework/mutation.py    |   3 +-
 .../rest_framework/serializer_converter.py    |   8 +-
 .../tests/test_field_converter.py             |   6 +-
 .../rest_framework/tests/test_mutation.py     |   2 +-
 graphene_django/settings.py                   |   7 +-
 .../static/graphene_django/graphiql.js        | 165 ++++++----
 .../templates/graphene/graphiql.html          |   6 +-
 graphene_django/tests/issues/test_520.py      |   4 +-
 graphene_django/tests/models.py               |  13 +-
 graphene_django/tests/schema_view.py          |   1 +
 graphene_django/tests/test_command.py         |   5 +-
 graphene_django/tests/test_converter.py       |   7 +-
 graphene_django/tests/test_fields.py          | 153 +--------
 graphene_django/tests/test_forms.py           |   2 +-
 graphene_django/tests/test_get_queryset.py    | 235 --------------
 graphene_django/tests/test_query.py           | 224 ++-----------
 graphene_django/tests/test_schema.py          |   2 +-
 graphene_django/tests/test_types.py           |  26 +-
 graphene_django/tests/test_utils.py           |  10 +-
 graphene_django/tests/test_views.py           |   9 +-
 graphene_django/tests/urls.py                 |   6 +-
 graphene_django/tests/urls_inherited.py       |   4 +-
 graphene_django/tests/urls_pretty.py          |   4 +-
 graphene_django/types.py                      |  14 +-
 graphene_django/utils/testing.py              |  16 +-
 .../utils/tests/test_str_converters.py        |   2 +-
 graphene_django/utils/tests/test_testing.py   |   9 -
 graphene_django/views.py                      |  21 +-
 setup.cfg                                     |   2 +-
 setup.py                                      |  28 +-
 tox.ini                                       |  38 ++-
 95 files changed, 633 insertions(+), 1771 deletions(-)
 delete mode 100644 .pre-commit-config.yaml
 delete mode 100644 graphene_django/tests/test_get_queryset.py

diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 26d84aa48..2c933d734 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -27,8 +27,8 @@ a github repo, https://repl.it or similar (you can use this template as a starti
 
 
 * **Please tell us about your environment:**
-
-  - Version:
-  - Platform:
+  
+  - Version: 
+  - Platform: 
 
 * **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow)
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 139c6f683..07c0766f8 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -10,17 +10,17 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Python 3.11
-      uses: actions/setup-python@v4
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.9
+      uses: actions/setup-python@v2
       with:
-        python-version: '3.11'
+        python-version: 3.9
     - name: Build wheel and source tarball
       run: |
         pip install wheel
         python setup.py sdist bdist_wheel
     - name: Publish a Python distribution to PyPI
-      uses: pypa/gh-action-pypi-publish@v1.8.6
+      uses: pypa/gh-action-pypi-publish@v1.1.0
       with:
         user: __token__
         password: ${{ secrets.pypi_password }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index bfafa67c2..559326c41 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -7,16 +7,16 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Python 3.11
-      uses: actions/setup-python@v4
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.9
+      uses: actions/setup-python@v2
       with:
-        python-version: '3.11'
+        python-version: 3.9
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
         pip install tox
-    - name: Run pre-commit 💅
+    - name: Run lint 💅
       run: tox
       env:
-        TOXENV: pre-commit
+        TOXENV: flake8
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 2c5b7550c..2dbf8229f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,17 +8,13 @@ jobs:
     strategy:
       max-parallel: 4
       matrix:
-        django: ["3.2", "4.0", "4.1"]
-        python-version: ["3.8", "3.9", "3.10"]
-        include:
-          - django: "3.2"
-            python-version: "3.7"
-          - django: "4.1"
-            python-version: "3.11"
+        django: ["2.2", "3.0", "3.1", "3.2"]
+        python-version: ["3.6", "3.7", "3.8", "3.9"]
+
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v2
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index 9214d35eb..000000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-default_language_version:
-  python: python3.11
-repos:
--   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.4.0
-    hooks:
-    -   id: check-merge-conflict
-    -   id: check-json
-    -   id: check-yaml
-    -   id: debug-statements
-    -   id: end-of-file-fixer
-        exclude: ^docs/.*$
-    -   id: pretty-format-json
-        args:
-        - --autofix
-    -   id: trailing-whitespace
-        exclude: README.md
--   repo: https://github.com/asottile/pyupgrade
-    rev: v3.3.2
-    hooks:
-    -   id: pyupgrade
-        args: [--py37-plus]
--   repo: https://github.com/psf/black
-    rev: 23.3.0
-    hooks:
-    -   id: black
--   repo: https://github.com/PyCQA/flake8
-    rev: 6.0.0
-    hooks:
-    -   id: flake8
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6a226ab3a..f9428e978 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -59,4 +59,4 @@ Then to produce a HTML version of the documentation:
 
 ```sh
 make html
-```
+```
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index 1ede730c8..045af08f9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,4 +3,4 @@ recursive-include graphene_django/templates *
 recursive-include graphene_django/static *
 
 include examples/cookbook/cookbook/ingredients/fixtures/ingredients.json
-include examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json
+include examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 29c412bcc..b850ae899 100644
--- a/Makefile
+++ b/Makefile
@@ -1,22 +1,22 @@
-.PHONY: help
-help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
-
 .PHONY: dev-setup ## Install development dependencies
 dev-setup:
 	pip install -e ".[dev]"
-	python -m pre_commit install
 
-.PHONY: tests ## Run unit tests
+.PHONY: install-dev
+install-dev: dev-setup  # Alias install-dev -> dev-setup
+
+.PHONY: tests
 tests:
 	py.test graphene_django --cov=graphene_django -vv
 
-.PHONY: format ## Format code
+.PHONY: test
+test: tests  # Alias test -> tests
+
+.PHONY: format
 format:
-	black graphene_django examples setup.py
+	black --exclude "/migrations/" graphene_django examples setup.py
 
-.PHONY: lint ## Lint code
+.PHONY: lint
 lint:
 	flake8 graphene_django examples
 
diff --git a/README.md b/README.md
index 6f06ccc2b..5045e787a 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ from graphene_django.views import GraphQLView
 
 urlpatterns = [
     # ...
-    path('graphql/', GraphQLView.as_view(graphiql=True)),
+    path('graphql', GraphQLView.as_view(graphiql=True)),
 ]
 ```
 
diff --git a/docs/authorization.rst b/docs/authorization.rst
index bc88cdac5..39305f6b1 100644
--- a/docs/authorization.rst
+++ b/docs/authorization.rst
@@ -198,7 +198,7 @@ For Django 2.2 and above:
 
     urlpatterns = [
       # some other urls
-      path('graphql/', PrivateGraphQLView.as_view(graphiql=True, schema=schema)),
+      path('graphql', PrivateGraphQLView.as_view(graphiql=True, schema=schema)),
     ]
 
 .. _LoginRequiredMixin: https://docs.djangoproject.com/en/dev/topics/auth/default/#the-loginrequired-mixin
diff --git a/docs/filtering.rst b/docs/filtering.rst
index 95576a091..934bad6fb 100644
--- a/docs/filtering.rst
+++ b/docs/filtering.rst
@@ -2,8 +2,8 @@ Filtering
 =========
 
 Graphene integrates with
-`django-filter <https://django-filter.readthedocs.io/en/stable/>`__ to provide filtering of results.
-See the `usage documentation <https://django-filter.readthedocs.io/en/stable/guide/usage.html#the-filter>`__
+`django-filter <https://django-filter.readthedocs.io/en/master/>`__ to provide filtering of results.
+See the `usage documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__
 for details on the format for ``filter_fields``.
 
 This filtering is automatically available when implementing a ``relay.Node``.
@@ -26,7 +26,7 @@ After installing ``django-filter`` you'll need to add the application in the ``s
     ]
 
 Note: The techniques below are demoed in the `cookbook example
-app <https://github.com/graphql-python/graphene-django/tree/main/examples/cookbook>`__.
+app <https://github.com/graphql-python/graphene-django/tree/master/examples/cookbook>`__.
 
 Filterable fields
 -----------------
@@ -34,7 +34,7 @@ Filterable fields
 The ``filter_fields`` parameter is used to specify the fields which can
 be filtered upon. The value specified here is passed directly to
 ``django-filter``, so see the `filtering
-documentation <https://django-filter.readthedocs.io/en/main/guide/usage.html#the-filter>`__
+documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__
 for full details on the range of options available.
 
 For example:
@@ -192,7 +192,7 @@ in unison  with the ``filter_fields`` parameter:
         all_animals = DjangoFilterConnectionField(AnimalNode)
 
 
-The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/main/guide/usage.html#request-based-filtering>`__
+The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/master/guide/usage.html#request-based-filtering>`__
 in a ``django_filters.FilterSet`` instance. You can use this to customize your
 filters to be context-dependent. We could modify the ``AnimalFilter`` above to
 pre-filter animals owned by the authenticated user (set in ``context.user``).
diff --git a/docs/queries.rst b/docs/queries.rst
index 8b85d456c..1e1ba821e 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:: graphql
+.. code::
 
    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:: graphql
+.. code::
 
   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=None, bar=None):
+        def resolve_question(root, info, foo, bar):
             # 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:: graphql
+.. code:: python
 
     {
         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:: json
+.. code:: python
 
     {
         "data": {
diff --git a/docs/schema.py b/docs/schema.py
index 058a58723..1de92edf1 100644
--- a/docs/schema.py
+++ b/docs/schema.py
@@ -1,57 +1,60 @@
-import graphene
+  import graphene
 
-from graphene_django.types import DjangoObjectType
+  from graphene_django.types import DjangoObjectType
 
-from cookbook.ingredients.models import Category, Ingredient
+  from cookbook.ingredients.models import Category, Ingredient
 
 
-class CategoryType(DjangoObjectType):
-    class Meta:
-        model = Category
-        fields = "__all__"
+  class CategoryType(DjangoObjectType):
+      class Meta:
+          model = Category
+          fields = '__all__'
 
 
-class IngredientType(DjangoObjectType):
-    class Meta:
-        model = Ingredient
-        fields = "__all__"
+  class IngredientType(DjangoObjectType):
+      class Meta:
+          model = Ingredient
+          fields = '__all__'
 
 
-class Query:
-    category = graphene.Field(CategoryType, id=graphene.Int(), name=graphene.String())
-    all_categories = graphene.List(CategoryType)
+  class Query(object):
+      category = graphene.Field(CategoryType,
+                                id=graphene.Int(),
+                                name=graphene.String())
+      all_categories = graphene.List(CategoryType)
 
-    ingredient = graphene.Field(
-        IngredientType, id=graphene.Int(), name=graphene.String()
-    )
-    all_ingredients = graphene.List(IngredientType)
 
-    def resolve_all_categories(self, info, **kwargs):
-        return Category.objects.all()
+      ingredient = graphene.Field(IngredientType,
+                                  id=graphene.Int(),
+                                  name=graphene.String())
+      all_ingredients = graphene.List(IngredientType)
 
-    def resolve_all_ingredients(self, info, **kwargs):
-        return Ingredient.objects.all()
+      def resolve_all_categories(self, info, **kwargs):
+          return Category.objects.all()
 
-    def resolve_category(self, info, **kwargs):
-        id = kwargs.get("id")
-        name = kwargs.get("name")
+      def resolve_all_ingredients(self, info, **kwargs):
+          return Ingredient.objects.all()
 
-        if id is not None:
-            return Category.objects.get(pk=id)
+      def resolve_category(self, info, **kwargs):
+          id = kwargs.get('id')
+          name = kwargs.get('name')
 
-        if name is not None:
-            return Category.objects.get(name=name)
+          if id is not None:
+              return Category.objects.get(pk=id)
 
-        return None
+          if name is not None:
+              return Category.objects.get(name=name)
 
-    def resolve_ingredient(self, info, **kwargs):
-        id = kwargs.get("id")
-        name = kwargs.get("name")
+          return None
 
-        if id is not None:
-            return Ingredient.objects.get(pk=id)
+      def resolve_ingredient(self, info, **kwargs):
+          id = kwargs.get('id')
+          name = kwargs.get('name')
 
-        if name is not None:
-            return Ingredient.objects.get(name=name)
+          if id is not None:
+              return Ingredient.objects.get(pk=id)
 
-        return None
+          if name is not None:
+              return Ingredient.objects.get(name=name)
+
+          return None
\ No newline at end of file
diff --git a/docs/settings.rst b/docs/settings.rst
index 5bffd08f9..ff1c05e3b 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -189,7 +189,7 @@ Default: ``None``
 
 
 ``GRAPHIQL_HEADER_EDITOR_ENABLED``
-----------------------------------
+---------------------
 
 GraphiQL starting from version 1.0.0 allows setting custom headers in similar fashion to query variables.
 
@@ -207,36 +207,3 @@ Default: ``True``
    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 --git a/docs/testing.rst b/docs/testing.rst
index 1b3235218..65b6f64a4 100644
--- a/docs/testing.rst
+++ b/docs/testing.rst
@@ -6,8 +6,7 @@ Using unittest
 
 If you want to unittest your API calls derive your test case from the class `GraphQLTestCase`.
 
-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>`__.
-
+Your endpoint is set through the `GRAPHQL_URL` attribute on `GraphQLTestCase`. The default endpoint is `GRAPHQL_URL = "/graphql/"`.
 
 Usage:
 
@@ -28,7 +27,7 @@ Usage:
                     }
                 }
                 ''',
-                operation_name='myModel'
+                op_name='myModel'
             )
 
             content = json.loads(response.content)
@@ -49,7 +48,7 @@ Usage:
                     }
                 }
                 ''',
-                operation_name='myModel',
+                op_name='myModel',
                 variables={'id': 1}
             )
 
@@ -73,7 +72,7 @@ Usage:
                     }
                 }
                 ''',
-                operation_name='myMutation',
+                op_name='myMutation',
                 input_data={'my_field': 'foo', 'other_field': 'bar'}
             )
 
@@ -108,7 +107,7 @@ Usage:
                     }
                 }
                 ''',
-                operation_name='myMutation',
+                op_name='myMutation',
                 input_data={'my_field': 'foo', 'other_field': 'bar'}
             )
 
@@ -148,7 +147,7 @@ To use pytest define a simple fixture using the query helper below
                     }
                 }
                 ''',
-                operation_name='myModel'
+                op_name='myModel'
             )
 
             content = json.loads(response.content)
diff --git a/docs/tutorial-plain.rst b/docs/tutorial-plain.rst
index 9073c82e4..45927a510 100644
--- a/docs/tutorial-plain.rst
+++ b/docs/tutorial-plain.rst
@@ -35,7 +35,6 @@ Now sync your database for the first time:
 
 .. code:: bash
 
-    cd ..
     python manage.py migrate
 
 Let's create a few simple models...
@@ -78,18 +77,6 @@ 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/docs/tutorial-relay.rst b/docs/tutorial-relay.rst
index a27e2555c..3de902227 100644
--- a/docs/tutorial-relay.rst
+++ b/docs/tutorial-relay.rst
@@ -151,7 +151,7 @@ Create ``cookbook/ingredients/schema.py`` and type the following:
             interfaces = (relay.Node, )
 
 
-    class Query(ObjectType):
+    class Query(graphene.ObjectType):
         category = relay.Node.Field(CategoryNode)
         all_categories = DjangoFilterConnectionField(CategoryNode)
 
diff --git a/examples/cookbook-plain/README.md b/examples/cookbook-plain/README.md
index dcd242087..0ec906b00 100644
--- a/examples/cookbook-plain/README.md
+++ b/examples/cookbook-plain/README.md
@@ -14,7 +14,7 @@ whole Graphene repository:
 ```bash
 # Get the example project code
 git clone https://github.com/graphql-python/graphene-django.git
-cd graphene-django/examples/cookbook-plain
+cd graphene-django/examples/cookbook
 ```
 
 It is good idea (but not required) to create a virtual environment
diff --git a/examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json b/examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json
index 213bd8b74..8625d3c70 100644
--- a/examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json
+++ b/examples/cookbook-plain/cookbook/ingredients/fixtures/ingredients.json
@@ -1,52 +1 @@
-[
-  {
-    "fields": {
-      "name": "Dairy"
-    },
-    "model": "ingredients.category",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "name": "Meat"
-    },
-    "model": "ingredients.category",
-    "pk": 2
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Eggs",
-      "notes": "Good old eggs"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Milk",
-      "notes": "Comes from a cow"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 2
-  },
-  {
-    "fields": {
-      "category": 2,
-      "name": "Beef",
-      "notes": "Much like milk, this comes from a cow"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 3
-  },
-  {
-    "fields": {
-      "category": 2,
-      "name": "Chicken",
-      "notes": "Definitely doesn't come from a cow"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 4
-  }
-]
+[{"model": "ingredients.category", "pk": 1, "fields": {"name": "Dairy"}}, {"model": "ingredients.category", "pk": 2, "fields": {"name": "Meat"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Eggs", "notes": "Good old eggs", "category": 1}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Milk", "notes": "Comes from a cow", "category": 1}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Beef", "notes": "Much like milk, this comes from a cow", "category": 2}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Chicken", "notes": "Definitely doesn't come from a cow", "category": 2}}]
\ No newline at end of file
diff --git a/examples/cookbook-plain/cookbook/ingredients/migrations/0001_initial.py b/examples/cookbook-plain/cookbook/ingredients/migrations/0001_initial.py
index 23d71e87b..04949239f 100644
--- a/examples/cookbook-plain/cookbook/ingredients/migrations/0001_initial.py
+++ b/examples/cookbook-plain/cookbook/ingredients/migrations/0001_initial.py
@@ -1,52 +1,33 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2015-12-04 18:15
+from __future__ import unicode_literals
 
 import django.db.models.deletion
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     initial = True
 
-    dependencies = []
+    dependencies = [
+    ]
 
     operations = [
         migrations.CreateModel(
-            name="Category",
+            name='Category',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=100)),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
             ],
         ),
         migrations.CreateModel(
-            name="Ingredient",
+            name='Ingredient',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=100)),
-                ("notes", models.TextField()),
-                (
-                    "category",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        related_name="ingredients",
-                        to="ingredients.Category",
-                    ),
-                ),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+                ('notes', models.TextField()),
+                ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ingredients', to='ingredients.Category')),
             ],
         ),
     ]
diff --git a/examples/cookbook-plain/cookbook/ingredients/migrations/0002_auto_20161104_0050.py b/examples/cookbook-plain/cookbook/ingredients/migrations/0002_auto_20161104_0050.py
index 5f9e7a06a..359d4fc4c 100644
--- a/examples/cookbook-plain/cookbook/ingredients/migrations/0002_auto_20161104_0050.py
+++ b/examples/cookbook-plain/cookbook/ingredients/migrations/0002_auto_20161104_0050.py
@@ -1,17 +1,20 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2016-11-04 00:50
+from __future__ import unicode_literals
 
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     dependencies = [
-        ("ingredients", "0001_initial"),
+        ('ingredients', '0001_initial'),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name="ingredient",
-            name="notes",
+            model_name='ingredient',
+            name='notes',
             field=models.TextField(blank=True, null=True),
         ),
     ]
diff --git a/examples/cookbook-plain/cookbook/ingredients/migrations/0003_auto_20181018_1746.py b/examples/cookbook-plain/cookbook/ingredients/migrations/0003_auto_20181018_1746.py
index e823a2e30..184e79e4f 100644
--- a/examples/cookbook-plain/cookbook/ingredients/migrations/0003_auto_20181018_1746.py
+++ b/examples/cookbook-plain/cookbook/ingredients/migrations/0003_auto_20181018_1746.py
@@ -4,13 +4,14 @@
 
 
 class Migration(migrations.Migration):
+
     dependencies = [
-        ("ingredients", "0002_auto_20161104_0050"),
+        ('ingredients', '0002_auto_20161104_0050'),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name="category",
-            options={"verbose_name_plural": "Categories"},
+            name='category',
+            options={'verbose_name_plural': 'Categories'},
         ),
     ]
diff --git a/examples/cookbook-plain/cookbook/ingredients/schema.py b/examples/cookbook-plain/cookbook/ingredients/schema.py
index b8de8f9ed..24a5e9530 100644
--- a/examples/cookbook-plain/cookbook/ingredients/schema.py
+++ b/examples/cookbook-plain/cookbook/ingredients/schema.py
@@ -16,7 +16,7 @@ class Meta:
         fields = "__all__"
 
 
-class Query:
+class Query(object):
     category = graphene.Field(CategoryType, id=graphene.Int(), name=graphene.String())
     all_categories = graphene.List(CategoryType)
 
diff --git a/examples/cookbook-plain/cookbook/recipes/migrations/0001_initial.py b/examples/cookbook-plain/cookbook/recipes/migrations/0001_initial.py
index c41514778..338c71a1b 100644
--- a/examples/cookbook-plain/cookbook/recipes/migrations/0001_initial.py
+++ b/examples/cookbook-plain/cookbook/recipes/migrations/0001_initial.py
@@ -1,69 +1,36 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2015-12-04 18:20
+from __future__ import unicode_literals
 
 import django.db.models.deletion
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     initial = True
 
     dependencies = [
-        ("ingredients", "0001_initial"),
+        ('ingredients', '0001_initial'),
     ]
 
     operations = [
         migrations.CreateModel(
-            name="Recipe",
+            name='Recipe',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("title", models.CharField(max_length=100)),
-                ("instructions", models.TextField()),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=100)),
+                ('instructions', models.TextField()),
             ],
         ),
         migrations.CreateModel(
-            name="RecipeIngredient",
+            name='RecipeIngredient',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("amount", models.FloatField()),
-                (
-                    "unit",
-                    models.CharField(
-                        choices=[("kg", "Kilograms"), ("l", "Litres"), ("", "Units")],
-                        max_length=20,
-                    ),
-                ),
-                (
-                    "ingredient",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        related_name="used_by",
-                        to="ingredients.Ingredient",
-                    ),
-                ),
-                (
-                    "recipes",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        related_name="amounts",
-                        to="recipes.Recipe",
-                    ),
-                ),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('amount', models.FloatField()),
+                ('unit', models.CharField(choices=[('kg', 'Kilograms'), ('l', 'Litres'), ('', 'Units')], max_length=20)),
+                ('ingredient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to='ingredients.Ingredient')),
+                ('recipes', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='amounts', to='recipes.Recipe')),
             ],
         ),
     ]
diff --git a/examples/cookbook-plain/cookbook/recipes/migrations/0002_auto_20161104_0106.py b/examples/cookbook-plain/cookbook/recipes/migrations/0002_auto_20161104_0106.py
index f38bb697b..f13539265 100644
--- a/examples/cookbook-plain/cookbook/recipes/migrations/0002_auto_20161104_0106.py
+++ b/examples/cookbook-plain/cookbook/recipes/migrations/0002_auto_20161104_0106.py
@@ -1,30 +1,25 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2016-11-04 01:06
+from __future__ import unicode_literals
 
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     dependencies = [
-        ("recipes", "0001_initial"),
+        ('recipes', '0001_initial'),
     ]
 
     operations = [
         migrations.RenameField(
-            model_name="recipeingredient",
-            old_name="recipes",
-            new_name="recipe",
+            model_name='recipeingredient',
+            old_name='recipes',
+            new_name='recipe',
         ),
         migrations.AlterField(
-            model_name="recipeingredient",
-            name="unit",
-            field=models.CharField(
-                choices=[
-                    (b"unit", b"Units"),
-                    (b"kg", b"Kilograms"),
-                    (b"l", b"Litres"),
-                    (b"st", b"Shots"),
-                ],
-                max_length=20,
-            ),
+            model_name='recipeingredient',
+            name='unit',
+            field=models.CharField(choices=[(b'unit', b'Units'), (b'kg', b'Kilograms'), (b'l', b'Litres'), (b'st', b'Shots')], max_length=20),
         ),
     ]
diff --git a/examples/cookbook-plain/cookbook/recipes/migrations/0003_auto_20181018_1728.py b/examples/cookbook-plain/cookbook/recipes/migrations/0003_auto_20181018_1728.py
index dacdb3041..7a8df493b 100644
--- a/examples/cookbook-plain/cookbook/recipes/migrations/0003_auto_20181018_1728.py
+++ b/examples/cookbook-plain/cookbook/recipes/migrations/0003_auto_20181018_1728.py
@@ -4,22 +4,15 @@
 
 
 class Migration(migrations.Migration):
+
     dependencies = [
-        ("recipes", "0002_auto_20161104_0106"),
+        ('recipes', '0002_auto_20161104_0106'),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name="recipeingredient",
-            name="unit",
-            field=models.CharField(
-                choices=[
-                    ("unit", "Units"),
-                    ("kg", "Kilograms"),
-                    ("l", "Litres"),
-                    ("st", "Shots"),
-                ],
-                max_length=20,
-            ),
+            model_name='recipeingredient',
+            name='unit',
+            field=models.CharField(choices=[('unit', 'Units'), ('kg', 'Kilograms'), ('l', 'Litres'), ('st', 'Shots')], max_length=20),
         ),
     ]
diff --git a/examples/cookbook-plain/cookbook/recipes/schema.py b/examples/cookbook-plain/cookbook/recipes/schema.py
index 7f40d519e..aa7fd2df0 100644
--- a/examples/cookbook-plain/cookbook/recipes/schema.py
+++ b/examples/cookbook-plain/cookbook/recipes/schema.py
@@ -16,7 +16,7 @@ class Meta:
         fields = "__all__"
 
 
-class Query:
+class Query(object):
     recipe = graphene.Field(RecipeType, id=graphene.Int(), title=graphene.String())
     all_recipes = graphene.List(RecipeType)
 
diff --git a/examples/cookbook/README.md b/examples/cookbook/README.md
index 098b11979..0ec906b00 100644
--- a/examples/cookbook/README.md
+++ b/examples/cookbook/README.md
@@ -1,4 +1,4 @@
-Cookbook Example (Relay) Django Project
+Cookbook Example Django Project
 ===============================
 
 This example project demos integration between Graphene and Django.
@@ -60,5 +60,5 @@ Now you should be ready to start the server:
 Now head on over to
 [http://127.0.0.1:8000/graphql](http://127.0.0.1:8000/graphql)
 and run some queries!
-(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial-relay/#testing-our-graphql-schema)
+(See the [Graphene-Django Tutorial](http://docs.graphene-python.org/projects/django/en/latest/tutorial-plain/#testing-our-graphql-schema)
 for some example queries)
diff --git a/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json b/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json
index 213bd8b74..8625d3c70 100644
--- a/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json
+++ b/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json
@@ -1,52 +1 @@
-[
-  {
-    "fields": {
-      "name": "Dairy"
-    },
-    "model": "ingredients.category",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "name": "Meat"
-    },
-    "model": "ingredients.category",
-    "pk": 2
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Eggs",
-      "notes": "Good old eggs"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Milk",
-      "notes": "Comes from a cow"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 2
-  },
-  {
-    "fields": {
-      "category": 2,
-      "name": "Beef",
-      "notes": "Much like milk, this comes from a cow"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 3
-  },
-  {
-    "fields": {
-      "category": 2,
-      "name": "Chicken",
-      "notes": "Definitely doesn't come from a cow"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 4
-  }
-]
+[{"model": "ingredients.category", "pk": 1, "fields": {"name": "Dairy"}}, {"model": "ingredients.category", "pk": 2, "fields": {"name": "Meat"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Eggs", "notes": "Good old eggs", "category": 1}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Milk", "notes": "Comes from a cow", "category": 1}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Beef", "notes": "Much like milk, this comes from a cow", "category": 2}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Chicken", "notes": "Definitely doesn't come from a cow", "category": 2}}]
\ No newline at end of file
diff --git a/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py b/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py
index 23d71e87b..04949239f 100644
--- a/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py
+++ b/examples/cookbook/cookbook/ingredients/migrations/0001_initial.py
@@ -1,52 +1,33 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2015-12-04 18:15
+from __future__ import unicode_literals
 
 import django.db.models.deletion
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     initial = True
 
-    dependencies = []
+    dependencies = [
+    ]
 
     operations = [
         migrations.CreateModel(
-            name="Category",
+            name='Category',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=100)),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
             ],
         ),
         migrations.CreateModel(
-            name="Ingredient",
+            name='Ingredient',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=100)),
-                ("notes", models.TextField()),
-                (
-                    "category",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        related_name="ingredients",
-                        to="ingredients.Category",
-                    ),
-                ),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+                ('notes', models.TextField()),
+                ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ingredients', to='ingredients.Category')),
             ],
         ),
     ]
diff --git a/examples/cookbook/cookbook/ingredients/migrations/0002_auto_20161104_0050.py b/examples/cookbook/cookbook/ingredients/migrations/0002_auto_20161104_0050.py
index 5f9e7a06a..359d4fc4c 100644
--- a/examples/cookbook/cookbook/ingredients/migrations/0002_auto_20161104_0050.py
+++ b/examples/cookbook/cookbook/ingredients/migrations/0002_auto_20161104_0050.py
@@ -1,17 +1,20 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2016-11-04 00:50
+from __future__ import unicode_literals
 
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     dependencies = [
-        ("ingredients", "0001_initial"),
+        ('ingredients', '0001_initial'),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name="ingredient",
-            name="notes",
+            model_name='ingredient',
+            name='notes',
             field=models.TextField(blank=True, null=True),
         ),
     ]
diff --git a/examples/cookbook/cookbook/ingredients/schema.py b/examples/cookbook/cookbook/ingredients/schema.py
index 4ed9eff13..43b6118a0 100644
--- a/examples/cookbook/cookbook/ingredients/schema.py
+++ b/examples/cookbook/cookbook/ingredients/schema.py
@@ -28,7 +28,7 @@ class Meta:
         }
 
 
-class Query:
+class Query(object):
     category = Node.Field(CategoryNode)
     all_categories = DjangoFilterConnectionField(CategoryNode)
 
diff --git a/examples/cookbook/cookbook/recipes/migrations/0001_initial.py b/examples/cookbook/cookbook/recipes/migrations/0001_initial.py
index c41514778..338c71a1b 100644
--- a/examples/cookbook/cookbook/recipes/migrations/0001_initial.py
+++ b/examples/cookbook/cookbook/recipes/migrations/0001_initial.py
@@ -1,69 +1,36 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2015-12-04 18:20
+from __future__ import unicode_literals
 
 import django.db.models.deletion
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     initial = True
 
     dependencies = [
-        ("ingredients", "0001_initial"),
+        ('ingredients', '0001_initial'),
     ]
 
     operations = [
         migrations.CreateModel(
-            name="Recipe",
+            name='Recipe',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("title", models.CharField(max_length=100)),
-                ("instructions", models.TextField()),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=100)),
+                ('instructions', models.TextField()),
             ],
         ),
         migrations.CreateModel(
-            name="RecipeIngredient",
+            name='RecipeIngredient',
             fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("amount", models.FloatField()),
-                (
-                    "unit",
-                    models.CharField(
-                        choices=[("kg", "Kilograms"), ("l", "Litres"), ("", "Units")],
-                        max_length=20,
-                    ),
-                ),
-                (
-                    "ingredient",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        related_name="used_by",
-                        to="ingredients.Ingredient",
-                    ),
-                ),
-                (
-                    "recipes",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        related_name="amounts",
-                        to="recipes.Recipe",
-                    ),
-                ),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('amount', models.FloatField()),
+                ('unit', models.CharField(choices=[('kg', 'Kilograms'), ('l', 'Litres'), ('', 'Units')], max_length=20)),
+                ('ingredient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to='ingredients.Ingredient')),
+                ('recipes', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='amounts', to='recipes.Recipe')),
             ],
         ),
     ]
diff --git a/examples/cookbook/cookbook/recipes/migrations/0002_auto_20161104_0106.py b/examples/cookbook/cookbook/recipes/migrations/0002_auto_20161104_0106.py
index f38bb697b..f13539265 100644
--- a/examples/cookbook/cookbook/recipes/migrations/0002_auto_20161104_0106.py
+++ b/examples/cookbook/cookbook/recipes/migrations/0002_auto_20161104_0106.py
@@ -1,30 +1,25 @@
+# -*- coding: utf-8 -*-
 # Generated by Django 1.9 on 2016-11-04 01:06
+from __future__ import unicode_literals
 
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
+
     dependencies = [
-        ("recipes", "0001_initial"),
+        ('recipes', '0001_initial'),
     ]
 
     operations = [
         migrations.RenameField(
-            model_name="recipeingredient",
-            old_name="recipes",
-            new_name="recipe",
+            model_name='recipeingredient',
+            old_name='recipes',
+            new_name='recipe',
         ),
         migrations.AlterField(
-            model_name="recipeingredient",
-            name="unit",
-            field=models.CharField(
-                choices=[
-                    (b"unit", b"Units"),
-                    (b"kg", b"Kilograms"),
-                    (b"l", b"Litres"),
-                    (b"st", b"Shots"),
-                ],
-                max_length=20,
-            ),
+            model_name='recipeingredient',
+            name='unit',
+            field=models.CharField(choices=[(b'unit', b'Units'), (b'kg', b'Kilograms'), (b'l', b'Litres'), (b'st', b'Shots')], max_length=20),
         ),
     ]
diff --git a/examples/cookbook/cookbook/recipes/schema.py b/examples/cookbook/cookbook/recipes/schema.py
index ea5ed38f1..c7298aacd 100644
--- a/examples/cookbook/cookbook/recipes/schema.py
+++ b/examples/cookbook/cookbook/recipes/schema.py
@@ -25,7 +25,7 @@ class Meta:
         }
 
 
-class Query:
+class Query(object):
     recipe = Node.Field(RecipeNode)
     all_recipes = DjangoFilterConnectionField(RecipeNode)
 
diff --git a/examples/cookbook/dummy_data.json b/examples/cookbook/dummy_data.json
index c585bfcdf..f541da5f5 100644
--- a/examples/cookbook/dummy_data.json
+++ b/examples/cookbook/dummy_data.json
@@ -1,302 +1 @@
-[
-  {
-    "fields": {
-      "date_joined": "2016-11-03T18:24:40Z",
-      "email": "asdf@example.com",
-      "first_name": "",
-      "groups": [],
-      "is_active": true,
-      "is_staff": true,
-      "is_superuser": true,
-      "last_login": "2016-11-04T00:46:58Z",
-      "last_name": "",
-      "password": "pbkdf2_sha256$24000$0SgBlSlnbv5c$ijVQipm2aNDlcrTL8Qi3SVNHphTm4HIsDfUi4kn9tog=",
-      "user_permissions": [],
-      "username": "admin"
-    },
-    "model": "auth.user",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "instructions": "https://xkcd.com/720/",
-      "title": "Cheerios With a Shot of Vermouth"
-    },
-    "model": "recipes.recipe",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "instructions": "https://xkcd.com/720/",
-      "title": "Quail Eggs in Whipped Cream and MSG"
-    },
-    "model": "recipes.recipe",
-    "pk": 2
-  },
-  {
-    "fields": {
-      "instructions": "https://xkcd.com/720/",
-      "title": "Deep Fried Skittles"
-    },
-    "model": "recipes.recipe",
-    "pk": 3
-  },
-  {
-    "fields": {
-      "instructions": "https://xkcd.com/720/",
-      "title": "Newt ala Doritos"
-    },
-    "model": "recipes.recipe",
-    "pk": 4
-  },
-  {
-    "fields": {
-      "instructions": "Chop up and add together",
-      "title": "Fruit Salad"
-    },
-    "model": "recipes.recipe",
-    "pk": 5
-  },
-  {
-    "fields": {
-      "amount": 1.0,
-      "ingredient": 9,
-      "recipes": 5,
-      "unit": "unit"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "amount": 2.0,
-      "ingredient": 10,
-      "recipes": 5,
-      "unit": "unit"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 2
-  },
-  {
-    "fields": {
-      "amount": 3.0,
-      "ingredient": 7,
-      "recipes": 5,
-      "unit": "unit"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 3
-  },
-  {
-    "fields": {
-      "amount": 4.0,
-      "ingredient": 8,
-      "recipes": 5,
-      "unit": "unit"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 4
-  },
-  {
-    "fields": {
-      "amount": 1.0,
-      "ingredient": 5,
-      "recipes": 4,
-      "unit": "kg"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 5
-  },
-  {
-    "fields": {
-      "amount": 2.0,
-      "ingredient": 6,
-      "recipes": 4,
-      "unit": "l"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 6
-  },
-  {
-    "fields": {
-      "amount": 1.0,
-      "ingredient": 4,
-      "recipes": 3,
-      "unit": "unit"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 7
-  },
-  {
-    "fields": {
-      "amount": 1.0,
-      "ingredient": 2,
-      "recipes": 2,
-      "unit": "kg"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 8
-  },
-  {
-    "fields": {
-      "amount": 2.0,
-      "ingredient": 11,
-      "recipes": 2,
-      "unit": "l"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 9
-  },
-  {
-    "fields": {
-      "amount": 3.0,
-      "ingredient": 12,
-      "recipes": 2,
-      "unit": "st"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 10
-  },
-  {
-    "fields": {
-      "amount": 1.0,
-      "ingredient": 1,
-      "recipes": 1,
-      "unit": "kg"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 11
-  },
-  {
-    "fields": {
-      "amount": 1.0,
-      "ingredient": 3,
-      "recipes": 1,
-      "unit": "st"
-    },
-    "model": "recipes.recipeingredient",
-    "pk": 12
-  },
-  {
-    "fields": {
-      "name": "fruit"
-    },
-    "model": "ingredients.category",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "name": "xkcd"
-    },
-    "model": "ingredients.category",
-    "pk": 3
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "Cheerios",
-      "notes": "this is a note"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 1
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "Quail Eggs",
-      "notes": "has more notes"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 2
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "Vermouth",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 3
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "Skittles",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 4
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "Newt",
-      "notes": "Braised and Confuesd"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 5
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "Doritos",
-      "notes": "Crushed"
-    },
-    "model": "ingredients.ingredient",
-    "pk": 6
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Apple",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 7
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Orange",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 8
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Banana",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 9
-  },
-  {
-    "fields": {
-      "category": 1,
-      "name": "Grapes",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 10
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "Whipped Cream",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 11
-  },
-  {
-    "fields": {
-      "category": 3,
-      "name": "MSG",
-      "notes": ""
-    },
-    "model": "ingredients.ingredient",
-    "pk": 12
-  }
-]
+[{"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$24000$0SgBlSlnbv5c$ijVQipm2aNDlcrTL8Qi3SVNHphTm4HIsDfUi4kn9tog=", "last_login": "2016-11-04T00:46:58Z", "is_superuser": true, "username": "admin", "first_name": "", "last_name": "", "email": "asdf@example.com", "is_staff": true, "is_active": true, "date_joined": "2016-11-03T18:24:40Z", "groups": [], "user_permissions": []}}, {"model": "recipes.recipe", "pk": 1, "fields": {"title": "Cheerios With a Shot of Vermouth", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 2, "fields": {"title": "Quail Eggs in Whipped Cream and MSG", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 3, "fields": {"title": "Deep Fried Skittles", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 4, "fields": {"title": "Newt ala Doritos", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 5, "fields": {"title": "Fruit Salad", "instructions": "Chop up and add together"}}, {"model": "recipes.recipeingredient", "pk": 1, "fields": {"recipes": 5, "ingredient": 9, "amount": 1.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 2, "fields": {"recipes": 5, "ingredient": 10, "amount": 2.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 3, "fields": {"recipes": 5, "ingredient": 7, "amount": 3.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 4, "fields": {"recipes": 5, "ingredient": 8, "amount": 4.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 5, "fields": {"recipes": 4, "ingredient": 5, "amount": 1.0, "unit": "kg"}}, {"model": "recipes.recipeingredient", "pk": 6, "fields": {"recipes": 4, "ingredient": 6, "amount": 2.0, "unit": "l"}}, {"model": "recipes.recipeingredient", "pk": 7, "fields": {"recipes": 3, "ingredient": 4, "amount": 1.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 8, "fields": {"recipes": 2, "ingredient": 2, "amount": 1.0, "unit": "kg"}}, {"model": "recipes.recipeingredient", "pk": 9, "fields": {"recipes": 2, "ingredient": 11, "amount": 2.0, "unit": "l"}}, {"model": "recipes.recipeingredient", "pk": 10, "fields": {"recipes": 2, "ingredient": 12, "amount": 3.0, "unit": "st"}}, {"model": "recipes.recipeingredient", "pk": 11, "fields": {"recipes": 1, "ingredient": 1, "amount": 1.0, "unit": "kg"}}, {"model": "recipes.recipeingredient", "pk": 12, "fields": {"recipes": 1, "ingredient": 3, "amount": 1.0, "unit": "st"}}, {"model": "ingredients.category", "pk": 1, "fields": {"name": "fruit"}}, {"model": "ingredients.category", "pk": 3, "fields": {"name": "xkcd"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Cheerios", "notes": "this is a note", "category": 3}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Quail Eggs", "notes": "has more notes", "category": 3}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Vermouth", "notes": "", "category": 3}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Skittles", "notes": "", "category": 3}}, {"model": "ingredients.ingredient", "pk": 5, "fields": {"name": "Newt", "notes": "Braised and Confuesd", "category": 3}}, {"model": "ingredients.ingredient", "pk": 6, "fields": {"name": "Doritos", "notes": "Crushed", "category": 3}}, {"model": "ingredients.ingredient", "pk": 7, "fields": {"name": "Apple", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 8, "fields": {"name": "Orange", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 9, "fields": {"name": "Banana", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 10, "fields": {"name": "Grapes", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 11, "fields": {"name": "Whipped Cream", "notes": "", "category": 3}}, {"model": "ingredients.ingredient", "pk": 12, "fields": {"name": "MSG", "notes": "", "category": 3}}]
\ No newline at end of file
diff --git a/examples/starwars/models.py b/examples/starwars/models.py
index fb76b0395..03e06a292 100644
--- a/examples/starwars/models.py
+++ b/examples/starwars/models.py
@@ -1,3 +1,5 @@
+from __future__ import absolute_import
+
 from django.db import models
 
 
diff --git a/graphene_django/__init__.py b/graphene_django/__init__.py
index 12408a4da..999f3de86 100644
--- a/graphene_django/__init__.py
+++ b/graphene_django/__init__.py
@@ -1,7 +1,7 @@
 from .fields import DjangoConnectionField, DjangoListField
 from .types import DjangoObjectType
 
-__version__ = "3.0.2"
+__version__ = "3.0.0b7"
 
 __all__ = [
     "__version__",
diff --git a/graphene_django/compat.py b/graphene_django/compat.py
index b0e475357..195678610 100644
--- a/graphene_django/compat.py
+++ b/graphene_django/compat.py
@@ -1,4 +1,4 @@
-class MissingType:
+class MissingType(object):
     def __init__(self, *args, **kwargs):
         pass
 
diff --git a/graphene_django/converter.py b/graphene_django/converter.py
index 375d68312..c243e8227 100644
--- a/graphene_django/converter.py
+++ b/graphene_django/converter.py
@@ -24,15 +24,8 @@
     Decimal,
 )
 from graphene.types.json import JSONString
-from graphene.types.scalars import BigInt
 from graphene.utils.str_converters import to_camel_case
-from graphql import GraphQLError
-
-try:
-    from graphql import assert_name
-except ImportError:
-    # Support for older versions of graphql
-    from graphql import assert_valid_name as assert_name
+from graphql import GraphQLError, assert_valid_name
 from graphql.pyutils import register_description
 
 from .compat import ArrayField, HStoreField, JSONField, PGJSONField, RangeField
@@ -62,7 +55,7 @@ def wrapped_resolver(*args, **kwargs):
 def convert_choice_name(name):
     name = to_const(force_str(name))
     try:
-        assert_name(name)
+        assert_valid_name(name)
     except GraphQLError:
         name = "A_%s" % name
     return name
@@ -74,7 +67,8 @@ def get_choices(choices):
         choices = choices.items()
     for value, help_text in choices:
         if isinstance(help_text, (tuple, list)):
-            yield from get_choices(help_text)
+            for choice in get_choices(help_text):
+                yield choice
         else:
             name = convert_choice_name(value)
             while name in converted_names:
@@ -91,17 +85,12 @@ def convert_choices_to_named_enum_with_descriptions(name, choices):
     named_choices = [(c[0], c[1]) for c in choices]
     named_choices_descriptions = {c[0]: c[2] for c in choices}
 
-    class EnumWithDescriptionsType:
+    class EnumWithDescriptionsType(object):
         @property
         def description(self):
             return str(named_choices_descriptions[self.name])
 
-    return_type = Enum(
-        name,
-        list(named_choices),
-        type=EnumWithDescriptionsType,
-        description="An enumeration.",  # Temporary fix until https://github.com/graphql-python/graphene/pull/1502 is merged
-    )
+    return_type = Enum(name, list(named_choices), type=EnumWithDescriptionsType)
     return return_type
 
 
@@ -113,7 +102,7 @@ def generate_enum_name(django_model_meta, field):
         )
         name = custom_func(field)
     elif graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V2_NAMING is True:
-        name = to_camel_case(f"{django_model_meta.object_name}_{field.name}")
+        name = to_camel_case("{}_{}".format(django_model_meta.object_name, field.name))
     else:
         name = "{app_label}{object_name}{field_name}Choices".format(
             app_label=to_camel_case(django_model_meta.app_label.title()),
@@ -159,9 +148,7 @@ def get_django_field_description(field):
 @singledispatch
 def convert_django_field(field, registry=None):
     raise Exception(
-        "Don't know how to convert the Django field {} ({})".format(
-            field, field.__class__
-        )
+        "Don't know how to convert the Django field %s (%s)" % (field, field.__class__)
     )
 
 
@@ -199,14 +186,10 @@ def convert_field_to_uuid(field, registry=None):
     )
 
 
-@convert_django_field.register(models.BigIntegerField)
-def convert_big_int_field(field, registry=None):
-    return BigInt(description=field.help_text, required=not field.null)
-
-
 @convert_django_field.register(models.PositiveIntegerField)
 @convert_django_field.register(models.PositiveSmallIntegerField)
 @convert_django_field.register(models.SmallIntegerField)
+@convert_django_field.register(models.BigIntegerField)
 @convert_django_field.register(models.IntegerField)
 def convert_field_to_int(field, registry=None):
     return Int(description=get_django_field_description(field), required=not field.null)
@@ -222,9 +205,7 @@ def convert_field_to_boolean(field, registry=None):
 
 @convert_django_field.register(models.DecimalField)
 def convert_field_to_decimal(field, registry=None):
-    return Decimal(
-        description=get_django_field_description(field), required=not field.null
-    )
+    return Decimal(description=field.help_text, required=not field.null)
 
 
 @convert_django_field.register(models.FloatField)
diff --git a/graphene_django/debug/exception/formating.py b/graphene_django/debug/exception/formating.py
index 0d477b330..ed7ebabf3 100644
--- a/graphene_django/debug/exception/formating.py
+++ b/graphene_django/debug/exception/formating.py
@@ -11,7 +11,7 @@ def wrap_exception(exception):
         exc_type=force_str(type(exception)),
         stack="".join(
             traceback.format_exception(
-                exception, value=exception, tb=exception.__traceback__
+                etype=type(exception), value=exception, tb=exception.__traceback__
             )
         ),
     )
diff --git a/graphene_django/debug/middleware.py b/graphene_django/debug/middleware.py
index d3052a14a..804e7c838 100644
--- a/graphene_django/debug/middleware.py
+++ b/graphene_django/debug/middleware.py
@@ -7,34 +7,34 @@
 from .types import DjangoDebug
 
 
-class DjangoDebugContext:
+class DjangoDebugContext(object):
     def __init__(self):
-        self.debug_result = None
-        self.results = []
+        self.debug_promise = None
+        self.promises = []
         self.object = DjangoDebug(sql=[], exceptions=[])
         self.enable_instrumentation()
 
-    def get_debug_result(self):
-        if not self.debug_result:
-            self.debug_result = self.results
-            self.results = []
-        return self.on_resolve_all_results()
+    def get_debug_promise(self):
+        if not self.debug_promise:
+            self.debug_promise = Promise.all(self.promises)
+            self.promises = []
+        return self.debug_promise.then(self.on_resolve_all_promises).get()
 
     def on_resolve_error(self, value):
         if hasattr(self, "object"):
             self.object.exceptions.append(wrap_exception(value))
-        return value
+        return Promise.reject(value)
 
-    def on_resolve_all_results(self):
-        if self.results:
-            self.debug_result = None
-            return self.get_debug_result()
+    def on_resolve_all_promises(self, values):
+        if self.promises:
+            self.debug_promise = None
+            return self.get_debug_promise()
         self.disable_instrumentation()
         return self.object
 
-    def add_result(self, result):
-        if self.debug_result:
-            self.results.append(result)
+    def add_promise(self, promise):
+        if self.debug_promise:
+            self.promises.append(promise)
 
     def enable_instrumentation(self):
         # This is thread-safe because database connections are thread-local.
@@ -46,7 +46,7 @@ def disable_instrumentation(self):
             unwrap_cursor(connection)
 
 
-class DjangoDebugMiddleware:
+class DjangoDebugMiddleware(object):
     def resolve(self, next, root, info, **args):
         context = info.context
         django_debug = getattr(context, "django_debug", None)
@@ -62,10 +62,10 @@ def resolve(self, next, root, info, **args):
                     )
                 )
         if info.schema.get_type("DjangoDebug") == info.return_type:
-            return context.django_debug.get_debug_result()
+            return context.django_debug.get_debug_promise()
         try:
-            result = next(root, info, **args)
+            promise = next(root, info, **args)
         except Exception as e:
             return context.django_debug.on_resolve_error(e)
-        context.django_debug.add_result(result)
-        return result
+        context.django_debug.add_promise(promise)
+        return promise
diff --git a/graphene_django/debug/sql/tracking.py b/graphene_django/debug/sql/tracking.py
index bf0ea367f..f7346e642 100644
--- a/graphene_django/debug/sql/tracking.py
+++ b/graphene_django/debug/sql/tracking.py
@@ -1,4 +1,5 @@
 # Code obtained from django-debug-toolbar sql panel tracking
+from __future__ import absolute_import, unicode_literals
 
 import json
 from threading import local
@@ -49,7 +50,7 @@ def unwrap_cursor(connection):
         del connection._graphene_cursor
 
 
-class ExceptionCursorWrapper:
+class ExceptionCursorWrapper(object):
     """
     Wraps a cursor and raises an exception on any operation.
     Used in Templates panel.
@@ -62,7 +63,7 @@ def __getattr__(self, attr):
         raise SQLQueryTriggered()
 
 
-class NormalCursorWrapper:
+class NormalCursorWrapper(object):
     """
     Wraps a cursor and logs queries.
     """
@@ -84,7 +85,7 @@ def _quote_params(self, params):
         if not params:
             return params
         if isinstance(params, dict):
-            return {key: self._quote_expr(value) for key, value in params.items()}
+            return dict((key, self._quote_expr(value)) for key, value in params.items())
         return list(map(self._quote_expr, params))
 
     def _decode(self, param):
diff --git a/graphene_django/debug/tests/test_query.py b/graphene_django/debug/tests/test_query.py
index 1ea86b123..eae94dcd4 100644
--- a/graphene_django/debug/tests/test_query.py
+++ b/graphene_django/debug/tests/test_query.py
@@ -8,7 +8,7 @@
 from ..types import DjangoDebug
 
 
-class context:
+class context(object):
     pass
 
 
diff --git a/graphene_django/fields.py b/graphene_django/fields.py
index 0fe123deb..e1972c7d9 100644
--- a/graphene_django/fields.py
+++ b/graphene_django/fields.py
@@ -1,14 +1,12 @@
 from functools import partial
 
 from django.db.models.query import QuerySet
-
-from graphql_relay import (
+from graphql_relay.connection.arrayconnection import (
     connection_from_array_slice,
     cursor_to_offset,
     get_offset_with_default,
     offset_to_cursor,
 )
-
 from promise import Promise
 
 from graphene import Int, NonNull
@@ -28,7 +26,7 @@ def __init__(self, _type, *args, **kwargs):
             _type = _type.of_type
 
         # Django would never return a Set of None  vvvvvvv
-        super().__init__(List(NonNull(_type)), *args, **kwargs)
+        super(DjangoListField, self).__init__(List(NonNull(_type)), *args, **kwargs)
 
         assert issubclass(
             self._underlying_type, DjangoObjectType
@@ -63,16 +61,13 @@ def list_resolver(
         return queryset
 
     def wrap_resolve(self, parent_resolver):
-        resolver = super().wrap_resolve(parent_resolver)
+        resolver = super(DjangoListField, self).wrap_resolve(parent_resolver)
         _type = self.type
         if isinstance(_type, NonNull):
             _type = _type.of_type
         django_object_type = _type.of_type.of_type
         return partial(
-            self.list_resolver,
-            django_object_type,
-            resolver,
-            self.get_manager(),
+            self.list_resolver, django_object_type, resolver, self.get_manager(),
         )
 
 
@@ -87,7 +82,7 @@ def __init__(self, *args, **kwargs):
             graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST,
         )
         kwargs.setdefault("offset", Int())
-        super().__init__(*args, **kwargs)
+        super(DjangoConnectionField, self).__init__(*args, **kwargs)
 
     @property
     def type(self):
@@ -149,40 +144,36 @@ def resolve_connection(cls, connection, args, iterable, max_limit=None):
         iterable = maybe_queryset(iterable)
 
         if isinstance(iterable, QuerySet):
-            array_length = iterable.count()
+            list_length = iterable.count()
         else:
-            array_length = len(iterable)
+            list_length = len(iterable)
+        list_slice_length = (
+            min(max_limit, list_length) if max_limit is not None else list_length
+        )
 
-        # If after is higher than array_length, connection_from_array_slice
+        # If after is higher than list_length, connection_from_list_slice
         # would try to do a negative slicing which makes django throw an
         # AssertionError
-        slice_start = min(
-            get_offset_with_default(args.get("after"), -1) + 1,
-            array_length,
-        )
-        array_slice_length = array_length - slice_start
+        after = min(get_offset_with_default(args.get("after"), -1) + 1, list_length)
 
-        # Impose the maximum limit via the `first` field if neither first or last are already provided
-        # (note that if any of them is provided they must be under max_limit otherwise an error is raised).
-        if (
-            max_limit is not None
-            and args.get("first", None) is None
-            and args.get("last", None) is None
-        ):
-            args["first"] = max_limit
+        if max_limit is not None and args.get("first", None) is None:
+            if args.get("last", None) is not None:
+                after = list_length - args["last"]
+            else:
+                args["first"] = max_limit
 
         connection = connection_from_array_slice(
-            iterable[slice_start:],
+            iterable[after:],
             args,
-            slice_start=slice_start,
-            array_length=array_length,
-            array_slice_length=array_slice_length,
+            slice_start=after,
+            array_length=list_length,
+            array_slice_length=list_slice_length,
             connection_type=partial(connection_adapter, connection),
             edge_type=connection.Edge,
             page_info_type=page_info_adapter,
         )
         connection.iterable = iterable
-        connection.length = array_length
+        connection.length = list_length
         return connection
 
     @classmethod
diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py
index cdb8f850f..c6dd50ee6 100644
--- a/graphene_django/filter/fields.py
+++ b/graphene_django/filter/fields.py
@@ -30,7 +30,7 @@ def convert_enum(data):
 class DjangoFilterConnectionField(DjangoConnectionField):
     def __init__(
         self,
-        type_,
+        type,
         fields=None,
         order_by=None,
         extra_filter_meta=None,
@@ -44,7 +44,7 @@ def __init__(
         self._filtering_args = None
         self._extra_filter_meta = extra_filter_meta
         self._base_args = None
-        super().__init__(type_, *args, **kwargs)
+        super(DjangoFilterConnectionField, self).__init__(type, *args, **kwargs)
 
     @property
     def args(self):
@@ -90,7 +90,9 @@ def filter_kwargs():
                     kwargs[k] = convert_enum(v)
             return kwargs
 
-        qs = super().resolve_queryset(connection, iterable, info, args)
+        qs = super(DjangoFilterConnectionField, cls).resolve_queryset(
+            connection, iterable, info, args
+        )
 
         filterset = filterset_class(
             data=filter_kwargs(), queryset=qs, request=info.context
diff --git a/graphene_django/filter/filters/array_filter.py b/graphene_django/filter/filters/array_filter.py
index b6f4808ec..e886cff97 100644
--- a/graphene_django/filter/filters/array_filter.py
+++ b/graphene_django/filter/filters/array_filter.py
@@ -22,6 +22,6 @@ def filter(self, qs, value):
             return qs
         if self.distinct:
             qs = qs.distinct()
-        lookup = f"{self.field_name}__{self.lookup_expr}"
+        lookup = "%s__%s" % (self.field_name, self.lookup_expr)
         qs = self.get_method(qs)(**{lookup: value})
         return qs
diff --git a/graphene_django/filter/filters/global_id_filter.py b/graphene_django/filter/filters/global_id_filter.py
index 37877d58b..a612a8a2a 100644
--- a/graphene_django/filter/filters/global_id_filter.py
+++ b/graphene_django/filter/filters/global_id_filter.py
@@ -13,11 +13,11 @@ class GlobalIDFilter(Filter):
     field_class = GlobalIDFormField
 
     def filter(self, qs, value):
-        """Convert the filter value to a primary key before filtering"""
+        """ Convert the filter value to a primary key before filtering """
         _id = None
         if value is not None:
             _, _id = from_global_id(value)
-        return super().filter(qs, _id)
+        return super(GlobalIDFilter, self).filter(qs, _id)
 
 
 class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
@@ -25,4 +25,4 @@ class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
 
     def filter(self, qs, value):
         gids = [from_global_id(v)[1] for v in value]
-        return super().filter(qs, gids)
+        return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids)
diff --git a/graphene_django/filter/filters/list_filter.py b/graphene_django/filter/filters/list_filter.py
index 6689877cb..9689be3f1 100644
--- a/graphene_django/filter/filters/list_filter.py
+++ b/graphene_django/filter/filters/list_filter.py
@@ -23,4 +23,4 @@ def filter(self, qs, value):
             else:
                 return qs.none()
         else:
-            return super().filter(qs, value)
+            return super(ListFilter, self).filter(qs, value)
diff --git a/graphene_django/filter/filters/typed_filter.py b/graphene_django/filter/filters/typed_filter.py
index 76f903aea..2c813e4c6 100644
--- a/graphene_django/filter/filters/typed_filter.py
+++ b/graphene_django/filter/filters/typed_filter.py
@@ -12,7 +12,7 @@ class TypedFilter(Filter):
 
     def __init__(self, input_type=None, *args, **kwargs):
         self._input_type = input_type
-        super().__init__(*args, **kwargs)
+        super(TypedFilter, self).__init__(*args, **kwargs)
 
     @property
     def input_type(self):
diff --git a/graphene_django/filter/filterset.py b/graphene_django/filter/filterset.py
index fa91477f1..b3333bf2a 100644
--- a/graphene_django/filter/filterset.py
+++ b/graphene_django/filter/filterset.py
@@ -18,8 +18,8 @@
 
 
 class GrapheneFilterSetMixin(BaseFilterSet):
-    """A django_filters.filterset.BaseFilterSet with default filter overrides
-    to handle global IDs"""
+    """ A django_filters.filterset.BaseFilterSet with default filter overrides
+    to handle global IDs """
 
     FILTER_DEFAULTS = dict(
         itertools.chain(
@@ -29,18 +29,20 @@ class GrapheneFilterSetMixin(BaseFilterSet):
 
 
 def setup_filterset(filterset_class):
-    """Wrap a provided filterset in Graphene-specific functionality"""
+    """ Wrap a provided filterset in Graphene-specific functionality
+    """
     return type(
-        f"Graphene{filterset_class.__name__}",
+        "Graphene{}".format(filterset_class.__name__),
         (filterset_class, GrapheneFilterSetMixin),
         {},
     )
 
 
 def custom_filterset_factory(model, filterset_base_class=FilterSet, **meta):
-    """Create a filterset for the given model using the provided meta data"""
+    """ Create a filterset for the given model using the provided meta data
+    """
     meta.update({"model": model})
-    meta_class = type("Meta", (object,), meta)
+    meta_class = type(str("Meta"), (object,), meta)
     filterset = type(
         str("%sFilterSet" % model._meta.object_name),
         (filterset_base_class, GrapheneFilterSetMixin),
diff --git a/graphene_django/filter/tests/conftest.py b/graphene_django/filter/tests/conftest.py
index f8a65d7b2..57924aff6 100644
--- a/graphene_django/filter/tests/conftest.py
+++ b/graphene_django/filter/tests/conftest.py
@@ -1,4 +1,4 @@
-from unittest.mock import MagicMock
+from mock import MagicMock
 import pytest
 
 from django.db import models
@@ -87,11 +87,12 @@ class Query(graphene.ObjectType):
         events = DjangoFilterConnectionField(EventType)
 
         def resolve_events(self, info, **kwargs):
+
             events = [
-                Event(name="Live Show", tags=["concert", "music", "rock"]),
-                Event(name="Musical", tags=["movie", "music"]),
-                Event(name="Ballet", tags=["concert", "dance"]),
-                Event(name="Speech", tags=[]),
+                Event(name="Live Show", tags=["concert", "music", "rock"],),
+                Event(name="Musical", tags=["movie", "music"],),
+                Event(name="Ballet", tags=["concert", "dance"],),
+                Event(name="Speech", tags=[],),
             ]
 
             STORE["events"] = events
diff --git a/graphene_django/filter/tests/test_array_field_exact_filter.py b/graphene_django/filter/tests/test_array_field_exact_filter.py
index 10e32ef73..cd72868c9 100644
--- a/graphene_django/filter/tests/test_array_field_exact_filter.py
+++ b/graphene_django/filter/tests/test_array_field_exact_filter.py
@@ -120,7 +120,10 @@ def test_array_field_filter_schema_type(Query):
         "randomField": "[Boolean!]",
     }
     filters_str = ", ".join(
-        [f"{filter_field}: {gql_type}" for filter_field, gql_type in filters.items()]
+        [
+            f"{filter_field}: {gql_type} = null"
+            for filter_field, gql_type in filters.items()
+        ]
     )
     assert (
         f"type Query {{\n  events({filters_str}): EventTypeConnection\n}}" in schema_str
diff --git a/graphene_django/filter/tests/test_enum_filtering.py b/graphene_django/filter/tests/test_enum_filtering.py
index a284d0834..09c69b393 100644
--- a/graphene_django/filter/tests/test_enum_filtering.py
+++ b/graphene_django/filter/tests/test_enum_filtering.py
@@ -54,13 +54,13 @@ def reporter_article_data():
         first_name="Jane", last_name="Doe", email="janedoe@example.com", a_choice=2
     )
     Article.objects.create(
-        headline="Article Node 1", reporter=john, editor=john, lang="es"
+        headline="Article Node 1", reporter=john, editor=john, lang="es",
     )
     Article.objects.create(
-        headline="Article Node 2", reporter=john, editor=john, lang="en"
+        headline="Article Node 2", reporter=john, editor=john, lang="en",
     )
     Article.objects.create(
-        headline="Article Node 3", reporter=jane, editor=jane, lang="en"
+        headline="Article Node 3", reporter=jane, editor=jane, lang="en",
     )
 
 
@@ -80,13 +80,7 @@ def test_filter_enum_on_connection(schema, reporter_article_data):
         }
     """
 
-    expected = {
-        "allArticles": {
-            "edges": [
-                {"node": {"headline": "Article Node 1"}},
-            ]
-        }
-    }
+    expected = {"allArticles": {"edges": [{"node": {"headline": "Article Node 1"}},]}}
 
     result = schema.execute(query)
     assert not result.errors
@@ -158,6 +152,9 @@ def test_filter_enum_field_schema_type(schema):
         "reporter_AChoice_In": "[TestsReporterAChoiceChoices]",
     }
     filters_str = ", ".join(
-        [f"{filter_field}: {gql_type}" for filter_field, gql_type in filters.items()]
+        [
+            f"{filter_field}: {gql_type} = null"
+            for filter_field, gql_type in filters.items()
+        ]
     )
     assert f"  allArticles({filters_str}): ArticleTypeConnection\n" in schema_str
diff --git a/graphene_django/filter/tests/test_fields.py b/graphene_django/filter/tests/test_fields.py
index bee3c6cf4..17b46306b 100644
--- a/graphene_django/filter/tests/test_fields.py
+++ b/graphene_django/filter/tests/test_fields.py
@@ -5,7 +5,7 @@
 from django.db.models import TextField, Value
 from django.db.models.functions import Concat
 
-from graphene import Argument, Boolean, Decimal, Field, ObjectType, Schema, String
+from graphene import Argument, Boolean, Field, Float, ObjectType, Schema, String
 from graphene.relay import Node
 from graphene_django import DjangoObjectType
 from graphene_django.forms import GlobalIDFormField, GlobalIDMultipleChoiceField
@@ -67,7 +67,7 @@ def assert_arguments(field, *arguments):
     actual = [name for name in args if name not in ignore and not name.startswith("_")]
     assert set(arguments) == set(
         actual
-    ), f"Expected arguments ({arguments}) did not match actual ({actual})"
+    ), "Expected arguments ({}) did not match actual ({})".format(arguments, actual)
 
 
 def assert_orderable(field):
@@ -141,7 +141,7 @@ class Meta:
 
         @property
         def qs(self):
-            qs = super().qs
+            qs = super(ArticleContextFilter, self).qs
             return qs.filter(reporter=self.request.reporter)
 
     class Query(ObjectType):
@@ -166,7 +166,7 @@ class Query(ObjectType):
         editor=r2,
     )
 
-    class context:
+    class context(object):
         reporter = r2
 
     query = """
@@ -401,7 +401,7 @@ class Meta:
     field = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleIdFilter)
     max_time = field.args["max_time"]
     assert isinstance(max_time, Argument)
-    assert max_time.type == Decimal
+    assert max_time.type == Float
     assert max_time.description == "The maximum time"
 
 
@@ -1008,7 +1008,7 @@ class Query(ObjectType):
     assert str(schema) == dedent(
         """\
         type Query {
-          pets(offset: Int, before: String, after: String, first: Int, last: Int, age: Int): PetTypeConnection
+          pets(offset: Int = null, before: String = null, after: String = null, first: Int = null, last: Int = null, age: Int = null): PetTypeConnection
         }
 
         type PetTypeConnection {
@@ -1056,7 +1056,8 @@ class Query(ObjectType):
         interface Node {
           \"""The ID of the object\"""
           id: ID!
-        }"""
+        }
+    """
     )
 
 
@@ -1076,7 +1077,7 @@ class Query(ObjectType):
     assert str(schema) == dedent(
         """\
         type Query {
-          pets(offset: Int, before: String, after: String, first: Int, last: Int, age: Int, age_Isnull: Boolean, age_Lt: Int): PetTypeConnection
+          pets(offset: Int = null, before: String = null, after: String = null, first: Int = null, last: Int = null, age: Int = null, age_Isnull: Boolean = null, age_Lt: Int = null): PetTypeConnection
         }
 
         type PetTypeConnection {
@@ -1124,7 +1125,8 @@ class Query(ObjectType):
         interface Node {
           \"""The ID of the object\"""
           id: ID!
-        }"""
+        }
+        """
     )
 
 
@@ -1224,7 +1226,7 @@ class Query(ObjectType):
         }
     }
 
-    result = schema.execute(query, variable_values={"email": reporter_1.email})
+    result = schema.execute(query, variable_values={"email": reporter_1.email},)
 
     assert not result.errors
     assert result.data == expected
@@ -1265,23 +1267,13 @@ class Query(ObjectType):
     result = schema.execute(query, variables={"filter": "Ja"})
     assert not result.errors
     assert result.data == {
-        "people": {
-            "edges": [
-                {"node": {"name": "Jack"}},
-                {"node": {"name": "Jane"}},
-            ]
-        }
+        "people": {"edges": [{"node": {"name": "Jack"}}, {"node": {"name": "Jane"}},]}
     }
 
     result = schema.execute(query, variables={"filter": "o"})
     assert not result.errors
     assert result.data == {
-        "people": {
-            "edges": [
-                {"node": {"name": "Joe"}},
-                {"node": {"name": "Bob"}},
-            ]
-        }
+        "people": {"edges": [{"node": {"name": "Joe"}}, {"node": {"name": "Bob"}},]}
     }
 
 
diff --git a/graphene_django/filter/tests/test_in_filter.py b/graphene_django/filter/tests/test_in_filter.py
index a69d6f5e4..7ad0286ac 100644
--- a/graphene_django/filter/tests/test_in_filter.py
+++ b/graphene_django/filter/tests/test_in_filter.py
@@ -349,19 +349,19 @@ def test_fk_id_in_filter(query):
     schema = Schema(query=query)
 
     query = """
-    query {{
-        articles (reporter_In: [{}, {}]) {{
-            edges {{
-                node {{
+    query {
+        articles (reporter_In: [%s, %s]) {
+            edges {
+                node {
                     headline
-                    reporter {{
+                    reporter {
                         lastName
-                    }}
-                }}
-            }}
-        }}
-    }}
-    """.format(
+                    }
+                }
+            }
+        }
+    }
+    """ % (
         john_doe.id,
         jean_bon.id,
     )
diff --git a/graphene_django/filter/tests/test_typed_filter.py b/graphene_django/filter/tests/test_typed_filter.py
index f22138ff2..b903b590f 100644
--- a/graphene_django/filter/tests/test_typed_filter.py
+++ b/graphene_django/filter/tests/test_typed_filter.py
@@ -98,14 +98,20 @@ def test_typed_filter_schema(schema):
     )
 
     for filter_field, gql_type in filters.items():
-        assert f"{filter_field}: {gql_type}" in all_articles_filters
+        assert "{}: {} = null".format(filter_field, gql_type) in all_articles_filters
 
 
 def test_typed_filters_work(schema):
     reporter = Reporter.objects.create(first_name="John", last_name="Doe", email="")
-    Article.objects.create(headline="A", reporter=reporter, editor=reporter, lang="es")
-    Article.objects.create(headline="B", reporter=reporter, editor=reporter, lang="es")
-    Article.objects.create(headline="C", reporter=reporter, editor=reporter, lang="en")
+    Article.objects.create(
+        headline="A", reporter=reporter, editor=reporter, lang="es",
+    )
+    Article.objects.create(
+        headline="B", reporter=reporter, editor=reporter, lang="es",
+    )
+    Article.objects.create(
+        headline="C", reporter=reporter, editor=reporter, lang="en",
+    )
 
     query = "query { articles (lang_In: [ES]) { edges { node { headline } } } }"
 
diff --git a/graphene_django/filter/utils.py b/graphene_django/filter/utils.py
index 3055ec788..05256f967 100644
--- a/graphene_django/filter/utils.py
+++ b/graphene_django/filter/utils.py
@@ -97,9 +97,7 @@ def get_filtering_args_from_filterset(filterset_class, type):
                 field_type = graphene.List(field_type)
 
         args[name] = graphene.Argument(
-            field_type,
-            description=filter_field.label,
-            required=required,
+            field_type, description=filter_field.label, required=required,
         )
 
     return args
diff --git a/graphene_django/forms/converter.py b/graphene_django/forms/converter.py
index 47eb51d5f..b64e4783b 100644
--- a/graphene_django/forms/converter.py
+++ b/graphene_django/forms/converter.py
@@ -3,19 +3,7 @@
 from django import forms
 from django.core.exceptions import ImproperlyConfigured
 
-from graphene import (
-    ID,
-    Boolean,
-    Decimal,
-    Float,
-    Int,
-    List,
-    String,
-    UUID,
-    Date,
-    DateTime,
-    Time,
-)
+from graphene import ID, Boolean, Float, Int, List, String, UUID, Date, DateTime, Time
 
 from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField
 
@@ -69,18 +57,12 @@ def convert_form_field_to_nullboolean(field):
     return Boolean(description=get_form_field_description(field))
 
 
+@convert_form_field.register(forms.DecimalField)
 @convert_form_field.register(forms.FloatField)
 def convert_form_field_to_float(field):
     return Float(description=get_form_field_description(field), required=field.required)
 
 
-@convert_form_field.register(forms.DecimalField)
-def convert_form_field_to_decimal(field):
-    return Decimal(
-        description=get_form_field_description(field), required=field.required
-    )
-
-
 @convert_form_field.register(forms.MultipleChoiceField)
 def convert_form_field_to_string_list(field):
     return List(
diff --git a/graphene_django/forms/mutation.py b/graphene_django/forms/mutation.py
index 40d1d3c7c..5a3d8e70e 100644
--- a/graphene_django/forms/mutation.py
+++ b/graphene_django/forms/mutation.py
@@ -82,6 +82,7 @@ class Meta:
     def __init_subclass_with_meta__(
         cls, form_class=None, only_fields=(), exclude_fields=(), **options
     ):
+
         if not form_class:
             raise Exception("form_class is required for DjangoFormMutation")
 
@@ -94,7 +95,7 @@ def __init_subclass_with_meta__(
         _meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
 
         input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
-        super().__init_subclass_with_meta__(
+        super(DjangoFormMutation, cls).__init_subclass_with_meta__(
             _meta=_meta, input_fields=input_fields, **options
         )
 
@@ -116,7 +117,7 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
     class Meta:
         abstract = True
 
-    errors = graphene.List(graphene.NonNull(ErrorType), required=True)
+    errors = graphene.List(ErrorType)
 
     @classmethod
     def __init_subclass_with_meta__(
@@ -126,8 +127,9 @@ def __init_subclass_with_meta__(
         return_field_name=None,
         only_fields=(),
         exclude_fields=(),
-        **options,
+        **options
     ):
+
         if not form_class:
             raise Exception("form_class is required for DjangoModelFormMutation")
 
@@ -145,7 +147,7 @@ def __init_subclass_with_meta__(
         registry = get_global_registry()
         model_type = registry.get_type_for_model(model)
         if not model_type:
-            raise Exception(f"No type registered for model: {model.__name__}")
+            raise Exception("No type registered for model: {}".format(model.__name__))
 
         if not return_field_name:
             model_name = model.__name__
@@ -161,7 +163,7 @@ def __init_subclass_with_meta__(
         _meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
 
         input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
-        super().__init_subclass_with_meta__(
+        super(DjangoModelFormMutation, cls).__init_subclass_with_meta__(
             _meta=_meta, input_fields=input_fields, **options
         )
 
diff --git a/graphene_django/forms/tests/test_converter.py b/graphene_django/forms/tests/test_converter.py
index b61227b77..ccf630f2c 100644
--- a/graphene_django/forms/tests/test_converter.py
+++ b/graphene_django/forms/tests/test_converter.py
@@ -1,12 +1,11 @@
 from django import forms
-from pytest import raises
+from py.test import raises
 
 import graphene
 from graphene import (
     String,
     Int,
     Boolean,
-    Decimal,
     Float,
     ID,
     UUID,
@@ -98,8 +97,8 @@ def test_should_float_convert_float():
     assert_conversion(forms.FloatField, Float)
 
 
-def test_should_decimal_convert_decimal():
-    assert_conversion(forms.DecimalField, Decimal)
+def test_should_decimal_convert_float():
+    assert_conversion(forms.DecimalField, Float)
 
 
 def test_should_multiple_choice_convert_list():
diff --git a/graphene_django/forms/tests/test_mutation.py b/graphene_django/forms/tests/test_mutation.py
index 14c407c24..0770acb3c 100644
--- a/graphene_django/forms/tests/test_mutation.py
+++ b/graphene_django/forms/tests/test_mutation.py
@@ -1,7 +1,7 @@
 import pytest
 from django import forms
 from django.core.exceptions import ValidationError
-from pytest import raises
+from py.test import raises
 
 from graphene import Field, ObjectType, Schema, String
 from graphene_django import DjangoObjectType
diff --git a/graphene_django/management/commands/graphql_schema.py b/graphene_django/management/commands/graphql_schema.py
index 42c41c173..565f5d8e1 100644
--- a/graphene_django/management/commands/graphql_schema.py
+++ b/graphene_django/management/commands/graphql_schema.py
@@ -48,7 +48,7 @@ def add_arguments(self, parser):
 class Command(CommandArguments):
     help = "Dump Graphene schema as a JSON or GraphQL file"
     can_import_settings = True
-    requires_system_checks = []
+    requires_system_checks = False
 
     def save_json_file(self, out, schema_dict, indent):
         with open(out, "w") as outfile:
@@ -63,7 +63,7 @@ def get_schema(self, schema, out, indent):
         if out == "-" or out == "-.json":
             self.stdout.write(json.dumps(schema_dict, indent=indent, sort_keys=True))
         elif out == "-.graphql":
-            self.stdout.write(print_schema(schema.graphql_schema))
+            self.stdout.write(print_schema(schema))
         else:
             # Determine format
             _, file_extension = os.path.splitext(out)
@@ -73,12 +73,16 @@ def get_schema(self, schema, out, indent):
             elif file_extension == ".json":
                 self.save_json_file(out, schema_dict, indent)
             else:
-                raise CommandError(f'Unrecognised file format "{file_extension}"')
+                raise CommandError(
+                    'Unrecognised file format "{}"'.format(file_extension)
+                )
 
             style = getattr(self, "style", None)
             success = getattr(style, "SUCCESS", lambda x: x)
 
-            self.stdout.write(success(f"Successfully dumped GraphQL schema to {out}"))
+            self.stdout.write(
+                success("Successfully dumped GraphQL schema to {}".format(out))
+            )
 
     def handle(self, *args, **options):
         options_schema = options.get("schema")
diff --git a/graphene_django/registry.py b/graphene_django/registry.py
index 470863779..50a8ae510 100644
--- a/graphene_django/registry.py
+++ b/graphene_django/registry.py
@@ -1,4 +1,4 @@
-class Registry:
+class Registry(object):
     def __init__(self):
         self._registry = {}
         self._field_registry = {}
diff --git a/graphene_django/rest_framework/mutation.py b/graphene_django/rest_framework/mutation.py
index 4062a4423..000b21e18 100644
--- a/graphene_django/rest_framework/mutation.py
+++ b/graphene_django/rest_framework/mutation.py
@@ -72,6 +72,7 @@ def __init_subclass_with_meta__(
         _meta=None,
         **options
     ):
+
         if not serializer_class:
             raise Exception("serializer_class is required for the SerializerMutation")
 
@@ -113,7 +114,7 @@ def __init_subclass_with_meta__(
         _meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
 
         input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
-        super().__init_subclass_with_meta__(
+        super(SerializerMutation, cls).__init_subclass_with_meta__(
             _meta=_meta, input_fields=input_fields, **options
         )
 
diff --git a/graphene_django/rest_framework/serializer_converter.py b/graphene_django/rest_framework/serializer_converter.py
index 1d850f031..b26e5e67e 100644
--- a/graphene_django/rest_framework/serializer_converter.py
+++ b/graphene_django/rest_framework/serializer_converter.py
@@ -72,7 +72,7 @@ def convert_serializer_to_input_type(serializer_class):
         for name, field in serializer.fields.items()
     }
     ret_type = type(
-        f"{serializer.__class__.__name__}Input",
+        "{}Input".format(serializer.__class__.__name__),
         (graphene.InputObjectType,),
         items,
     )
@@ -110,15 +110,11 @@ def convert_serializer_field_to_bool(field):
 
 
 @get_graphene_type_from_serializer_field.register(serializers.FloatField)
+@get_graphene_type_from_serializer_field.register(serializers.DecimalField)
 def convert_serializer_field_to_float(field):
     return graphene.Float
 
 
-@get_graphene_type_from_serializer_field.register(serializers.DecimalField)
-def convert_serializer_field_to_decimal(field):
-    return graphene.Decimal
-
-
 @get_graphene_type_from_serializer_field.register(serializers.DateTimeField)
 def convert_serializer_field_to_datetime_time(field):
     return graphene.types.datetime.DateTime
diff --git a/graphene_django/rest_framework/tests/test_field_converter.py b/graphene_django/rest_framework/tests/test_field_converter.py
index 8da8377c0..daa83495f 100644
--- a/graphene_django/rest_framework/tests/test_field_converter.py
+++ b/graphene_django/rest_framework/tests/test_field_converter.py
@@ -3,7 +3,7 @@
 import graphene
 from django.db import models
 from graphene import InputObjectType
-from pytest import raises
+from py.test import raises
 from rest_framework import serializers
 
 from ..serializer_converter import convert_serializer_field
@@ -133,9 +133,9 @@ def test_should_float_convert_float():
     assert_conversion(serializers.FloatField, graphene.Float)
 
 
-def test_should_decimal_convert_decimal():
+def test_should_decimal_convert_float():
     assert_conversion(
-        serializers.DecimalField, graphene.Decimal, max_digits=4, decimal_places=2
+        serializers.DecimalField, graphene.Float, max_digits=4, decimal_places=2
     )
 
 
diff --git a/graphene_django/rest_framework/tests/test_mutation.py b/graphene_django/rest_framework/tests/test_mutation.py
index 5de823773..e0e560259 100644
--- a/graphene_django/rest_framework/tests/test_mutation.py
+++ b/graphene_django/rest_framework/tests/test_mutation.py
@@ -1,6 +1,6 @@
 import datetime
 
-from pytest import raises
+from py.test import raises
 from rest_framework import serializers
 
 from graphene import Field, ResolveInfo
diff --git a/graphene_django/settings.py b/graphene_django/settings.py
index 9c7dc3861..467c6a320 100644
--- a/graphene_django/settings.py
+++ b/graphene_django/settings.py
@@ -11,6 +11,7 @@
 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
@@ -40,9 +41,7 @@
     # 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:
@@ -77,7 +76,7 @@ def import_from_string(val, setting_name):
         module = importlib.import_module(module_path)
         return getattr(module, class_name)
     except (ImportError, AttributeError) as e:
-        msg = "Could not import '{}' for Graphene setting '{}'. {}: {}.".format(
+        msg = "Could not import '%s' for Graphene setting '%s'. %s: %s." % (
             val,
             setting_name,
             e.__class__.__name__,
@@ -86,7 +85,7 @@ def import_from_string(val, setting_name):
         raise ImportError(msg)
 
 
-class GrapheneSettings:
+class GrapheneSettings(object):
     """
     A settings object, that allows API settings to be accessed as properties.
     For example:
diff --git a/graphene_django/static/graphene_django/graphiql.js b/graphene_django/static/graphene_django/graphiql.js
index 901c9910b..ac010e83d 100644
--- a/graphene_django/static/graphene_django/graphiql.js
+++ b/graphene_django/static/graphene_django/graphiql.js
@@ -5,12 +5,19 @@
   GraphiQL,
   React,
   ReactDOM,
-  graphqlWs,
-  GraphiQLPluginExplorer,
+  SubscriptionsTransportWs,
   fetch,
   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 = {};
@@ -53,34 +60,98 @@
 
   var fetchURL = locationQuery(otherParams);
 
+  // Defines a GraphQL fetcher using the fetch API.
+  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
+    }
+    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;
+        }
+      });
+  }
+
   // Derive the subscription URL. If the SUBSCRIPTION_URL setting is specified, uses that value. Otherwise
   // assumes the current window location with an appropriate websocket protocol.
   var subscribeURL =
     location.origin.replace(/^http/, "ws") +
     (GRAPHENE_SETTINGS.subscriptionPath || location.pathname);
 
-  function trueLambda() { return true; };
+  // Create a subscription client.
+  var subscriptionClient = new SubscriptionsTransportWs.SubscriptionClient(
+    subscribeURL,
+    {
+      // Reconnect after any interruptions.
+      reconnect: true,
+      // Delay socket initialization until the first subscription is started.
+      lazy: true,
+    },
+  );
 
-  var headers = {};
-  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
+  // Keep a reference to the currently-active subscription, if available.
+  var activeSubscription = null;
+
+  // Define a GraphQL fetcher that can intelligently route queries based on the operation type.
+  function graphQLFetcher(graphQLParams, opts) {
+    var operationType = getOperationType(graphQLParams);
+
+    // If we're about to execute a new operation, and we have an active subscription,
+    // unsubscribe before continuing.
+    if (activeSubscription) {
+      activeSubscription.unsubscribe();
+      activeSubscription = null;
+    }
+
+    if (operationType === "subscription") {
+      return {
+        subscribe: function (observer) {
+          activeSubscription = subscriptionClient;
+          return subscriptionClient.request(graphQLParams, opts).subscribe(observer);
+        },
+      };
+    } else {
+      return httpClient(graphQLParams, opts);
+    }
   }
 
-  var graphQLFetcher = GraphiQL.createFetcher({
-    url: fetchURL,
-    wsClient: graphqlWs.createClient({
-      url: subscribeURL,
-      shouldRetry: trueLambda,
-      lazy: true,
-    }),
-    headers: headers
-  })
+  // Determine the type of operation being executed for a given set of GraphQL parameters.
+  function getOperationType(graphQLParams) {
+    // Run a regex against the query to determine the operation type (query, mutation, subscription).
+    var operationRegex = new RegExp(
+      // Look for lines that start with an operation keyword, ignoring whitespace.
+      "^\\s*(query|mutation|subscription)\\s*" +
+        // The operation keyword should be followed by whitespace and the operationName in the GraphQL parameters (if available).
+        (graphQLParams.operationName ? ("\\s+" + graphQLParams.operationName) : "") +
+        // The line should eventually encounter an opening curly brace.
+        "[^\\{]*\\{",
+      // Enable multiline matching.
+      "m",
+    );
+    var match = operationRegex.exec(graphQLParams.query);
+    if (!match) {
+      return "query";
+    }
+
+    return match[1];
+  }
 
   // When the query and variables string is edited, update the URL bar so
   // that it can be easily shared.
@@ -99,44 +170,23 @@
   function updateURL() {
     history.replaceState(null, null, locationQuery(parameters));
   }
-
-  function GraphiQLWithExplorer() {
-    var [query, setQuery] = React.useState(parameters.query);
-
-    function handleQuery(query) {
-      setQuery(query);
-      onEditQuery(query);
-    }
-
-    var explorerPlugin = GraphiQLPluginExplorer.useExplorerPlugin({
-      query: query,
-      onEdit: handleQuery,
-    });
-
-    var options = {
-      fetcher: graphQLFetcher,
-      plugins: [explorerPlugin],
-      defaultEditorToolsVisibility: true,
-      onEditQuery: handleQuery,
-      onEditVariables: onEditVariables,
-      onEditOperationName: onEditOperationName,
-      isHeadersEditorEnabled: GRAPHENE_SETTINGS.graphiqlHeaderEditorEnabled,
-      shouldPersistHeaders: GRAPHENE_SETTINGS.graphiqlShouldPersistHeaders,
-      query: query,
-    };
-    if (parameters.variables) {
-      options.variables = parameters.variables;
-    }
-    if (parameters.operation_name) {
-      options.operationName = parameters.operation_name;
-    }
-
-    return React.createElement(GraphiQL, options);
+  var options = {
+    fetcher: graphQLFetcher,
+    onEditQuery: onEditQuery,
+    onEditVariables: onEditVariables,
+    onEditOperationName: onEditOperationName,
+    headerEditorEnabled: GRAPHENE_SETTINGS.graphiqlHeaderEditorEnabled,
+    query: parameters.query,
+  };
+  if (parameters.variables) {
+    options.variables = parameters.variables;
+  }
+  if (parameters.operation_name) {
+    options.operationName = parameters.operation_name;
   }
-
   // Render <GraphiQL /> into the body.
   ReactDOM.render(
-    React.createElement(GraphiQLWithExplorer),
+    React.createElement(GraphiQL, options),
     document.getElementById("editor"),
   );
 })(
@@ -146,8 +196,7 @@
   window.GraphiQL,
   window.React,
   window.ReactDOM,
-  window.graphqlWs,
-  window.GraphiQLPluginExplorer,
+  window.SubscriptionsTransportWs,
   window.fetch,
   window.history,
   window.location,
diff --git a/graphene_django/templates/graphene/graphiql.html b/graphene_django/templates/graphene/graphiql.html
index ddff8fc8e..cec48930b 100644
--- a/graphene_django/templates/graphene/graphiql.html
+++ b/graphene_django/templates/graphene/graphiql.html
@@ -33,12 +33,9 @@
   <script src="https://cdn.jsdelivr.net/npm/graphiql@{{graphiql_version}}/graphiql.min.js"
           integrity="{{graphiql_sri}}"
           crossorigin="anonymous"></script>
-  <script src="https://cdn.jsdelivr.net/npm/graphql-ws@{{subscriptions_transport_ws_version}}/umd/graphql-ws.min.js"
+  <script src="https://cdn.jsdelivr.net/npm/subscriptions-transport-ws@{{subscriptions_transport_ws_version}}/browser/client.js"
           integrity="{{subscriptions_transport_ws_sri}}"
           crossorigin="anonymous"></script>
-  <script src="https://cdn.jsdelivr.net/npm/@graphiql/plugin-explorer@{{graphiql_plugin_explorer_version}}/dist/graphiql-plugin-explorer.umd.js"
-          integrity="{{graphiql_plugin_explorer_sri}}"
-          crossorigin="anonymous"></script>
 </head>
 <body>
   <div id="editor"></div>
@@ -49,7 +46,6 @@
       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 --git a/graphene_django/tests/issues/test_520.py b/graphene_django/tests/issues/test_520.py
index 4e55f9655..60c5b543c 100644
--- a/graphene_django/tests/issues/test_520.py
+++ b/graphene_django/tests/issues/test_520.py
@@ -8,8 +8,8 @@
 
 from graphene import Field, ResolveInfo
 from graphene.types.inputobjecttype import InputObjectType
-from pytest import raises
-from pytest import mark
+from py.test import raises
+from py.test import mark
 from rest_framework import serializers
 
 from ...types import DjangoObjectType
diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py
index 636f74c6d..7b76cd378 100644
--- a/graphene_django/tests/models.py
+++ b/graphene_django/tests/models.py
@@ -1,3 +1,5 @@
+from __future__ import absolute_import
+
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 
@@ -11,9 +13,6 @@ class Person(models.Model):
 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):
@@ -35,7 +34,7 @@ class Film(models.Model):
 
 class DoeReporterManager(models.Manager):
     def get_queryset(self):
-        return super().get_queryset().filter(last_name="Doe")
+        return super(DoeReporterManager, self).get_queryset().filter(last_name="Doe")
 
 
 class Reporter(models.Model):
@@ -55,7 +54,7 @@ class Reporter(models.Model):
     )
 
     def __str__(self):  # __unicode__ on Python 2
-        return f"{self.first_name} {self.last_name}"
+        return "%s %s" % (self.first_name, self.last_name)
 
     def __init__(self, *args, **kwargs):
         """
@@ -65,7 +64,7 @@ def __init__(self, *args, **kwargs):
         when a CNNReporter is pulled from the database, it is still
         of type Reporter. This was added to test proxy model support.
         """
-        super().__init__(*args, **kwargs)
+        super(Reporter, self).__init__(*args, **kwargs)
         if self.reporter_type == 2:  # quick and dirty way without enums
             self.__class__ = CNNReporter
 
@@ -75,7 +74,7 @@ def some_method(self):
 
 class CNNReporterManager(models.Manager):
     def get_queryset(self):
-        return super().get_queryset().filter(reporter_type=2)
+        return super(CNNReporterManager, self).get_queryset().filter(reporter_type=2)
 
 
 class CNNReporter(Reporter):
diff --git a/graphene_django/tests/schema_view.py b/graphene_django/tests/schema_view.py
index 4d538ba74..8ed2ecfde 100644
--- a/graphene_django/tests/schema_view.py
+++ b/graphene_django/tests/schema_view.py
@@ -5,6 +5,7 @@
 
 
 class QueryRoot(ObjectType):
+
     thrower = graphene.String(required=True)
     request = graphene.String(required=True)
     test = graphene.String(who=graphene.String())
diff --git a/graphene_django/tests/test_command.py b/graphene_django/tests/test_command.py
index a281abbd2..70116b8ab 100644
--- a/graphene_django/tests/test_command.py
+++ b/graphene_django/tests/test_command.py
@@ -2,7 +2,7 @@
 
 from django.core import management
 from io import StringIO
-from unittest.mock import mock_open, patch
+from mock import mock_open, patch
 
 from graphene import ObjectType, Schema, String
 
@@ -53,5 +53,6 @@ class Query(ObjectType):
         """\
         type Query {
           hi: String
-        }"""
+        }
+    """
     )
diff --git a/graphene_django/tests/test_converter.py b/graphene_django/tests/test_converter.py
index 4996505a9..afd744f33 100644
--- a/graphene_django/tests/test_converter.py
+++ b/graphene_django/tests/test_converter.py
@@ -3,14 +3,13 @@
 import pytest
 from django.db import models
 from django.utils.translation import gettext_lazy as _
-from pytest import raises
+from py.test import raises
 
 import graphene
 from graphene import NonNull
 from graphene.relay import ConnectionField, Node
 from graphene.types.datetime import Date, DateTime, Time
 from graphene.types.json import JSONString
-from graphene.types.scalars import BigInt
 
 from ..compat import (
     ArrayField,
@@ -141,8 +140,8 @@ def test_should_small_integer_convert_int():
     assert_conversion(models.SmallIntegerField, graphene.Int)
 
 
-def test_should_big_integer_convert_big_int():
-    assert_conversion(models.BigIntegerField, BigInt)
+def test_should_big_integer_convert_int():
+    assert_conversion(models.BigIntegerField, graphene.Int)
 
 
 def test_should_integer_convert_int():
diff --git a/graphene_django/tests/test_fields.py b/graphene_django/tests/test_fields.py
index 8c7b78d36..835de7837 100644
--- a/graphene_django/tests/test_fields.py
+++ b/graphene_django/tests/test_fields.py
@@ -1,6 +1,5 @@
 import datetime
-import re
-from django.db.models import Count, Prefetch
+from django.db.models import Count
 
 import pytest
 
@@ -8,12 +7,8 @@
 
 from ..fields import DjangoListField
 from ..types import DjangoObjectType
-from .models import (
-    Article as ArticleModel,
-    Film as FilmModel,
-    FilmDetails as FilmDetailsModel,
-    Reporter as ReporterModel,
-)
+from .models import Article as ArticleModel
+from .models import Reporter as ReporterModel
 
 
 class TestDjangoListField:
@@ -505,145 +500,3 @@ class Query(ObjectType):
 
         assert not result.errors
         assert result.data == {"reporters": [{"firstName": "Tara"}]}
-
-    def test_select_related_and_prefetch_related_are_respected(
-        self, django_assert_num_queries
-    ):
-        class Article(DjangoObjectType):
-            class Meta:
-                model = ArticleModel
-                fields = ("headline", "editor", "reporter")
-
-        class Film(DjangoObjectType):
-            class Meta:
-                model = FilmModel
-                fields = ("genre", "details")
-
-        class FilmDetail(DjangoObjectType):
-            class Meta:
-                model = FilmDetailsModel
-                fields = ("location",)
-
-        class Reporter(DjangoObjectType):
-            class Meta:
-                model = ReporterModel
-                fields = ("first_name", "articles", "films")
-
-        class Query(ObjectType):
-            articles = DjangoListField(Article)
-
-            @staticmethod
-            def resolve_articles(root, info):
-                # Optimize for querying associated editors and reporters, and the films and film
-                # details of those reporters. This is similar to what would happen using a library
-                # like https://github.com/tfoxy/graphene-django-optimizer for a query like the one
-                # below (albeit simplified and hardcoded here).
-                return ArticleModel.objects.select_related(
-                    "editor", "reporter"
-                ).prefetch_related(
-                    Prefetch(
-                        "reporter__films",
-                        queryset=FilmModel.objects.select_related("details"),
-                    ),
-                )
-
-        schema = Schema(query=Query)
-
-        query = """
-            query {
-                articles {
-                    headline
-
-                    editor {
-                        firstName
-                    }
-
-                    reporter {
-                        firstName
-
-                        films {
-                            genre
-
-                            details {
-                                location
-                            }
-                        }
-                    }
-                }
-            }
-        """
-
-        r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
-        r2 = ReporterModel.objects.create(first_name="Debra", last_name="Payne")
-
-        ArticleModel.objects.create(
-            headline="Amazing news",
-            reporter=r1,
-            pub_date=datetime.date.today(),
-            pub_date_time=datetime.datetime.now(),
-            editor=r2,
-        )
-        ArticleModel.objects.create(
-            headline="Not so good news",
-            reporter=r2,
-            pub_date=datetime.date.today(),
-            pub_date_time=datetime.datetime.now(),
-            editor=r1,
-        )
-
-        film1 = FilmModel.objects.create(genre="ac")
-        film2 = FilmModel.objects.create(genre="ot")
-        film3 = FilmModel.objects.create(genre="do")
-        FilmDetailsModel.objects.create(location="Hollywood", film=film1)
-        FilmDetailsModel.objects.create(location="Antarctica", film=film3)
-        r1.films.add(film1, film2)
-        r2.films.add(film3)
-
-        # We expect 2 queries to be performed based on the above resolver definition: one for all
-        # articles joined with the reporters model (for associated editors and reporters), and one
-        # for the films prefetch (which includes its `select_related` JOIN logic in its queryset)
-        with django_assert_num_queries(2) as captured:
-            result = schema.execute(query)
-
-        assert not result.errors
-        assert result.data == {
-            "articles": [
-                {
-                    "headline": "Amazing news",
-                    "editor": {"firstName": "Debra"},
-                    "reporter": {
-                        "firstName": "Tara",
-                        "films": [
-                            {"genre": "AC", "details": {"location": "Hollywood"}},
-                            {"genre": "OT", "details": None},
-                        ],
-                    },
-                },
-                {
-                    "headline": "Not so good news",
-                    "editor": {"firstName": "Tara"},
-                    "reporter": {
-                        "firstName": "Debra",
-                        "films": [
-                            {"genre": "DO", "details": {"location": "Antarctica"}},
-                        ],
-                    },
-                },
-            ]
-        }
-
-        assert len(captured.captured_queries) == 2  # Sanity-check
-
-        # First we should have queried for all articles in a single query, joining on the reporters
-        # model (for the editors and reporters ForeignKeys)
-        assert re.match(
-            r'SELECT .* "tests_article" INNER JOIN "tests_reporter"',
-            captured.captured_queries[0]["sql"],
-        )
-
-        # Then we should have queried for all of the films of all reporters, joined with the film
-        # details for each film, using a single query
-        assert re.match(
-            r'SELECT .* FROM "tests_film" INNER JOIN "tests_film_reporters" .* LEFT OUTER JOIN "tests_filmdetails"',
-            captured.captured_queries[1]["sql"],
-        )
diff --git a/graphene_django/tests/test_forms.py b/graphene_django/tests/test_forms.py
index a42fcee9e..fa6628d26 100644
--- a/graphene_django/tests/test_forms.py
+++ b/graphene_django/tests/test_forms.py
@@ -1,5 +1,5 @@
 from django.core.exceptions import ValidationError
-from pytest import raises
+from py.test import raises
 
 from ..forms import GlobalIDFormField, GlobalIDMultipleChoiceField
 
diff --git a/graphene_django/tests/test_get_queryset.py b/graphene_django/tests/test_get_queryset.py
deleted file mode 100644
index 7cbaa54d2..000000000
--- a/graphene_django/tests/test_get_queryset.py
+++ /dev/null
@@ -1,235 +0,0 @@
-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)
-
-    NOTE: For now, we do not expect this get_queryset method to be called for nested
-    objects, as the original attempt to do so prevented SQL query-optimization with
-    `select_related`/`prefetch_related` and caused N+1 queries. See discussions here
-    https://github.com/graphql-python/graphene-django/pull/1315/files#r1015659857
-    and here https://github.com/graphql-python/graphene-django/pull/1401.
-    """
-
-    @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"}}
-
-
-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"}}
diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py
index 383ff2e33..aabe19ceb 100644
--- a/graphene_django/tests/test_query.py
+++ b/graphene_django/tests/test_query.py
@@ -6,7 +6,7 @@
 from django.db.models import Q
 from django.utils.functional import SimpleLazyObject
 from graphql_relay import to_global_id
-from pytest import raises
+from py.test import raises
 
 import graphene
 from graphene.relay import Node
@@ -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, Person, Pet, Reporter
+from .models import Article, CNNReporter, Film, FilmDetails, Reporter
 
 
 def test_should_query_only_fields():
@@ -251,8 +251,8 @@ def resolve_reporter(self, info):
 
 
 def test_should_query_onetoone_fields():
-    film = Film.objects.create(id=1)
-    film_details = FilmDetails.objects.create(id=1, film=film)
+    film = Film(id=1)
+    film_details = FilmDetails(id=1, film=film)
 
     class FilmNode(DjangoObjectType):
         class Meta:
@@ -780,6 +780,7 @@ def resolve_all_reporters(self, info, **args):
 
 
 def test_should_query_connectionfields_with_last():
+
     r = Reporter.objects.create(
         first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
     )
@@ -817,6 +818,7 @@ def resolve_all_reporters(self, info, **args):
 
 
 def test_should_query_connectionfields_with_manager():
+
     r = Reporter.objects.create(
         first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1
     )
@@ -1149,9 +1151,9 @@ class Query(graphene.ObjectType):
 
 REPORTERS = [
     dict(
-        first_name=f"First {i}",
-        last_name=f"Last {i}",
-        email=f"johndoe+{i}@example.com",
+        first_name="First {}".format(i),
+        last_name="Last {}".format(i),
+        email="johndoe+{}@example.com".format(i),
         a_choice=1,
     )
     for i in range(6)
@@ -1241,7 +1243,6 @@ class Query(graphene.ObjectType):
     }
 
 
-@pytest.mark.parametrize("max_limit", [100, 4])
 class TestBackwardPagination:
     def setup_schema(self, graphene_settings, max_limit):
         graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
@@ -1260,8 +1261,8 @@ class Query(graphene.ObjectType):
         schema = graphene.Schema(query=Query)
         return schema
 
-    def test_query_last(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
+    def do_queries(self, schema):
+        # Simply last 3
         query_last = """
             query {
                 allReporters(last: 3) {
@@ -1281,8 +1282,7 @@ def test_query_last(self, graphene_settings, max_limit):
             e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
         ] == ["First 3", "First 4", "First 5"]
 
-    def test_query_first_and_last(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
+        # Use a combination of first and last
         query_first_and_last = """
             query {
                 allReporters(first: 4, last: 3) {
@@ -1302,8 +1302,7 @@ def test_query_first_and_last(self, graphene_settings, max_limit):
             e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
         ] == ["First 1", "First 2", "First 3"]
 
-    def test_query_first_last_and_after(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
+        # Use a combination of first and last and after
         query_first_last_and_after = """
             query queryAfter($after: String) {
                 allReporters(first: 4, last: 3, after: $after) {
@@ -1318,8 +1317,7 @@ def test_query_first_last_and_after(self, graphene_settings, max_limit):
 
         after = base64.b64encode(b"arrayconnection:0").decode()
         result = schema.execute(
-            query_first_last_and_after,
-            variable_values=dict(after=after),
+            query_first_last_and_after, variable_values=dict(after=after)
         )
         assert not result.errors
         assert len(result.data["allReporters"]["edges"]) == 3
@@ -1327,35 +1325,20 @@ def test_query_first_last_and_after(self, graphene_settings, max_limit):
             e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
         ] == ["First 2", "First 3", "First 4"]
 
-    def test_query_last_and_before(self, graphene_settings, max_limit):
-        schema = self.setup_schema(graphene_settings, max_limit=max_limit)
-        query_first_last_and_after = """
-            query queryAfter($before: String) {
-                allReporters(last: 1, before: $before) {
-                    edges {
-                        node {
-                            firstName
-                        }
-                    }
-                }
-            }
+    def test_should_query(self, graphene_settings):
         """
+        Backward pagination should work as expected
+        """
+        schema = self.setup_schema(graphene_settings, max_limit=100)
+        self.do_queries(schema)
 
-        result = schema.execute(
-            query_first_last_and_after,
-        )
-        assert not result.errors
-        assert len(result.data["allReporters"]["edges"]) == 1
-        assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 5"
-
-        before = base64.b64encode(b"arrayconnection:5").decode()
-        result = schema.execute(
-            query_first_last_and_after,
-            variable_values=dict(before=before),
-        )
-        assert not result.errors
-        assert len(result.data["allReporters"]["edges"]) == 1
-        assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 4"
+    def test_should_query_with_low_max_limit(self, graphene_settings):
+        """
+        When doing backward pagination (using last) in combination with a max limit higher than the number of objects
+        we should really retrieve the last ones.
+        """
+        schema = self.setup_schema(graphene_settings, max_limit=4)
+        self.do_queries(schema)
 
 
 def test_should_preserve_prefetch_related(django_assert_num_queries):
@@ -1497,11 +1480,7 @@ class Query(graphene.ObjectType):
     result = schema.execute(query)
     assert not result.errors
     expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Some", "lastName": "Guy"}},
-            ]
-        }
+        "allReporters": {"edges": [{"node": {"firstName": "Some", "lastName": "Guy"}},]}
     }
     assert result.data == expected
 
@@ -1542,9 +1521,7 @@ class Query(graphene.ObjectType):
     assert not result.errors
     expected = {
         "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
+            "edges": [{"node": {"firstName": "Some", "lastName": "Lady"}},]
         }
     }
     assert result.data == expected
@@ -1613,149 +1590,6 @@ class Query(graphene.ObjectType):
     result = schema.execute(query, variable_values=dict(after=after))
     assert not result.errors
     expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-
-def test_connection_should_succeed_if_last_higher_than_number_of_objects():
-    class ReporterType(DjangoObjectType):
-        class Meta:
-            model = Reporter
-            interfaces = (Node,)
-            fields = "__all__"
-
-    class Query(graphene.ObjectType):
-        all_reporters = DjangoConnectionField(ReporterType)
-
-    schema = graphene.Schema(query=Query)
-    query = """
-        query ReporterPromiseConnectionQuery ($last: Int) {
-            allReporters(last: $last) {
-                edges {
-                    node {
-                        firstName
-                        lastName
-                    }
-                }
-            }
-        }
-    """
-
-    result = schema.execute(query, variable_values=dict(last=2))
-    assert not result.errors
-    expected = {"allReporters": {"edges": []}}
-    assert result.data == expected
-
-    Reporter.objects.create(first_name="John", last_name="Doe")
-    Reporter.objects.create(first_name="Some", last_name="Guy")
-    Reporter.objects.create(first_name="Jane", last_name="Roe")
-    Reporter.objects.create(first_name="Some", last_name="Lady")
-
-    result = schema.execute(query, variable_values=dict(last=2))
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-    result = schema.execute(query, variable_values=dict(last=4))
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "John", "lastName": "Doe"}},
-                {"node": {"firstName": "Some", "lastName": "Guy"}},
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
-        }
-    }
-    assert result.data == expected
-
-    result = schema.execute(query, variable_values=dict(last=20))
-    assert not result.errors
-    expected = {
-        "allReporters": {
-            "edges": [
-                {"node": {"firstName": "John", "lastName": "Doe"}},
-                {"node": {"firstName": "Some", "lastName": "Guy"}},
-                {"node": {"firstName": "Jane", "lastName": "Roe"}},
-                {"node": {"firstName": "Some", "lastName": "Lady"}},
-            ]
-        }
+        "allReporters": {"edges": [{"node": {"firstName": "Jane", "lastName": "Roe"}},]}
     }
     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 --git a/graphene_django/tests/test_schema.py b/graphene_django/tests/test_schema.py
index ff2d8a668..1c889f11c 100644
--- a/graphene_django/tests/test_schema.py
+++ b/graphene_django/tests/test_schema.py
@@ -1,4 +1,4 @@
-from pytest import raises
+from py.test import raises
 
 from ..registry import Registry
 from ..types import DjangoObjectType
diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py
index fad26e2ab..bde72c7a6 100644
--- a/graphene_django/tests/test_types.py
+++ b/graphene_django/tests/test_types.py
@@ -3,7 +3,7 @@
 
 import pytest
 from django.db import models
-from unittest.mock import patch
+from mock import patch
 
 from graphene import Connection, Field, Interface, ObjectType, Schema, String
 from graphene.relay import Node
@@ -104,7 +104,7 @@ class Meta:
         @classmethod
         def __init_subclass_with_meta__(cls, **options):
             options.setdefault("_meta", ArticleTypeOptions(cls))
-            super().__init_subclass_with_meta__(**options)
+            super(ArticleType, cls).__init_subclass_with_meta__(**options)
 
     class Article(ArticleType):
         class Meta:
@@ -183,7 +183,7 @@ def test_schema_representation():
           pets: [Reporter!]!
           aChoice: TestsReporterAChoiceChoices
           reporterType: TestsReporterReporterTypeChoices
-          articles(offset: Int, before: String, after: String, first: Int, last: Int): ArticleConnection!
+          articles(offset: Int = null, before: String = null, after: String = null, first: Int = null, last: Int = null): ArticleConnection!
         }
 
         \"""An enumeration.\"""
@@ -244,7 +244,8 @@ def test_schema_representation():
             \"""The ID of the object\"""
             id: ID!
           ): Node
-        }"""
+        }
+        """
     )
     assert str(schema) == expected
 
@@ -484,7 +485,7 @@ class Meta:
 
 
 def custom_enum_name(field):
-    return f"CustomEnum{field.name.title()}"
+    return "CustomEnum{}".format(field.name.title())
 
 
 class TestDjangoObjectType:
@@ -524,7 +525,8 @@ class Query(ObjectType):
               id: ID!
               kind: String!
               cuteness: Int!
-            }"""
+            }
+            """
         )
 
     def test_django_objecttype_convert_choices_enum_list(self, PetModel):
@@ -558,7 +560,8 @@ class Query(ObjectType):
 
               \"""Dog\"""
               DOG
-            }"""
+            }
+            """
         )
 
     def test_django_objecttype_convert_choices_enum_empty_list(self, PetModel):
@@ -583,7 +586,8 @@ class Query(ObjectType):
               id: ID!
               kind: String!
               cuteness: Int!
-            }"""
+            }
+            """
         )
 
     def test_django_objecttype_convert_choices_enum_naming_collisions(
@@ -617,7 +621,8 @@ class Query(ObjectType):
 
               \"""Dog\"""
               DOG
-            }"""
+            }
+            """
         )
 
     def test_django_objecttype_choices_custom_enum_name(
@@ -655,7 +660,8 @@ class Query(ObjectType):
 
               \"""Dog\"""
               DOG
-            }"""
+            }
+            """
         )
 
 
diff --git a/graphene_django/tests/test_utils.py b/graphene_django/tests/test_utils.py
index fa269b44c..adad00efa 100644
--- a/graphene_django/tests/test_utils.py
+++ b/graphene_django/tests/test_utils.py
@@ -2,7 +2,7 @@
 
 import pytest
 from django.utils.translation import gettext_lazy
-from unittest.mock import patch
+from mock import patch
 
 from ..utils import camelize, get_model_fields, GraphQLTestCase
 from .models import Film, Reporter
@@ -11,11 +11,11 @@
 
 def test_get_model_fields_no_duplication():
     reporter_fields = get_model_fields(Reporter)
-    reporter_name_set = {field[0] for field in reporter_fields}
+    reporter_name_set = set([field[0] for field in reporter_fields])
     assert len(reporter_fields) == len(reporter_name_set)
 
     film_fields = get_model_fields(Film)
-    film_name_set = {field[0] for field in film_fields}
+    film_name_set = set([field[0] for field in film_fields])
     assert len(film_fields) == len(film_name_set)
 
 
@@ -54,7 +54,7 @@ def runTest(self):
     tc._pre_setup()
     tc.setUpClass()
     tc.query("query { }", operation_name="QueryName")
-    body = json.loads(post_mock.call_args[0][1])
+    body = json.loads(post_mock.call_args.args[1])
     # `operationName` field from https://graphql.org/learn/serving-over-http/#post-request
     assert (
         "operationName",
@@ -66,7 +66,7 @@ def runTest(self):
 @patch("graphene_django.utils.testing.Client.post")
 def test_graphql_query_case_operation_name(post_mock):
     graphql_query("query { }", operation_name="QueryName")
-    body = json.loads(post_mock.call_args[0][1])
+    body = json.loads(post_mock.call_args.args[1])
     # `operationName` field from https://graphql.org/learn/serving-over-http/#post-request
     assert (
         "operationName",
diff --git a/graphene_django/tests/test_views.py b/graphene_django/tests/test_views.py
index 5cadefe7e..945fa87eb 100644
--- a/graphene_django/tests/test_views.py
+++ b/graphene_django/tests/test_views.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-from unittest.mock import patch
+from mock import patch
 
 from django.db import connection
 
@@ -109,10 +109,12 @@ def test_reports_validation_errors(client):
             {
                 "message": "Cannot query field 'unknownOne' on type 'QueryRoot'.",
                 "locations": [{"line": 1, "column": 9}],
+                "path": None,
             },
             {
                 "message": "Cannot query field 'unknownTwo' on type 'QueryRoot'.",
                 "locations": [{"line": 1, "column": 21}],
+                "path": None,
             },
         ]
     }
@@ -133,6 +135,8 @@ def test_errors_when_missing_operation_name(client):
         "errors": [
             {
                 "message": "Must provide operation name if query contains multiple operations.",
+                "locations": None,
+                "path": None,
             }
         ]
     }
@@ -473,6 +477,7 @@ def test_handles_syntax_errors_caught_by_graphql(client):
             {
                 "locations": [{"column": 1, "line": 1}],
                 "message": "Syntax Error: Unexpected Name 'syntaxerror'.",
+                "path": None,
             }
         ]
     }
@@ -507,7 +512,7 @@ def test_handles_invalid_json_bodies(client):
 
 def test_handles_django_request_error(client, monkeypatch):
     def mocked_read(*args):
-        raise OSError("foo-bar")
+        raise IOError("foo-bar")
 
     monkeypatch.setattr("django.http.request.HttpRequest.read", mocked_read)
 
diff --git a/graphene_django/tests/urls.py b/graphene_django/tests/urls.py
index 3702ce5cf..66b3fc4d2 100644
--- a/graphene_django/tests/urls.py
+++ b/graphene_django/tests/urls.py
@@ -1,8 +1,8 @@
-from django.urls import path
+from django.conf.urls import url
 
 from ..views import GraphQLView
 
 urlpatterns = [
-    path("graphql/batch", GraphQLView.as_view(batch=True)),
-    path("graphql", GraphQLView.as_view(graphiql=True)),
+    url(r"^graphql/batch", GraphQLView.as_view(batch=True)),
+    url(r"^graphql", GraphQLView.as_view(graphiql=True)),
 ]
diff --git a/graphene_django/tests/urls_inherited.py b/graphene_django/tests/urls_inherited.py
index 1e65da0a5..6fa801916 100644
--- a/graphene_django/tests/urls_inherited.py
+++ b/graphene_django/tests/urls_inherited.py
@@ -1,4 +1,4 @@
-from django.urls import path
+from django.conf.urls import url
 
 from ..views import GraphQLView
 from .schema_view import schema
@@ -10,4 +10,4 @@ class CustomGraphQLView(GraphQLView):
     pretty = True
 
 
-urlpatterns = [path("graphql/inherited/", CustomGraphQLView.as_view())]
+urlpatterns = [url(r"^graphql/inherited/$", CustomGraphQLView.as_view())]
diff --git a/graphene_django/tests/urls_pretty.py b/graphene_django/tests/urls_pretty.py
index 62759347b..1133c870f 100644
--- a/graphene_django/tests/urls_pretty.py
+++ b/graphene_django/tests/urls_pretty.py
@@ -1,6 +1,6 @@
-from django.urls import path
+from django.conf.urls import url
 
 from ..views import GraphQLView
 from .schema_view import schema
 
-urlpatterns = [path("graphql", GraphQLView.as_view(schema=schema, pretty=True))]
+urlpatterns = [url(r"^graphql", GraphQLView.as_view(schema=schema, pretty=True))]
diff --git a/graphene_django/types.py b/graphene_django/types.py
index a6e54af41..d27241262 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: Type[Model]
+    model = None  # type: Model
     registry = None  # type: Registry
     connection = None  # type: Type[Connection]
 
@@ -168,8 +168,10 @@ def __init_subclass_with_meta__(
 
         if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
             raise Exception(
-                "Can only set filter_fields or filterset_class if "
-                "Django-Filter is installed"
+                (
+                    "Can only set filter_fields or filterset_class if "
+                    "Django-Filter is installed"
+                )
             )
 
         assert not (fields and exclude), (
@@ -214,7 +216,7 @@ def __init_subclass_with_meta__(
                 "Creating a DjangoObjectType without either the `fields` "
                 "or the `exclude` option is deprecated. Add an explicit `fields "
                 "= '__all__'` option on DjangoObjectType {class_name} to use all "
-                "fields".format(class_name=cls.__name__),
+                "fields".format(class_name=cls.__name__,),
                 DeprecationWarning,
                 stacklevel=2,
             )
@@ -226,7 +228,7 @@ def __init_subclass_with_meta__(
 
         if use_connection is None and interfaces:
             use_connection = any(
-                issubclass(interface, Node) for interface in interfaces
+                (issubclass(interface, Node) for interface in interfaces)
             )
 
         if use_connection and not connection:
@@ -253,7 +255,7 @@ def __init_subclass_with_meta__(
         _meta.fields = django_fields
         _meta.connection = connection
 
-        super().__init_subclass_with_meta__(
+        super(DjangoObjectType, cls).__init_subclass_with_meta__(
             _meta=_meta, interfaces=interfaces, **options
         )
 
diff --git a/graphene_django/utils/testing.py b/graphene_django/utils/testing.py
index ad9ff35f8..763196dc5 100644
--- a/graphene_django/utils/testing.py
+++ b/graphene_django/utils/testing.py
@@ -3,9 +3,7 @@
 
 from django.test import Client, TestCase, TransactionTestCase
 
-from graphene_django.settings import graphene_settings
-
-DEFAULT_GRAPHQL_URL = "/graphql"
+DEFAULT_GRAPHQL_URL = "/graphql/"
 
 
 def graphql_query(
@@ -21,7 +19,7 @@ def graphql_query(
     Args:
         query (string)              - GraphQL query to run
         operation_name (string)     - If the query is a mutation or named query, you must
-                                      supply the operation_name.  For annon queries ("{ ... }"),
+                                      supply the op_name.  For annon queries ("{ ... }"),
                                       should be None (default).
         input_data (dict)           - If provided, the $input variable in GraphQL will be set
                                       to this value. If both ``input_data`` and ``variables``,
@@ -42,7 +40,7 @@ def graphql_query(
     if client is None:
         client = Client()
     if not graphql_url:
-        graphql_url = graphene_settings.TESTING_ENDPOINT
+        graphql_url = DEFAULT_GRAPHQL_URL
 
     body = {"query": query}
     if operation_name:
@@ -65,13 +63,13 @@ def graphql_query(
     return resp
 
 
-class GraphQLTestMixin:
+class GraphQLTestMixin(object):
     """
     Based on: https://www.sam.today/blog/testing-graphql-with-graphene-django/
     """
 
     # URL to graphql endpoint
-    GRAPHQL_URL = graphene_settings.TESTING_ENDPOINT
+    GRAPHQL_URL = DEFAULT_GRAPHQL_URL
 
     def query(
         self, query, operation_name=None, input_data=None, variables=None, headers=None
@@ -80,7 +78,7 @@ def query(
         Args:
             query (string)    - GraphQL query to run
             operation_name (string)  - If the query is a mutation or named query, you must
-                                supply the operation_name.  For annon queries ("{ ... }"),
+                                supply the op_name.  For annon queries ("{ ... }"),
                                 should be None (default).
             input_data (dict) - If provided, the $input variable in GraphQL will be set
                                 to this value. If both ``input_data`` and ``variables``,
@@ -91,7 +89,7 @@ def query(
             headers (dict)    - If provided, the headers in POST request to GRAPHQL_URL
                                 will be set to this value. Keys should be prepended with
                                 "HTTP_" (e.g. to specify the "Authorization" HTTP header,
-                                use "HTTP_AUTHORIZATION" as the key).
+                                use "HTTP_AUTHORIZATION" as the key).       
 
         Returns:
             Response object from client
diff --git a/graphene_django/utils/tests/test_str_converters.py b/graphene_django/utils/tests/test_str_converters.py
index d3d33c25c..6460c4e23 100644
--- a/graphene_django/utils/tests/test_str_converters.py
+++ b/graphene_django/utils/tests/test_str_converters.py
@@ -6,4 +6,4 @@ def test_to_const():
 
 
 def test_to_const_unicode():
-    assert to_const("Skoða þetta unicode stöff") == "SKODA_THETTA_UNICODE_STOFF"
+    assert to_const(u"Skoða þetta unicode stöff") == "SKODA_THETTA_UNICODE_STOFF"
diff --git a/graphene_django/utils/tests/test_testing.py b/graphene_django/utils/tests/test_testing.py
index de5615859..2ef78f99b 100644
--- a/graphene_django/utils/tests/test_testing.py
+++ b/graphene_django/utils/tests/test_testing.py
@@ -2,7 +2,6 @@
 
 from .. import GraphQLTestCase
 from ...tests.test_types import with_local_registry
-from ...settings import graphene_settings
 from django.test import Client
 
 
@@ -44,11 +43,3 @@ def runTest(self):
 
     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 --git a/graphene_django/views.py b/graphene_django/views.py
index 57336601c..e90d94fa3 100644
--- a/graphene_django/views.py
+++ b/graphene_django/views.py
@@ -11,6 +11,7 @@
 from django.views.generic import View
 from graphql import OperationType, get_operation_ast, parse, validate
 from graphql.error import GraphQLError
+from graphql.error import format_error as format_graphql_error
 from graphql.execution import ExecutionResult
 
 from graphene import Schema
@@ -26,7 +27,7 @@ class HttpError(Exception):
     def __init__(self, response, message=None, *args, **kwargs):
         self.response = response
         self.message = message = message or response.content.decode()
-        super().__init__(message, *args, **kwargs)
+        super(HttpError, self).__init__(message, *args, **kwargs)
 
 
 def get_accepted_content_types(request):
@@ -66,19 +67,16 @@ class GraphQLView(View):
     react_dom_sri = "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0="
 
     # The GraphiQL React app.
-    graphiql_version = "2.4.1"  # "1.0.3"
-    graphiql_sri = "sha256-s+f7CFAPSUIygFnRC2nfoiEKd3liCUy+snSdYFAoLUc="  # "sha256-VR4buIDY9ZXSyCNFHFNik6uSe0MhigCzgN4u7moCOTk="
-    graphiql_css_sri = "sha256-88yn8FJMyGboGs4Bj+Pbb3kWOWXo7jmb+XCRHE+282k="  # "sha256-LwqxjyZgqXDYbpxQJ5zLQeNcf7WVNSJ+r8yp2rnWE/E="
+    graphiql_version = "1.4.1"  # "1.0.3"
+    graphiql_sri = "sha256-JUMkXBQWZMfJ7fGEsTXalxVA10lzKOS9loXdLjwZKi4="  # "sha256-VR4buIDY9ZXSyCNFHFNik6uSe0MhigCzgN4u7moCOTk="
+    graphiql_css_sri = "sha256-Md3vdR7PDzWyo/aGfsFVF4tvS5/eAUWuIsg9QHUusCY="  # "sha256-LwqxjyZgqXDYbpxQJ5zLQeNcf7WVNSJ+r8yp2rnWE/E="
 
     # The websocket transport library for subscriptions.
-    subscriptions_transport_ws_version = "5.12.1"
+    subscriptions_transport_ws_version = "0.9.18"
     subscriptions_transport_ws_sri = (
-        "sha256-EZhvg6ANJrBsgLvLAa0uuHNLepLJVCFYS+xlb5U/bqw="
+        "sha256-i0hAXd4PdJ/cHX3/8tIy/Q/qKiWr5WSTxMFuL9tACkw="
     )
 
-    graphiql_plugin_explorer_version = "0.1.15"
-    graphiql_plugin_explorer_sri = "sha256-3hUuhBXdXlfCj6RTeEkJFtEh/kUG+TCDASFpFPLrzvE="
-
     schema = None
     graphiql = False
     middleware = None
@@ -161,13 +159,10 @@ def dispatch(self, request, *args, **kwargs):
                     graphiql_css_sri=self.graphiql_css_sri,
                     subscriptions_transport_ws_version=self.subscriptions_transport_ws_version,
                     subscriptions_transport_ws_sri=self.subscriptions_transport_ws_sri,
-                    graphiql_plugin_explorer_version=self.graphiql_plugin_explorer_version,
-                    graphiql_plugin_explorer_sri=self.graphiql_plugin_explorer_sri,
                     # The SUBSCRIPTION_PATH setting.
                     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:
@@ -392,7 +387,7 @@ def get_graphql_params(request, data):
     @staticmethod
     def format_error(error):
         if isinstance(error, GraphQLError):
-            return error.formatted
+            return format_graphql_error(error)
 
         return {"message": str(error)}
 
diff --git a/setup.cfg b/setup.cfg
index c725df1bf..52f6bf697 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,7 +5,7 @@ test=pytest
 universal=1
 
 [flake8]
-exclude = docs,graphene_django/debug/sql/*
+exclude = docs,graphene_django/debug/sql/*,migrations
 max-line-length = 120
 select =
 	# Dictionary key repeated
diff --git a/setup.py b/setup.py
index 37b57a839..fd403c0df 100644
--- a/setup.py
+++ b/setup.py
@@ -14,23 +14,22 @@
 
 
 tests_require = [
-    "pytest>=7.3.1",
+    "pytest>=3.6.3",
     "pytest-cov",
     "pytest-random-order",
     "coveralls",
     "mock",
     "pytz",
-    "django-filter>=22.1",
-    "pytest-django>=4.5.2",
+    "django-filter>=2",
+    "pytest-django>=3.3.2",
 ] + rest_framework_require
 
 
 dev_requires = [
-    "black==23.3.0",
-    "flake8==6.0.0",
-    "flake8-black==0.3.6",
-    "flake8-bugbear==23.3.23",
-    "pre-commit",
+    "black==19.10b0",
+    "flake8==3.7.9",
+    "flake8-black==0.1.1",
+    "flake8-bugbear==20.1.4",
 ] + tests_require
 
 setup(
@@ -47,24 +46,23 @@
         "Intended Audience :: Developers",
         "Topic :: Software Development :: Libraries",
         "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
-        "Programming Language :: Python :: 3.10",
-        "Programming Language :: Python :: 3.11",
         "Programming Language :: Python :: Implementation :: PyPy",
         "Framework :: Django",
+        "Framework :: Django :: 2.2",
+        "Framework :: Django :: 3.0",
+        "Framework :: Django :: 3.1",
         "Framework :: Django :: 3.2",
-        "Framework :: Django :: 4.0",
-        "Framework :: Django :: 4.1",
     ],
     keywords="api graphql protocol rest relay graphene",
     packages=find_packages(exclude=["tests", "examples", "examples.*"]),
     install_requires=[
-        "graphene>=3.0,<4",
+        "graphene>=3.0.0b5,<4",
         "graphql-core>=3.1.0,<4",
-        "graphql-relay>=3.1.1,<4",
-        "Django>=3.2",
+        "Django>=2.2",
         "promise>=2.1",
         "text-unidecode",
     ],
diff --git a/tox.ini b/tox.ini
index e186f30ef..7128afe09 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,23 +1,21 @@
 [tox]
 envlist =
-    py{37,38,39,310}-django32,
-    py{38,39,310}-django{40,41,main},
-    py311-django{41,main}
-    pre-commit
+    py{36,37,38,39}-django{22,30,31,32,main},
+    black,flake8
 
 [gh-actions]
 python =
+    3.6: py36
     3.7: py37
     3.8: py38
     3.9: py39
-    3.10: py310
-    3.11: py311
 
 [gh-actions:env]
 DJANGO =
+    2.2: django22
+    3.0: django30
+    3.1: django31
     3.2: django32
-    4.0: django40
-    4.1: django41
     main: djangomain
 
 [testenv]
@@ -25,18 +23,26 @@ passenv = *
 usedevelop = True
 setenv =
     DJANGO_SETTINGS_MODULE=examples.django_test_settings
-    PYTHONPATH=.
 deps =
     -e.[test]
     psycopg2-binary
-    django32: Django>=3.2,<4.0
-    django40: Django>=4.0,<4.1
-    django41: Django>=4.1,<4.2
+    django20: Django>=2.0,<2.1
+    django21: Django>=2.1,<2.2
+    django22: Django>=2.2,<3.0
+    django30: Django>=3.0a1,<3.1
+    django31: Django>=3.1,<3.2
+    django32: Django>=3.2a1,<3.3
     djangomain: https://github.com/django/django/archive/main.zip
 commands = {posargs:py.test --cov=graphene_django graphene_django examples}
 
-[testenv:pre-commit]
-skip_install = true
-deps = pre-commit
+[testenv:black]
+basepython = python3.9
+deps = -e.[dev]
+commands  =
+    black --exclude "/migrations/" graphene_django examples setup.py --check
+
+[testenv:flake8]
+basepython = python3.9
+deps = -e.[dev]
 commands =
-    pre-commit run --all-files --show-diff-on-failure
+    flake8 graphene_django examples setup.py