Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #15499 -- Ensure that cache control headers don't try to set pu…

…blic 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
  • Loading branch information...
commit 6cd90236350b6531122ddbb5a99874add09f3f15 1 parent 2e56066
Russell Keith-Magee authored August 23, 2011
6  django/utils/cache.py
@@ -66,6 +66,12 @@ def dictvalue(t):
66 66
     if 'max-age' in cc and 'max_age' in kwargs:
67 67
         kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
68 68
 
  69
+    # Allow overriding private caching and vice versa
  70
+    if 'private' in cc and 'public' in kwargs:
  71
+        del cc['private']
  72
+    elif 'public' in cc and 'private' in kwargs:
  73
+        del cc['public']
  74
+
69 75
     for (k, v) in kwargs.items():
70 76
         cc[k.replace('_', '-')] = v
71 77
     cc = ', '.join([dictvalue(el) for el in cc.items()])
22  docs/topics/cache.txt
@@ -1059,6 +1059,28 @@ Django, use the ``cache_control`` view decorator. Example::
1059 1059
 This decorator takes care of sending out the appropriate HTTP header behind the
1060 1060
 scenes.
1061 1061
 
  1062
+Note that the cache control settings "private" and "public" are mutually
  1063
+exclusive. The decorator ensures that the "public" directive is removed if
  1064
+"private" should be set (and vice versa). An example use of the two directives
  1065
+would be a blog site that offers both private and public entries. Public
  1066
+entries may be cached on any shared cache. The following code uses
  1067
+``patch_cache_control``, the manual way to modify the cache control header
  1068
+(it is internally called by the ``cache_control`` decorator)::
  1069
+
  1070
+    from django.views.decorators.cache import patch_cache_control
  1071
+    from django.views.decorators.vary import vary_on_cookie
  1072
+
  1073
+    @vary_on_cookie
  1074
+    def list_blog_entries_view(request):
  1075
+        if request.user.is_anonymous():
  1076
+            response = render_only_public_entries()
  1077
+            patch_cache_control(response, public=True)
  1078
+        else:
  1079
+            response = render_private_and_public_entries(request.user)
  1080
+            patch_cache_control(response, private=True)
  1081
+
  1082
+        return response
  1083
+
1062 1084
 There are a few other ways to control cache parameters. For example, HTTP
1063 1085
 allows applications to do the following:
1064 1086
 
28  tests/regressiontests/cache/tests.py
@@ -5,6 +5,7 @@
5 5
 
6 6
 import hashlib
7 7
 import os
  8
+import re
8 9
 import tempfile
9 10
 import time
10 11
 import warnings
@@ -19,7 +20,7 @@
19 20
 from django.test.utils import get_warnings_state, restore_warnings_state
20 21
 from django.utils import translation
21 22
 from django.utils import unittest
22  
-from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key
  23
+from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key, patch_cache_control
23 24
 from django.views.decorators.cache import cache_page
24 25
 
25 26
 from regressiontests.cache.models import Poll, expensive_calculation
@@ -1003,6 +1004,31 @@ def test_learn_cache_key(self):
1003 1004
         learn_cache_key(request, response)
1004 1005
         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
1005 1006
 
  1007
+    def test_patch_cache_control(self):
  1008
+        tests = (
  1009
+            # Initial Cache-Control, kwargs to patch_cache_control, expected Cache-Control parts
  1010
+            (None, {'private' : True}, set(['private'])),
  1011
+
  1012
+            # Test whether private/public attributes are mutually exclusive
  1013
+            ('private', {'private' : True}, set(['private'])),
  1014
+            ('private', {'public' : True}, set(['public'])),
  1015
+            ('public', {'public' : True}, set(['public'])),
  1016
+            ('public', {'private' : True}, set(['private'])),
  1017
+            ('must-revalidate,max-age=60,private', {'public' : True}, set(['must-revalidate', 'max-age=60', 'public'])),
  1018
+            ('must-revalidate,max-age=60,public', {'private' : True}, set(['must-revalidate', 'max-age=60', 'private'])),
  1019
+            ('must-revalidate,max-age=60', {'public' : True}, set(['must-revalidate', 'max-age=60', 'public'])),
  1020
+        )
  1021
+
  1022
+        cc_delim_re = re.compile(r'\s*,\s*')
  1023
+
  1024
+        for initial_cc, newheaders, expected_cc in tests:
  1025
+            response = HttpResponse()
  1026
+            if initial_cc is not None:
  1027
+                response['Cache-Control'] = initial_cc
  1028
+            patch_cache_control(response, **newheaders)
  1029
+            parts = set(cc_delim_re.split(response['Cache-Control']))
  1030
+            self.assertEqual(parts, expected_cc)
  1031
+
1006 1032
 class PrefixedCacheUtils(CacheUtils):
1007 1033
     def setUp(self):
1008 1034
         super(PrefixedCacheUtils, self).setUp()

0 notes on commit 6cd9023

Please sign in to comment.
Something went wrong with that request. Please try again.