diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 754e6e5cdf..4d7c5854e2 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -21,11 +21,6 @@ DEBUG = False # on a live site. DEBUG_PROPAGATE_EXCEPTIONS = False -# Whether to use the "ETag" header. This saves bandwidth but slows down performance. -# Deprecated (RemovedInDjango21Warning) in favor of ConditionalGetMiddleware -# which sets the ETag regardless of this setting. -USE_ETAGS = False - # People who get code error notifications. # In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')] ADMINS = [] diff --git a/django/middleware/common.py b/django/middleware/common.py index d8cfb9a8b0..7e520b7061 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -1,5 +1,4 @@ import re -import warnings from urllib.parse import urlparse from django.conf import settings @@ -7,10 +6,7 @@ from django.core.exceptions import PermissionDenied from django.core.mail import mail_managers from django.http import HttpResponsePermanentRedirect from django.urls import is_valid_path -from django.utils.cache import ( - cc_delim_re, get_conditional_response, set_response_etag, -) -from django.utils.deprecation import MiddlewareMixin, RemovedInDjango21Warning +from django.utils.deprecation import MiddlewareMixin class CommonMiddleware(MiddlewareMixin): @@ -30,11 +26,6 @@ class CommonMiddleware(MiddlewareMixin): This behavior can be customized by subclassing CommonMiddleware and overriding the response_redirect_class attribute. - - - ETags: If the USE_ETAGS setting is set, ETags will be calculated from - the entire page content and Not Modified responses will be returned - appropriately. USE_ETAGS is deprecated in favor of - ConditionalGetMiddleware. """ response_redirect_class = HttpResponsePermanentRedirect @@ -114,23 +105,6 @@ class CommonMiddleware(MiddlewareMixin): if self.should_redirect_with_slash(request): return self.response_redirect_class(self.get_full_path_with_slash(request)) - if settings.USE_ETAGS and self.needs_etag(response): - warnings.warn( - "The USE_ETAGS setting is deprecated in favor of " - "ConditionalGetMiddleware which sets the ETag regardless of " - "the setting. CommonMiddleware won't do ETag processing in " - "Django 2.1.", - RemovedInDjango21Warning - ) - if not response.has_header('ETag'): - set_response_etag(response) - - if response.has_header('ETag'): - return get_conditional_response( - request, - etag=response['ETag'], - response=response, - ) # Add the Content-Length header to non-streaming responses if not # already set. if not response.streaming and not response.has_header('Content-Length'): @@ -138,11 +112,6 @@ class CommonMiddleware(MiddlewareMixin): return response - def needs_etag(self, response): - """Return True if an ETag header should be added to response.""" - cache_control_headers = cc_delim_re.split(response.get('Cache-Control', '')) - return all(header.lower() != 'no-store' for header in cache_control_headers) - class BrokenLinkEmailsMiddleware(MiddlewareMixin): diff --git a/django/utils/cache.py b/django/utils/cache.py index b1abbf0648..81fc44293e 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -20,12 +20,10 @@ import hashlib import logging import re import time -import warnings from django.conf import settings from django.core.cache import caches from django.http import HttpResponse, HttpResponseNotModified -from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_bytes, force_text, iri_to_uri from django.utils.http import ( http_date, parse_etags, parse_http_date_safe, quote_etag, @@ -248,18 +246,6 @@ def patch_response_headers(response, cache_timeout=None): cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS if cache_timeout < 0: cache_timeout = 0 # Can't have max-age negative - if settings.USE_ETAGS and not response.has_header('ETag'): - warnings.warn( - "The USE_ETAGS setting is deprecated in favor of " - "ConditionalGetMiddleware which sets the ETag regardless of the " - "setting. patch_response_headers() won't do ETag processing in " - "Django 2.1.", - RemovedInDjango21Warning - ) - if hasattr(response, 'render') and callable(response.render): - response.add_post_render_callback(set_response_etag) - else: - response = set_response_etag(response) if not response.has_header('Expires'): response['Expires'] = http_date(time.time() + cache_timeout) patch_cache_control(response, max_age=cache_timeout) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 5c33d196d3..05f06092aa 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -61,23 +61,12 @@ Adds a few conveniences for perfectionists: indexer would treat them as separate URLs -- so it's best practice to normalize URLs. -* Handles ETags based on the :setting:`USE_ETAGS` setting. If - :setting:`USE_ETAGS` is set to ``True``, Django will calculate an ETag - for each request by MD5-hashing the page content, and it'll take care of - sending ``Not Modified`` responses, if appropriate. - * Sets the ``Content-Length`` header for non-streaming responses. .. versionchanged:: 1.11 Older versions didn't set the ``Content-Length`` header. -.. deprecated:: 1.11 - - The :setting:`USE_ETAGS` setting is deprecated in favor of using - :class:`~django.middleware.http.ConditionalGetMiddleware` for ETag - processing. - .. attribute:: CommonMiddleware.response_redirect_class Defaults to :class:`~django.http.HttpResponsePermanentRedirect`. Subclass @@ -472,11 +461,6 @@ Here are some hints about the ordering of various Django middleware classes: After ``UpdateCacheMiddleware``: Modifies ``Vary`` header. -#. :class:`~django.middleware.http.ConditionalGetMiddleware` - - Before ``CommonMiddleware``: uses its ``ETag`` header when - :setting:`USE_ETAGS` = ``True``. - #. :class:`~django.contrib.sessions.middleware.SessionMiddleware` After ``UpdateCacheMiddleware``: Modifies ``Vary`` header. diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 358de5eca0..b9dc61686b 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2593,23 +2593,6 @@ the correct environment. .. _list of time zones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones -.. setting:: USE_ETAGS - -``USE_ETAGS`` -------------- - -Default: ``False`` - -A boolean that specifies whether to output the ``ETag`` header. This saves -bandwidth but slows down performance. This is used by the -:class:`~django.middleware.common.CommonMiddleware` and in the :doc:`cache -framework `. - -.. deprecated:: 1.11 - - This setting is deprecated in favor of using ``ConditionalGetMiddleware``, - which sets an ETag regardless of this setting. - .. setting:: USE_I18N ``USE_I18N`` @@ -3438,7 +3421,6 @@ HTTP * :setting:`SECURE_SSL_HOST` * :setting:`SECURE_SSL_REDIRECT` * :setting:`SIGNING_BACKEND` -* :setting:`USE_ETAGS` * :setting:`USE_X_FORWARDED_HOST` * :setting:`USE_X_FORWARDED_PORT` * :setting:`WSGI_APPLICATION` diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index c9fa10e0e5..596edaa09a 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -52,7 +52,6 @@ need to distinguish caches by the ``Accept-language`` header. Adds some useful headers to the given ``HttpResponse`` object: - * ``ETag`` * ``Expires`` * ``Cache-Control`` @@ -65,11 +64,6 @@ need to distinguish caches by the ``Accept-language`` header. In older versions, the ``Last-Modified`` header was also set. - .. deprecated:: 1.11 - - Since the ``USE_ETAGS`` setting is deprecated, this function won't set - the ``ETag`` header when the deprecation ends in Django 2.1. - .. function:: add_never_cache_headers(response) Adds a ``Cache-Control: max-age=0, no-cache, no-store, must-revalidate`` diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 5effa6eba9..de7150b7b7 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -251,3 +251,6 @@ how to remove usage of these features. as the first positional argument. * The ``django.db.models.permalink()`` decorator is removed. + +* The ``USE_ETAGS`` setting is removed. ``CommonMiddleware`` and + ``django.utils.cache.patch_response_headers()`` no longer set ETags. diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 10e1303659..ff06dbae19 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -25,14 +25,12 @@ from django.core.files import temp as tempfile from django.forms.utils import ErrorList from django.template.response import TemplateResponse from django.test import ( - SimpleTestCase, TestCase, ignore_warnings, modify_settings, - override_settings, skipUnlessDBFeature, + TestCase, modify_settings, override_settings, skipUnlessDBFeature, ) from django.test.utils import override_script_prefix, patch_logger from django.urls import NoReverseMatch, resolve, reverse from django.utils import formats, translation from django.utils.cache import get_max_age -from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_bytes, force_text, iri_to_uri from django.utils.html import escape from django.utils.http import urlencode @@ -5809,22 +5807,6 @@ class InlineAdminViewOnSiteTest(TestCase): self.assertContains(response, '"/worker_inline/%s/%s/"' % (self.w1.surname, self.w1.name)) -@override_settings(ROOT_URLCONF='admin_views.urls') -class TestETagWithAdminView(SimpleTestCase): - # The admin is compatible with ETags (#16003). - - def test_admin(self): - with self.settings(USE_ETAGS=False): - response = self.client.get(reverse('admin:index')) - self.assertEqual(response.status_code, 302) - self.assertFalse(response.has_header('ETag')) - - with self.settings(USE_ETAGS=True), ignore_warnings(category=RemovedInDjango21Warning): - response = self.client.get(reverse('admin:index')) - self.assertEqual(response.status_code, 302) - self.assertTrue(response.has_header('ETag')) - - @override_settings(ROOT_URLCONF='admin_views.urls') class GetFormsetsWithInlinesArgumentTest(TestCase): """ diff --git a/tests/cache/tests.py b/tests/cache/tests.py index d3c9513303..57e4ec15b4 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -32,15 +32,13 @@ from django.template.context_processors import csrf from django.template.response import TemplateResponse from django.test import ( RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, - ignore_warnings, override_settings, + override_settings, ) from django.test.signals import setting_changed from django.utils import timezone, translation from django.utils.cache import ( - get_cache_key, learn_cache_key, patch_cache_control, - patch_response_headers, patch_vary_headers, + get_cache_key, learn_cache_key, patch_cache_control, patch_vary_headers, ) -from django.utils.deprecation import RemovedInDjango21Warning from django.views.decorators.cache import cache_page from .models import Poll, expensive_calculation @@ -1838,11 +1836,9 @@ class CacheI18nTest(TestCase): "Cache keys should include the time zone name when time zones are active" ) - @ignore_warnings(category=RemovedInDjango21Warning) # USE_ETAGS=True @override_settings( CACHE_MIDDLEWARE_KEY_PREFIX="test", CACHE_MIDDLEWARE_SECONDS=60, - USE_ETAGS=True, USE_I18N=True, ) def test_middleware(self): @@ -1884,14 +1880,6 @@ class CacheI18nTest(TestCase): # The cache can be recovered self.assertIsNotNone(get_cache_data) self.assertEqual(get_cache_data.content, en_message.encode()) - # ETags are used. - self.assertTrue(get_cache_data.has_header('ETag')) - # ETags can be disabled. - with self.settings(USE_ETAGS=False): - request._cache_update_cache = True - set_cache(request, 'en', en_message) - get_cache_data = FetchFromCacheMiddleware().process_request(request) - self.assertFalse(get_cache_data.has_header('ETag')) # change the session language and set content request = self.factory.get(self.path) request._cache_update_cache = True @@ -1911,7 +1899,6 @@ class CacheI18nTest(TestCase): @override_settings( CACHE_MIDDLEWARE_KEY_PREFIX="test", CACHE_MIDDLEWARE_SECONDS=60, - USE_ETAGS=True, ) def test_middleware_doesnt_cache_streaming_response(self): request = self.factory.get(self.path) @@ -2232,27 +2219,6 @@ class TestWithTemplateResponse(SimpleTestCase): '0f1c2d56633c943073c4569d9a9502fe.d41d8cd98f00b204e9800998ecf8427e' ) - @override_settings(USE_ETAGS=False) - def test_without_etag(self): - template = engines['django'].from_string("This is a test") - response = TemplateResponse(HttpRequest(), template) - self.assertFalse(response.has_header('ETag')) - patch_response_headers(response) - self.assertFalse(response.has_header('ETag')) - response = response.render() - self.assertFalse(response.has_header('ETag')) - - @ignore_warnings(category=RemovedInDjango21Warning) - @override_settings(USE_ETAGS=True) - def test_with_etag(self): - template = engines['django'].from_string("This is a test") - response = TemplateResponse(HttpRequest(), template) - self.assertFalse(response.has_header('ETag')) - patch_response_headers(response) - self.assertFalse(response.has_header('ETag')) - response = response.render() - self.assertTrue(response.has_header('ETag')) - class TestMakeTemplateFragmentKey(SimpleTestCase): def test_without_vary_on(self): diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 8006938a5e..f3c8b9ca06 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -18,10 +18,7 @@ from django.middleware.common import ( ) from django.middleware.gzip import GZipMiddleware from django.middleware.http import ConditionalGetMiddleware -from django.test import ( - RequestFactory, SimpleTestCase, ignore_warnings, override_settings, -) -from django.utils.deprecation import RemovedInDjango21Warning +from django.test import RequestFactory, SimpleTestCase, override_settings int2byte = struct.Struct(">B").pack @@ -265,57 +262,6 @@ class CommonMiddlewareTest(SimpleTestCase): self.assertEqual(r.status_code, 301) self.assertEqual(r.url, 'http://www.testserver/customurlconf/slash/') - # ETag + If-Not-Modified support tests - - @ignore_warnings(category=RemovedInDjango21Warning) - @override_settings(USE_ETAGS=True) - def test_etag(self): - req = HttpRequest() - res = HttpResponse('content') - self.assertTrue(CommonMiddleware().process_response(req, res).has_header('ETag')) - - @ignore_warnings(category=RemovedInDjango21Warning) - @override_settings(USE_ETAGS=True) - def test_etag_streaming_response(self): - req = HttpRequest() - res = StreamingHttpResponse(['content']) - res['ETag'] = 'tomatoes' - self.assertEqual(CommonMiddleware().process_response(req, res).get('ETag'), 'tomatoes') - - @ignore_warnings(category=RemovedInDjango21Warning) - @override_settings(USE_ETAGS=True) - def test_no_etag_streaming_response(self): - req = HttpRequest() - res = StreamingHttpResponse(['content']) - self.assertFalse(CommonMiddleware().process_response(req, res).has_header('ETag')) - - @ignore_warnings(category=RemovedInDjango21Warning) - @override_settings(USE_ETAGS=True) - def test_no_etag_no_store_cache(self): - req = HttpRequest() - res = HttpResponse('content') - res['Cache-Control'] = 'No-Cache, No-Store, Max-age=0' - self.assertFalse(CommonMiddleware().process_response(req, res).has_header('ETag')) - - @ignore_warnings(category=RemovedInDjango21Warning) - @override_settings(USE_ETAGS=True) - def test_etag_extended_cache_control(self): - req = HttpRequest() - res = HttpResponse('content') - res['Cache-Control'] = 'my-directive="my-no-store"' - self.assertTrue(CommonMiddleware().process_response(req, res).has_header('ETag')) - - @ignore_warnings(category=RemovedInDjango21Warning) - @override_settings(USE_ETAGS=True) - def test_if_none_match(self): - first_req = HttpRequest() - first_res = CommonMiddleware().process_response(first_req, HttpResponse('content')) - second_req = HttpRequest() - second_req.method = 'GET' - second_req.META['HTTP_IF_NONE_MATCH'] = first_res['ETag'] - second_res = CommonMiddleware().process_response(second_req, HttpResponse('content')) - self.assertEqual(second_res.status_code, 304) - # Tests for the Content-Length header def test_content_length_header_added(self): @@ -855,8 +801,6 @@ class GZipMiddlewareTest(SimpleTestCase): self.assertEqual(self.get_mtime(r2.content), 0) -@ignore_warnings(category=RemovedInDjango21Warning) -@override_settings(USE_ETAGS=True) class ETagGZipMiddlewareTest(SimpleTestCase): """ ETags are handled properly by GZipMiddleware.