Commit 6cd90236 authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #15499 -- Ensure that cache control headers don't try to set public and...

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
parent 2e56066a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -66,6 +66,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
@@ -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
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
@@ -5,6 +5,7 @@

import hashlib
import os
import re
import tempfile
import time
import warnings
@@ -19,7 +20,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.views.decorators.cache import cache_page

from regressiontests.cache.models import Poll, expensive_calculation
@@ -1003,6 +1004,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()