From 8b0014869f666b44cd20692e38073ec0a0a8cb08 Mon Sep 17 00:00:00 2001 From: Ryan Kaskel Date: Mon, 20 May 2013 20:22:38 +0100 Subject: [PATCH] Fixed #19321 -- Allowed redirect middleware HTTP responses to be overridden. Thanks Melevir for the suggestion. --- django/contrib/redirects/middleware.py | 12 ++++++-- django/contrib/redirects/tests.py | 30 ++++++++++++++++++++ docs/ref/contrib/redirects.txt | 39 ++++++++++++++++++++++---- docs/releases/1.7.txt | 11 ++++++++ 4 files changed, 83 insertions(+), 9 deletions(-) diff --git a/django/contrib/redirects/middleware.py b/django/contrib/redirects/middleware.py index 03c9d97c0d..9053c64a9a 100644 --- a/django/contrib/redirects/middleware.py +++ b/django/contrib/redirects/middleware.py @@ -8,6 +8,11 @@ from django import http class RedirectFallbackMiddleware(object): + + # Defined as class-level attributes to be subclassing-friendly. + response_gone_class = http.HttpResponseGone + response_redirect_class = http.HttpResponsePermanentRedirect + def __init__(self): if 'django.contrib.sites' not in settings.INSTALLED_APPS: raise ImproperlyConfigured( @@ -16,8 +21,9 @@ class RedirectFallbackMiddleware(object): ) def process_response(self, request, response): + # No need to check for a redirect for non-404 responses. if response.status_code != 404: - return response # No need to check for a redirect for non-404 responses. + return response full_path = request.get_full_path() current_site = get_current_site(request) @@ -37,8 +43,8 @@ class RedirectFallbackMiddleware(object): pass if r is not None: if r.new_path == '': - return http.HttpResponseGone() - return http.HttpResponsePermanentRedirect(r.new_path) + return self.response_gone_class() + return self.response_redirect_class(r.new_path) # No redirect was found. Return the response. return response diff --git a/django/contrib/redirects/tests.py b/django/contrib/redirects/tests.py index bdcdf4a9e1..c255caf980 100644 --- a/django/contrib/redirects/tests.py +++ b/django/contrib/redirects/tests.py @@ -1,3 +1,4 @@ +from django import http from django.conf import settings from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured @@ -61,3 +62,32 @@ class RedirectTests(TestCase): def test_sites_not_installed(self): with self.assertRaises(ImproperlyConfigured): RedirectFallbackMiddleware() + + +class OverriddenRedirectFallbackMiddleware(RedirectFallbackMiddleware): + # Use HTTP responses different from the defaults + response_gone_class = http.HttpResponseForbidden + response_redirect_class = http.HttpResponseRedirect + + +@override_settings( + MIDDLEWARE_CLASSES=list(settings.MIDDLEWARE_CLASSES) + + ['django.contrib.redirects.tests.OverriddenRedirectFallbackMiddleware'], + SITE_ID=1, +) +class OverriddenRedirectMiddlewareTests(TestCase): + + def setUp(self): + self.site = Site.objects.get(pk=settings.SITE_ID) + + def test_response_gone_class(self): + Redirect.objects.create( + site=self.site, old_path='/initial/', new_path='') + response = self.client.get('/initial/') + self.assertEqual(response.status_code, 403) + + def test_response_redirect_class(self): + Redirect.objects.create( + site=self.site, old_path='/initial/', new_path='/new_target/') + response = self.client.get('/initial/') + self.assertEqual(response.status_code, 302) diff --git a/docs/ref/contrib/redirects.txt b/docs/ref/contrib/redirects.txt index eefbb96721..817fc8228a 100644 --- a/docs/ref/contrib/redirects.txt +++ b/docs/ref/contrib/redirects.txt @@ -26,10 +26,11 @@ How it works ``manage.py migrate`` creates a ``django_redirect`` table in your database. This is a simple lookup table with ``site_id``, ``old_path`` and ``new_path`` fields. -The ``RedirectFallbackMiddleware`` does all of the work. Each time any Django -application raises a 404 error, this middleware checks the redirects database -for the requested URL as a last resort. Specifically, it checks for a redirect -with the given ``old_path`` with a site ID that corresponds to the +The :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware` +does all of the work. Each time any Django application raises a 404 +error, this middleware checks the redirects database for the requested +URL as a last resort. Specifically, it checks for a redirect with the +given ``old_path`` with a site ID that corresponds to the :setting:`SITE_ID` setting. * If it finds a match, and ``new_path`` is not empty, it redirects to @@ -43,8 +44,8 @@ The middleware only gets activated for 404s -- not for 500s or responses of any other status code. Note that the order of :setting:`MIDDLEWARE_CLASSES` matters. Generally, you -can put ``RedirectFallbackMiddleware`` at the end of the list, because it's a -last resort. +can put :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware` +at the end of the list, because it's a last resort. For more on middleware, read the :doc:`middleware docs `. @@ -69,3 +70,29 @@ Via the Python API objects via the :doc:`Django database API `. .. _django/contrib/redirects/models.py: https://github.com/django/django/blob/master/django/contrib/redirects/models.py + +Middleware +========== + +.. class:: middleware.RedirectFallbackMiddleware + + You can change the :class:`~django.http.HttpResponse` classes used + by the middleware by creating a subclass of + :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware` + and overriding ``response_gone_class`` and/or ``response_redirect_class``. + + .. attribute:: response_gone_class + + The :class:`~django.http.HttpResponse` class used when a + :class:`~django.contrib.redirects.models.Redirect` is not + found for the requested path or has a blank ``new_path`` + value. + + Defaults to :class:`~django.http.HttpResponseGone`. + + .. attribute:: response_redirect_class + + The :class:`~django.http.HttpResponse` class that handles the + redirect. + + Defaults to :class:`~django.http.HttpResponsePermanentRedirect`. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 4014007bfa..0f1a5de073 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -193,6 +193,17 @@ Minor features follow the :setting:`SESSION_COOKIE_SECURE` and :setting:`SESSION_COOKIE_HTTPONLY` settings. +:mod:`django.contrib.redirects` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware` + has two new attributes + (:attr:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware.response_gone_class` + and + :attr:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware.response_redirect_class`) + that specify the types of :class:`~django.http.HttpResponse` instances the + middleware returns. + :mod:`django.contrib.sessions` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^