Fixed #15499 -- Ensure that cache control headers don't try to set public and private as a result of multiple calls to patch_cache_control with different arguments. Thanks to AndiDog for the report and patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16657 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2011-08-23 03:51:10 +00:00
parent 2e56066a5b
commit 6cd9023635
3 changed files with 55 additions and 1 deletions

View File

@ -66,6 +66,12 @@ def patch_cache_control(response, **kwargs):
if 'max-age' in cc and 'max_age' in kwargs: if 'max-age' in cc and 'max_age' in kwargs:
kwargs['max_age'] = min(cc['max-age'], kwargs['max_age']) kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
# Allow overriding private caching and vice versa
if 'private' in cc and 'public' in kwargs:
del cc['private']
elif 'public' in cc and 'private' in kwargs:
del cc['public']
for (k, v) in kwargs.items(): for (k, v) in kwargs.items():
cc[k.replace('_', '-')] = v cc[k.replace('_', '-')] = v
cc = ', '.join([dictvalue(el) for el in cc.items()]) cc = ', '.join([dictvalue(el) for el in cc.items()])

View File

@ -1059,6 +1059,28 @@ Django, use the ``cache_control`` view decorator. Example::
This decorator takes care of sending out the appropriate HTTP header behind the This decorator takes care of sending out the appropriate HTTP header behind the
scenes. scenes.
Note that the cache control settings "private" and "public" are mutually
exclusive. The decorator ensures that the "public" directive is removed if
"private" should be set (and vice versa). An example use of the two directives
would be a blog site that offers both private and public entries. Public
entries may be cached on any shared cache. The following code uses
``patch_cache_control``, the manual way to modify the cache control header
(it is internally called by the ``cache_control`` decorator)::
from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie
@vary_on_cookie
def list_blog_entries_view(request):
if request.user.is_anonymous():
response = render_only_public_entries()
patch_cache_control(response, public=True)
else:
response = render_private_and_public_entries(request.user)
patch_cache_control(response, private=True)
return response
There are a few other ways to control cache parameters. For example, HTTP There are a few other ways to control cache parameters. For example, HTTP
allows applications to do the following: allows applications to do the following:

View File

@ -5,6 +5,7 @@
import hashlib import hashlib
import os import os
import re
import tempfile import tempfile
import time import time
import warnings import warnings
@ -19,7 +20,7 @@ from django.test import RequestFactory
from django.test.utils import get_warnings_state, restore_warnings_state from django.test.utils import get_warnings_state, restore_warnings_state
from django.utils import translation from django.utils import translation
from django.utils import unittest from django.utils import unittest
from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key, patch_cache_control
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
@ -1003,6 +1004,31 @@ class CacheUtils(unittest.TestCase):
learn_cache_key(request, response) learn_cache_key(request, response)
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
def test_patch_cache_control(self):
tests = (
# Initial Cache-Control, kwargs to patch_cache_control, expected Cache-Control parts
(None, {'private' : True}, set(['private'])),
# Test whether private/public attributes are mutually exclusive
('private', {'private' : True}, set(['private'])),
('private', {'public' : True}, set(['public'])),
('public', {'public' : True}, set(['public'])),
('public', {'private' : True}, set(['private'])),
('must-revalidate,max-age=60,private', {'public' : True}, set(['must-revalidate', 'max-age=60', 'public'])),
('must-revalidate,max-age=60,public', {'private' : True}, set(['must-revalidate', 'max-age=60', 'private'])),
('must-revalidate,max-age=60', {'public' : True}, set(['must-revalidate', 'max-age=60', 'public'])),
)
cc_delim_re = re.compile(r'\s*,\s*')
for initial_cc, newheaders, expected_cc in tests:
response = HttpResponse()
if initial_cc is not None:
response['Cache-Control'] = initial_cc
patch_cache_control(response, **newheaders)
parts = set(cc_delim_re.split(response['Cache-Control']))
self.assertEqual(parts, expected_cc)
class PrefixedCacheUtils(CacheUtils): class PrefixedCacheUtils(CacheUtils):
def setUp(self): def setUp(self):
super(PrefixedCacheUtils, self).setUp() super(PrefixedCacheUtils, self).setUp()