Refs #30997 -- Removed HttpRequest.is_ajax() usage.

This commit is contained in:
Claude Paroz 2019-12-15 15:30:35 +01:00 committed by Mariusz Felisiak
parent 5d654e1e71
commit 7fa0fa45c5
7 changed files with 47 additions and 30 deletions

View File

@ -48,12 +48,12 @@ def technical_500_response(request, exc_type, exc_value, tb, status_code=500):
the values returned from sys.exc_info() and friends. the values returned from sys.exc_info() and friends.
""" """
reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb) reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb)
if request.is_ajax(): if request.accepts('text/html'):
text = reporter.get_traceback_text()
return HttpResponse(text, status=status_code, content_type='text/plain; charset=utf-8')
else:
html = reporter.get_traceback_html() html = reporter.get_traceback_html()
return HttpResponse(html, status=status_code, content_type='text/html') return HttpResponse(html, status=status_code, content_type='text/html')
else:
text = reporter.get_traceback_text()
return HttpResponse(text, status=status_code, content_type='text/plain; charset=utf-8')
@functools.lru_cache() @functools.lru_cache()

View File

@ -33,7 +33,7 @@ def set_language(request):
""" """
next_url = request.POST.get('next', request.GET.get('next')) next_url = request.POST.get('next', request.GET.get('next'))
if ( if (
(next_url or not request.is_ajax()) and (next_url or request.accepts('text/html')) and
not url_has_allowed_host_and_scheme( not url_has_allowed_host_and_scheme(
url=next_url, url=next_url,
allowed_hosts={request.get_host()}, allowed_hosts={request.get_host()},

View File

@ -388,6 +388,11 @@ Miscellaneous
Django 3.1, the first request to any previously cached template fragment will Django 3.1, the first request to any previously cached template fragment will
be a cache miss. be a cache miss.
* The logic behind the decision to return a redirection fallback or a 204 HTTP
response from the :func:`~django.views.i18n.set_language` view is now based
on the ``Accept`` HTTP header instead of the ``X-Requested-With`` HTTP header
presence.
* The compatibility imports of ``django.core.exceptions.EmptyResultSet`` in * The compatibility imports of ``django.core.exceptions.EmptyResultSet`` in
``django.db.models.query``, ``django.db.models.sql``, and ``django.db.models.query``, ``django.db.models.sql``, and
``django.db.models.sql.datastructures`` are removed. ``django.db.models.sql.datastructures`` are removed.

View File

@ -1803,9 +1803,18 @@ redirect to that URL will be performed. Otherwise, Django may fall back to
redirecting the user to the URL from the ``Referer`` header or, if it is not redirecting the user to the URL from the ``Referer`` header or, if it is not
set, to ``/``, depending on the nature of the request: set, to ``/``, depending on the nature of the request:
* For AJAX requests, the fallback will be performed only if the ``next`` * If the request accepts HTML content (based on its ``Accept`` HTTP header),
parameter was set. Otherwise a 204 status code (No Content) will be returned. the fallback will always be performed.
* For non-AJAX requests, the fallback will always be performed.
* If the request doesn't accept HTML, the fallback will be performed only if
the ``next`` parameter was set. Otherwise a 204 status code (No Content) will
be returned.
.. versionchanged:: 3.1
In older versions, the distinction for the fallback is based on whether the
``X-Requested-With`` header is set to the value ``XMLHttpRequest``. This is
set by the jQuery ``ajax()`` method.
Here's example HTML template code: Here's example HTML template code:

View File

@ -159,11 +159,11 @@ Use the ``django.test.Client`` class to make requests.
>>> c = Client() >>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, >>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
... HTTP_X_REQUESTED_WITH='XMLHttpRequest') ... HTTP_ACCEPT='application/json')
...will send the HTTP header ``HTTP_X_REQUESTED_WITH`` to the ...will send the HTTP header ``HTTP_ACCEPT`` to the details view, which
details view, which is a good way to test code paths that use the is a good way to test code paths that use the
:meth:`django.http.HttpRequest.is_ajax()` method. :meth:`django.http.HttpRequest.accepts()` method.
.. admonition:: CGI specification .. admonition:: CGI specification

View File

@ -1247,7 +1247,7 @@ class ExceptionReporterFilterTests(ExceptionReportTestMixin, LoggingCaptureMixin
response = self.client.get( response = self.client.get(
'/raises500/', '/raises500/',
HTTP_SECRET_HEADER='super_secret', HTTP_SECRET_HEADER='super_secret',
HTTP_X_REQUESTED_WITH='XMLHttpRequest', HTTP_ACCEPT='application/json',
) )
self.assertNotIn(b'super_secret', response.content) self.assertNotIn(b'super_secret', response.content)
@ -1289,17 +1289,17 @@ class CustomExceptionReporterFilterTests(SimpleTestCase):
) )
class AjaxResponseExceptionReporterFilter(ExceptionReportTestMixin, LoggingCaptureMixin, SimpleTestCase): class NonHTMLResponseExceptionReporterFilter(ExceptionReportTestMixin, LoggingCaptureMixin, SimpleTestCase):
""" """
Sensitive information can be filtered out of error reports. Sensitive information can be filtered out of error reports.
Here we specifically test the plain text 500 debug-only error page served The plain text 500 debug-only error page is served when it has been
when it has been detected the request was sent by JS code. We don't check detected the request doesn't accept HTML content. Don't check for
for (non)existence of frames vars in the traceback information section of (non)existence of frames vars in the traceback information section of the
the response content because we don't include them in these error pages. response content because they're not included in these error pages.
Refs #14614. Refs #14614.
""" """
rf = RequestFactory(HTTP_X_REQUESTED_WITH='XMLHttpRequest') rf = RequestFactory(HTTP_ACCEPT='application/json')
def test_non_sensitive_request(self): def test_non_sensitive_request(self):
""" """
@ -1346,8 +1346,8 @@ class AjaxResponseExceptionReporterFilter(ExceptionReportTestMixin, LoggingCaptu
self.verify_unsafe_response(custom_exception_reporter_filter_view, check_for_vars=False) self.verify_unsafe_response(custom_exception_reporter_filter_view, check_for_vars=False)
@override_settings(DEBUG=True, ROOT_URLCONF='view_tests.urls') @override_settings(DEBUG=True, ROOT_URLCONF='view_tests.urls')
def test_ajax_response_encoding(self): def test_non_html_response_encoding(self):
response = self.client.get('/raises500/', HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.get('/raises500/', HTTP_ACCEPT='application/json')
self.assertEqual(response['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(response['Content-Type'], 'text/plain; charset=utf-8')

View File

@ -111,11 +111,12 @@ class SetLanguageTests(TestCase):
def test_setlang_performs_redirect_for_ajax_if_explicitly_requested(self): def test_setlang_performs_redirect_for_ajax_if_explicitly_requested(self):
""" """
The set_language view redirects to the "next" parameter for AJAX calls. The set_language view redirects to the "next" parameter for requests
not accepting HTML response content.
""" """
lang_code = self._get_inactive_language_code() lang_code = self._get_inactive_language_code()
post_data = {'language': lang_code, 'next': '/'} post_data = {'language': lang_code, 'next': '/'}
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.post('/i18n/setlang/', post_data, HTTP_ACCEPT='application/json')
self.assertRedirects(response, '/') self.assertRedirects(response, '/')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code) self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning): with ignore_warnings(category=RemovedInDjango40Warning):
@ -123,12 +124,12 @@ class SetLanguageTests(TestCase):
def test_setlang_doesnt_perform_a_redirect_to_referer_for_ajax(self): def test_setlang_doesnt_perform_a_redirect_to_referer_for_ajax(self):
""" """
The set_language view doesn't redirect to the HTTP referer header for The set_language view doesn't redirect to the HTTP referer header if
AJAX calls. the request doesn't accept HTML response content.
""" """
lang_code = self._get_inactive_language_code() lang_code = self._get_inactive_language_code()
post_data = {'language': lang_code} post_data = {'language': lang_code}
headers = {'HTTP_REFERER': '/', 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} headers = {'HTTP_REFERER': '/', 'HTTP_ACCEPT': 'application/json'}
response = self.client.post('/i18n/setlang/', post_data, **headers) response = self.client.post('/i18n/setlang/', post_data, **headers)
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code) self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
@ -137,11 +138,12 @@ class SetLanguageTests(TestCase):
def test_setlang_doesnt_perform_a_default_redirect_for_ajax(self): def test_setlang_doesnt_perform_a_default_redirect_for_ajax(self):
""" """
The set_language view returns 204 for AJAX calls by default. The set_language view returns 204 by default for requests not accepting
HTML response content.
""" """
lang_code = self._get_inactive_language_code() lang_code = self._get_inactive_language_code()
post_data = {'language': lang_code} post_data = {'language': lang_code}
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.post('/i18n/setlang/', post_data, HTTP_ACCEPT='application/json')
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code) self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning): with ignore_warnings(category=RemovedInDjango40Warning):
@ -149,11 +151,12 @@ class SetLanguageTests(TestCase):
def test_setlang_unsafe_next_for_ajax(self): def test_setlang_unsafe_next_for_ajax(self):
""" """
The fallback to root URL for the set_language view works for AJAX calls. The fallback to root URL for the set_language view works for requests
not accepting HTML response content.
""" """
lang_code = self._get_inactive_language_code() lang_code = self._get_inactive_language_code()
post_data = {'language': lang_code, 'next': '//unsafe/redirection/'} post_data = {'language': lang_code, 'next': '//unsafe/redirection/'}
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.post('/i18n/setlang/', post_data, HTTP_ACCEPT='application/json')
self.assertEqual(response.url, '/') self.assertEqual(response.url, '/')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code) self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)