diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 864ff7395..b3a9cdfeb 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -8,6 +8,7 @@ methods on viewsets that should be included by routers. """ import types +from django import VERSION as DJANGO_VERSION from django.forms.utils import pretty_name from rest_framework.views import APIView @@ -73,6 +74,11 @@ def api_view(http_method_names=None): WrappedAPIView.schema = getattr(func, 'schema', APIView.schema) + # Exempt all DRF views from Django's LoginRequiredMiddleware. Users should set + # DEFAULT_PERMISSION_CLASSES to 'rest_framework.permissions.IsAuthenticated' instead + if DJANGO_VERSION >= (5, 1): + func.login_required = False + return WrappedAPIView.as_view() return decorator diff --git a/rest_framework/views.py b/rest_framework/views.py index 411c1ee38..327ebe903 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -1,6 +1,7 @@ """ Provides an APIView class that is the base of all views in REST framework. """ +from django import VERSION as DJANGO_VERSION from django.conf import settings from django.core.exceptions import PermissionDenied from django.db import connections, models @@ -139,6 +140,11 @@ class APIView(View): view.cls = cls view.initkwargs = initkwargs + # Exempt all DRF views from Django's LoginRequiredMiddleware. Users should set + # DEFAULT_PERMISSION_CLASSES to 'rest_framework.permissions.IsAuthenticated' instead + if DJANGO_VERSION >= (5, 1): + view.login_required = False + # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. return csrf_exempt(view) diff --git a/tests/test_views.py b/tests/test_views.py index 2648c9fb3..4fd39d368 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,5 +1,7 @@ import copy +import unittest +from django import VERSION as DJANGO_VERSION from django.test import TestCase from rest_framework import status @@ -81,6 +83,10 @@ class ClassBasedViewIntegrationTests(TestCase): assert response.status_code == status.HTTP_400_BAD_REQUEST assert sanitise_json_error(response.data) == expected + @unittest.skipUnless(DJANGO_VERSION >= (5, 1), 'Only for Django 5.1+') + def test_django_51_login_required_disabled(self): + assert self.view.login_required is False + class FunctionBasedViewIntegrationTests(TestCase): def setUp(self): @@ -95,6 +101,10 @@ class FunctionBasedViewIntegrationTests(TestCase): assert response.status_code == status.HTTP_400_BAD_REQUEST assert sanitise_json_error(response.data) == expected + @unittest.skipUnless(DJANGO_VERSION >= (5, 1), 'Only for Django 5.1+') + def test_django_51_login_required_disabled(self): + assert self.view.login_required is False + class TestCustomExceptionHandler(TestCase): def setUp(self):