diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 529400df28..b5556aaa11 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -43,6 +43,7 @@ class LoginView(SuccessURLAllowedHostsMixin, FormView): """ form_class = AuthenticationForm authentication_form = None + next_page = None redirect_field_name = REDIRECT_FIELD_NAME template_name = 'registration/login.html' redirect_authenticated_user = False @@ -63,8 +64,7 @@ class LoginView(SuccessURLAllowedHostsMixin, FormView): return super().dispatch(request, *args, **kwargs) def get_success_url(self): - url = self.get_redirect_url() - return url or resolve_url(settings.LOGIN_REDIRECT_URL) + return self.get_redirect_url() or self.get_default_redirect_url() def get_redirect_url(self): """Return the user-originating redirect URL if it's safe.""" @@ -79,6 +79,10 @@ class LoginView(SuccessURLAllowedHostsMixin, FormView): ) return redirect_to if url_is_safe else '' + def get_default_redirect_url(self): + """Return the default redirect URL.""" + return resolve_url(self.next_page or settings.LOGIN_REDIRECT_URL) + def get_form_class(self): return self.authentication_form or self.form_class diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 37783b906b..296967a92b 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -47,6 +47,12 @@ Minor features * The default iteration count for the PBKDF2 password hasher is increased from 260,000 to 320,000. +* The new + :attr:`LoginView.next_page ` + attribute and + :meth:`~django.contrib.auth.views.LoginView.get_default_redirect_url` method + allow customizing the redirect after login. + :mod:`django.contrib.contenttypes` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 05ccc5eed6..82cf58c128 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -996,17 +996,26 @@ implementation details see :ref:`using-the-views`. See :doc:`the URL documentation ` for details on using named URL patterns. - **Attributes:** + **Methods and Attributes** .. attribute:: template_name The name of a template to display for the view used to log the user in. Defaults to :file:`registration/login.html`. + .. attribute:: next_page + + .. versionadded:: 4.0 + + The URL to redirect to after login. Defaults to + :setting:`LOGIN_REDIRECT_URL`. + .. attribute:: redirect_field_name The name of a ``GET`` field containing the URL to redirect to after - login. Defaults to ``next``. + login. Defaults to ``next``. Overrides the + :meth:`get_default_redirect_url` URL if the given ``GET`` parameter is + passed. .. attribute:: authentication_form @@ -1043,6 +1052,14 @@ implementation details see :ref:`using-the-views`. `, that are safe for redirecting after login. Defaults to an empty :class:`set`. + .. method:: get_default_redirect_url() + + .. versionadded:: 4.0 + + Returns the URL to redirect to after login. The default implementation + resolves and returns :attr:`next_page` if set, or + :setting:`LOGIN_REDIRECT_URL` otherwise. + Here's what ``LoginView`` does: * If called via ``GET``, it displays a login form that POSTs to the diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index e57d661772..0ec3a134c2 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -52,8 +52,8 @@ class AuthViewsTestCase(TestCase): cls.u1 = User.objects.create_user(username='testclient', password='password', email='testclient@example.com') cls.u3 = User.objects.create_user(username='staff', password='password', email='staffmember@example.com') - def login(self, username='testclient', password='password'): - response = self.client.post('/login/', { + def login(self, username='testclient', password='password', url='/login/'): + response = self.client.post(url, { 'username': username, 'password': password, }) @@ -726,6 +726,31 @@ class LoginTest(AuthViewsTestCase): self.login() self.assertNotEqual(original_session_key, self.client.session.session_key) + def test_login_get_default_redirect_url(self): + response = self.login(url='/login/get_default_redirect_url/') + self.assertRedirects(response, '/custom/', fetch_redirect_response=False) + + def test_login_next_page(self): + response = self.login(url='/login/next_page/') + self.assertRedirects(response, '/somewhere/', fetch_redirect_response=False) + + def test_login_named_next_page_named(self): + response = self.login(url='/login/next_page/named/') + self.assertRedirects(response, '/password_reset/', fetch_redirect_response=False) + + @override_settings(LOGIN_REDIRECT_URL='/custom/') + def test_login_next_page_overrides_login_redirect_url_setting(self): + response = self.login(url='/login/next_page/') + self.assertRedirects(response, '/somewhere/', fetch_redirect_response=False) + + def test_login_redirect_url_overrides_next_page(self): + response = self.login(url='/login/next_page/?next=/test/') + self.assertRedirects(response, '/test/', fetch_redirect_response=False) + + def test_login_redirect_url_overrides_get_default_redirect_url(self): + response = self.login(url='/login/get_default_redirect_url/?next=/test/') + self.assertRedirects(response, '/test/', fetch_redirect_response=False) + class LoginURLSettings(AuthViewsTestCase): """Tests for settings.LOGIN_URL.""" diff --git a/tests/auth_tests/urls.py b/tests/auth_tests/urls.py index e225ab9362..044f0da037 100644 --- a/tests/auth_tests/urls.py +++ b/tests/auth_tests/urls.py @@ -3,6 +3,7 @@ from django.contrib.auth import views from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.urls import urlpatterns as auth_urlpatterns +from django.contrib.auth.views import LoginView from django.contrib.messages.api import info from django.http import HttpRequest, HttpResponse from django.shortcuts import render @@ -78,6 +79,11 @@ def login_and_permission_required_exception(request): pass +class CustomDefaultRedirectURLLoginView(LoginView): + def get_default_redirect_url(self): + return '/custom/' + + # special urls for auth test cases urlpatterns = auth_urlpatterns + [ path('logout/custom_query/', views.LogoutView.as_view(redirect_field_name='follow')), @@ -149,6 +155,9 @@ urlpatterns = auth_urlpatterns + [ views.LoginView.as_view(redirect_authenticated_user=True)), path('login/allowed_hosts/', views.LoginView.as_view(success_url_allowed_hosts={'otherserver'})), + path('login/get_default_redirect_url/', CustomDefaultRedirectURLLoginView.as_view()), + path('login/next_page/', views.LoginView.as_view(next_page='/somewhere/')), + path('login/next_page/named/', views.LoginView.as_view(next_page='password_reset')), path('permission_required_redirect/', permission_required_redirect), path('permission_required_exception/', permission_required_exception),