Ticket #6213: common_middleware.3.py.diff

File common_middleware.3.py.diff, 9.1 KB (added by esaj, 7 years ago)

Updated patch with slightly improved unit tests (more descriptive docstring comments).

  • django/middleware/common.py

     
    4040                if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
    4141                    return http.HttpResponseForbidden('<h1>Forbidden</h1>')
    4242
    43         # Check for a redirect based on settings.APPEND_SLASH
    44         # and settings.PREPEND_WWW
    45         host = request.get_host()
    46         old_url = [host, request.path]
    47         new_url = old_url[:]
     43        _response = _handle_redirects(request)
     44        if _response:
     45            return _response
    4846
    49         if (settings.PREPEND_WWW and old_url[0] and
    50                 not old_url[0].startswith('www.')):
    51             new_url[0] = 'www.' + old_url[0]
    52 
    53         # Append a slash if APPEND_SLASH is set and the URL doesn't have a
    54         # trailing slash and there is no pattern for the current path
    55         if settings.APPEND_SLASH and (not old_url[1].endswith('/')):
    56             try:
    57                 urlresolvers.resolve(request.path)
    58             except urlresolvers.Resolver404:
    59                 new_url[1] = new_url[1] + '/'
    60                 if settings.DEBUG and request.method == 'POST':
    61                     raise RuntimeError, (""
    62                     "You called this URL via POST, but the URL doesn't end "
    63                     "in a slash and you have APPEND_SLASH set. Django can't "
    64                     "redirect to the slash URL while maintaining POST data. "
    65                     "Change your form to point to %s%s (note the trailing "
    66                     "slash), or set APPEND_SLASH=False in your Django "
    67                     "settings.") % (new_url[0], new_url[1])
    68 
    69         if new_url != old_url:
    70             # Redirect if the target url exists
    71             try:
    72                 urlresolvers.resolve(new_url[1])
    73             except urlresolvers.Resolver404:
    74                 pass
    75             else:
    76                 if new_url[0]:
    77                     newurl = "%s://%s%s" % (
    78                         request.is_secure() and 'https' or 'http',
    79                         new_url[0], urlquote(new_url[1]))
    80                 else:
    81                     newurl = urlquote(new_url[1])
    82                 if request.GET:
    83                     newurl += '?' + request.GET.urlencode()
    84                 return http.HttpResponsePermanentRedirect(newurl)
    85 
    8647        return None
    8748
    8849    def process_response(self, request, response):
    8950        "Check for a flat page (for 404s) and calculate the Etag, if needed."
    9051        if response.status_code == 404:
     52            _response = _handle_redirects(request, resolve=False)
     53            if _response:
     54                return _response
    9155            if settings.SEND_BROKEN_LINK_EMAILS:
    9256                # If the referrer was from an internal link or a non-search-engine site,
    9357                # send a note to the managers.
     
    11882
    11983        return response
    12084
     85def _handle_redirects(request, resolve=True):
     86    host = request.get_host()
     87    old_url = [host, request.path]
     88    new_url = old_url[:]
     89
     90    if (settings.PREPEND_WWW and old_url[0] and
     91            not old_url[0].startswith('www.')):
     92        new_url[0] = 'www.' + old_url[0]
     93
     94    # Append a slash if APPEND_SLASH is set and the URL doesn't have a
     95    # trailing slash and there is no pattern for the current path
     96    if settings.APPEND_SLASH and (not old_url[1].endswith('/')):
     97        if resolve:
     98            try:
     99                urlresolvers.resolve(request.path)
     100            except urlresolvers.Resolver404:
     101                new_url[1] = new_url[1] + '/'
     102        else:
     103            new_url[1] = new_url[1] + '/'
     104
     105    if new_url != old_url:
     106        # Redirect if the target url exists
     107        try:
     108            if resolve and new_url[0] == new_url[0] and new_url[1] != old_url[1]:
     109                urlresolvers.resolve(new_url[1])
     110        except urlresolvers.Resolver404:
     111            pass
     112        else:
     113            if new_url[0]:
     114                newurl = "%s://%s%s" % (
     115                    request.is_secure() and 'https' or 'http',
     116                    new_url[0], urlquote(new_url[1]))
     117            else:
     118                newurl = urlquote(new_url[1])
     119            if request.GET:
     120                newurl += '?' + request.GET.urlencode()
     121            if settings.DEBUG and request.method == 'POST':
     122                raise RuntimeError, (""
     123                "You called this URL via POST, but the URL doesn't end "
     124                "in a slash and you have APPEND_SLASH set. Django can't "
     125                "redirect to the slash URL while maintaining POST data. "
     126                "Change your form to point to %s%s (note the trailing "
     127                "slash), or set APPEND_SLASH=False in your Django "
     128                "settings.") % (new_url[0], new_url[1])
     129            return http.HttpResponsePermanentRedirect(newurl)
     130
    121131def _is_ignorable_404(uri):
    122132    "Returns True if a 404 at the given URL *shouldn't* notify the site managers"
    123133    for start in settings.IGNORABLE_404_STARTS:
  • tests/regressiontests/middleware/tests.py

     
    11# -*- coding: utf-8 -*-
    22
    33from django.test import TestCase
    4 from django.http import HttpRequest
     4from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
    55from django.middleware.common import CommonMiddleware
    66from django.conf import settings
    77
     
    8989        self.assertEquals(
    9090            r['Location'],
    9191            'http://testserver/middleware/needsquoting%23/')
     92
     93    def test_append_slash_enabled_custom_middleware_slash(self):
     94        """
     95        Tests that if APPEND_SLASH is True and some intermediate middleware
     96        (e.g. FlatpageFallbackMiddleware) has processed the request and
     97        returned a 404 response, then this is redirected.
     98        """
     99        settings.APPEND_SLASH = True
     100        request = self._get_request('custom/slash')
     101        common_middleware = CommonMiddleware()
     102        custom_middleware = CustomMiddleware()
     103        response = common_middleware.process_request(request)
     104        self.assertEquals(response, None)
     105        response = custom_middleware.process_request(request)
     106        self.assertEquals(response.status_code, 404)
     107        response = custom_middleware.process_response(request, response)
     108        self.assertEquals(response.status_code, 404)
     109        response = common_middleware.process_response(request, response)
     110        self.assertEquals(response.status_code, 301)
     111        self.assertEquals(
     112            response['Location'],
     113            'http://testserver/middleware/custom/slash/')
     114
     115    def test_append_slash_disabled_custom_middleware_slash(self):
     116        """
     117        Tests that if APPEND_SLASH is False and some intermediate middleware
     118        (e.g. FlatpageFallbackMiddleware) has processed the request and
     119        returned a 404 response, then this is not redirected.
     120        """
     121        settings.APPEND_SLASH = False
     122        request = self._get_request('custom/slash')
     123        common_middleware = CommonMiddleware()
     124        custom_middleware = CustomMiddleware()
     125        response = common_middleware.process_request(request)
     126        self.assertEquals(response, None)
     127        response = custom_middleware.process_request(request)
     128        self.assertEquals(response.status_code, 404)
     129        response = custom_middleware.process_response(request, response)
     130        self.assertEquals(response.status_code, 404)
     131        response = common_middleware.process_response(request, response)
     132        self.assertEquals(response.status_code, 404)
     133
     134    def test_append_slash_custom_middleware_slashless(self):
     135        """
     136        Tests that whether APPEND_SLASH is True or not, and some intermediate
     137        middleware (e.g. FlatpageFallbackMiddleware) has processed the request
     138        and returned a response, this is not redirected.
     139        """
     140        for append_slash in (True, False):
     141            settings.APPEND_SLASH = append_slash
     142            request = self._get_request('custom/noslash')
     143            common_middleware = CommonMiddleware()
     144            custom_middleware = CustomMiddleware()
     145            response = common_middleware.process_request(request)
     146            self.assertEquals(response, None)
     147            response = custom_middleware.process_request(request)
     148            self.assertContains(response, 'Custom middleware response')
     149            response = custom_middleware.process_response(request, response)
     150            self.assertContains(response, 'Custom middleware response')
     151            response = common_middleware.process_response(request, response)
     152            self.assertContains(response, 'Custom middleware response')
     153
     154class CustomMiddleware(object):
     155    def process_request(self, request):
     156        if request.path in ('/middleware/custom/slash/', '/middleware/custom/noslash'):
     157            return HttpResponse('Custom middleware response')
     158        return HttpResponseNotFound()
     159    def process_response(self, request, response):
     160        return response
Back to Top