~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/utils/cache.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
13
13
different header content for headers named in "Vary" need to get different
14
14
cache keys to prevent delivery of wrong content.
15
15
 
16
 
A example: i18n middleware would need to distinguish caches by the
 
16
An example: i18n middleware would need to distinguish caches by the
17
17
"Accept-language" header.
18
18
"""
19
19
 
20
 
import datetime, md5, re
 
20
import re
 
21
import time
 
22
try:
 
23
    set
 
24
except NameError:
 
25
    from sets import Set as set   # Python 2.3 fallback
 
26
 
21
27
from django.conf import settings
22
28
from django.core.cache import cache
 
29
from django.utils.encoding import smart_str, iri_to_uri
 
30
from django.utils.http import http_date
 
31
from django.utils.hashcompat import md5_constructor
23
32
 
24
33
cc_delim_re = re.compile(r'\s*,\s*')
25
34
 
36
45
      str() to it.
37
46
    """
38
47
    def dictitem(s):
39
 
        t = s.split('=',1)
 
48
        t = s.split('=', 1)
40
49
        if len(t) > 1:
41
 
            return (t[0].lower().replace('-', '_'), t[1])
 
50
            return (t[0].lower(), t[1])
42
51
        else:
43
 
            return (t[0].lower().replace('-', '_'), True)
 
52
            return (t[0].lower(), True)
44
53
 
45
54
    def dictvalue(t):
46
 
        if t[1] == True:
 
55
        if t[1] is True:
47
56
            return t[0]
48
57
        else:
49
 
            return t[0] + '=' + str(t[1])
 
58
            return t[0] + '=' + smart_str(t[1])
50
59
 
51
60
    if response.has_header('Cache-Control'):
52
61
        cc = cc_delim_re.split(response['Cache-Control'])
53
62
        cc = dict([dictitem(el) for el in cc])
54
63
    else:
55
64
        cc = {}
56
 
    for (k,v) in kwargs.items():
 
65
 
 
66
    # If there's already a max-age header but we're being asked to set a new
 
67
    # max-age, use the minimum of the two ages. In practice this happens when
 
68
    # a decorator and a piece of middleware both operate on a given view.
 
69
    if 'max-age' in cc and 'max_age' in kwargs:
 
70
        kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
 
71
 
 
72
    for (k, v) in kwargs.items():
57
73
        cc[k.replace('_', '-')] = v
58
74
    cc = ', '.join([dictvalue(el) for el in cc.items()])
59
75
    response['Cache-Control'] = cc
60
76
 
61
 
vary_delim_re = re.compile(r',\s*')
 
77
def get_max_age(response):
 
78
    """
 
79
    Returns the max-age from the response Cache-Control header as an integer
 
80
    (or ``None`` if it wasn't found or wasn't an integer.
 
81
    """
 
82
    if not response.has_header('Cache-Control'):
 
83
        return
 
84
    cc = dict([_to_tuple(el) for el in
 
85
        cc_delim_re.split(response['Cache-Control'])])
 
86
    if 'max-age' in cc:
 
87
        try:
 
88
            return int(cc['max-age'])
 
89
        except (ValueError, TypeError):
 
90
            pass
62
91
 
63
92
def patch_response_headers(response, cache_timeout=None):
64
93
    """
72
101
    """
73
102
    if cache_timeout is None:
74
103
        cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
75
 
    now = datetime.datetime.utcnow()
 
104
    if cache_timeout < 0:
 
105
        cache_timeout = 0 # Can't have max-age negative
76
106
    if not response.has_header('ETag'):
77
 
        response['ETag'] = md5.new(response.content).hexdigest()
 
107
        response['ETag'] = '"%s"' % md5_constructor(response.content).hexdigest()
78
108
    if not response.has_header('Last-Modified'):
79
 
        response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
 
109
        response['Last-Modified'] = http_date()
80
110
    if not response.has_header('Expires'):
81
 
        expires = now + datetime.timedelta(0, cache_timeout)
82
 
        response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
83
 
    if cache_timeout < 0:
84
 
        cache_timeout = 0 # Can't have max-age negative
 
111
        response['Expires'] = http_date(time.time() + cache_timeout)
85
112
    patch_cache_control(response, max_age=cache_timeout)
86
113
 
87
114
def add_never_cache_headers(response):
88
115
    """
89
 
    Add headers to a response to indicate that 
90
 
    a page should never be cached.
 
116
    Adds headers to a response to indicate that a page should never be cached.
91
117
    """
92
118
    patch_response_headers(response, cache_timeout=-1)
93
119
 
100
126
    # Note that we need to keep the original order intact, because cache
101
127
    # implementations may rely on the order of the Vary contents in, say,
102
128
    # computing an MD5 hash.
103
 
    vary = []
104
129
    if response.has_header('Vary'):
105
 
        vary = vary_delim_re.split(response['Vary'])
106
 
    oldheaders = dict([(el.lower(), 1) for el in vary])
107
 
    for newheader in newheaders:
108
 
        if not newheader.lower() in oldheaders:
109
 
            vary.append(newheader)
110
 
    response['Vary'] = ', '.join(vary)
 
130
        vary_headers = cc_delim_re.split(response['Vary'])
 
131
    else:
 
132
        vary_headers = []
 
133
    # Use .lower() here so we treat headers as case-insensitive.
 
134
    existing_headers = set([header.lower() for header in vary_headers])
 
135
    additional_headers = [newheader for newheader in newheaders
 
136
                          if newheader.lower() not in existing_headers]
 
137
    response['Vary'] = ', '.join(vary_headers + additional_headers)
111
138
 
112
139
def _generate_cache_key(request, headerlist, key_prefix):
113
 
    "Returns a cache key from the headers given in the header list."
114
 
    ctx = md5.new()
 
140
    """Returns a cache key from the headers given in the header list."""
 
141
    ctx = md5_constructor()
115
142
    for header in headerlist:
116
143
        value = request.META.get(header, None)
117
144
        if value is not None:
118
145
            ctx.update(value)
119
 
    return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, request.path, ctx.hexdigest())
 
146
    return 'views.decorators.cache.cache_page.%s.%s.%s' % (
 
147
               key_prefix, iri_to_uri(request.path), ctx.hexdigest())
120
148
 
121
149
def get_cache_key(request, key_prefix=None):
122
150
    """
130
158
    """
131
159
    if key_prefix is None:
132
160
        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
133
 
    cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
 
161
    cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
 
162
                    key_prefix, iri_to_uri(request.path))
134
163
    headerlist = cache.get(cache_key, None)
135
164
    if headerlist is not None:
136
165
        return _generate_cache_key(request, headerlist, key_prefix)
154
183
        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
155
184
    if cache_timeout is None:
156
185
        cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
157
 
    cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
 
186
    cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
 
187
                    key_prefix, iri_to_uri(request.path))
158
188
    if response.has_header('Vary'):
159
 
        headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])]
 
189
        headerlist = ['HTTP_'+header.upper().replace('-', '_')
 
190
                      for header in cc_delim_re.split(response['Vary'])]
160
191
        cache.set(cache_key, headerlist, cache_timeout)
161
192
        return _generate_cache_key(request, headerlist, key_prefix)
162
193
    else:
164
195
        # for the request.path
165
196
        cache.set(cache_key, [], cache_timeout)
166
197
        return _generate_cache_key(request, [], key_prefix)
 
198
 
 
199
 
 
200
def _to_tuple(s):
 
201
    t = s.split('=',1)
 
202
    if len(t) == 2:
 
203
        return t[0].lower(), t[1]
 
204
    return t[0].lower(), True