mirror of https://github.com/django/django.git
Fixed #16003 -- Restored compatibility of the admin when using USE_ETAGS. Thanks for the initial patch, pterk.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16729 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
2189a8e50a
commit
7cb140e6d8
|
@ -92,6 +92,10 @@ def get_max_age(response):
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _set_response_etag(response):
|
||||||
|
response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest()
|
||||||
|
return response
|
||||||
|
|
||||||
def patch_response_headers(response, cache_timeout=None):
|
def patch_response_headers(response, cache_timeout=None):
|
||||||
"""
|
"""
|
||||||
Adds some useful headers to the given HttpResponse object:
|
Adds some useful headers to the given HttpResponse object:
|
||||||
|
@ -107,7 +111,10 @@ def patch_response_headers(response, cache_timeout=None):
|
||||||
if cache_timeout < 0:
|
if cache_timeout < 0:
|
||||||
cache_timeout = 0 # Can't have max-age negative
|
cache_timeout = 0 # Can't have max-age negative
|
||||||
if settings.USE_ETAGS and not response.has_header('ETag'):
|
if settings.USE_ETAGS and not response.has_header('ETag'):
|
||||||
response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest()
|
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('Last-Modified'):
|
if not response.has_header('Last-Modified'):
|
||||||
response['Last-Modified'] = http_date()
|
response['Last-Modified'] = http_date()
|
||||||
if not response.has_header('Expires'):
|
if not response.has_header('Expires'):
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
# Unit tests for cache framework
|
# Unit tests for cache framework
|
||||||
# Uses whatever cache backend is set in the test settings file.
|
# Uses whatever cache backend is set in the test settings file.
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
@ -13,14 +14,19 @@ import warnings
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import management
|
from django.core import management
|
||||||
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
|
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
|
||||||
from django.core.cache.backends.base import CacheKeyWarning, InvalidCacheBackendError
|
from django.core.cache.backends.base import (CacheKeyWarning,
|
||||||
|
InvalidCacheBackendError)
|
||||||
from django.http import HttpResponse, HttpRequest, QueryDict
|
from django.http import HttpResponse, HttpRequest, QueryDict
|
||||||
from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware, CacheMiddleware
|
from django.middleware.cache import (FetchFromCacheMiddleware,
|
||||||
from django.test import RequestFactory
|
UpdateCacheMiddleware, CacheMiddleware)
|
||||||
from django.test.utils import get_warnings_state, restore_warnings_state
|
from django.template import Template
|
||||||
from django.utils import translation
|
from django.template.response import TemplateResponse
|
||||||
from django.utils import unittest
|
from django.test import TestCase, RequestFactory
|
||||||
from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key, patch_cache_control
|
from django.test.utils import (get_warnings_state, restore_warnings_state,
|
||||||
|
override_settings)
|
||||||
|
from django.utils import translation, unittest
|
||||||
|
from django.utils.cache import (patch_vary_headers, get_cache_key,
|
||||||
|
learn_cache_key, patch_cache_control, patch_response_headers)
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
from regressiontests.cache.models import Poll, expensive_calculation
|
from regressiontests.cache.models import Poll, expensive_calculation
|
||||||
|
@ -1491,5 +1497,110 @@ class CacheMiddlewareTest(unittest.TestCase):
|
||||||
response = other_with_timeout_view(request, '18')
|
response = other_with_timeout_view(request, '18')
|
||||||
self.assertEqual(response.content, 'Hello World 18')
|
self.assertEqual(response.content, 'Hello World 18')
|
||||||
|
|
||||||
|
|
||||||
|
class TestWithTemplateResponse(TestCase):
|
||||||
|
"""
|
||||||
|
Tests various headers w/ TemplateResponse.
|
||||||
|
|
||||||
|
Most are probably redundant since they manipulate the same object
|
||||||
|
anyway but the Etag header is 'special' because it relies on the
|
||||||
|
content being complete (which is not necessarily always the case
|
||||||
|
with a TemplateResponse)
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.path = '/cache/test/'
|
||||||
|
|
||||||
|
def _get_request(self, path, method='GET'):
|
||||||
|
request = HttpRequest()
|
||||||
|
request.META = {
|
||||||
|
'SERVER_NAME': 'testserver',
|
||||||
|
'SERVER_PORT': 80,
|
||||||
|
}
|
||||||
|
request.method = method
|
||||||
|
request.path = request.path_info = "/cache/%s" % path
|
||||||
|
return request
|
||||||
|
|
||||||
|
def test_patch_vary_headers(self):
|
||||||
|
headers = (
|
||||||
|
# Initial vary, new headers, resulting vary.
|
||||||
|
(None, ('Accept-Encoding',), 'Accept-Encoding'),
|
||||||
|
('Accept-Encoding', ('accept-encoding',), 'Accept-Encoding'),
|
||||||
|
('Accept-Encoding', ('ACCEPT-ENCODING',), 'Accept-Encoding'),
|
||||||
|
('Cookie', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
|
||||||
|
('Cookie, Accept-Encoding', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
|
||||||
|
('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
|
||||||
|
(None, ('Accept-Encoding', 'COOKIE'), 'Accept-Encoding, COOKIE'),
|
||||||
|
('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
|
||||||
|
('Cookie , Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
|
||||||
|
)
|
||||||
|
for initial_vary, newheaders, resulting_vary in headers:
|
||||||
|
response = TemplateResponse(HttpResponse(), Template("This is a test"))
|
||||||
|
if initial_vary is not None:
|
||||||
|
response['Vary'] = initial_vary
|
||||||
|
patch_vary_headers(response, newheaders)
|
||||||
|
self.assertEqual(response['Vary'], resulting_vary)
|
||||||
|
|
||||||
|
def test_get_cache_key(self):
|
||||||
|
request = self._get_request(self.path)
|
||||||
|
response = TemplateResponse(HttpResponse(), Template("This is a test"))
|
||||||
|
key_prefix = 'localprefix'
|
||||||
|
# Expect None if no headers have been set yet.
|
||||||
|
self.assertEqual(get_cache_key(request), None)
|
||||||
|
# Set headers to an empty list.
|
||||||
|
learn_cache_key(request, response)
|
||||||
|
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
|
||||||
|
# Verify that a specified key_prefix is taken into account.
|
||||||
|
learn_cache_key(request, response, key_prefix=key_prefix)
|
||||||
|
self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
|
||||||
|
|
||||||
|
def test_get_cache_key_with_query(self):
|
||||||
|
request = self._get_request(self.path + '?test=1')
|
||||||
|
response = TemplateResponse(HttpResponse(), Template("This is a test"))
|
||||||
|
# Expect None if no headers have been set yet.
|
||||||
|
self.assertEqual(get_cache_key(request), None)
|
||||||
|
# Set headers to an empty list.
|
||||||
|
learn_cache_key(request, response)
|
||||||
|
# Verify that the querystring is taken into account.
|
||||||
|
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
|
||||||
|
|
||||||
|
@override_settings(USE_ETAGS=False)
|
||||||
|
def test_without_etag(self):
|
||||||
|
response = TemplateResponse(HttpResponse(), Template("This is a test"))
|
||||||
|
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'))
|
||||||
|
|
||||||
|
@override_settings(USE_ETAGS=True)
|
||||||
|
def test_with_etag(self):
|
||||||
|
response = TemplateResponse(HttpResponse(), Template("This is a test"))
|
||||||
|
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'))
|
||||||
|
|
||||||
|
TestWithTemplateResponse = override_settings(
|
||||||
|
CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix',
|
||||||
|
CACHE_MIDDLEWARE_SECONDS=1,
|
||||||
|
USE_I18N=False,
|
||||||
|
)(TestWithTemplateResponse)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEtagWithAdmin(TestCase):
|
||||||
|
# See https://code.djangoproject.com/ticket/16003
|
||||||
|
def test_admin(self):
|
||||||
|
with self.settings(USE_ETAGS=False):
|
||||||
|
response = self.client.get('/test_admin/admin/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFalse(response.has_header('ETag'))
|
||||||
|
|
||||||
|
with self.settings(USE_ETAGS=True):
|
||||||
|
response = self.client.get('/test_admin/admin/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue(response.has_header('ETag'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue