Skip to content

Commit 6966ddc

Browse files
committed
Decorate the login view with login_not_required on Django 5.1+
1 parent 68ef964 commit 6966ddc

3 files changed

Lines changed: 36 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
### Added
88
- Support confirmation for Django 5.1.
9+
- The login view is also decorated with the `login_not_required` decorator for
10+
projects using the new `LoginRequiredMiddleware` available with Django 5.1+.
911

1012
### Removed
1113
- Dropped support for Django <4.2.

tests/test_views_login.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import json
22
from importlib import import_module
33
from time import sleep
4-
from unittest import mock
4+
from unittest import mock, skipUnless
55

66
from django.conf import settings
77
from django.core.signing import BadSignature
88
from django.shortcuts import resolve_url
99
from django.test import RequestFactory, TestCase
10-
from django.test.utils import override_settings
10+
from django.test.utils import modify_settings, override_settings
1111
from django.urls import reverse
1212
from django_otp import DEVICE_ID_SESSION_KEY
1313
from django_otp.oath import totp
@@ -18,12 +18,27 @@
1818

1919
from .utils import UserMixin, totp_str
2020

21+
try:
22+
from django.contrib.auth.middleware import LoginRequiredMiddleware # NOQA
23+
has_login_required_middleware = True
24+
except ImportError:
25+
# Django < 5.1
26+
has_login_required_middleware = False
27+
2128

2229
class LoginTest(UserMixin, TestCase):
2330
def _post(self, data=None):
2431
return self.client.post(reverse('two_factor:login'), data=data)
2532

26-
def test_form(self):
33+
def test_get_to_login(self):
34+
response = self.client.get(reverse('two_factor:login'))
35+
self.assertContains(response, 'Password:')
36+
37+
@skipUnless(has_login_required_middleware, 'LoginRequiredMiddleware needs Django 5.1+')
38+
@modify_settings(
39+
MIDDLEWARE={'append': 'django.contrib.auth.middleware.LoginRequiredMiddleware'}
40+
)
41+
def test_get_to_login_with_loginrequiredmiddleware(self):
2742
response = self.client.get(reverse('two_factor:login'))
2843
self.assertContains(response, 'Password:')
2944

two_factor/views/core.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,27 @@
5151
validate_remember_device_cookie,
5252
)
5353

54+
try:
55+
from django.contrib.auth.decorators import login_not_required
56+
except ImportError:
57+
# For Django < 5.1, copy the current Django implementation
58+
def login_not_required(view_func):
59+
"""
60+
Decorator for views that allows access to unauthenticated requests.
61+
"""
62+
view_func.login_required = False
63+
return view_func
64+
65+
5466
logger = logging.getLogger(__name__)
5567

5668
REMEMBER_COOKIE_PREFIX = getattr(settings, 'TWO_FACTOR_REMEMBER_COOKIE_PREFIX', 'remember-cookie_')
5769

5870

59-
@method_decorator([sensitive_post_parameters(), csrf_protect, never_cache], name='dispatch')
71+
@method_decorator(
72+
[login_not_required, sensitive_post_parameters(), csrf_protect, never_cache],
73+
name='dispatch'
74+
)
6075
class LoginView(RedirectURLMixin, IdempotentSessionWizardView):
6176
"""
6277
View for handling the login process, including OTP verification.

0 commit comments

Comments
 (0)