Fixed #32124 -- Added per-view opt-out for APPEND_SLASH behavior.
This commit is contained in:
parent
3418092238
commit
ad11f5b8c9
|
@ -67,10 +67,11 @@ class CommonMiddleware(MiddlewareMixin):
|
|||
"""
|
||||
if settings.APPEND_SLASH and not request.path_info.endswith('/'):
|
||||
urlconf = getattr(request, 'urlconf', None)
|
||||
return (
|
||||
not is_valid_path(request.path_info, urlconf) and
|
||||
is_valid_path('%s/' % request.path_info, urlconf)
|
||||
)
|
||||
if not is_valid_path(request.path_info, urlconf):
|
||||
match = is_valid_path('%s/' % request.path_info, urlconf)
|
||||
if match:
|
||||
view = match.func
|
||||
return getattr(view, 'should_append_slash', True)
|
||||
return False
|
||||
|
||||
def get_full_path_with_slash(self, request):
|
||||
|
|
|
@ -145,13 +145,12 @@ def get_urlconf(default=None):
|
|||
|
||||
def is_valid_path(path, urlconf=None):
|
||||
"""
|
||||
Return True if the given path resolves against the default URL resolver,
|
||||
False otherwise. This is a convenience method to make working with "is
|
||||
this a match?" cases easier, avoiding try...except blocks.
|
||||
Return the ResolverMatch if the given path resolves against the default URL
|
||||
resolver, False otherwise. This is a convenience method to make working
|
||||
with "is this a match?" cases easier, avoiding try...except blocks.
|
||||
"""
|
||||
try:
|
||||
resolve(path, urlconf)
|
||||
return True
|
||||
return resolve(path, urlconf)
|
||||
except Resolver404:
|
||||
return False
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
from functools import wraps
|
||||
|
||||
|
||||
def no_append_slash(view_func):
|
||||
"""
|
||||
Mark a view function as excluded from CommonMiddleware's APPEND_SLASH
|
||||
redirection.
|
||||
"""
|
||||
# view_func.should_append_slash = False would also work, but decorators are
|
||||
# nicer if they don't have side effects, so return a new function.
|
||||
def wrapped_view(*args, **kwargs):
|
||||
return view_func(*args, **kwargs)
|
||||
wrapped_view.should_append_slash = False
|
||||
return wraps(view_func)(wrapped_view)
|
|
@ -61,6 +61,22 @@ Adds a few conveniences for perfectionists:
|
|||
indexer would treat them as separate URLs -- so it's best practice to
|
||||
normalize URLs.
|
||||
|
||||
If necessary, individual views may be excluded from the ``APPEND_SLASH``
|
||||
behavior using the :func:`~django.views.decorators.common.no_append_slash`
|
||||
decorator::
|
||||
|
||||
from django.views.decorators.common import no_append_slash
|
||||
|
||||
@no_append_slash
|
||||
def sensitive_fbv(request, *args, **kwargs):
|
||||
"""View to be excluded from APPEND_SLASH."""
|
||||
return HttpResponse()
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
|
||||
Support for the :func:`~django.views.decorators.common.no_append_slash`
|
||||
decorator was added.
|
||||
|
||||
* Sets the ``Content-Length`` header for non-streaming responses.
|
||||
|
||||
.. attribute:: CommonMiddleware.response_redirect_class
|
||||
|
|
|
@ -185,6 +185,13 @@ CSRF
|
|||
|
||||
* ...
|
||||
|
||||
Decorators
|
||||
~~~~~~~~~~
|
||||
|
||||
* The new :func:`~django.views.decorators.common.no_append_slash` decorator
|
||||
allows individual views to be excluded from :setting:`APPEND_SLASH` URL
|
||||
normalization.
|
||||
|
||||
Email
|
||||
~~~~~
|
||||
|
||||
|
|
|
@ -121,3 +121,18 @@ client-side caching.
|
|||
This decorator adds a ``Cache-Control: max-age=0, no-cache, no-store,
|
||||
must-revalidate, private`` header to a response to indicate that a page
|
||||
should never be cached.
|
||||
|
||||
.. module:: django.views.decorators.common
|
||||
|
||||
Common
|
||||
======
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
The decorators in :mod:`django.views.decorators.common` allow per-view
|
||||
customization of :class:`~django.middleware.common.CommonMiddleware` behavior.
|
||||
|
||||
.. function:: no_append_slash()
|
||||
|
||||
This decorator allows individual views to be excluded from
|
||||
:setting:`APPEND_SLASH` URL normalization.
|
||||
|
|
|
@ -127,6 +127,17 @@ class CommonMiddlewareTest(SimpleTestCase):
|
|||
request = self.rf.get('/slash')
|
||||
self.assertEqual(CommonMiddleware(get_response_404)(request).status_code, 404)
|
||||
|
||||
@override_settings(APPEND_SLASH=True)
|
||||
def test_append_slash_opt_out(self):
|
||||
"""
|
||||
Views marked with @no_append_slash should be left alone.
|
||||
"""
|
||||
request = self.rf.get('/sensitive_fbv')
|
||||
self.assertEqual(CommonMiddleware(get_response_404)(request).status_code, 404)
|
||||
|
||||
request = self.rf.get('/sensitive_cbv')
|
||||
self.assertEqual(CommonMiddleware(get_response_404)(request).status_code, 404)
|
||||
|
||||
@override_settings(APPEND_SLASH=True)
|
||||
def test_append_slash_quoted(self):
|
||||
"""
|
||||
|
|
|
@ -8,4 +8,7 @@ urlpatterns = [
|
|||
path('needsquoting#/', views.empty_view),
|
||||
# Accepts paths with two leading slashes.
|
||||
re_path(r'^(.+)/security/$', views.empty_view),
|
||||
# Should not append slash.
|
||||
path('sensitive_fbv/', views.sensitive_fbv),
|
||||
path('sensitive_cbv/', views.SensitiveCBV.as_view()),
|
||||
]
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
from django.http import HttpResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.common import no_append_slash
|
||||
from django.views.generic import View
|
||||
|
||||
|
||||
def empty_view(request, *args, **kwargs):
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@no_append_slash
|
||||
def sensitive_fbv(request, *args, **kwargs):
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@method_decorator(no_append_slash, name='dispatch')
|
||||
class SensitiveCBV(View):
|
||||
def get(self, *args, **kwargs):
|
||||
return HttpResponse()
|
||||
|
|
Loading…
Reference in New Issue