Author: russellm
Date: 2011-08-22 20:51:10 -0700 (Mon, 22 Aug 2011)
New Revision: 16657

Modified:
   django/trunk/django/utils/cache.py
   django/trunk/docs/topics/cache.txt
   django/trunk/tests/regressiontests/cache/tests.py
Log:
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.

Modified: django/trunk/django/utils/cache.py
===================================================================
--- django/trunk/django/utils/cache.py  2011-08-23 03:38:42 UTC (rev 16656)
+++ django/trunk/django/utils/cache.py  2011-08-23 03:51:10 UTC (rev 16657)
@@ -66,6 +66,12 @@
     if 'max-age' in cc and 'max_age' in kwargs:
         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():
         cc[k.replace('_', '-')] = v
     cc = ', '.join([dictvalue(el) for el in cc.items()])

Modified: django/trunk/docs/topics/cache.txt
===================================================================
--- django/trunk/docs/topics/cache.txt  2011-08-23 03:38:42 UTC (rev 16656)
+++ django/trunk/docs/topics/cache.txt  2011-08-23 03:51:10 UTC (rev 16657)
@@ -1059,6 +1059,28 @@
 This decorator takes care of sending out the appropriate HTTP header behind the
 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
 allows applications to do the following:
 

Modified: django/trunk/tests/regressiontests/cache/tests.py
===================================================================
--- django/trunk/tests/regressiontests/cache/tests.py   2011-08-23 03:38:42 UTC 
(rev 16656)
+++ django/trunk/tests/regressiontests/cache/tests.py   2011-08-23 03:51:10 UTC 
(rev 16657)
@@ -5,6 +5,7 @@
 
 import hashlib
 import os
+import re
 import tempfile
 import time
 import warnings
@@ -19,7 +20,7 @@
 from django.test.utils import get_warnings_state, restore_warnings_state
 from django.utils import translation
 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 regressiontests.cache.models import Poll, expensive_calculation
@@ -1003,6 +1004,31 @@
         learn_cache_key(request, response)
         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):
     def setUp(self):
         super(PrefixedCacheUtils, self).setUp()

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to