From 55d6aebfecb527d64f50e608bb51cb5ea81f4c62 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Fri, 14 Sep 2007 19:25:37 +0000 Subject: [PATCH] Fixed #5394 -- REDIRECT_FIELD_NAME is now configurable. Thanks, Petr Marhoun, DavidReynolds and effbot git-svn-id: http://code.djangoproject.com/svn/django/trunk@6206 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/decorators.py | 13 +++++++++---- django/contrib/auth/views.py | 10 +++++----- docs/authentication.txt | 20 +++++++++++++++++++- tests/modeltests/test_client/models.py | 16 ++++++++++++++++ tests/modeltests/test_client/urls.py | 1 + tests/modeltests/test_client/views.py | 8 ++++++++ 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 2fb4a6f510..de0dc21c5d 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -2,7 +2,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from django.http import HttpResponseRedirect from urllib import quote -def user_passes_test(test_func, login_url=None): +def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): """ Decorator for views that checks that the user passes the given test, redirecting to the log-in page if necessary. The test should be a callable @@ -15,20 +15,25 @@ def user_passes_test(test_func, login_url=None): def _checklogin(request, *args, **kwargs): if test_func(request.user): return view_func(request, *args, **kwargs) - return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path()))) + return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, quote(request.get_full_path()))) _checklogin.__doc__ = view_func.__doc__ _checklogin.__dict__ = view_func.__dict__ return _checklogin return _dec -login_required = user_passes_test(lambda u: u.is_authenticated()) -login_required.__doc__ = ( +def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME): """ Decorator for views that checks that the user is logged in, redirecting to the log-in page if necessary. """ + actual_decorator = user_passes_test( + lambda u: u.is_authenticated(), + redirect_field_name=redirect_field_name ) + if function: + return actual_decorator(function) + return actual_decorator def permission_required(perm, login_url=None): """ diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index f1129379d6..d3d8b4ccb7 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -9,10 +9,10 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth import REDIRECT_FIELD_NAME from django.utils.translation import ugettext as _ -def login(request, template_name='registration/login.html'): +def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME): "Displays the login form and handles the login action." manipulator = AuthenticationForm(request) - redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '') + redirect_to = request.REQUEST.get(redirect_field_name, '') if request.POST: errors = manipulator.get_validation_errors(request.POST) if not errors: @@ -35,7 +35,7 @@ def login(request, template_name='registration/login.html'): return render_to_response(template_name, { 'form': oldforms.FormWrapper(manipulator, request.POST, errors), - REDIRECT_FIELD_NAME: redirect_to, + redirect_field_name: redirect_to, 'site_name': current_site.name, }, context_instance=RequestContext(request)) @@ -56,12 +56,12 @@ def logout_then_login(request, login_url=None): login_url = settings.LOGIN_URL return logout(request, login_url) -def redirect_to_login(next, login_url=None): +def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): "Redirects the user to the login page, passing the given 'next' page" if not login_url: from django.conf import settings login_url = settings.LOGIN_URL - return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, next)) + return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, next)) def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html', email_template_name='registration/password_reset_email.html'): diff --git a/docs/authentication.txt b/docs/authentication.txt index 131a8930b5..820aff2712 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -402,11 +402,29 @@ introduced in Python 2.4:: def my_view(request): # ... +In the Django development version, ``login_required`` also takes an optional +``redirect_field_name`` parameter. Example:: + + from django.contrib.auth.decorators import login_required + + def my_view(request): + # ... + my_view = login_required(redirect_field_name='redirect_to')(my_view) + +Again, an equivalent example of the more compact decorator syntax introduced in Python 2.4:: + + from django.contrib.auth.decorators import login_required + + @login_required(redirect_field_name='redirect_to') + def my_view(request): + # ... + ``login_required`` does the following: * If the user isn't logged in, redirect to ``settings.LOGIN_URL`` (``/accounts/login/`` by default), passing the current absolute URL - in the query string as ``next``. For example: + in the query string as ``next`` or the value of ``redirect_field_name``. + For example: ``/accounts/login/?next=/polls/3/``. * If the user is logged in, execute the view normally. The view code is free to assume the user is logged in. diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 32647552eb..2df5d3cf77 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -250,6 +250,22 @@ class ClientTest(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['user'].username, 'testclient') + def test_view_with_login_and_custom_redirect(self): + "Request a page that is protected with @login_required(redirect_field_name='redirect_to')" + + # Get the page without logging in. Should result in 302. + response = self.client.get('/test_client/login_protected_view_custom_redirect/') + self.assertRedirects(response, 'http://testserver/accounts/login/?redirect_to=/test_client/login_protected_view_custom_redirect/') + + # Log in + login = self.client.login(username='testclient', password='password') + self.failUnless(login, 'Could not log in') + + # Request a page that requires a login + response = self.client.get('/test_client/login_protected_view_custom_redirect/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['user'].username, 'testclient') + def test_view_with_bad_login(self): "Request a page that is protected with @login, but use bad credentials" diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 538c0e4b43..3779a0ecd1 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -13,6 +13,7 @@ urlpatterns = patterns('', (r'^form_view/$', views.form_view), (r'^form_view_with_template/$', views.form_view_with_template), (r'^login_protected_view/$', views.login_protected_view), + (r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect), (r'^session_view/$', views.session_view), (r'^broken_view/$', views.broken_view), (r'^mail_sending_view/$', views.mail_sending_view), diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index e2a9081fb2..c406e17d30 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -122,6 +122,14 @@ def login_protected_view(request): return HttpResponse(t.render(c)) login_protected_view = login_required(login_protected_view) +def login_protected_view_changed_redirect(request): + "A simple view that is login protected with a custom redirect field set" + t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template') + c = Context({'user': request.user}) + + return HttpResponse(t.render(c)) +login_protected_view_changed_redirect = login_required(redirect_field_name="redirect_to")(login_protected_view_changed_redirect) + def session_view(request): "A view that modifies the session" request.session['tobacconist'] = 'hovercraft'