Repository: tOgg1/graphene-django-cud (language: python)

Issue:
feat: Change `only_fields`->`fields`and `exclude_fields`->`fields`
Closes #73.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8c4578..e918940 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## Version 0.10.0
+* Put `select_for_update` behind a new option `use_select_for_update`, which is enabled by default.
+* Rename `only_fields` -> `fields` and `exclude_fields` -> `exclude`. Alias the old names but add deprecation warnings.
+* Make the library graphene(-django) 3.x compatible.
+* Respect required_fields in Patch and BatchPatch mutations (thanks @mbuvarp).
+
 ## Version 0.9.1
 * Improve atomicity of patch/update-calls (thanks @keithhackbarth)
 * Improve documentation
diff --git a/README.md b/README.md
index f64a62e..f00cfe5 100644
--- a/README.md
+++ b/README.md
@@ -52,8 +52,15 @@ class CreateUserMutation(DjangoCreateMutation):
 class Mutation(graphene.ObjectType):
     create_user = CreateUserMutation.Field()
 
+    
+class Query(graphene.ObjectType):
+    user = graphene.Field(UserNode, id=graphene.String())
 
-schema = Schema(mutation=Mutation)
+    def resolve_user(self, info, id):
+        return User.objects.get(pk=id)
+
+
+schema = Schema(query=Query, mutation=Mutation)
 ```
 
 Note that the `UserNode` has to be registered as a field before the mutation is instantiated. This will be configurable in the future.
diff --git a/docs/conf.py b/docs/conf.py
index 03f9f9d..fa9ac18 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -29,7 +29,7 @@
 author = 'Tormod Haugland'
 
 # The full version, including alpha/beta/rc tags
-release = '0.9.1'
+release = '0.10.0'
 
 # -- General configuration ---------------------------------------------------
 
diff --git a/docs/guide/usage.rst b/docs/guide/usage.rst
index 6114b73..fbcec2a 100644
--- a/docs/guide/usage.rst
+++ b/docs/guide/usage.rst
@@ -24,12 +24,16 @@ Then we can create a create mutation with the following schema
         class Meta:
             model = User
 
-
     class Mutation(graphene.ObjectType):
         create_user = CreateUserMutation.Field()
 
+    class Query(graphene.ObjectType):
+        user = graphene.Field(UserNode, id=graphene.String())
+
+        def resolve_user(self, info, id):
+            return User.objects.get(pk=id)
 
-    schema = Schema(mutation=Mutation)
+    schema = Schema(query=Query, mutation=Mutation)
 
 Note that the ``UserNode`` has to be registered as a field before the
 mutation is instantiated. This will be configurable in the future.
diff --git a/docs/ref/models/DjangoPatchMutation.rst b/docs/ref/models/DjangoPatchMutation.rst
index 224d4f6..2b790b1 100644
--- a/docs/ref/models/DjangoPatchMutation.rst
+++ b/docs/ref/models/DjangoPatchMutation.rst
@@ -53,6 +53,8 @@ All meta arguments:
 +--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
 | one\_to\_one\_extras     | Dict       | {}        | A dict with extra information regarding one to one extras.                                                                                                                        |
 +--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| use_select_for_update    | Boolean    | True      | If true, the queryset will be altered with ``select_for_update``, locking the database rows in question. Used to ensure data integrity on updates.                                |
++--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
 
 Example mutation
 ^^^^^^^^^^^^^^^^
diff --git a/docs/ref/models/DjangoUpdateMutation.rst b/docs/ref/models/DjangoUpdateMutation.rst
index 1d71d23..95d316d 100644
--- a/docs/ref/models/DjangoUpdateMutation.rst
+++ b/docs/ref/models/DjangoUpdateMutation.rst
@@ -53,6 +53,8 @@ All meta arguments:
 +--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
 | one\_to\_one\_extras     | Dict       | {}        | A dict with extra information regarding one to one extras.                                                                                                                        |
 +--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| use_select_for_update    | Boolean    | True      | If true, the queryset will be altered with ``select_for_update``, locking the database rows in question. Used to ensure data integrity on updates.                                |
++--------------------------+------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
 
 
 .. code::
diff --git a/graphene_django_cud/converter.py b/graphene_django_cud/converter.py
index 3673625..b3af9b4 100644
--- a/graphene_django_cud/converter.py
+++ b/graphene_django_cud/converter.py
@@ -30,12 +30,12 @@
     Decimal,
 )
 from graphene.types.json import JSONString
-from graphene.utils.str_converters import to_camel_case, to_const
 from graphene_django.compat import ArrayField, HStoreField, JSONField, RangeField
 from graphene_file_upload.scalars import Upload
 from graphql import assert_valid_name, GraphQLError
 
 from graphene_django_cud.types import TimeDelta
+from graphene_django_cud.util.string import to_camel_case, to_const
 
 
 def is_required(field, required=None, is_many_to_many=False):
diff --git a/graphene_django_cud/mutations/batch_create.py b/graphene_django_cud/mutations/batch_create.py
index 12dce77..ace506f 100644
--- a/graphene_django_cud/mutations/batch_create.py
+++ b/graphene_django_cud/mutations/batch_create.py
@@ -1,3 +1,4 @@
+import warnings
 from collections import OrderedDict
 from typing import Iterable
 
@@ -29,8 +30,10 @@ def __init_subclass_with_meta__(
         model=None,
         permissions=None,
         login_required=None,
-        only_fields=(),
-        exclude_fields=(),
+        fields=(),
+        only_fields=(),  # Deprecated in favor of `fields`
+        exclude=(),
+        exclude_fields=(),  # Deprecated in favor of `exclude`
         optional_fields=(),
         required_fields=(),
         auto_context_fields={},
@@ -73,6 +76,27 @@ def __init_subclass_with_meta__(
             # Pluralize
             return_field_name = to_snake_case(model.__name__) + "s"
 
+        if fields and only_fields:
+            raise Exception("Cannot set both `fields` and `only_fields` on a mutation")
+
+        if exclude and exclude_fields:
+            raise Exception(
+                "Cannot set both `exclude` and `exclude_fields` on a mutation"
+            )
+
+        if only_fields:
+            fields = only_fields
+            warnings.warn(
+                "`only_fields` is deprecated in favor of `fields`", DeprecationWarning
+            )
+
+        if exclude_fields:
+            exclude = exclude_fields
+            warnings.warn(
+                "`exclude_fields` is deprecated in favor of `exclude`",
+                DeprecationWarning,
+            )
+
         if use_type_name:
             input_type_name = use_type_name
             InputType = registry.get_converted_field(input_type_name)
@@ -85,8 +109,8 @@ def __init_subclass_with_meta__(
 
             model_fields = get_input_fields_for_model(
                 model,
-                only_fields,
-                exclude_fields,
+                fields,
+                exclude,
                 tuple(auto_context_fields.keys()) + optional_fields,
                 required_fields,
                 many_to_many_extras,
diff --git a/graphene_django_cud/mutations/batch_delete.py b/graphene_django_cud/mutations/batch_delete.py
index 2a0f47c..d5f98c4 100644
--- a/graphene_django_cud/mutations/batch_delete.py
+++ b/graphene_django_cud/mutations/batch_delete.py
@@ -29,8 +29,6 @@ def __init_subclass_with_meta__(
         model=None,
         permissions=None,
         login_required=None,
-        only_fields=(),
-        exclude_fields=(),
         return_field_name=None,
         **kwargs,
     ):
diff --git a/graphene_django_cud/mutations/batch_patch.py b/graphene_django_cud/mutations/batch_patch.py
index 639ca75..f53bc8e 100644
--- a/graphene_django_cud/mutations/batch_patch.py
+++ b/graphene_django_cud/mutations/batch_patch.py
@@ -20,6 +20,7 @@ def __init_subclass_with_meta__(
             _meta=None,
             model=None,
             optional_fields=None,
+            required_fields=None,
             type_name=None,
             **kwargs
     ):
@@ -28,12 +29,16 @@ def __init_subclass_with_meta__(
         if optional_fields is None:
             optional_fields = all_field_names
 
+        if required_fields is not None:
+            optional_fields = tuple(set(optional_fields) - set(required_fields))
+
         input_type_name = type_name or f"BatchPatch{model.__name__}Input"
 
         return super().__init_subclass_with_meta__(
             _meta=_meta,
             model=model,
             optional_fields=optional_fields,
+            required_fields=required_fields,
             type_name=input_type_name,
             **kwargs
         )
diff --git a/graphene_django_cud/mutations/batch_update.py b/graphene_django_cud/mutations/batch_update.py
index 5aa0517..4aeb18f 100644
--- a/graphene_django_cud/mutations/batch_update.py
+++ b/graphene_django_cud/mutations/batch_update.py
@@ -1,3 +1,4 @@
+import warnings
 from collections import OrderedDict
 from typing import Iterable
 
@@ -29,8 +30,10 @@ def __init_subclass_with_meta__(
         model=None,
         permissions=None,
         login_required=None,
-        only_fields=(),
-        exclude_fields=(),
+        fields=(),
+        only_fields=(),  # Deprecated in favor of `fields`
+        exclude=(),
+        exclude_fields=(),  # Deprecated in favor of `exclude`
         optional_fields=(),
         required_fields=(),
         auto_context_fields={},
@@ -73,6 +76,27 @@ def __init_subclass_with_meta__(
             # Pluralize
             return_field_name = to_snake_case(model.__name__) + "s"
 
+        if fields and only_fields:
+            raise Exception("Cannot set both `fields` and `only_fields` on a mutation")
+
+        if exclude and exclude_fields:
+            raise Exception(
+                "Cannot set both `exclude` and `exclude_fields` on a mutation"
+            )
+
+        if only_fields:
+            fields = only_fields
+            warnings.warn(
+                "`only_fields` is deprecated in favor of `fields`", DeprecationWarning
+            )
+
+        if exclude_fields:
+            exclude = exclude_fields
+            warnings.warn(
+                "`exclude_fields` is deprecated in favor of `exclude`",
+                DeprecationWarning,
+            )
+
         if use_type_name:
             input_type_name = use_type_name
             InputType = registry.get_converted_field(input_type_name)
@@ -85,8 +109,8 @@ def __init_subclass_with_meta__(
 
             model_fields = get_input_fields_for_model(
                 model,
-                only_fields,
-                exclude_fields,
+                fields,
+                exclude,
                 tuple(auto_context_fields.keys()) + optional_fields,
                 required_fields,
                 many_to_many_extras,
diff --git a/graphene_django_cud/mutations/core.py b/graphene_django_cud/mutations/core.py
index 35055e8..d67dfdb 100644
--- a/graphene_django_cud/mutations/core.py
+++ b/graphene_django_cud/mutations/core.py
@@ -1,5 +1,7 @@
+import enum
 from typing import Iterable, Union
 
+import graphene
 from django.db import models
 from graphene import Mutation
 from graphene.types.mutation import MutationOptions
@@ -14,7 +16,8 @@
     disambiguate_ids,
     is_field_many_to_many,
     is_field_one_to_one,
-    is_field_many_to_one, get_model_field_or_none,
+    is_field_many_to_one,
+    get_model_field_or_none,
 )
 
 meta_registry = get_type_meta_registry()
@@ -102,23 +105,23 @@ def get_or_create_m2m_objs(cls, field, values, data, operation, info):
                     info,
                     {
                         **input_type_meta.get("auto_context_fields", {}),
-                        **data.get("auto_context_fields", {})
+                        **data.get("auto_context_fields", {}),
                     },
                     {
                         **input_type_meta.get("many_to_many_extras", {}),
-                        **data.get("many_to_many_extras", {})
+                        **data.get("many_to_many_extras", {}),
                     },
                     {
                         **input_type_meta.get("foreign_key_extras", {}),
-                        **data.get("foreign_key_extras", {})
+                        **data.get("foreign_key_extras", {}),
                     },
                     {
                         **input_type_meta.get("many_to_one_extras", {}),
-                        **data.get("many_to_one_extras", {})
+                        **data.get("many_to_one_extras", {}),
                     },
                     {
                         **input_type_meta.get("one_to_one_extras", {}),
-                        **data.get("one_to_one_extras", {})
+                        **data.get("one_to_one_extras", {}),
                     },
                     field.related_model,
                 )
@@ -136,30 +139,29 @@ def get_or_upsert_m2o_objs(cls, obj, field, values, data, operation, info, Model
         field_type = data.get("type", "auto")
         for value in values:
             if field_type == "ID":
-                related_obj = field.related_model.objects.get(
-                    pk=cls.resolve_id(value))
+                related_obj = field.related_model.objects.get(pk=cls.resolve_id(value))
                 results.append(related_obj)
             else:
                 input_type_meta = meta_registry.get_meta_for_type(field_type)
                 auto_context_fields = {
                     **input_type_meta.get("auto_context_fields", {}),
-                    **data.get("auto_context_fields", {})
+                    **data.get("auto_context_fields", {}),
                 }
                 many_to_many_extras = {
                     **input_type_meta.get("many_to_many_extras", {}),
-                    **data.get("many_to_many_extras", {})
+                    **data.get("many_to_many_extras", {}),
                 }
                 foreign_key_extras = {
                     **input_type_meta.get("foreign_key_extras", {}),
-                    **data.get("foreign_key_extras", {})
+                    **data.get("foreign_key_extras", {}),
                 }
                 many_to_one_extras = {
                     **input_type_meta.get("many_to_one_extras", {}),
-                    **data.get("many_to_one_extras", {})
+                    **data.get("many_to_one_extras", {}),
                 }
                 one_to_one_extras = {
                     **input_type_meta.get("one_to_one_extras", {}),
-                    **data.get("one_to_one_extras", {})
+                    **data.get("one_to_one_extras", {}),
                 }
 
                 if field_type == "auto":
@@ -373,11 +375,17 @@ def create_obj(
                     new_value = cls.resolve_id(value)
                 elif field_is_many_to_many:
                     new_value = cls.resolve_ids(value)
+                elif isinstance(new_value, enum.Enum):
+                    new_value = new_value.value
 
             if field_is_many_to_many:
-                many_to_many_to_set[name] = cls.get_all_objs(field.related_model, new_value)
+                many_to_many_to_set[name] = cls.get_all_objs(
+                    field.related_model, new_value
+                )
             elif field_is_many_to_one:
-                many_to_one_to_set[name] = cls.get_all_objs(field.related_model, new_value)
+                many_to_one_to_set[name] = cls.get_all_objs(
+                    field.related_model, new_value
+                )
             else:
                 model_field_values[name] = new_value
 
@@ -391,6 +399,7 @@ def create_obj(
 
             model_field_values[name + "_id"] = obj_id
 
+        print(model_field_values)
         # Foreign keys are added, we are ready to create our object
         obj = Model.objects.create(**model_field_values)
 
@@ -647,12 +656,19 @@ def update_obj(
                     new_value = cls.resolve_id(value)
                 elif field_is_many_to_many:
                     new_value = cls.resolve_ids(value)
+                elif isinstance(new_value, enum.Enum):
+                    new_value = new_value.value
 
             if field_is_many_to_many:
-                many_to_many_to_set[name] = cls.get_all_objs(field.related_model, new_value)
+                many_to_many_to_set[name] = cls.get_all_objs(
+                    field.related_model, new_value
+                )
             elif field_is_many_to_one:
-                many_to_one_to_set[name] = cls.get_all_objs(field.related_model, new_value)
+                many_to_one_to_set[name] = cls.get_all_objs(
+                    field.related_model, new_value
+                )
             else:
+                print(obj, name, new_value)
                 setattr(obj, name, new_value)
 
         # Handle extras fields
@@ -823,10 +839,15 @@ def resolve_ids(cls, ids):
 class DjangoCudBaseOptions(MutationOptions):
     model = None
 
-    only_fields = None
-    exclude_fields = None
+    only_fields = None  # Deprecated in favor of `fields`
+    exclude_fields = None  # Deprecated in favor of `exclude`
+
+    fields = None
+    exclude = None
+
     optional_fields = None
     required_fields = None
+
     auto_context_fields = None
 
     permissions = None
diff --git a/graphene_django_cud/mutations/create.py b/graphene_django_cud/mutations/create.py
index 82d2616..cc5162a 100644
--- a/graphene_django_cud/mutations/create.py
+++ b/graphene_django_cud/mutations/create.py
@@ -1,3 +1,4 @@
+import warnings
 from collections import OrderedDict
 from typing import Iterable
 
@@ -29,8 +30,10 @@ def __init_subclass_with_meta__(
         model=None,
         permissions=None,
         login_required=None,
-        only_fields=(),
-        exclude_fields=(),
+        fields=(),
+        only_fields=(),  # Deprecated in favor of `fields`
+        exclude=(),
+        exclude_fields=(),  # Deprecated in favor of `exclude`
         optional_fields=(),
         required_fields=(),
         auto_context_fields={},
@@ -72,12 +75,33 @@ def __init_subclass_with_meta__(
         if not return_field_name:
             return_field_name = to_snake_case(model.__name__)
 
+        if fields and only_fields:
+            raise Exception("Cannot set both `fields` and `only_fields` on a mutation")
+
+        if exclude and exclude_fields:
+            raise Exception(
+                "Cannot set both `exclude` and `exclude_fields` on a mutation"
+            )
+
+        if only_fields:
+            fields = only_fields
+            warnings.warn(
+                "`only_fields` is deprecated in favor of `fields`", DeprecationWarning
+            )
+
+        if exclude_fields:
+            exclude = exclude_fields
+            warnings.warn(
+                "`exclude_fields` is deprecated in favor of `exclude`",
+                DeprecationWarning,
+            )
+
         input_type_name = type_name or f"Create{model.__name__}Input"
 
         model_fields = get_input_fields_for_model(
             model,
-            only_fields,
-            exclude_fields,
+            fields,
+            exclude,
             tuple(auto_context_fields.keys()) + optional_fields,
             required_fields,
             many_to_many_extras,
diff --git a/graphene_django_cud/mutations/filter_update.py b/graphene_django_cud/mutations/filter_update.py
index 74e3e96..6d23335 100644
--- a/graphene_django_cud/mutations/filter_update.py
+++ b/graphene_django_cud/mutations/filter_update.py
@@ -1,3 +1,4 @@
+import warnings
 from collections import OrderedDict
 from typing import Iterable
 
@@ -40,8 +41,10 @@ def __init_subclass_with_meta__(
         filter_fields=(),
         filter_class=None,
         type_name=None,
-        only_fields=(),
-        exclude_fields=(),
+        fields=(),
+        only_fields=(),  # Deprecated in favor of `fields`
+        exclude=(),
+        exclude_fields=(),  # Deprecated in favor of `exclude`
         optional_fields=None,
         required_fields=(),
         field_types=None,
@@ -59,6 +62,27 @@ def __init_subclass_with_meta__(
             len(filter_fields) > 0
         ), f"You must specify at least one field to filter on for deletion."
 
+        if fields and only_fields:
+            raise Exception("Cannot set both `fields` and `only_fields` on a mutation")
+
+        if exclude and exclude_fields:
+            raise Exception(
+                "Cannot set both `exclude` and `exclude_fields` on a mutation"
+            )
+
+        if only_fields:
+            fields = only_fields
+            warnings.warn(
+                "`only_fields` is deprecated in favor of `fields`", DeprecationWarning
+            )
+
+        if exclude_fields:
+            exclude = exclude_fields
+            warnings.warn(
+                "`exclude_fields` is deprecated in favor of `exclude`",
+                DeprecationWarning,
+            )
+
         input_arguments = get_filter_fields_input_args(filter_fields, model)
 
         FilterInputType = type(
@@ -71,8 +95,8 @@ def __init_subclass_with_meta__(
 
         model_fields = get_input_fields_for_model(
             model,
-            only_fields,
-            exclude_fields,
+            fields,
+            exclude,
             tuple(auto_context_fields.keys()) + optional_fields,
             required_fields,
             None,
diff --git a/graphene_django_cud/mutations/patch.py b/graphene_django_cud/mutations/patch.py
index 0983b91..d036f58 100644
--- a/graphene_django_cud/mutations/patch.py
+++ b/graphene_django_cud/mutations/patch.py
@@ -19,6 +19,7 @@ def __init_subclass_with_meta__(
         _meta=None,
         model=None,
         optional_fields=None,
+        required_fields=None,
         type_name=None,
         **kwargs
     ):
@@ -27,12 +28,16 @@ def __init_subclass_with_meta__(
         if optional_fields is None:
             optional_fields = all_field_names
 
+        if required_fields is not None:
+            optional_fields = tuple(set(optional_fields) - set(required_fields))
+
         input_type_name = type_name or f"Patch{model.__name__}Input"
 
         return super().__init_subclass_with_meta__(
             _meta=_meta,
             model=model,
             optional_fields=optional_fields,
+            required_fields=required_fields,
             type_name=input_type_name,
             **kwargs
         )
diff --git a/graphene_django_cud/mutations/update.py b/graphene_django_cud/mutations/update.py
index 0d1b0f0..623de1d 100644
--- a/graphene_django_cud/mutations/update.py
+++ b/graphene_django_cud/mutations/update.py
@@ -1,3 +1,4 @@
+import warnings
 from collections import OrderedDict
 from typing import Iterable
 
@@ -5,13 +6,13 @@
 from django.db import transaction
 from graphene import InputObjectType
 from graphene.types.utils import yank_fields_from_attrs
-from graphene.utils.str_converters import to_snake_case
 from graphene_django.registry import get_global_registry
 from graphql import GraphQLError
 
 from graphene_django_cud.mutations.core import DjangoCudBaseOptions, DjangoCudBase
 from graphene_django_cud.registry import get_type_meta_registry
-from graphene_django_cud.util import get_input_fields_for_model
+from graphene_django_cud.util import get_input_fields_for_model, to_snake_case
+
 
 class DjangoUpdateMutationOptions(DjangoCudBaseOptions):
     pass
@@ -28,9 +29,11 @@ def __init_subclass_with_meta__(
         model=None,
         permissions=None,
         login_required=None,
-        only_fields=(),
-        exclude_fields=(),
-        optional_fields=(),
+        fields=(),
+            only_fields=(),  # Deprecated in favor of `fields`
+            exclude=(),
+            exclude_fields=(),  # Deprecated in favor of `exclude`
+            optional_fields=(),
         required_fields=(),
         auto_context_fields=None,
         return_field_name=None,
@@ -41,6 +44,7 @@ def __init_subclass_with_meta__(
         type_name=None,
         field_types=None,
         custom_fields=None,
+        use_select_for_update=True,
         **kwargs,
     ):
         registry = get_global_registry()
@@ -70,12 +74,33 @@ def __init_subclass_with_meta__(
         if not return_field_name:
             return_field_name = to_snake_case(model.__name__)
 
+        if fields and only_fields:
+            raise Exception("Cannot set both `fields` and `only_fields` on a mutation")
+
+        if exclude and exclude_fields:
+            raise Exception(
+                "Cannot set both `exclude` and `exclude_fields` on a mutation"
+            )
+
+        if only_fields:
+            fields = only_fields
+            warnings.warn(
+                "`only_fields` is deprecated in favor of `fields`", DeprecationWarning
+            )
+
+        if exclude_fields:
+            exclude = exclude_fields
+            warnings.warn(
+                "`exclude_fields` is deprecated in favor of `exclude`",
+                DeprecationWarning,
+            )
+
         input_type_name = type_name or f"Update{model.__name__}Input"
 
         model_fields = get_input_fields_for_model(
             model,
-            only_fields,
-            exclude_fields,
+            fields,
+            exclude,
             optional_fields=tuple(auto_context_fields.keys()) + optional_fields,
             required_fields=required_fields,
             many_to_many_extras=many_to_many_extras,
@@ -136,6 +161,7 @@ def __init_subclass_with_meta__(
         _meta.login_required = login_required or (
             _meta.permissions and len(_meta.permissions) > 0
         )
+        _meta.use_select_for_update = use_select_for_update
 
         super().__init_subclass_with_meta__(arguments=arguments, _meta=_meta, **kwargs)
 
@@ -182,7 +208,11 @@ def mutate(cls, root, info, input, id):
             id = cls.resolve_id(id)
             Model = cls._meta.model
             queryset = cls.get_queryset(root, info, input, id)
-            obj = queryset.select_for_update().get(pk=id)
+
+            if cls._meta.use_select_for_update:
+                queryset = queryset.select_for_update()
+
+            obj = queryset.get(pk=id)
             auto_context_fields = cls._meta.auto_context_fields or {}
 
             cls.check_permissions(root, info, input, id, obj)
diff --git a/graphene_django_cud/string_util.py b/graphene_django_cud/string_util.py
new file mode 100644
index 0000000..e69de29
diff --git a/graphene_django_cud/util/__init__.py b/graphene_django_cud/util/__init__.py
new file mode 100644
index 0000000..28fe2c1
--- /dev/null
+++ b/graphene_django_cud/util/__init__.py
@@ -0,0 +1,3 @@
+
+from .model import *
+from .string import *
\ No newline at end of file
diff --git a/graphene_django_cud/util.py b/graphene_django_cud/util/model.py
similarity index 93%
rename from graphene_django_cud/util.py
rename to graphene_django_cud/util/model.py
index cfc3164..d9f0fdf 100644
--- a/graphene_django_cud/util.py
+++ b/graphene_django_cud/util/model.py
@@ -1,4 +1,5 @@
 import binascii
+import re
 import uuid
 from collections import OrderedDict
 from typing import Union, List, Optional
@@ -84,8 +85,8 @@ def overload_nested_fields(nested_fields):
 
 def get_input_fields_for_model(
     model,
-    only_fields,
-    exclude_fields,
+    fields,
+    exclude,
     optional_fields=(),
     required_fields=(),
     many_to_many_extras=None,
@@ -117,7 +118,7 @@ def get_input_fields_for_model(
     field_types = field_types or {}
     one_to_one_fields: List[Union[models.OneToOneRel, models.OneToOneField]] = []
 
-    fields = OrderedDict()
+    converted_input_fields = OrderedDict()
     fields_lookup = {}
 
     for name, field in model_fields:
@@ -127,15 +128,15 @@ def get_input_fields_for_model(
 
         # If the field has an override, use that
         if name in field_types:
-            fields[name] = field_types[name]
+            converted_input_fields[name] = field_types[name]
             continue
 
         # Save for later
         fields_lookup[name] = field
 
-        is_not_in_only = only_fields and name not in only_fields
+        is_not_in_only = fields and name not in fields
         # is_already_created = name in options.fields
-        is_excluded = name in exclude_fields  # or is_already_created
+        is_excluded = name in exclude  # or is_already_created
         # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name
         is_no_backref = str(name).endswith("+")
         if is_not_in_only or is_excluded or is_no_backref:
@@ -158,7 +159,7 @@ def get_input_fields_for_model(
             foreign_key_extras.get(name, {}),
             one_to_one_extras.get(name, {}),
         )
-        fields[name] = converted
+        converted_input_fields[name] = converted
 
         if type(field) in (models.OneToOneRel, models.OneToOneField):
             one_to_one_fields.append(field)
@@ -186,11 +187,13 @@ def get_input_fields_for_model(
         # On ForeignKeys we can get the reverse field via field.remote_field
         reverse_field_name = field.remote_field.name
 
-        converted_fields = get_input_fields_for_model(
+        foreign_key_converted_fields = get_input_fields_for_model(
             field.related_model,
-            data.get("only_fields", ()),
-            data.get(
-                "exclude_fields", (reverse_field_name,)
+            # TODO: Remove only_fields and exclude_fields when the deprecated fields are removed.
+            tuple(data.get("fields", data.get("only_fields", ()))),
+            tuple(data.get("exclude", data.get("exclude_fields", ())))
+            + (
+                reverse_field_name,
             ),  # Exclude the field referring back to the foreign key
             data.get("optional_fields", ()),
             data.get("required_fields", ()),
@@ -201,7 +204,7 @@ def get_input_fields_for_model(
             field_types=data.get("field_types"),
         )
 
-        InputType = type(type_name, (InputObjectType,), converted_fields)
+        InputType = type(type_name, (InputObjectType,), foreign_key_converted_fields)
         registry.register_converted_field(type_name, InputType)
         meta_registry.register(type_name, data)
 
@@ -227,11 +230,13 @@ def get_input_fields_for_model(
             else field.remote_field.name
         )
 
-        converted_fields = get_input_fields_for_model(
+        one_to_one_converted_fields = get_input_fields_for_model(
             field.related_model,
-            data.get("only_fields", ()),
-            data.get(
-                "exclude_fields", (reverse_field_name,)
+            # TODO: Remove only_fields and exclude_fields when the deprecated fields are removed.
+            tuple(data.get("fields", data.get("only_fields", ()))),
+            tuple(data.get("exclude", data.get("exclude_fields", ())))
+            + (
+                reverse_field_name,
             ),  # Exclude the field referring back to the foreign key
             data.get("optional_fields", ()),
             data.get("required_fields", ()),
@@ -242,7 +247,7 @@ def get_input_fields_for_model(
             field_types=data.get("field_types"),
         )
 
-        InputType = type(type_name, (InputObjectType,), converted_fields)
+        InputType = type(type_name, (InputObjectType,), one_to_one_converted_fields)
         registry.register_converted_field(type_name, InputType)
         meta_registry.register(type_name, data)
 
@@ -264,7 +269,7 @@ def get_input_fields_for_model(
             if extra_name == "exact":
                 argument_name = name
 
-            fields[argument_name] = convert_many_to_many_like_field(
+            converted_input_fields[argument_name] = convert_many_to_many_like_field(
                 data, name, extra_name, parent_type_name, field, registry, meta_registry
             )
 
@@ -283,11 +288,11 @@ def get_input_fields_for_model(
             if extra_name == "exact":
                 argument_name = name
 
-            fields[argument_name] = convert_many_to_many_like_field(
+            converted_input_fields[argument_name] = convert_many_to_many_like_field(
                 data, name, extra_name, parent_type_name, field, registry, meta_registry
             )
 
-    return fields
+    return converted_input_fields
 
 
 def convert_many_to_many_like_field(
@@ -351,7 +356,10 @@ def convert_many_to_many_like_field(
             },
         )
         registry.register_converted_field(type_name, InputType)
-        _field = graphene.List(InputType, required=False,)
+        _field = graphene.List(
+            InputType,
+            required=False,
+        )
     else:
         _field = convert_many_to_many_field(field, registry, False, data, None)
 
diff --git a/graphene_django_cud/util/string.py b/graphene_django_cud/util/string.py
new file mode 100644
index 0000000..ef3dd13
--- /dev/null
+++ b/graphene_django_cud/util/string.py
@@ -0,0 +1,16 @@
+import re
+
+# http://stackoverflow.com/a/1176023/1072990
+def to_snake_case(name):
+    s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
+    return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
+
+
+# http://stackoverflow.com/a/19053800/1072990
+def to_camel_case(snake_str):
+    components = snake_str.split("_")
+    return components[0] + "".join(x.capitalize() if x else "_" for x in components[1:])
+
+
+def to_const(string):
+    return re.sub(r"[\W|^]+", "_", string).upper()  # noqa
diff --git a/poetry.lock b/poetry.lock
index 41e6415..7ed55e4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,6 +1,6 @@
 [[package]]
 name = "addict"
-version = "2.2.1"
+version = "2.4.0"
 description = "Addict is a dictionary whose items can be set using both attribute and item syntax."
 category = "dev"
 optional = false
@@ -8,15 +8,18 @@ python-versions = "*"
 
 [[package]]
 name = "aniso8601"
-version = "7.0.0"
+version = "9.0.1"
 description = "A library for parsing ISO 8601 strings."
 category = "main"
 optional = false
 python-versions = "*"
 
+[package.extras]
+dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"]
+
 [[package]]
 name = "atomicwrites"
-version = "1.3.0"
+version = "1.4.0"
 description = "Atomic file writes."
 category = "dev"
 optional = false
@@ -24,21 +27,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "attrs"
-version = "19.3.0"
+version = "21.4.0"
 description = "Classes Without Boilerplate"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.extras]
-azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
-dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
-docs = ["sphinx", "zope.interface"]
-tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
 
 [[package]]
 name = "colorama"
-version = "0.4.3"
+version = "0.4.4"
 description = "Cross-platform colored terminal text."
 category = "dev"
 optional = false
@@ -46,18 +49,18 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "coverage"
-version = "5.0.3"
+version = "6.2"
 description = "Code coverage measurement for Python"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+python-versions = ">=3.6"
 
 [package.extras]
-toml = ["toml"]
+toml = ["tomli"]
 
 [[package]]
 name = "django"
-version = "2.2.25"
+version = "2.2.27"
 description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
 category = "main"
 optional = false
@@ -84,60 +87,56 @@ Faker = ">=0.7.0"
 
 [[package]]
 name = "faker"
-version = "4.0.0"
+version = "13.3.1"
 description = "Faker is a Python package that generates fake data for you."
 category = "dev"
 optional = false
-python-versions = ">=3.4"
+python-versions = ">=3.6"
 
 [package.dependencies]
 python-dateutil = ">=2.4"
-text-unidecode = "1.3"
+typing-extensions = {version = ">=3.10.0.2", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "graphene"
-version = "2.1.8"
+version = "3.0"
 description = "GraphQL Framework for Python"
 category = "main"
 optional = false
 python-versions = "*"
 
 [package.dependencies]
-aniso8601 = ">=3,<=7"
-graphql-core = ">=2.1,<3"
-graphql-relay = ">=2,<3"
-six = ">=1.10.0,<2"
+aniso8601 = ">=8,<10"
+graphql-core = ">=3.1.2,<3.2.0"
+graphql-relay = ">=3.0,<4"
 
 [package.extras]
-django = ["graphene-django"]
-sqlalchemy = ["graphene-sqlalchemy"]
-test = ["pytest", "pytest-benchmark", "pytest-cov", "pytest-mock", "snapshottest", "coveralls", "promise", "six", "mock", "pytz", "iso8601"]
+dev = ["black (==19.10b0)", "flake8 (>=3.7,<4)", "pytest (>=5.3,<6)", "pytest-benchmark (>=3.2,<4)", "pytest-cov (>=2.8,<3)", "pytest-mock (>=2,<3)", "pytest-asyncio (>=0.10,<2)", "snapshottest (>=0.5,<1)", "coveralls (>=1.11,<2)", "promise (>=2.3,<3)", "mock (>=4.0,<5)", "pytz (==2021.1)", "iso8601 (>=0.1,<2)"]
+test = ["pytest (>=5.3,<6)", "pytest-benchmark (>=3.2,<4)", "pytest-cov (>=2.8,<3)", "pytest-mock (>=2,<3)", "pytest-asyncio (>=0.10,<2)", "snapshottest (>=0.5,<1)", "coveralls (>=1.11,<2)", "promise (>=2.3,<3)", "mock (>=4.0,<5)", "pytz (==2021.1)", "iso8601 (>=0.1,<2)"]
 
 [[package]]
 name = "graphene-django"
-version = "2.15.0"
+version = "3.0.0b7"
 description = "Graphene Django integration"
 category = "main"
 optional = false
 python-versions = "*"
 
 [package.dependencies]
-Django = ">=1.11"
-graphene = ">=2.1.7,<3"
-graphql-core = ">=2.1.0,<3"
+Django = ">=2.2"
+graphene = ">=3.0.0b5,<4"
+graphql-core = ">=3.1.0,<4"
 promise = ">=2.1"
-singledispatch = ">=3.4.0.3"
-six = ">=1.10.0"
 text-unidecode = "*"
 
 [package.extras]
-dev = ["black (==19.10b0)", "flake8 (==3.7.9)", "flake8-black (==0.1.1)", "flake8-bugbear (==20.1.4)", "pytest (>=3.6.3)", "pytest-cov", "coveralls", "mock", "pytz", "pytest-django (>=3.3.2)", "djangorestframework (>=3.6.3)", "django-filter (<2)", "django-filter (>=2)"]
+dev = ["black (==19.10b0)", "flake8 (==3.7.9)", "flake8-black (==0.1.1)", "flake8-bugbear (==20.1.4)", "pytest (>=3.6.3)", "pytest-cov", "pytest-random-order", "coveralls", "mock", "pytz", "django-filter (>=2)", "pytest-django (>=3.3.2)", "djangorestframework (>=3.6.3)"]
 rest_framework = ["djangorestframework (>=3.6.3)"]
-test = ["pytest (>=3.6.3)", "pytest-cov", "coveralls", "mock", "pytz", "pytest-django (>=3.3.2)", "djangorestframework (>=3.6.3)", "django-filter (<2)", "django-filter (>=2)"]
+test = ["pytest (>=3.6.3)", "pytest-cov", "pytest-random-order", "coveralls", "mock", "pytz", "django-filter (>=2)", "pytest-django (>=3.3.2)", "djangorestframework (>=3.6.3)"]
 
 [[package]]
 name = "graphene-file-upload"
-version = "1.2.2"
+version = "1.3.0"
 description = "Lib for adding file upload functionality to GraphQL mutations in Graphene Django and Flask-Graphql"
 category = "main"
 optional = false
@@ -150,55 +149,48 @@ six = ">=1.11.0"
 all = ["Flask (>=1.0.2)", "graphene (>=2.1.2)", "Flask-Graphql (>=2.0.0)", "graphene-django (>=2.0.0)"]
 django = ["graphene-django (>=2.0.0)"]
 flask = ["Flask (>=1.0.2)", "graphene (>=2.1.2)", "Flask-Graphql (>=2.0.0)"]
+tests = ["coverage", "pytest", "pytest-cov", "pytest-django"]
 
 [[package]]
 name = "graphql-core"
-version = "2.2.1"
-description = "GraphQL implementation for Python"
+version = "3.1.7"
+description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL."
 category = "main"
 optional = false
-python-versions = "*"
-
-[package.dependencies]
-promise = ">=2.1"
-rx = ">=1.6,<3"
-six = ">=1.10.0"
-
-[package.extras]
-gevent = ["gevent (>=1.1)"]
-test = ["pytest (>=3.3,<4.0)", "pytest-django (==2.9.1)", "pytest-cov (==2.3.1)", "coveralls", "gevent (>=1.1)", "six (>=1.10.0)", "pytest-benchmark (==3.0.0)", "pytest-mock (==1.2)"]
+python-versions = ">=3.6,<4"
 
 [[package]]
 name = "graphql-relay"
-version = "2.0.1"
-description = "Relay implementation for Python"
+version = "3.1.5"
+description = "Relay library for graphql-core"
 category = "main"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6,<4"
 
 [package.dependencies]
-graphql-core = ">=2.2,<3"
-promise = ">=2.2,<3"
-six = ">=1.12"
+graphql-core = ">=3.1,<3.2"
+typing-extensions = {version = ">=4,<5", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "importlib-metadata"
-version = "1.4.0"
+version = "4.8.3"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.6"
 
 [package.dependencies]
+typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["sphinx", "rst.linker"]
-testing = ["packaging", "importlib-resources"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+perf = ["ipython"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
 name = "more-itertools"
-version = "8.1.0"
+version = "8.12.0"
 description = "More routines for operating on iterables, beyond itertools"
 category = "dev"
 optional = false
@@ -206,15 +198,14 @@ python-versions = ">=3.5"
 
 [[package]]
 name = "packaging"
-version = "20.0"
+version = "21.3"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
 
 [package.dependencies]
-pyparsing = ">=2.0.2"
-six = "*"
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
 
 [[package]]
 name = "pluggy"
@@ -246,23 +237,26 @@ test = ["pytest (>=2.7.3)", "pytest-cov", "coveralls", "futures", "pytest-benchm
 
 [[package]]
 name = "py"
-version = "1.10.0"
+version = "1.11.0"
 description = "library with cross-python path, ini-parsing, io, code, log facilities"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "pyparsing"
-version = "2.4.6"
+version = "3.0.7"
 description = "Python parsing module"
 category = "dev"
 optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+python-versions = ">=3.6"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
 
 [[package]]
 name = "pytest"
-version = "5.3.4"
+version = "5.4.3"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 optional = false
@@ -285,22 +279,23 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm
 
 [[package]]
 name = "pytest-cov"
-version = "2.8.1"
+version = "2.12.1"
 description = "Pytest plugin for measuring coverage."
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.dependencies]
-coverage = ">=4.4"
-pytest = ">=3.6"
+coverage = ">=5.2.1"
+pytest = ">=4.6"
+toml = "*"
 
 [package.extras]
-testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "virtualenv"]
+testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
 
 [[package]]
 name = "pytest-django"
-version = "3.8.0"
+version = "3.10.0"
 description = "A Django plugin for pytest."
 category = "dev"
 optional = false
@@ -315,7 +310,7 @@ testing = ["django", "django-configurations (>=2.0)", "six"]
 
 [[package]]
 name = "python-dateutil"
-version = "2.8.1"
+version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
 category = "dev"
 optional = false
@@ -326,34 +321,15 @@ six = ">=1.5"
 
 [[package]]
 name = "pytz"
-version = "2019.3"
+version = "2021.3"
 description = "World timezone definitions, modern and historical"
 category = "main"
 optional = false
 python-versions = "*"
 
-[[package]]
-name = "rx"
-version = "1.6.1"
-description = "Reactive Extensions (Rx) for Python"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "singledispatch"
-version = "3.4.0.3"
-description = "This library brings functools.singledispatch from Python 3.4 to Python 2.6-3.3."
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-six = "*"
-
 [[package]]
 name = "six"
-version = "1.14.0"
+version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
 category = "main"
 optional = false
@@ -361,11 +337,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "sqlparse"
-version = "0.3.0"
-description = "Non-validating SQL parser"
+version = "0.4.2"
+description = "A non-validating SQL parser."
 category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.5"
 
 [[package]]
 name = "text-unidecode"
@@ -375,130 +351,160 @@ category = "main"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "typing-extensions"
+version = "4.1.1"
+description = "Backported and Experimental Type Hints for Python 3.6+"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
 [[package]]
 name = "wcwidth"
-version = "0.1.8"
-description = "Measures number of Terminal column cells of wide-character codes"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
 category = "dev"
 optional = false
 python-versions = "*"
 
 [[package]]
 name = "zipp"
-version = "2.0.1"
+version = "3.6.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 category = "dev"
 optional = false
 python-versions = ">=3.6"
 
-[package.dependencies]
-more-itertools = "*"
-
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.6"
-content-hash = "70f2d0ca9b23717aff477e7b9d5cd06ad6c8ef4a3258c2b177634687d55c9851"
+content-hash = "4bd0181f28757238e172111f7a62abff07a9e306bc09c0bae8a18d684332f81f"
 
 [metadata.files]
 addict = [
-    {file = "addict-2.2.1-py3-none-any.whl", hash = "sha256:1948c2a5d93ba6026eb91aef2c971234aaf72488a9c07ab8a7950f82ae30eea7"},
-    {file = "addict-2.2.1.tar.gz", hash = "sha256:f22493f056032f50e4931a82444fcba8ef74c8fc994c5d06aa546a1433c2b8b0"},
+    {file = "addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc"},
+    {file = "addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494"},
 ]
 aniso8601 = [
-    {file = "aniso8601-7.0.0-py2.py3-none-any.whl", hash = "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b"},
-    {file = "aniso8601-7.0.0.tar.gz", hash = "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e"},
+    {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"},
+    {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"},
 ]
 atomicwrites = [
-    {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"},
-    {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"},
+    {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
+    {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
 ]
 attrs = [
-    {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
-    {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
+    {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
+    {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
 ]
 colorama = [
-    {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
-    {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
 coverage = [
-    {file = "coverage-5.0.3-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f"},
-    {file = "coverage-5.0.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc"},
-    {file = "coverage-5.0.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a"},
-    {file = "coverage-5.0.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52"},
-    {file = "coverage-5.0.3-cp27-cp27m-win32.whl", hash = "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c"},
-    {file = "coverage-5.0.3-cp27-cp27m-win_amd64.whl", hash = "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73"},
-    {file = "coverage-5.0.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68"},
-    {file = "coverage-5.0.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691"},
-    {file = "coverage-5.0.3-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301"},
-    {file = "coverage-5.0.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf"},
-    {file = "coverage-5.0.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3"},
-    {file = "coverage-5.0.3-cp35-cp35m-win32.whl", hash = "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"},
-    {file = "coverage-5.0.3-cp35-cp35m-win_amd64.whl", hash = "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0"},
-    {file = "coverage-5.0.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2"},
-    {file = "coverage-5.0.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894"},
-    {file = "coverage-5.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf"},
-    {file = "coverage-5.0.3-cp36-cp36m-win32.whl", hash = "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477"},
-    {file = "coverage-5.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc"},
-    {file = "coverage-5.0.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8"},
-    {file = "coverage-5.0.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987"},
-    {file = "coverage-5.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea"},
-    {file = "coverage-5.0.3-cp37-cp37m-win32.whl", hash = "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc"},
-    {file = "coverage-5.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e"},
-    {file = "coverage-5.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb"},
-    {file = "coverage-5.0.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37"},
-    {file = "coverage-5.0.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d"},
-    {file = "coverage-5.0.3-cp38-cp38m-win32.whl", hash = "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954"},
-    {file = "coverage-5.0.3-cp38-cp38m-win_amd64.whl", hash = "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e"},
-    {file = "coverage-5.0.3-cp39-cp39m-win32.whl", hash = "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40"},
-    {file = "coverage-5.0.3-cp39-cp39m-win_amd64.whl", hash = "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af"},
-    {file = "coverage-5.0.3.tar.gz", hash = "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef"},
+    {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"},
+    {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"},
+    {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"},
+    {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"},
+    {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"},
+    {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"},
+    {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"},
+    {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"},
+    {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"},
+    {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"},
+    {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"},
+    {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"},
+    {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"},
+    {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"},
+    {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"},
+    {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"},
+    {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"},
+    {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"},
+    {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
+    {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
 ]
 django = [
-    {file = "Django-2.2.25-py3-none-any.whl", hash = "sha256:08bad7ef7e90286b438dbe1412c3e633fbc7b96db04735f0c7baadeed52f3fad"},
-    {file = "Django-2.2.25.tar.gz", hash = "sha256:b1e65eaf371347d4b13eb7e061b09786c973061de95390c327c85c1e2aa2349c"},
+    {file = "Django-2.2.27-py3-none-any.whl", hash = "sha256:90763c764738586b11d7e1f44828032c153366e43ad7f782908193a1bb2d6d92"},
+    {file = "Django-2.2.27.tar.gz", hash = "sha256:1ee37046b0bf2b61e83b3a01d067323516ec3b6f2b17cd49b1326dd4ba9dc913"},
 ]
 factory-boy = [
     {file = "factory_boy-2.12.0-py2.py3-none-any.whl", hash = "sha256:728df59b372c9588b83153facf26d3d28947fc750e8e3c95cefa9bed0e6394ee"},
     {file = "factory_boy-2.12.0.tar.gz", hash = "sha256:faf48d608a1735f0d0a3c9cbf536d64f9132b547dae7ba452c4d99a79e84a370"},
 ]
 faker = [
-    {file = "Faker-4.0.0-py3-none-any.whl", hash = "sha256:047d4d1791bfb3756264da670d99df13d799bb36e7d88774b1585a82d05dbaec"},
-    {file = "Faker-4.0.0.tar.gz", hash = "sha256:1b1a58961683b30c574520d0c739c4443e0ef6a185c04382e8cc888273dbebed"},
+    {file = "Faker-13.3.1-py3-none-any.whl", hash = "sha256:c88c8b5ee9376a242deca8fe829f9a3215ffa43c31da6f66d9594531fb344453"},
+    {file = "Faker-13.3.1.tar.gz", hash = "sha256:fa060e331ffffb57cfa4c07f95d54911e339984ed72596ba6a9e7b6fa569d799"},
 ]
 graphene = [
-    {file = "graphene-2.1.8-py2.py3-none-any.whl", hash = "sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896"},
-    {file = "graphene-2.1.8.tar.gz", hash = "sha256:2cbe6d4ef15cfc7b7805e0760a0e5b80747161ce1b0f990dfdc0d2cf497c12f9"},
+    {file = "graphene-3.0-py2.py3-none-any.whl", hash = "sha256:57ce5ee7c9dc194224a1df96e4f7cb48d31eae96c791091d059f8f3d4d131390"},
+    {file = "graphene-3.0.tar.gz", hash = "sha256:af48066e152200a071aac1e0b703954c717ec7268720ba190a0c91d9bcb0a122"},
 ]
 graphene-django = [
-    {file = "graphene-django-2.15.0.tar.gz", hash = "sha256:b78c9b05bc899016b9cc5bf13faa1f37fe1faa8c5407552c6ddd1a28f46fc31a"},
-    {file = "graphene_django-2.15.0-py2.py3-none-any.whl", hash = "sha256:02671d195f0c09c8649acff2a8f4ad4f297d0f7d98ea6e6cdf034b81bab92880"},
+    {file = "graphene-django-3.0.0b7.tar.gz", hash = "sha256:b1a4ce1a2227b1e77f67f0bc2fadd59c1d05016cb9aced45ab65f8612fba2c87"},
+    {file = "graphene_django-3.0.0b7-py2.py3-none-any.whl", hash = "sha256:0f226ec7db744a54dbc5d6db2aa52d945701ae800e1055046dec2b76a539550a"},
 ]
 graphene-file-upload = [
-    {file = "graphene_file_upload-1.2.2-py3-none-any.whl", hash = "sha256:034ff72d2834b7aebd06fda412fa88a10aba34bac604317f7552457e47040654"},
-    {file = "graphene_file_upload-1.2.2.tar.gz", hash = "sha256:b5f47a81f5614d1184ba9480a63835769b26303cb7759316122401784858e9c7"},
+    {file = "graphene_file_upload-1.3.0-py3-none-any.whl", hash = "sha256:5afe50f409f50e3d198fd92c883d98d868e6c6aaadf5df3a3f4d88ecad90ed97"},
+    {file = "graphene_file_upload-1.3.0.tar.gz", hash = "sha256:6898480b0556826472c80971032917c01968ade5800d84054008fe598795b063"},
 ]
 graphql-core = [
-    {file = "graphql-core-2.2.1.tar.gz", hash = "sha256:da64c472d720da4537a2e8de8ba859210b62841bd47a9be65ca35177f62fe0e4"},
-    {file = "graphql_core-2.2.1-py2.py3-none-any.whl", hash = "sha256:1488f2a5c2272dc9ba66e3042a6d1c30cea0db4c80bd1e911c6791ad6187d91b"},
+    {file = "graphql-core-3.1.7.tar.gz", hash = "sha256:62ec192150ccecd9a18cfb79e3e72eb7d1fd68fb594ef19c40099b6deec8ef0c"},
+    {file = "graphql_core-3.1.7-py3-none-any.whl", hash = "sha256:9b460f60320be01c7f3b1766cf3e406933003008055079b9d983b8f3988f4400"},
 ]
 graphql-relay = [
-    {file = "graphql-relay-2.0.1.tar.gz", hash = "sha256:870b6b5304123a38a0b215a79eace021acce5a466bf40cd39fa18cb8528afabb"},
-    {file = "graphql_relay-2.0.1-py3-none-any.whl", hash = "sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d"},
+    {file = "graphql-relay-3.1.5.tar.gz", hash = "sha256:127f40913f11cb847452ef794931261aae3b222eaaf976f7c8430298534e54d3"},
+    {file = "graphql_relay-3.1.5-py3-none-any.whl", hash = "sha256:d1455316182e1017d06f0b4d4a2ce8bafad38253a6c7c5666fc80d5d516ee5c8"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-1.4.0-py2.py3-none-any.whl", hash = "sha256:bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359"},
-    {file = "importlib_metadata-1.4.0.tar.gz", hash = "sha256:f17c015735e1a88296994c0697ecea7e11db24290941983b08c9feb30921e6d8"},
+    {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"},
+    {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"},
 ]
 more-itertools = [
-    {file = "more-itertools-8.1.0.tar.gz", hash = "sha256:c468adec578380b6281a114cb8a5db34eb1116277da92d7c46f904f0b52d3288"},
-    {file = "more_itertools-8.1.0-py3-none-any.whl", hash = "sha256:1a2a32c72400d365000412fe08eb4a24ebee89997c18d3d147544f70f5403b39"},
+    {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"},
+    {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"},
 ]
 packaging = [
-    {file = "packaging-20.0-py2.py3-none-any.whl", hash = "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb"},
-    {file = "packaging-20.0.tar.gz", hash = "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"},
+    {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+    {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
 ]
 pluggy = [
     {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
@@ -508,58 +514,58 @@ promise = [
     {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"},
 ]
 py = [
-    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
-    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
+    {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+    {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
 ]
 pyparsing = [
-    {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
-    {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
+    {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
+    {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
 ]
 pytest = [
-    {file = "pytest-5.3.4-py3-none-any.whl", hash = "sha256:c13d1943c63e599b98cf118fcb9703e4d7bde7caa9a432567bcdcae4bf512d20"},
-    {file = "pytest-5.3.4.tar.gz", hash = "sha256:1d122e8be54d1a709e56f82e2d85dcba3018313d64647f38a91aec88c239b600"},
+    {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
+    {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
 ]
 pytest-cov = [
-    {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"},
-    {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"},
+    {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
+    {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"},
 ]
 pytest-django = [
-    {file = "pytest-django-3.8.0.tar.gz", hash = "sha256:489b904f695f9fb880ce591cf5a4979880afb467763b1f180c07574554bdfd26"},
-    {file = "pytest_django-3.8.0-py2.py3-none-any.whl", hash = "sha256:456fa6854d04ee625d6bbb8b38ca2259e7040a6f93333bfe8bc8159b7e987203"},
+    {file = "pytest-django-3.10.0.tar.gz", hash = "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6"},
+    {file = "pytest_django-3.10.0-py2.py3-none-any.whl", hash = "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"},
 ]
 python-dateutil = [
-    {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
-    {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
+    {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+    {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
 ]
 pytz = [
-    {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"},
-    {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
-]
-rx = [
-    {file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"},
-    {file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"},
-]
-singledispatch = [
-    {file = "singledispatch-3.4.0.3-py2.py3-none-any.whl", hash = "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8"},
-    {file = "singledispatch-3.4.0.3.tar.gz", hash = "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c"},
+    {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
+    {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
 ]
 six = [
-    {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
-    {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
 ]
 sqlparse = [
-    {file = "sqlparse-0.3.0-py2.py3-none-any.whl", hash = "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177"},
-    {file = "sqlparse-0.3.0.tar.gz", hash = "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"},
+    {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
+    {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
 ]
 text-unidecode = [
     {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
     {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
 ]
+toml = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+typing-extensions = [
+    {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
+    {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
+]
 wcwidth = [
-    {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"},
-    {file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"},
+    {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
+    {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
 ]
 zipp = [
-    {file = "zipp-2.0.1-py3-none-any.whl", hash = "sha256:e013e7800f60ec4dde789ebf4e9f7a54236e4bbf5df2a1a4e20ce9e1d9609d67"},
-    {file = "zipp-2.0.1.tar.gz", hash = "sha256:b338014b9bc7102ca69e0fb96ed07215a8954d2989bc5d83658494ab2ba634af"},
+    {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
+    {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index 2cc98f8..aa8cbb0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,13 +1,13 @@
 [tool.poetry]
 name = "graphene-django-cud"
-version = "0.9.1"
+version = "0.10.0"
 description = ""
 authors = ["Tormod Haugland <tormod.haugland@gmail.com>"]
 license = "MIT"
 
 [tool.poetry.dependencies]
 python = "^3.6"
-graphene-django = "^2.15"
+graphene-django = "^3.0.0b7"
 graphene-file-upload = "^1.2"
 
 [tool.poetry.dev-dependencies]
diff --git a/setup.py b/setup.py
index 511f12a..4ae7dbb 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
 
 setuptools.setup(
     name="graphene-django-cud",
-    version="0.9.1",
+    version="0.10.0",
     author="Tormod Haugland",
     author_email="tormod.haugland@gmail.com",
     description="Create, update and delete mutations for graphene-django",


Test patch:
diff --git a/graphene_django_cud/tests/dummy_query.py b/graphene_django_cud/tests/dummy_query.py
new file mode 100644
index 0000000..5869e9e
--- /dev/null
+++ b/graphene_django_cud/tests/dummy_query.py
@@ -0,0 +1,11 @@
+
+
+# Dummy Query object for testing so that GraphQL 3.0 does not complain
+import graphene
+
+
+class DummyQuery(graphene.ObjectType):
+    ok = graphene.Boolean()
+
+    def resolve_ok(self, info):
+        return True
diff --git a/graphene_django_cud/tests/migrations/0008_auto_20220313_1242.py b/graphene_django_cud/tests/migrations/0008_auto_20220313_1242.py
new file mode 100644
index 0000000..41f3867
--- /dev/null
+++ b/graphene_django_cud/tests/migrations/0008_auto_20220313_1242.py
@@ -0,0 +1,32 @@
+# Generated by Django 2.2.27 on 2022-03-13 12:42
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tests', '0007_auto_20210102_0320'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='CatUserRelation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('friends', models.BooleanField(default=False)),
+                ('cat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cat_user_relations', to='tests.Cat')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cat_user_relations', to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'unique_together': {('cat', 'user')},
+            },
+        ),
+        migrations.AddField(
+            model_name='cat',
+            name='user_relations',
+            field=models.ManyToManyField(blank=True, related_name='related_to_cats', through='tests.CatUserRelation', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/graphene_django_cud/tests/models.py b/graphene_django_cud/tests/models.py
index 62e4fca..5a5af2b 100644
--- a/graphene_django_cud/tests/models.py
+++ b/graphene_django_cud/tests/models.py
@@ -18,6 +18,10 @@ class Cat(models.Model):
     name = models.TextField()
     targets = models.ManyToManyField(Mouse, blank=True, related_name="predators")
 
+    user_relations = models.ManyToManyField(
+        User, through="CatUserRelation", blank=True, related_name="related_to_cats"
+    )
+
 
 class Dog(models.Model):
     owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="dogs")
@@ -36,7 +40,7 @@ class Dog(models.Model):
             ("BULLDOG", "BULLDOG"),
             ("FLATCOAT", "Flatcoat retriever"),
             ("MALAMUTE", "Alaskan Malamuate"),
-        ),
+        )
     )
 
     enemies = models.ManyToManyField(Cat, blank=True, related_name="enemies")
@@ -51,3 +55,15 @@ class DogRegistration(models.Model):
         Dog, related_name="registration", on_delete=models.CASCADE
     )
     registration_number = models.CharField(max_length=32)
+
+
+class CatUserRelation(models.Model):
+    cat = models.ForeignKey(Cat, on_delete=models.CASCADE, related_name="cat_user_relations")
+    user = models.ForeignKey(
+        User, on_delete=models.CASCADE, related_name="cat_user_relations"
+    )
+
+    friends = models.BooleanField(default=False)
+
+    class Meta:
+        unique_together = (("cat", "user"),)
diff --git a/graphene_django_cud/tests/schema.py b/graphene_django_cud/tests/schema.py
index f53f3e8..9291910 100644
--- a/graphene_django_cud/tests/schema.py
+++ b/graphene_django_cud/tests/schema.py
@@ -11,7 +11,14 @@
     DjangoBatchCreateMutation,
 )
 from graphene_django_cud.mutations.filter_update import DjangoFilterUpdateMutation
-from graphene_django_cud.tests.models import User, Cat, Dog, Mouse, DogRegistration
+from graphene_django_cud.tests.models import (
+    User,
+    Cat,
+    Dog,
+    Mouse,
+    DogRegistration,
+    CatUserRelation,
+)
 
 
 class UserNode(DjangoObjectType):
@@ -44,22 +51,30 @@ class Meta:
         interfaces = (Node,)
 
 
+class CatUserRelationNode(DjangoObjectType):
+    class Meta:
+        model = CatUserRelation
+        interfaces = (Node,)
+
+
 class Query(graphene.ObjectType):
     user = Node.Field(UserNode)
     cat = Node.Field(CatNode)
     dog = Node.Field(DogNode)
     mice = Node.Field(MouseNode)
+    cat_user_relation = Node.Field(CatUserRelationNode)
 
     all_users = DjangoConnectionField(UserNode)
     all_cats = DjangoConnectionField(CatNode)
     all_dogs = DjangoConnectionField(DogNode)
     all_mice = DjangoConnectionField(MouseNode)
+    all_cat_user_relations = DjangoConnectionField(CatUserRelationNode)
 
 
 class CreateUserMutation(DjangoCreateMutation):
     class Meta:
         model = User
-        exclude_fields = ("password",)
+        exclude = ("password",)
         many_to_one_extras = {
             "cats": {"exact": {"type": "auto"}},
             "dogs": {
@@ -116,7 +131,10 @@ class Meta:
             "enemies": {"exact": {"type": "CreateDogInput"}},
             "targets": {"exact": {"type": "CreateMouseInput"}},
         }
-        foreign_key_extras = {"owner": {"type": "CreateUserInput"}}
+        many_to_one_extras = {
+            "cat_user_relations": {"add": {"type": "auto"}},
+        }
+        # foreign_key_extras = {"owner": {"type": "CreateUserInput"}}
 
 
 class BatchCreateCatMutation(DjangoBatchCreateMutation):
@@ -132,9 +150,7 @@ class Meta:
                 "add": {
                     "type": "CreateDogInput",
                     "many_to_many_extras": {
-                        "friends": {
-                            "add": {"type": "CreateMouseInput"}
-                        }
+                        "friends": {"add": {"type": "CreateMouseInput"}}
                     },
                 },
                 "remove": True,
@@ -230,10 +246,7 @@ class Meta:
 class FilterDeleteDogMutation(DjangoFilterDeleteMutation):
     class Meta:
         model = Dog
-        filter_fields = (
-            "name",
-            "tag"
-        )
+        filter_fields = ("name", "tag")
 
 
 class FilterDeleteMouseMutation(DjangoFilterDeleteMutation):
@@ -245,10 +258,8 @@ class Meta:
 class FilterUpdateDogMutation(DjangoFilterUpdateMutation):
     class Meta:
         model = Dog
-        filter_fields = (
-            "name",
-            "name__startswith"
-        )
+        filter_fields = ("name", "name__startswith")
+
 
 class Mutations(graphene.ObjectType):
 
diff --git a/graphene_django_cud/tests/test_batch_delete_mutation.py b/graphene_django_cud/tests/test_batch_delete_mutation.py
index 94a3f49..aa44e40 100644
--- a/graphene_django_cud/tests/test_batch_delete_mutation.py
+++ b/graphene_django_cud/tests/test_batch_delete_mutation.py
@@ -6,6 +6,7 @@
 from graphene import Schema
 
 from graphene_django_cud.mutations.batch_delete import DjangoBatchDeleteMutation
+from graphene_django_cud.tests.dummy_query import DummyQuery
 from graphene_django_cud.tests.factories import UserFactory
 from graphene_django_cud.tests.models import User
 from graphene_django_cud.util import disambiguate_ids
@@ -25,7 +26,7 @@ class Mutations(graphene.ObjectType):
             batch_delete_user = BatchDeleteUserMutation.Field()
 
         users = UserFactory.create_batch(10)
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation BatchDeleteUser(
                 $ids: [ID]!,
@@ -68,7 +69,7 @@ class Mutations(graphene.ObjectType):
 
         ids_to_delete = [user.id for user in selection] + non_existing_ids
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation BatchDeleteUser(
                 $ids: [ID]!,
@@ -84,6 +85,7 @@ class Mutations(graphene.ObjectType):
         result = schema.execute(
             mutation, variables={"ids": ids_to_delete},
         )
+        print(result)
         data = Dict(result.data)
         self.assertEqual(5, data.batchDeleteUser.deletionCount)
         self.assertListEqual(
diff --git a/graphene_django_cud/tests/test_batch_patch_mutation.py b/graphene_django_cud/tests/test_batch_patch_mutation.py
index cb839fb..17aa7fd 100644
--- a/graphene_django_cud/tests/test_batch_patch_mutation.py
+++ b/graphene_django_cud/tests/test_batch_patch_mutation.py
@@ -6,6 +6,7 @@
 
 from graphene_django_cud.mutations.batch_patch import DjangoBatchPatchMutation
 from graphene_django_cud.tests.factories import DogFactory, UserFactory
+from graphene_django_cud.tests.dummy_query import DummyQuery
 from graphene_django_cud.tests.models import Dog
 
 
@@ -26,13 +27,13 @@ class Mutations(graphene.ObjectType):
         dog_2 = DogFactory.create()
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation BatchPatchDog(
                 $input: [BatchPatchDogInput]! 
             ){
                 batchPatchDog(input: $input){
-                    dogs{
+                    dogs {
                         id
                         name
                     }
@@ -68,3 +69,88 @@ class Mutations(graphene.ObjectType):
         dog_2.refresh_from_db()
         self.assertEqual("New name 1", dog_1.name)
         self.assertEqual("New name 2", dog_2.name)
+
+
+class TestBatchPatchMutationRequiredFields(TestCase):
+    def setUp(self):
+        # This registers the UserNode type
+        # noinspection PyUnresolvedReferences
+        from .schema import UserNode
+
+        class PatchDogMutation(DjangoBatchPatchMutation):
+            class Meta:
+                model = Dog
+                required_fields = ("owner",)
+
+        class Mutations(graphene.ObjectType):
+            batch_patch_dog = PatchDogMutation.Field()
+
+        self.user1 = UserFactory.create()
+        self.user2 = UserFactory.create()
+        self.dog1 = DogFactory.create(owner=self.user1)
+        self.dog2 = DogFactory.create(owner=self.user2)
+
+        self.user1_id = to_global_id("UserNode", self.user1.id)
+        self.user2_id = to_global_id("UserNode", self.user2.id)
+        self.dog1_id = to_global_id("DogNode", self.dog1.id)
+        self.dog2_id = to_global_id("DogNode", self.dog2.id)
+
+        self.schema = Schema(query=DummyQuery, mutation=Mutations)
+        self.mutation = """
+            mutation BatchPatchDog(
+                $input: [BatchPatchDogInput]!
+            ){
+                batchPatchDog(input: $input){
+                    dogs {
+                        id
+                    }
+                }
+            }
+        """
+        self.context = Dict(user=self.user1)
+
+    def test_required_fields__when_set_and_not_provided__returns_error(self):
+        result = self.schema.execute(
+            self.mutation,
+            variables={
+                "id": to_global_id("DogNode", self.dog1.id),
+                "input": [
+                    {
+                        "id": self.dog1_id,
+                        "name": "Lassie",
+                    },
+                    {
+                        "id": self.dog2_id,
+                        "name": "Richard",
+                    }
+                ]
+            },
+            context=self.context,
+        )
+        self.assertIsNotNone(result.errors)
+
+    def test_required_fields__when_set_and_provided__returns_no_error(self):
+        result = self.schema.execute(
+            self.mutation,
+            variables={
+                "input": [
+                    {
+                        "id": self.dog1_id,
+                        "name": "Lassie",
+                        "owner": self.user2_id,
+                    },
+                    {
+                        "id": self.dog2_id,
+                        "name": "Richard",
+                        "owner": self.user1_id,
+                    }
+                ]
+            },
+            context=self.context,
+        )
+        self.assertIsNone(result.errors)
+
+        self.dog1.refresh_from_db()
+        self.dog2.refresh_from_db()
+        self.assertEqual(self.dog1.owner.id, self.user2.id)
+        self.assertEqual(self.dog2.owner.id, self.user1.id)
diff --git a/graphene_django_cud/tests/test_batch_update_mutation.py b/graphene_django_cud/tests/test_batch_update_mutation.py
index 2bff04d..ce48c8f 100644
--- a/graphene_django_cud/tests/test_batch_update_mutation.py
+++ b/graphene_django_cud/tests/test_batch_update_mutation.py
@@ -6,6 +6,7 @@
 
 from graphene_django_cud.mutations.batch_update import DjangoBatchUpdateMutation
 from graphene_django_cud.tests.factories import DogFactory, UserFactory
+from graphene_django_cud.tests.dummy_query import DummyQuery
 from graphene_django_cud.tests.models import Dog
 
 
@@ -26,7 +27,7 @@ class Mutations(graphene.ObjectType):
         dog_2 = DogFactory.create()
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation BatchUpdateDog(
                 $input: [BatchUpdateDogInput]! 
diff --git a/graphene_django_cud/tests/test_create_mutation.py b/graphene_django_cud/tests/test_create_mutation.py
index 5dc729c..8f373d7 100644
--- a/graphene_django_cud/tests/test_create_mutation.py
+++ b/graphene_django_cud/tests/test_create_mutation.py
@@ -2,7 +2,7 @@
 from addict import Dict
 from django.test import TestCase
 from graphene import Schema
-from graphql import ResolveInfo
+from graphene import ResolveInfo
 from graphql_relay import to_global_id
 
 from graphene_django_cud.mutations import DjangoUpdateMutation, DjangoCreateMutation
@@ -11,9 +11,11 @@
     CatFactory,
     DogFactory,
 )
+from graphene_django_cud.tests.dummy_query import DummyQuery
 from graphene_django_cud.tests.models import User, Cat, Dog, DogRegistration
 from graphene_django_cud.util import disambiguate_id
 
+
 def mock_info(context=None):
     return ResolveInfo(
         None,
@@ -31,7 +33,7 @@ def mock_info(context=None):
 
 class TestCreateMutationManyToOneExtras(TestCase):
     def test_many_to_one_extras__auto_calling_mutation_with_setting_field__does_nothing(
-            self,
+        self,
     ):
         # This registers the UserNode type
         # noinspection PyUnresolvedReferences
@@ -40,7 +42,7 @@ def test_many_to_one_extras__auto_calling_mutation_with_setting_field__does_noth
         class CreateUserMutation(DjangoCreateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "auto"}}}
 
         class Mutations(graphene.ObjectType):
@@ -48,7 +50,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.build()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateUser(
                 $input: CreateUserInput! 
@@ -87,7 +89,7 @@ def test_many_to_one_extras__set_exact_by_id__sets_by_id(self):
         class CreateUserMutation(DjangoCreateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -96,7 +98,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.build()
         other_cats = CatFactory.create_batch(5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateUser(
                 $input: CreateUserInput! 
@@ -136,7 +138,7 @@ def test_many_to_one_extras__add_by_id__adds_by_id(self):
         class CreateUserMutation(DjangoCreateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"add": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -145,7 +147,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.build()
         other_cats = CatFactory.create_batch(5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateUser(
                 $input: CreateUserInput! 
@@ -189,7 +191,7 @@ class Meta:
         class CreateUserMutation(DjangoCreateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"add": {"type": "auto"}}}
 
         class Mutations(graphene.ObjectType):
@@ -198,7 +200,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.build()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateUser(
                 $input: CreateUserInput! 
@@ -250,7 +252,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create()
         DogRegistration.objects.create(dog=dog, registration_number="1234")
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -311,7 +313,7 @@ class Mutations(graphene.ObjectType):
             dog=dog, registration_number="1234"
         )
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDogRegistration(
                 $id: ID!,
@@ -352,9 +354,12 @@ class Mutations(graphene.ObjectType):
             "LABRADOR", data.updateDogRegistration.dogRegistration.dog.breed
         )
 
+        print(dog.breed, type(dog.breed))
         # Load from database
         dog_registration.refresh_from_db()
         dog.refresh_from_db()
+        print(dog.breed, type(dog.breed))
+
         self.assertEqual(dog.breed, "LABRADOR")
 
 
@@ -374,7 +379,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateDog(
                 $input: CreateDogInput!
@@ -430,7 +435,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateDogRegistration(
                 $input: CreateDogRegistrationInput!
@@ -495,7 +500,7 @@ def test__many_to_one_relation_exists__creates_specified_fields(self):
         class CreateUserMutation(DjangoCreateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
 
         class Mutations(graphene.ObjectType):
             create_user = CreateUserMutation.Field()
@@ -503,7 +508,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.create()
         cat = CatFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateUser(
                 $input: CreateUserInput!
@@ -566,7 +571,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.create()
         cat = CatFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateDog(
                 $input: CreateDogInput!
@@ -615,7 +620,9 @@ class Mutations(graphene.ObjectType):
 
 
 class TestCreateMutationCustomFields(TestCase):
-    def test_custom_field__separate_from_model_fields__adds_new_field_which_can_be_handled(self):
+    def test_custom_field__separate_from_model_fields__adds_new_field_which_can_be_handled(
+        self,
+    ):
         # This registers the UserNode type
         # noinspection PyUnresolvedReferences
         from .schema import UserNode
@@ -623,9 +630,7 @@ def test_custom_field__separate_from_model_fields__adds_new_field_which_can_be_h
         class CreateDogMutation(DjangoCreateMutation):
             class Meta:
                 model = Dog
-                custom_fields = {
-                    "bark": graphene.Boolean()
-                }
+                custom_fields = {"bark": graphene.Boolean()}
 
             @classmethod
             def before_save(cls, root, info, input, obj):
@@ -639,7 +644,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create()
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation CreateDog(
                 $input: CreateDogInput! 
@@ -662,7 +667,7 @@ class Mutations(graphene.ObjectType):
                     "tag": "tag",
                     "breed": "HUSKY",
                     "bark": True,
-                    "owner": to_global_id("UserNode", user.id)
+                    "owner": to_global_id("UserNode", user.id),
                 },
             },
             context=Dict(user=user),
@@ -678,7 +683,7 @@ class Mutations(graphene.ObjectType):
                     "name": "Sparky",
                     "tag": "tag-2",
                     "breed": "HUSKY",
-                    "owner": to_global_id("UserNode", user.id)
+                    "owner": to_global_id("UserNode", user.id),
                 },
             },
             context=Dict(user=user),
@@ -686,3 +691,73 @@ class Mutations(graphene.ObjectType):
         self.assertIsNone(result.errors)
 
         self.assertEqual(0, result.data["createDog"]["dog"]["barkCount"])
+
+
+class TestCreateWithManyToManyThroughModel(TestCase):
+    def test__creating_a_related_entity_with_a_through_model__works_as_intended(self):
+        # This registers the UserNode type
+        # noinspection PyUnresolvedReferences
+        from .schema import UserNode
+
+        class CreateCatMutation(DjangoCreateMutation):
+            class Meta:
+                model = Cat
+                many_to_one_extras = {
+                    "cat_user_relations": {
+                        "add": {
+                            "type": "auto",
+                        }
+                    }
+                }
+
+        owner = UserFactory.create()
+        other_user = UserFactory.create()
+
+        class Mutations(graphene.ObjectType):
+            create_cat = CreateCatMutation.Field()
+
+        schema = Schema(query=DummyQuery, mutation=Mutations)
+        mutation = """
+            mutation CreateCat(
+                $input: CreateCatInput! 
+            ){
+                createCat(input: $input){
+                    cat{
+                        id
+                        catUserRelations{
+                            edges{
+                                node {
+                                    id
+                                    friends
+                                    user{
+                                        id
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        """
+        result = schema.execute(
+            mutation,
+            variables={
+                "input": {
+                    "name": "Garfield",
+                    "owner": to_global_id("UserNode", owner.id),
+                    "catUserRelationsAdd": [
+                        {
+                            "user": to_global_id("UserNode", other_user.id),
+                            "friends": True,
+                        }
+                    ],
+                }
+            },
+        )
+
+        self.assertIsNone(result.errors)
+        cat = result.data["createCat"]["cat"]
+        self.assertEqual(
+            cat["catUserRelations"]["edges"][0]["node"]["user"]["id"],
+            to_global_id("UserNode", other_user.id),
+        )
diff --git a/graphene_django_cud/tests/test_delete_mutation.py b/graphene_django_cud/tests/test_delete_mutation.py
index 196d534..f0760cb 100644
--- a/graphene_django_cud/tests/test_delete_mutation.py
+++ b/graphene_django_cud/tests/test_delete_mutation.py
@@ -10,6 +10,7 @@
     CatFactory,
     UserFactory,
 )
+from graphene_django_cud.tests.dummy_query import DummyQuery
 from graphene_django_cud.tests.models import Cat
 from graphene_django_cud.util import disambiguate_id
 
@@ -30,7 +31,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserWithPermissionsFactory.create(permissions=["tests.delete_cat"])
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation DeleteCat(
                 $id: ID!
@@ -68,7 +69,7 @@ class Mutations(graphene.ObjectType):
             delete_cat = DeleteCatMutation.Field()
 
         user = UserWithPermissionsFactory.create(permissions=["tests.delete_cat"])
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation DeleteCat(
                 $id: ID!
@@ -104,7 +105,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation DeleteCat(
                 $id: ID!
diff --git a/graphene_django_cud/tests/test_filter_update_mutation.py b/graphene_django_cud/tests/test_filter_update_mutation.py
index 3bc8f06..c22b5a7 100644
--- a/graphene_django_cud/tests/test_filter_update_mutation.py
+++ b/graphene_django_cud/tests/test_filter_update_mutation.py
@@ -5,6 +5,7 @@
 
 from graphene_django_cud.mutations.filter_update import DjangoFilterUpdateMutation
 from graphene_django_cud.tests.factories import DogFactory, UserFactory
+from graphene_django_cud.tests.dummy_query import DummyQuery
 from graphene_django_cud.tests.models import Dog
 
 
@@ -25,7 +26,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create(name="Simen")
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation FilterUpdateDog(
                 $filter: FilterUpdateDogFilterInput!,
diff --git a/graphene_django_cud/tests/test_patch_mutation.py b/graphene_django_cud/tests/test_patch_mutation.py
index 3e7a8e1..9e26ba0 100644
--- a/graphene_django_cud/tests/test_patch_mutation.py
+++ b/graphene_django_cud/tests/test_patch_mutation.py
@@ -1,8 +1,7 @@
 import graphene
 from addict import Dict
 from django.test import TestCase
-from graphene import Schema
-from graphql import ResolveInfo
+from graphene import Schema, ResolveInfo
 from graphql_relay import to_global_id
 
 from graphene_django_cud.mutations import DjangoPatchMutation, DjangoCreateMutation
@@ -13,9 +12,11 @@
     DogFactory,
     MouseFactory,
 )
+from graphene_django_cud.tests.dummy_query import DummyQuery
 from graphene_django_cud.tests.models import User, Cat, Dog
 from graphene_django_cud.util import disambiguate_id
 
+
 def mock_info(context=None):
     return ResolveInfo(
         None,
@@ -31,7 +32,6 @@ def mock_info(context=None):
     )
 
 
-
 class TestPatchMutation(TestCase):
     def test__model_registered__does_not_raise_error(self):
         # This registers the UserNode type
@@ -57,7 +57,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserWithPermissionsFactory.create(permissions=["tests.change_cat"])
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -99,7 +99,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -137,7 +137,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserWithPermissionsFactory.create(permissions=["tests.change_cat"])
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -180,7 +180,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -226,7 +226,7 @@ class Mutations(graphene.ObjectType):
             permissions=["tests.change_cat"]
         )
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -282,7 +282,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.create()
         new_cat_owner = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -346,7 +346,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -396,7 +396,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -412,7 +412,7 @@ class Mutations(graphene.ObjectType):
         result = schema.execute(
             mutation,
             variables={
-                "id": to_global_id("UserNode", user.id),
+                "id": to_global_id("CatNode", cat.id),
                 "input": {"name": "Name", "owner": to_global_id("UserNode", user.id)},
             },
             context=Dict(user=user),
@@ -439,7 +439,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create(first_name="John", last_name="Doe")
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -455,7 +455,7 @@ class Mutations(graphene.ObjectType):
         result = schema.execute(
             mutation,
             variables={
-                "id": to_global_id("UserNode", user.id),
+                "id": to_global_id("CatNode", cat.id),
                 "input": {
                     "name": "John Doe",
                     "owner": to_global_id("UserNode", user.id),
@@ -494,7 +494,7 @@ class Mutations(graphene.ObjectType):
 
         dog = DogFactory.create()
         user = UserFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchDog(
                 $id: ID!,
@@ -538,6 +538,7 @@ class Mutations(graphene.ObjectType):
         )
         self.assertIsNone(result.errors)
 
+
 class TestPatchMutationManyToManyOnReverseField(TestCase):
     def test_default_setup__adding_resource_by_id__adds_resource(self):
         # This registers the UserNode type
@@ -555,7 +556,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.create()
         dog = DogFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -606,7 +607,7 @@ class Mutations(graphene.ObjectType):
         cat.enemies.set(dog)
         self.assertEqual(cat.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -658,7 +659,7 @@ class Mutations(graphene.ObjectType):
         cat.enemies.set(dog)
         self.assertEqual(cat.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -709,7 +710,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create_batch(5)
         self.assertEqual(cat.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -765,7 +766,7 @@ class Mutations(graphene.ObjectType):
         dogs = DogFactory.create_batch(5)
         self.assertEqual(cat.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -826,7 +827,7 @@ class Mutations(graphene.ObjectType):
         cat.enemies.set(dog)
         self.assertEqual(cat.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchCat(
                 $id: ID!,
@@ -880,7 +881,7 @@ class Mutations(graphene.ObjectType):
         dog.enemies.set(cats)
         self.assertEqual(dog.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchDog(
                 $id: ID!,
@@ -933,7 +934,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5)
         self.assertEqual(dog.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchDog(
                 $id: ID!,
@@ -991,7 +992,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5)
         self.assertEqual(dog.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchDog(
                 $id: ID!,
@@ -1047,7 +1048,7 @@ class Mutations(graphene.ObjectType):
         dog.enemies.set(cats)
         self.assertEqual(dog.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchDog(
                 $id: ID!,
@@ -1092,7 +1093,7 @@ def test_many_to_one_extras__auto_calling_mutation_with_setting_field__does_noth
         class PatchUserMutation(DjangoPatchMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "auto"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1102,7 +1103,7 @@ class Mutations(graphene.ObjectType):
 
         self.assertEqual(user.cats.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchUser(
                 $id: ID!,
@@ -1142,7 +1143,7 @@ def test_many_to_one_extras__calling_exact_with_empty_list__resets_relation(self
         class PatchUserMutation(DjangoPatchMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1154,7 +1155,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5, owner=user)
         self.assertEqual(user.cats.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchUser(
                 $id: ID!,
@@ -1195,7 +1196,7 @@ def test_many_to_one_extras__set_exact_by_id__sets_by_id(self):
         class PatchUserMutation(DjangoPatchMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1207,7 +1208,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5)
         self.assertEqual(user.cats.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchUser(
                 $id: ID!,
@@ -1248,7 +1249,7 @@ def test_many_to_one_extras__add_by_id__adds_by_id(self):
         class PatchUserMutation(DjangoPatchMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"add": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1261,7 +1262,7 @@ class Mutations(graphene.ObjectType):
         other_cats = CatFactory.create_batch(5)
         self.assertEqual(user.cats.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchUser(
                 $id: ID!,
@@ -1306,7 +1307,7 @@ class Meta:
         class PatchUserMutation(DjangoPatchMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"add": {"type": "auto"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1318,7 +1319,7 @@ class Mutations(graphene.ObjectType):
         # Create some cats
         self.assertEqual(user.cats.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchUser(
                 $id: ID!,
@@ -1359,7 +1360,7 @@ def test_many_to_one_extras__remove_extra_by_id__removes_by_id(self):
         class PatchUserMutation(DjangoPatchMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"remove": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1372,7 +1373,7 @@ class Mutations(graphene.ObjectType):
         user.cats.set(cats)
         self.assertEqual(user.cats.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchUser(
                 $id: ID!,
@@ -1413,7 +1414,7 @@ def test_many_to_one_extras__remove_nullable_field__removes_by_id(self):
         class PatchUserMutation(DjangoPatchMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"mice": {"remove": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1426,7 +1427,7 @@ class Mutations(graphene.ObjectType):
         user.mice.set(mice)
         self.assertEqual(user.mice.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchUser(
                 $id: ID!,
@@ -1470,7 +1471,7 @@ class PatchDogMutation(DjangoPatchMutation):
             class Meta:
                 model = Dog
                 foreign_key_extras = {
-                    "owner": {"type": "auto", "exclude_fields": ["password"]}
+                    "owner": {"type": "auto", "exclude": ["password"]}
                 }
 
         class Mutations(graphene.ObjectType):
@@ -1479,7 +1480,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create()
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchDog(
                 $id: ID!,
@@ -1542,7 +1543,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create()
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation PatchDog(
                 $id: ID!,
@@ -1593,3 +1594,71 @@ class Mutations(graphene.ObjectType):
 
         dog.refresh_from_db()
         self.assertEqual(1, dog.bark_count)
+
+
+class TestPatchMutationRequiredFields(TestCase):
+    def setUp(self):
+        # This registers the UserNode type
+        # noinspection PyUnresolvedReferences
+        from .schema import UserNode
+
+        class PatchDogMutation(DjangoPatchMutation):
+            class Meta:
+                model = Dog
+                required_fields = ("owner",)
+
+        class Mutations(graphene.ObjectType):
+            patch_dog = PatchDogMutation.Field()
+
+        self.user1 = UserFactory.create()
+        self.user2 = UserFactory.create()
+        self.dog = DogFactory.create(owner=self.user1)
+
+        self.user1_id = to_global_id("UserNode", self.user1.id)
+        self.user2_id = to_global_id("UserNode", self.user2.id)
+        self.dog_id = to_global_id("DogNode", self.dog.id)
+
+        self.schema = Schema(query=DummyQuery, mutation=Mutations)
+        self.mutation = """
+            mutation PatchDog(
+                $id: ID!,
+                $input: PatchDogInput! 
+            ){
+                patchDog(id: $id, input: $input){
+                    dog {
+                        id
+                    }
+                }
+            }
+        """
+        self.context = Dict(user=self.user1)
+
+    def test_required_fields__when_set_and_not_provided__returns_error(self):
+        result = self.schema.execute(
+            self.mutation,
+            variables={
+                "id": self.dog_id,
+                "input": {
+                    "name": "Lassie",
+                }
+            },
+            context=self.context,
+        )
+        self.assertIsNotNone(result.errors)
+
+    def test_required_fields__when_set_and_provided__returns_no_error(self):
+        result = self.schema.execute(
+            self.mutation,
+            variables={
+                "id": self.dog_id,
+                "input": {
+                    "name": "Lassie",
+                    "owner": self.user2_id
+                }
+            },
+            context=self.context,
+        )
+        self.assertIsNone(result.errors)
+
+        self.dog.refresh_from_db()
+        self.assertEqual(self.dog.owner.id, self.user2.id)
diff --git a/graphene_django_cud/tests/test_update_mutation.py b/graphene_django_cud/tests/test_update_mutation.py
index e44fd29..de99d5b 100644
--- a/graphene_django_cud/tests/test_update_mutation.py
+++ b/graphene_django_cud/tests/test_update_mutation.py
@@ -1,8 +1,7 @@
 import graphene
 from addict import Dict
 from django.test import TestCase
-from graphene import Schema
-from graphql import ResolveInfo
+from graphene import Schema, ResolveInfo
 from graphql_relay import to_global_id
 
 from graphene_django_cud.mutations import DjangoUpdateMutation, DjangoCreateMutation
@@ -15,6 +14,7 @@
 )
 from graphene_django_cud.tests.models import User, Cat, Dog
 from graphene_django_cud.util import disambiguate_id
+from graphene_django_cud.tests.dummy_query import DummyQuery
 
 
 def mock_info(context=None):
@@ -57,7 +57,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -95,7 +95,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserWithPermissionsFactory.create(permissions=["tests.change_cat"])
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -138,7 +138,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -184,7 +184,7 @@ class Mutations(graphene.ObjectType):
             permissions=["tests.change_cat"]
         )
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -240,7 +240,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.create()
         new_cat_owner = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -304,7 +304,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -354,7 +354,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create()
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -397,7 +397,7 @@ class Mutations(graphene.ObjectType):
 
         user = UserFactory.create(first_name="John", last_name="Doe")
         cat = CatFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -452,7 +452,7 @@ class Mutations(graphene.ObjectType):
 
         dog = DogFactory.create()
         user = UserFactory.create()
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -514,7 +514,7 @@ class Mutations(graphene.ObjectType):
         user = UserFactory.create()
         dog = DogFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -565,7 +565,7 @@ class Mutations(graphene.ObjectType):
         cat.enemies.set(dog)
         self.assertEqual(cat.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -617,7 +617,7 @@ class Mutations(graphene.ObjectType):
         cat.enemies.set(dog)
         self.assertEqual(cat.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -668,7 +668,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create_batch(5)
         self.assertEqual(cat.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -724,7 +724,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create_batch(5)
         self.assertEqual(cat.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -784,7 +784,7 @@ class Mutations(graphene.ObjectType):
         cat.enemies.set(dog)
         self.assertEqual(cat.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateCat(
                 $id: ID!,
@@ -838,7 +838,7 @@ class Mutations(graphene.ObjectType):
         dog.enemies.set(cats)
         self.assertEqual(dog.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -891,7 +891,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5)
         self.assertEqual(dog.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -949,7 +949,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5)
         self.assertEqual(dog.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -1005,7 +1005,7 @@ class Mutations(graphene.ObjectType):
         dog.enemies.set(cats)
         self.assertEqual(dog.enemies.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -1058,7 +1058,7 @@ class Mutations(graphene.ObjectType):
 
         self.assertEqual(dog.enemies.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -1108,7 +1108,7 @@ def test_many_to_one_extras__auto_calling_mutation_with_setting_field__does_noth
         class UpdateUserMutation(DjangoUpdateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "auto"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1118,7 +1118,7 @@ class Mutations(graphene.ObjectType):
 
         self.assertEqual(user.cats.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateUser(
                 $id: ID!,
@@ -1158,7 +1158,7 @@ def test_many_to_one_extras__calling_exact_with_empty_list__resets_relation(self
         class UpdateUserMutation(DjangoUpdateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1170,7 +1170,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5, owner=user)
         self.assertEqual(user.cats.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateUser(
                 $id: ID!,
@@ -1211,7 +1211,7 @@ def test_many_to_one_extras__set_exact_by_id__sets_by_id(self):
         class UpdateUserMutation(DjangoUpdateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"exact": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1223,7 +1223,7 @@ class Mutations(graphene.ObjectType):
         cats = CatFactory.create_batch(5)
         self.assertEqual(user.cats.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateUser(
                 $id: ID!,
@@ -1264,7 +1264,7 @@ def test_many_to_one_extras__add_by_id__adds_by_id(self):
         class UpdateUserMutation(DjangoUpdateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"add": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1277,7 +1277,7 @@ class Mutations(graphene.ObjectType):
         other_cats = CatFactory.create_batch(5)
         self.assertEqual(user.cats.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateUser(
                 $id: ID!,
@@ -1322,7 +1322,7 @@ class Meta:
         class UpdateUserMutation(DjangoUpdateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"add": {"type": "auto"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1334,7 +1334,7 @@ class Mutations(graphene.ObjectType):
         # Create some cats
         self.assertEqual(user.cats.all().count(), 0)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateUser(
                 $id: ID!,
@@ -1375,7 +1375,7 @@ def test_many_to_one_extras__remove_extra_by_id__removes_by_id(self):
         class UpdateUserMutation(DjangoUpdateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"cats": {"remove": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1388,7 +1388,7 @@ class Mutations(graphene.ObjectType):
         user.cats.set(cats)
         self.assertEqual(user.cats.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateUser(
                 $id: ID!,
@@ -1429,7 +1429,7 @@ def test_many_to_one_extras__remove_nullable_field__removes_by_id(self):
         class UpdateUserMutation(DjangoUpdateMutation):
             class Meta:
                 model = User
-                exclude_fields = ("password",)
+                exclude = ("password",)
                 many_to_one_extras = {"mice": {"remove": {"type": "ID"}}}
 
         class Mutations(graphene.ObjectType):
@@ -1442,7 +1442,7 @@ class Mutations(graphene.ObjectType):
         user.mice.set(mice)
         self.assertEqual(user.mice.all().count(), 5)
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateUser(
                 $id: ID!,
@@ -1486,7 +1486,7 @@ class UpdateDogMutation(DjangoUpdateMutation):
             class Meta:
                 model = Dog
                 foreign_key_extras = {
-                    "owner": {"type": "auto", "exclude_fields": ["password"]}
+                    "owner": {"type": "auto", "exclude": ["password"]}
                 }
 
         class Mutations(graphene.ObjectType):
@@ -1495,7 +1495,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create()
         user = UserFactory.create()
 
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -1534,7 +1534,9 @@ class Mutations(graphene.ObjectType):
 
 
 class TestUpdateMutationCustomFields(TestCase):
-    def test_custom_field__separate_from_model_fields__adds_new_field_which_can_be_handled(self):
+    def test_custom_field__separate_from_model_fields__adds_new_field_which_can_be_handled(
+        self,
+    ):
         # This registers the UserNode type
         # noinspection PyUnresolvedReferences
         from .schema import UserNode
@@ -1542,9 +1544,7 @@ def test_custom_field__separate_from_model_fields__adds_new_field_which_can_be_h
         class UpdateDogMutation(DjangoUpdateMutation):
             class Meta:
                 model = Dog
-                custom_fields = {
-                    "bark": graphene.Boolean()
-                }
+                custom_fields = {"bark": graphene.Boolean()}
 
             @classmethod
             def before_save(cls, root, info, input, id, obj: Dog):
@@ -1558,8 +1558,7 @@ class Mutations(graphene.ObjectType):
         dog = DogFactory.create()
         user = UserFactory.create()
 
-
-        schema = Schema(mutation=Mutations)
+        schema = Schema(query=DummyQuery, mutation=Mutations)
         mutation = """
             mutation UpdateDog(
                 $id: ID!,
@@ -1583,7 +1582,7 @@ class Mutations(graphene.ObjectType):
                     "tag": "tag",
                     "breed": "HUSKY",
                     "bark": True,
-                    "owner": to_global_id("UserNode", user.id)
+                    "owner": to_global_id("UserNode", user.id),
                 },
             },
             context=Dict(user=user),
@@ -1601,7 +1600,7 @@ class Mutations(graphene.ObjectType):
                     "name": "Sparky",
                     "tag": "tag",
                     "breed": "HUSKY",
-                    "owner": to_global_id("UserNode", user.id)
+                    "owner": to_global_id("UserNode", user.id),
                 },
             },
             context=Dict(user=user),