Commit 3e7d79b6 authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

[1.3.X] Fixed #15499 -- Ensure that cache control headers don't try to set...

[1.3.X] 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.

Backport of r16657 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.3.X@16673 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent e9a1c03d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -67,6 +67,12 @@ def patch_cache_control(response, **kwargs):
    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()])
+22 −0
Original line number Diff line number Diff line
@@ -1062,6 +1062,28 @@ Django, use the ``cache_control`` view decorator. Example::
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:

+27 −1
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
# Uses whatever cache backend is set in the test settings file.

import os
import re
import tempfile
import time
import warnings
@@ -18,7 +19,7 @@ from django.test import RequestFactory
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.utils.hashcompat import md5_constructor
from django.views.decorators.cache import cache_page

@@ -970,6 +971,31 @@ class CacheUtils(unittest.TestCase):
        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()