diff --git a/docs/authorization.rst b/docs/authorization.rst index 57817eb..8595def 100644 --- a/docs/authorization.rst +++ b/docs/authorization.rst @@ -155,6 +155,11 @@ If you are using ``DjangoObjectType`` you can define a custom `get_queryset`. to Django documentation about ``prefetch_related``: https://docs.djangoproject.com/en/4.2/ref/models/querysets/#prefetch-related. + If you want to explicitly disable the execution of the custom ``get_queryset`` when resolving, + you can decorate the resolver with `@graphene_django.bypass_get_queryset`. Note that this + can lead to authorization leaks if you are performing authorization checks in the custom + ``get_queryset``. + Filtering ID-based Node Access ------------------------------ @@ -207,8 +212,8 @@ For Django 2.2 and above: .. code:: python urlpatterns = [ - # some other urls - path('graphql/', PrivateGraphQLView.as_view(graphiql=True, schema=schema)), + # some other urls + path('graphql/', PrivateGraphQLView.as_view(graphiql=True, schema=schema)), ] .. _LoginRequiredMixin: https://docs.djangoproject.com/en/dev/topics/auth/default/#the-loginrequired-mixin diff --git a/docs/introspection.rst b/docs/introspection.rst index 2097c30..a4ecaae 100644 --- a/docs/introspection.rst +++ b/docs/introspection.rst @@ -57,9 +57,9 @@ specify the parameters in your settings.py: .. code:: python GRAPHENE = { - 'SCHEMA': 'tutorial.quickstart.schema', - 'SCHEMA_OUTPUT': 'data/schema.json', # defaults to schema.json, - 'SCHEMA_INDENT': 2, # Defaults to None (displays all data on a single line) + 'SCHEMA': 'tutorial.quickstart.schema', + 'SCHEMA_OUTPUT': 'data/schema.json', # defaults to schema.json, + 'SCHEMA_INDENT': 2, # Defaults to None (displays all data on a single line) } diff --git a/graphene_django/__init__.py b/graphene_django/__init__.py index d4ef76d..e45dec9 100644 --- a/graphene_django/__init__.py +++ b/graphene_django/__init__.py @@ -1,5 +1,6 @@ from .fields import DjangoConnectionField, DjangoListField from .types import DjangoObjectType +from .utils import bypass_get_queryset __version__ = "3.1.1" @@ -8,4 +9,5 @@ __all__ = [ "DjangoObjectType", "DjangoListField", "DjangoConnectionField", + "bypass_get_queryset", ] diff --git a/graphene_django/converter.py b/graphene_django/converter.py index 1fcc03f..f27119a 100644 --- a/graphene_django/converter.py +++ b/graphene_django/converter.py @@ -278,11 +278,13 @@ def convert_onetoone_field_to_djangomodel(field, registry=None): """ resolver = super().wrap_resolve(parent_resolver) - # If `get_queryset` was not overridden in the DjangoObjectType, + # If `get_queryset` was not overridden in the DjangoObjectType + # or if we explicitly bypass the `get_queryset` method, # we can just return the default resolver. if ( _type.get_queryset.__func__ is DjangoObjectType.get_queryset.__func__ + or getattr(resolver, "_bypass_get_queryset", False) ): return resolver @@ -379,11 +381,13 @@ def convert_field_to_djangomodel(field, registry=None): """ resolver = super().wrap_resolve(parent_resolver) - # If `get_queryset` was not overridden in the DjangoObjectType, + # If `get_queryset` was not overridden in the DjangoObjectType + # or if we explicitly bypass the `get_queryset` method, # we can just return the default resolver. if ( _type.get_queryset.__func__ is DjangoObjectType.get_queryset.__func__ + or getattr(resolver, "_bypass_get_queryset", False) ): return resolver diff --git a/graphene_django/utils/__init__.py b/graphene_django/utils/__init__.py index 671b060..e4780e6 100644 --- a/graphene_django/utils/__init__.py +++ b/graphene_django/utils/__init__.py @@ -6,6 +6,7 @@ from .utils import ( get_reverse_fields, is_valid_django_model, maybe_queryset, + bypass_get_queryset, ) __all__ = [ @@ -16,4 +17,5 @@ __all__ = [ "camelize", "is_valid_django_model", "GraphQLTestCase", + "bypass_get_queryset", ] diff --git a/graphene_django/utils/utils.py b/graphene_django/utils/utils.py index 343a3a7..e0b8b5f 100644 --- a/graphene_django/utils/utils.py +++ b/graphene_django/utils/utils.py @@ -105,3 +105,12 @@ def set_rollback(): atomic_requests = connection.settings_dict.get("ATOMIC_REQUESTS", False) if atomic_requests and connection.in_atomic_block: transaction.set_rollback(True) + + +def bypass_get_queryset(resolver): + """ + Adds a bypass_get_queryset attribute to the resolver, which is used to + bypass any custom get_queryset method of the DjangoObjectType. + """ + resolver._bypass_get_queryset = True + return resolver