Commit a78dd109 authored by Carl Meyer's avatar Carl Meyer
Browse files

Fixed #15552 -- LOGIN_URL and LOGIN_REDIRECT_URL can take URLpattern names.

Thanks UloPe and Eric Florenzano for the patch, and Malcolm Tredinnick for
review.
parent 518c5829
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import PermissionDenied
from django.utils.decorators import available_attrs
from django.utils.encoding import force_str
from django.shortcuts import resolve_url


def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
@@ -23,11 +24,10 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
            if test_func(request.user):
                return view_func(request, *args, **kwargs)
            path = request.build_absolute_uri()
            # urlparse chokes on lazy objects in Python 3
            login_url_as_str = force_str(login_url or settings.LOGIN_URL)
            resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
            # If the login url is the same scheme and net location then just
            # use the path as the "next" url.
            login_scheme, login_netloc = urlparse(login_url_as_str)[:2]
            login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
            current_scheme, current_netloc = urlparse(path)[:2]
            if ((not login_scheme or login_scheme == current_scheme) and
                (not login_netloc or login_netloc == current_netloc)):
+1 −1
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ class LoginRequiredTestCase(AuthViewsTestCase):
            pass
        login_required(normal_view)

    def testLoginRequired(self, view_url='/login_required/', login_url=settings.LOGIN_URL):
    def testLoginRequired(self, view_url='/login_required/', login_url='/login/'):
        """
        Check that login_required works on a simple view wrapped in a
        login_required decorator.
+9 −9
Original line number Diff line number Diff line
@@ -7,9 +7,9 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse
from django.utils.encoding import force_str
from django.utils.http import base36_to_int
from django.utils.translation import ugettext as _
from django.shortcuts import resolve_url
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
@@ -38,16 +38,16 @@ def login(request, template_name='registration/login.html',
    if request.method == "POST":
        form = authentication_form(data=request.POST)
        if form.is_valid():
            netloc = urlparse(redirect_to)[1]

            # Use default setting if redirect_to is empty
            if not redirect_to:
                redirect_to = settings.LOGIN_REDIRECT_URL
            redirect_to = resolve_url(redirect_to)

            netloc = urlparse(redirect_to)[1]
            # Heavier security check -- don't allow redirection to a different
            # host.
            elif netloc and netloc != request.get_host():
                redirect_to = settings.LOGIN_REDIRECT_URL
            if netloc and netloc != request.get_host():
                redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)

            # Okay, security checks complete. Log the user in.
            auth_login(request, form.get_user())
@@ -110,6 +110,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N
    """
    if not login_url:
        login_url = settings.LOGIN_URL
    login_url = resolve_url(login_url)
    return logout(request, login_url, current_app=current_app, extra_context=extra_context)

def redirect_to_login(next, login_url=None,
@@ -117,10 +118,9 @@ def redirect_to_login(next, login_url=None,
    """
    Redirects the user to the login page, passing the given 'next' page
    """
    # urlparse chokes on lazy objects in Python 3
    login_url_as_str = force_str(login_url or settings.LOGIN_URL)
    resolved_url = resolve_url(login_url or settings.LOGIN_URL)

    login_url_parts = list(urlparse(login_url_as_str))
    login_url_parts = list(urlparse(resolved_url))
    if redirect_field_name:
        querystring = QueryDict(login_url_parts[4], mutable=True)
        querystring[redirect_field_name] = next
@@ -229,7 +229,7 @@ def password_reset_complete(request,
                            template_name='registration/password_reset_complete.html',
                            current_app=None, extra_context=None):
    context = {
        'login_url': settings.LOGIN_URL
        'login_url': resolve_url(settings.LOGIN_URL)
    }
    if extra_context is not None:
        context.update(extra_context)
+32 −17
Original line number Diff line number Diff line
@@ -66,23 +66,7 @@ def redirect(to, *args, **kwargs):
    else:
        redirect_class = HttpResponseRedirect

    # If it's a model, use get_absolute_url()
    if hasattr(to, 'get_absolute_url'):
        return redirect_class(to.get_absolute_url())

    # Next try a reverse URL resolution.
    try:
        return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs))
    except urlresolvers.NoReverseMatch:
        # If this is a callable, re-raise.
        if callable(to):
            raise
        # If this doesn't "feel" like a URL, re-raise.
        if '/' not in to and '.' not in to:
            raise

    # Finally, fall back and assume it's a URL
    return redirect_class(to)
    return redirect_class(resolve_url(to, *args, **kwargs))

def _get_queryset(klass):
    """
@@ -128,3 +112,34 @@ def get_list_or_404(klass, *args, **kwargs):
        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
    return obj_list

def resolve_url(to, *args, **kwargs):
    """
    Return a URL appropriate for the arguments passed.

    The arguments could be:

        * A model: the model's `get_absolute_url()` function will be called.

        * A view name, possibly with arguments: `urlresolvers.reverse()` will
          be used to reverse-resolve the name.

        * A URL, which will be returned as-is.

    """
    # If it's a model, use get_absolute_url()
    if hasattr(to, 'get_absolute_url'):
        return to.get_absolute_url()

    # Next try a reverse URL resolution.
    try:
        return urlresolvers.reverse(to, args=args, kwargs=kwargs)
    except urlresolvers.NoReverseMatch:
        # If this is a callable, re-raise.
        if callable(to):
            raise
        # If this doesn't "feel" like a URL, re-raise.
        if '/' not in to and '.' not in to:
            raise

    # Finally, fall back and assume it's a URL
    return to
+13 −20
Original line number Diff line number Diff line
@@ -1304,25 +1304,13 @@ The URL where requests are redirected after login when the
This is used by the :func:`~django.contrib.auth.decorators.login_required`
decorator, for example.

.. _`note on LOGIN_REDIRECT_URL setting`:

.. note::
    You can use :func:`~django.core.urlresolvers.reverse_lazy` to reference
    URLs by their name instead of providing a hardcoded value. Assuming a
    ``urls.py`` with an URLpattern named ``home``::

        urlpatterns = patterns('',
            url('^welcome/$', 'test_app.views.home', name='home'),
        )

    You can use :func:`~django.core.urlresolvers.reverse_lazy` like this::

        from django.core.urlresolvers import reverse_lazy

        LOGIN_REDIRECT_URL = reverse_lazy('home')
.. versionchanged:: 1.5

    This also works fine with localized URLs using
    :func:`~django.conf.urls.i18n.i18n_patterns`.
This setting now also accepts view function names and
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
configuration duplication since you no longer have to define the URL in two
places (``settings`` and URLconf).
For backward compatibility reasons the default remains unchanged.

.. setting:: LOGIN_URL

@@ -1334,8 +1322,13 @@ Default: ``'/accounts/login/'``
The URL where requests are redirected for login, especially when using the
:func:`~django.contrib.auth.decorators.login_required` decorator.

.. note::
    See the `note on LOGIN_REDIRECT_URL setting`_
.. versionchanged:: 1.5

This setting now also accepts view function names and
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
configuration duplication since you no longer have to define the URL in two
places (``settings`` and URLconf).
For backward compatibility reasons the default remains unchanged.

.. setting:: LOGOUT_URL

Loading