diff --git a/django/http/__init__.py b/django/http/__init__.py
index 60a92d5..c48be9f 100644
a
|
b
|
|
1 | 1 | from __future__ import absolute_import |
2 | 2 | |
3 | 3 | import datetime |
| 4 | import hashlib |
4 | 5 | import os |
5 | 6 | import re |
6 | 7 | import sys |
… |
… |
class HttpResponse(object):
|
575 | 576 | self._charset) |
576 | 577 | self.content = content |
577 | 578 | self.cookies = SimpleCookie() |
| 579 | self.not_modified_checked = False |
578 | 580 | if status: |
579 | 581 | self.status_code = status |
580 | 582 | |
… |
… |
class HttpResponse(object):
|
682 | 684 | self.set_cookie(key, max_age=0, path=path, domain=domain, |
683 | 685 | expires='Thu, 01-Jan-1970 00:00:00 GMT') |
684 | 686 | |
| 687 | def set_etag(self): |
| 688 | """ |
| 689 | Compute the ETag header (if not already set) |
| 690 | """ |
| 691 | if settings.USE_ETAGS and not self.has_header('ETag'): |
| 692 | self['ETag'] = '"%s"' % hashlib.md5(self.content).hexdigest() |
| 693 | |
685 | 694 | def _get_content(self): |
686 | 695 | if self.has_header('Content-Encoding'): |
687 | 696 | return ''.join([str(e) for e in self._container]) |
diff --git a/django/middleware/common.py b/django/middleware/common.py
index d894ec8..abe97c8 100644
a
|
b
|
|
1 | | import hashlib |
2 | 1 | import re |
3 | 2 | |
4 | 3 | from django.conf import settings |
… |
… |
class CommonMiddleware(object):
|
109 | 108 | return response |
110 | 109 | |
111 | 110 | # Use ETags, if requested. |
112 | | if settings.USE_ETAGS: |
113 | | if response.has_header('ETag'): |
114 | | etag = response['ETag'] |
115 | | else: |
116 | | etag = '"%s"' % hashlib.md5(response.content).hexdigest() |
117 | | if response.status_code >= 200 and response.status_code < 300 and request.META.get('HTTP_IF_NONE_MATCH') == etag: |
| 111 | if settings.USE_ETAGS and response.not_modified_checked is not True: |
| 112 | response.set_etag() |
| 113 | if response.status_code >= 200 and response.status_code < 300 and \ |
| 114 | request.META.get('HTTP_IF_NONE_MATCH') == response['ETag']: |
118 | 115 | cookies = response.cookies |
119 | 116 | response = http.HttpResponseNotModified() |
120 | 117 | response.cookies = cookies |
121 | | else: |
122 | | response['ETag'] = etag |
123 | 118 | |
124 | 119 | return response |
125 | 120 | |
diff --git a/django/middleware/http.py b/django/middleware/http.py
index 86e46ce..97c805b 100644
a
|
b
|
class ConditionalGetMiddleware(object):
|
13 | 13 | if not response.has_header('Content-Length'): |
14 | 14 | response['Content-Length'] = str(len(response.content)) |
15 | 15 | |
| 16 | if response.status_code == 304 or response.not_modified_checked: |
| 17 | # Do not check Not Modified again |
| 18 | return response |
| 19 | |
16 | 20 | if response.has_header('ETag'): |
17 | 21 | if_none_match = request.META.get('HTTP_IF_NONE_MATCH') |
18 | 22 | if if_none_match == response['ETag']: |
diff --git a/django/template/response.py b/django/template/response.py
index bb0b9cb..6ed7509 100644
a
|
b
|
class SimpleTemplateResponse(HttpResponse):
|
132 | 132 | |
133 | 133 | content = property(_get_content, _set_content) |
134 | 134 | |
| 135 | def set_etag(self): |
| 136 | if not self._is_rendered: |
| 137 | self.add_post_render_callback(super(SimpleTemplateResponse, self).set_etag.__func__) |
| 138 | else: |
| 139 | super(SimpleTemplateResponse, self).set_etag() |
| 140 | |
135 | 141 | |
136 | 142 | class TemplateResponse(SimpleTemplateResponse): |
137 | 143 | rendering_attrs = SimpleTemplateResponse.rendering_attrs + \ |
diff --git a/django/utils/cache.py b/django/utils/cache.py
index 7ef3680..6603bfa 100644
a
|
b
|
def get_max_age(response):
|
93 | 93 | except (ValueError, TypeError): |
94 | 94 | pass |
95 | 95 | |
96 | | def _set_response_etag(response): |
97 | | response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest() |
98 | | return response |
99 | | |
100 | 96 | def patch_response_headers(response, cache_timeout=None): |
101 | 97 | """ |
102 | 98 | Adds some useful headers to the given HttpResponse object: |
… |
… |
def patch_response_headers(response, cache_timeout=None):
|
111 | 107 | cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS |
112 | 108 | if cache_timeout < 0: |
113 | 109 | cache_timeout = 0 # Can't have max-age negative |
114 | | if settings.USE_ETAGS and not response.has_header('ETag'): |
115 | | if hasattr(response, 'render') and callable(response.render): |
116 | | response.add_post_render_callback(_set_response_etag) |
117 | | else: |
118 | | response = _set_response_etag(response) |
| 110 | response.set_etag() |
119 | 111 | if not response.has_header('Last-Modified'): |
120 | 112 | response['Last-Modified'] = http_date() |
121 | 113 | if not response.has_header('Expires'): |
diff --git a/django/views/decorators/http.py b/django/views/decorators/http.py
index d5c4bff..37a9943 100644
a
|
b
|
def condition(etag_func=None, last_modified_func=None):
|
138 | 138 | } |
139 | 139 | ) |
140 | 140 | response = HttpResponse(status=412) |
141 | | elif (not if_none_match and request.method == "GET" and |
| 141 | elif (not (if_none_match and res_etag) and request.method == "GET" and |
142 | 142 | res_last_modified and if_modified_since and |
143 | 143 | res_last_modified <= if_modified_since): |
144 | 144 | response = HttpResponseNotModified() |
… |
… |
def condition(etag_func=None, last_modified_func=None):
|
151 | 151 | response['Last-Modified'] = http_date(res_last_modified) |
152 | 152 | if res_etag and not response.has_header('ETag'): |
153 | 153 | response['ETag'] = quote_etag(res_etag) |
| 154 | # Flag to signal middlewares to not check again |
| 155 | response.not_modified_checked = True |
154 | 156 | |
155 | 157 | return response |
156 | 158 | |
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index c0ae73e..338f4a5 100644
a
|
b
|
Methods
|
707 | 707 | values you used in ``set_cookie()`` -- otherwise the cookie may not be |
708 | 708 | deleted. |
709 | 709 | |
| 710 | .. method:: HttpResponse.set_etag() |
| 711 | |
| 712 | .. versionadded:: 1.5 |
| 713 | |
| 714 | Compute and set the ETag header from the response content. |
| 715 | |
710 | 716 | .. method:: HttpResponse.write(content) |
711 | 717 | |
712 | 718 | This method makes an :class:`HttpResponse` instance a file-like object. |
diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt
index 1979e89..5e570d1 100644
a
|
b
|
for your front page view::
|
91 | 91 | def front_page(request, blog_id): |
92 | 92 | ... |
93 | 93 | |
| 94 | .. admonition:: Relation with the USE_ETAGS setting |
| 95 | |
| 96 | Using the ``condition`` decorator on a view is not dependant on the |
| 97 | :setting:`USE_ETAGS` setting. However, if a response has been generated by |
| 98 | a view decorated with the ``condition`` decorator, the ETag and Last-Modified |
| 99 | headers will not be checked a second time in |
| 100 | :class:`~django.middleware.common.CommonMiddleware` or |
| 101 | :class:`~django.middleware.http.ConditionalGetMiddleware`. |
| 102 | |
| 103 | |
94 | 104 | Shortcuts for only computing one value |
95 | 105 | ====================================== |
96 | 106 | |
diff --git a/tests/regressiontests/conditional_processing/models.py b/tests/regressiontests/conditional_processing/models.py
index f7f48bc..4739ba6 100644
a
|
b
|
|
2 | 2 | from datetime import datetime |
3 | 3 | |
4 | 4 | from django.test import TestCase |
| 5 | from django.test.utils import override_settings |
5 | 6 | from django.utils import unittest |
6 | 7 | from django.utils.http import parse_etags, quote_etag, parse_http_date |
7 | 8 | |
… |
… |
class ConditionalGet(TestCase):
|
84 | 85 | self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % EXPIRED_ETAG |
85 | 86 | response = self.client.get('/condition/') |
86 | 87 | self.assertFullResponse(response) |
| 88 | # If-None-Match header should not disturb condition on last_modify only |
| 89 | # See #14722 |
| 90 | response = self.client.get('/condition/last_modified/') |
| 91 | self.assertNotModified(response) |
87 | 92 | |
88 | 93 | def testSingleCondition1(self): |
89 | 94 | self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR |
… |
… |
class ConditionalGet(TestCase):
|
128 | 133 | response = self.client.get('/condition/etag/') |
129 | 134 | self.assertFullResponse(response, check_last_modified=False) |
130 | 135 | |
| 136 | ConditionalGetWithUseEtags = override_settings( |
| 137 | USE_ETAGS = True, |
| 138 | )(ConditionalGet) |
131 | 139 | |
132 | 140 | class ETagProcessing(unittest.TestCase): |
133 | 141 | def testParsing(self): |