1
0
mirror of https://github.com/django/django.git synced 2024-12-23 01:25:58 +00:00

Fixed #24097 -- Prevented AttributeError in redirect_to_login

Thanks Peter Schmidt for the report and the initial patch.
Thanks to ​Oktay Sancak for writing the original failing test and
Alvin Savoy for supporting contributing back to the community.
This commit is contained in:
Claude Paroz 2015-01-09 22:18:34 +01:00
parent f5c3a8bff5
commit d7bc37d611
6 changed files with 48 additions and 8 deletions

View File

@ -3,7 +3,6 @@ from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils.decorators import available_attrs from django.utils.decorators import available_attrs
from django.utils.encoding import force_str
from django.utils.six.moves.urllib.parse import urlparse from django.utils.six.moves.urllib.parse import urlparse
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
@ -21,9 +20,7 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
if test_func(request.user): if test_func(request.user):
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
path = request.build_absolute_uri() path = request.build_absolute_uri()
# urlparse chokes on lazy objects in Python 3, force to str resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
resolved_login_url = force_str(
resolve_url(login_url or settings.LOGIN_URL))
# If the login url is the same scheme and net location then just # If the login url is the same scheme and net location then just
# use the path as the "next" url. # use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2] login_scheme, login_netloc = urlparse(resolved_login_url)[:2]

View File

@ -1,3 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from importlib import import_module from importlib import import_module
import itertools import itertools
import re import re
@ -10,9 +13,9 @@ from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm, from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
SetPasswordForm) SetPasswordForm)
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import login as login_view from django.contrib.auth.views import login as login_view, redirect_to_login
from django.core import mail from django.core import mail
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import NoReverseMatch, reverse, reverse_lazy
from django.http import QueryDict, HttpRequest from django.http import QueryDict, HttpRequest
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text from django.utils.encoding import force_text
@ -648,6 +651,10 @@ class LoginURLSettings(AuthViewsTestCase):
expected = 'http://remote.example.com/login/?next=%s' % quoted_next expected = 'http://remote.example.com/login/?next=%s' % quoted_next
self.assertLoginURLEquals(expected) self.assertLoginURLEquals(expected)
@override_settings(LOGIN_URL=reverse_lazy('login'))
def test_lazy_login_url(self):
self.assertLoginURLEquals('/login/?next=/login_required/')
@skipIfCustomUser @skipIfCustomUser
class LoginRedirectUrlTest(AuthViewsTestCase): class LoginRedirectUrlTest(AuthViewsTestCase):
@ -673,6 +680,21 @@ class LoginRedirectUrlTest(AuthViewsTestCase):
self.assertLoginRedirectURLEqual('http://remote.example.com/welcome/') self.assertLoginRedirectURLEqual('http://remote.example.com/welcome/')
class RedirectToLoginTests(AuthViewsTestCase):
"""Tests for the redirect_to_login view"""
@override_settings(LOGIN_URL=reverse_lazy('login'))
def test_redirect_to_login_with_lazy(self):
login_redirect_response = redirect_to_login(next='/else/where/')
expected = '/login/?next=/else/where/'
self.assertEqual(expected, login_redirect_response.url)
@override_settings(LOGIN_URL=reverse_lazy('login'))
def test_redirect_to_login_with_lazy_and_unicode(self):
login_redirect_response = redirect_to_login(next='/else/where/झ/')
expected = '/login/?next=/else/where/%E0%A4%9D/'
self.assertEqual(expected, login_redirect_response.url)
@skipIfCustomUser @skipIfCustomUser
class LogoutTest(AuthViewsTestCase): class LogoutTest(AuthViewsTestCase):

View File

@ -18,6 +18,8 @@ from django.db.models.query import QuerySet
from django.core import urlresolvers from django.core import urlresolvers
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text
from django.utils.functional import Promise
def render_to_response(template_name, context=None, def render_to_response(template_name, context=None,
@ -182,6 +184,11 @@ def resolve_url(to, *args, **kwargs):
if hasattr(to, 'get_absolute_url'): if hasattr(to, 'get_absolute_url'):
return to.get_absolute_url() return to.get_absolute_url()
if isinstance(to, Promise):
# Expand the lazy instance, as it can cause issues when it is passed
# further to some Python functions like urlparse.
to = force_text(to)
if isinstance(to, six.string_types): if isinstance(to, six.string_types):
# Handle relative URLs # Handle relative URLs
if any(to.startswith(path) for path in ('./', '../')): if any(to.startswith(path) for path in ('./', '../')):

View File

@ -20,3 +20,7 @@ Bugfixes
* Fixed a crash in the CSRF middleware when handling non-ASCII referer header * Fixed a crash in the CSRF middleware when handling non-ASCII referer header
(:ticket:`23815`). (:ticket:`23815`).
* Fixed a crash in the ``django.contrib.auth.redirect_to_login`` view when
passing a :func:`~django.core.urlresolvers.reverse_lazy` result on Python 3
(:ticket:`24097`).

View File

@ -1,10 +1,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.urlresolvers import NoReverseMatch from django.core.urlresolvers import NoReverseMatch, reverse_lazy
from django.contrib.auth.views import logout from django.contrib.auth.views import logout
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django.test import TestCase, ignore_warnings, override_settings from django.test import TestCase, ignore_warnings, override_settings
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
from django.utils import six
from .models import UnimportantThing from .models import UnimportantThing
@ -56,6 +57,15 @@ class ResolveUrlTests(TestCase):
resolved_url = resolve_url(logout) resolved_url = resolve_url(logout)
self.assertEqual('/accounts/logout/', resolved_url) self.assertEqual('/accounts/logout/', resolved_url)
def test_lazy_reverse(self):
"""
Tests that passing the result of reverse_lazy is resolved to a real URL
string.
"""
resolved_url = resolve_url(reverse_lazy('logout'))
self.assertIsInstance(resolved_url, six.text_type)
self.assertEqual('/accounts/logout/', resolved_url)
@ignore_warnings(category=RemovedInDjango20Warning) @ignore_warnings(category=RemovedInDjango20Warning)
def test_valid_view_name(self): def test_valid_view_name(self):
""" """

View File

@ -2,5 +2,5 @@ from django.conf.urls import url
from django.contrib.auth import views from django.contrib.auth import views
urlpatterns = [ urlpatterns = [
url(r'^accounts/logout/$', views.logout), url(r'^accounts/logout/$', views.logout, name='logout'),
] ]