mirror of https://github.com/django/django.git
Refs #27083 -- Updated conditional header comparison to match RFC 7232.
This commit is contained in:
parent
5a51b44936
commit
22e303887b
|
@ -131,72 +131,102 @@ def _not_modified(request, response=None):
|
||||||
|
|
||||||
|
|
||||||
def get_conditional_response(request, etag=None, last_modified=None, response=None):
|
def get_conditional_response(request, etag=None, last_modified=None, response=None):
|
||||||
# Get HTTP request headers
|
# Only return conditional responses on successful requests.
|
||||||
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
|
if response and not (200 <= response.status_code < 300):
|
||||||
if if_modified_since:
|
return response
|
||||||
if_modified_since = parse_http_date_safe(if_modified_since)
|
|
||||||
|
# Get HTTP request headers.
|
||||||
|
if_match_etags = parse_etags(request.META.get('HTTP_IF_MATCH', ''))
|
||||||
if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
|
if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
|
||||||
if if_unmodified_since:
|
if if_unmodified_since:
|
||||||
if_unmodified_since = parse_http_date_safe(if_unmodified_since)
|
if_unmodified_since = parse_http_date_safe(if_unmodified_since)
|
||||||
if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
|
if_none_match_etags = parse_etags(request.META.get('HTTP_IF_NONE_MATCH', ''))
|
||||||
if_match = request.META.get('HTTP_IF_MATCH')
|
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
|
||||||
etags = []
|
if if_modified_since:
|
||||||
if if_none_match or if_match:
|
if_modified_since = parse_http_date_safe(if_modified_since)
|
||||||
# There can be more than one ETag in the request, so we
|
|
||||||
# consider the list of values.
|
|
||||||
try:
|
|
||||||
etags = parse_etags(if_none_match or if_match)
|
|
||||||
except ValueError:
|
|
||||||
# In case of an invalid ETag, ignore all ETag headers.
|
|
||||||
# Apparently Opera sends invalidly quoted headers at times
|
|
||||||
# (we should be returning a 400 response, but that's a
|
|
||||||
# little extreme) -- this is bug #10681.
|
|
||||||
if_none_match = None
|
|
||||||
if_match = None
|
|
||||||
|
|
||||||
# If-None-Match must be ignored if original result would be anything
|
# Step 1 of section 6 of RFC 7232: Test the If-Match precondition.
|
||||||
# other than a 2XX or 304 status. 304 status would result in no change.
|
if (if_match_etags and not _if_match_passes(etag, if_match_etags)):
|
||||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
|
return _precondition_failed(request)
|
||||||
if response and not (200 <= response.status_code < 300):
|
|
||||||
if_none_match = None
|
|
||||||
if_match = None
|
|
||||||
|
|
||||||
# If-Modified-Since must be ignored if the original result was not a 200.
|
# Step 2: Test the If-Unmodified-Since precondition.
|
||||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
|
if (not if_match_etags and if_unmodified_since and
|
||||||
if response and response.status_code != 200:
|
not _if_unmodified_since_passes(last_modified, if_unmodified_since)):
|
||||||
if_modified_since = None
|
return _precondition_failed(request)
|
||||||
if_unmodified_since = None
|
|
||||||
|
|
||||||
if not ((if_match and if_modified_since) or
|
# Step 3: Test the If-None-Match precondition.
|
||||||
(if_none_match and if_unmodified_since) or
|
if (if_none_match_etags and not _if_none_match_passes(etag, if_none_match_etags)):
|
||||||
(if_modified_since and if_unmodified_since) or
|
if request.method in ('GET', 'HEAD'):
|
||||||
(if_match and if_none_match)):
|
|
||||||
# We only get here if no undefined combinations of headers are
|
|
||||||
# specified.
|
|
||||||
if ((if_none_match and (etag in etags or '*' in etags and etag)) and
|
|
||||||
(not if_modified_since or
|
|
||||||
(last_modified and if_modified_since and last_modified <= if_modified_since))):
|
|
||||||
if request.method in ('GET', 'HEAD'):
|
|
||||||
return _not_modified(request, response)
|
|
||||||
else:
|
|
||||||
return _precondition_failed(request)
|
|
||||||
elif (if_match and (
|
|
||||||
(not etag and '*' in etags) or (etag and etag not in etags) or
|
|
||||||
(last_modified and if_unmodified_since and last_modified > if_unmodified_since)
|
|
||||||
)):
|
|
||||||
return _precondition_failed(request)
|
|
||||||
elif (not if_none_match and request.method in ('GET', 'HEAD') and
|
|
||||||
last_modified and if_modified_since and
|
|
||||||
last_modified <= if_modified_since):
|
|
||||||
return _not_modified(request, response)
|
return _not_modified(request, response)
|
||||||
elif (not if_match and
|
else:
|
||||||
last_modified and if_unmodified_since and
|
|
||||||
last_modified > if_unmodified_since):
|
|
||||||
return _precondition_failed(request)
|
return _precondition_failed(request)
|
||||||
|
|
||||||
|
# Step 4: Test the If-Modified-Since precondition.
|
||||||
|
if (not if_none_match_etags and if_modified_since and
|
||||||
|
not _if_modified_since_passes(last_modified, if_modified_since)):
|
||||||
|
if request.method in ('GET', 'HEAD'):
|
||||||
|
return _not_modified(request, response)
|
||||||
|
|
||||||
|
# Step 5: Test the If-Range precondition (not supported).
|
||||||
|
# Step 6: Return original response since there isn't a conditional response.
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def _if_match_passes(target_etag, etags):
|
||||||
|
"""
|
||||||
|
Test the If-Match comparison as defined in section 3.1 of RFC 7232.
|
||||||
|
"""
|
||||||
|
if not target_etag:
|
||||||
|
# If there isn't an ETag, then there can't be a match.
|
||||||
|
return False
|
||||||
|
elif etags == ['*']:
|
||||||
|
# The existence of an ETag means that there is "a current
|
||||||
|
# representation for the target resource", even if the ETag is weak,
|
||||||
|
# so there is a match to '*'.
|
||||||
|
return True
|
||||||
|
elif target_etag.startswith('W/'):
|
||||||
|
# A weak ETag can never strongly match another ETag.
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Since the ETag is strong, this will only return True if there's a
|
||||||
|
# strong match.
|
||||||
|
return target_etag in etags
|
||||||
|
|
||||||
|
|
||||||
|
def _if_unmodified_since_passes(last_modified, if_unmodified_since):
|
||||||
|
"""
|
||||||
|
Test the If-Unmodified-Since comparison as defined in section 3.4 of
|
||||||
|
RFC 7232.
|
||||||
|
"""
|
||||||
|
return last_modified and last_modified <= if_unmodified_since
|
||||||
|
|
||||||
|
|
||||||
|
def _if_none_match_passes(target_etag, etags):
|
||||||
|
"""
|
||||||
|
Test the If-None-Match comparison as defined in section 3.2 of RFC 7232.
|
||||||
|
"""
|
||||||
|
if not target_etag:
|
||||||
|
# If there isn't an ETag, then there isn't a match.
|
||||||
|
return True
|
||||||
|
elif etags == ['*']:
|
||||||
|
# The existence of an ETag means that there is "a current
|
||||||
|
# representation for the target resource", so there is a match to '*'.
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# The comparison should be weak, so look for a match after stripping
|
||||||
|
# off any weak indicators.
|
||||||
|
target_etag = target_etag.strip('W/')
|
||||||
|
etags = (etag.strip('W/') for etag in etags)
|
||||||
|
return target_etag not in etags
|
||||||
|
|
||||||
|
|
||||||
|
def _if_modified_since_passes(last_modified, if_modified_since):
|
||||||
|
"""
|
||||||
|
Test the If-Modified-Since comparison as defined in section 3.3 of RFC 7232.
|
||||||
|
"""
|
||||||
|
return not last_modified or last_modified > if_modified_since
|
||||||
|
|
||||||
|
|
||||||
def patch_response_headers(response, cache_timeout=None):
|
def patch_response_headers(response, cache_timeout=None):
|
||||||
"""
|
"""
|
||||||
Adds some useful headers to the given HttpResponse object:
|
Adds some useful headers to the given HttpResponse object:
|
||||||
|
|
|
@ -513,8 +513,9 @@ Miscellaneous
|
||||||
* The admin's widget for ``IntegerField`` uses ``type="number"`` rather than
|
* The admin's widget for ``IntegerField`` uses ``type="number"`` rather than
|
||||||
``type="text"``.
|
``type="text"``.
|
||||||
|
|
||||||
* ETags are now parsed according to the :rfc:`7232` Conditional Requests
|
* Conditional HTTP headers are now parsed and compared according to the
|
||||||
specification rather than the syntax from :rfc:`2616`.
|
:rfc:`7232` Conditional Requests specification rather than the older
|
||||||
|
:rfc:`2616`.
|
||||||
|
|
||||||
* In the admin templates, ``<p class="help">`` is replaced with a ``<div>`` tag
|
* In the admin templates, ``<p class="help">`` is replaced with a ``<div>`` tag
|
||||||
to allow including lists inside help text.
|
to allow including lists inside help text.
|
||||||
|
|
|
@ -12,6 +12,7 @@ LAST_MODIFIED_NEWER_STR = 'Mon, 18 Oct 2010 16:56:23 GMT'
|
||||||
LAST_MODIFIED_INVALID_STR = 'Mon, 32 Oct 2010 16:56:23 GMT'
|
LAST_MODIFIED_INVALID_STR = 'Mon, 32 Oct 2010 16:56:23 GMT'
|
||||||
EXPIRED_LAST_MODIFIED_STR = 'Sat, 20 Oct 2007 23:21:47 GMT'
|
EXPIRED_LAST_MODIFIED_STR = 'Sat, 20 Oct 2007 23:21:47 GMT'
|
||||||
ETAG = '"b4246ffc4f62314ca13147c9d4f76974"'
|
ETAG = '"b4246ffc4f62314ca13147c9d4f76974"'
|
||||||
|
WEAK_ETAG = 'W/"b4246ffc4f62314ca13147c9d4f76974"' # weak match to ETAG
|
||||||
EXPIRED_ETAG = '"7fae4cd4b0f81e7d2914700043aa8ed6"'
|
EXPIRED_ETAG = '"7fae4cd4b0f81e7d2914700043aa8ed6"'
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,9 +39,13 @@ class ConditionalGet(SimpleTestCase):
|
||||||
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertNotModified(response)
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/')
|
||||||
|
self.assertFullResponse(response)
|
||||||
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_NEWER_STR
|
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_NEWER_STR
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertNotModified(response)
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/')
|
||||||
|
self.assertFullResponse(response)
|
||||||
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_INVALID_STR
|
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_INVALID_STR
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertFullResponse(response)
|
self.assertFullResponse(response)
|
||||||
|
@ -66,6 +71,8 @@ class ConditionalGet(SimpleTestCase):
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertNotModified(response)
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertFullResponse(response)
|
self.assertFullResponse(response)
|
||||||
|
@ -75,16 +82,68 @@ class ConditionalGet(SimpleTestCase):
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertNotModified(response)
|
self.assertNotModified(response)
|
||||||
|
|
||||||
|
def test_weak_if_none_match(self):
|
||||||
|
"""
|
||||||
|
If-None-Match comparisons use weak matching, so weak and strong ETags
|
||||||
|
with the same value result in a 304 response.
|
||||||
|
"""
|
||||||
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
||||||
|
response = self.client.get('/condition/weak_etag/')
|
||||||
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/weak_etag/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = WEAK_ETAG
|
||||||
|
response = self.client.get('/condition/weak_etag/')
|
||||||
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/weak_etag/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
response = self.client.get('/condition/')
|
||||||
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
|
def test_all_if_none_match(self):
|
||||||
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = '*'
|
||||||
|
response = self.client.get('/condition/')
|
||||||
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
response = self.client.get('/condition/no_etag/')
|
||||||
|
self.assertFullResponse(response, check_last_modified=False, check_etag=False)
|
||||||
|
|
||||||
def test_if_match(self):
|
def test_if_match(self):
|
||||||
self.client.defaults['HTTP_IF_MATCH'] = ETAG
|
self.client.defaults['HTTP_IF_MATCH'] = ETAG
|
||||||
response = self.client.put('/condition/etag/')
|
response = self.client.put('/condition/')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertFullResponse(response)
|
||||||
self.client.defaults['HTTP_IF_MATCH'] = EXPIRED_ETAG
|
self.client.defaults['HTTP_IF_MATCH'] = EXPIRED_ETAG
|
||||||
response = self.client.put('/condition/etag/')
|
response = self.client.put('/condition/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
|
def test_weak_if_match(self):
|
||||||
|
"""
|
||||||
|
If-Match comparisons use strong matching, so any comparison involving
|
||||||
|
a weak ETag return a 412 response.
|
||||||
|
"""
|
||||||
|
self.client.defaults['HTTP_IF_MATCH'] = ETAG
|
||||||
|
response = self.client.get('/condition/weak_etag/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
|
self.client.defaults['HTTP_IF_MATCH'] = WEAK_ETAG
|
||||||
|
response = self.client.get('/condition/weak_etag/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
response = self.client.get('/condition/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
|
def test_all_if_match(self):
|
||||||
|
self.client.defaults['HTTP_IF_MATCH'] = '*'
|
||||||
|
response = self.client.get('/condition/')
|
||||||
|
self.assertFullResponse(response)
|
||||||
|
response = self.client.get('/condition/no_etag/')
|
||||||
self.assertEqual(response.status_code, 412)
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
def test_both_headers(self):
|
def test_both_headers(self):
|
||||||
# see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
|
# see https://tools.ietf.org/html/rfc7232#section-6
|
||||||
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
|
@ -93,7 +152,7 @@ class ConditionalGet(SimpleTestCase):
|
||||||
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertFullResponse(response)
|
self.assertNotModified(response)
|
||||||
|
|
||||||
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG
|
||||||
|
@ -114,7 +173,7 @@ class ConditionalGet(SimpleTestCase):
|
||||||
self.client.defaults['HTTP_IF_UNMODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_UNMODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
|
||||||
self.client.defaults['HTTP_IF_MATCH'] = ETAG
|
self.client.defaults['HTTP_IF_MATCH'] = ETAG
|
||||||
response = self.client.get('/condition/')
|
response = self.client.get('/condition/')
|
||||||
self.assertEqual(response.status_code, 412)
|
self.assertFullResponse(response)
|
||||||
|
|
||||||
self.client.defaults['HTTP_IF_UNMODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_UNMODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
|
||||||
self.client.defaults['HTTP_IF_MATCH'] = EXPIRED_ETAG
|
self.client.defaults['HTTP_IF_MATCH'] = EXPIRED_ETAG
|
||||||
|
@ -169,7 +228,7 @@ class ConditionalGet(SimpleTestCase):
|
||||||
response = self.client.get('/condition/last_modified/')
|
response = self.client.get('/condition/last_modified/')
|
||||||
self.assertEqual(response.status_code, 412)
|
self.assertEqual(response.status_code, 412)
|
||||||
response = self.client.get('/condition/etag/')
|
response = self.client.get('/condition/etag/')
|
||||||
self.assertFullResponse(response, check_last_modified=False)
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
def test_single_condition_8(self):
|
def test_single_condition_8(self):
|
||||||
self.client.defaults['HTTP_IF_UNMODIFIED_SINCE'] = LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_UNMODIFIED_SINCE'] = LAST_MODIFIED_STR
|
||||||
|
@ -181,7 +240,7 @@ class ConditionalGet(SimpleTestCase):
|
||||||
response = self.client.get('/condition/last_modified2/')
|
response = self.client.get('/condition/last_modified2/')
|
||||||
self.assertEqual(response.status_code, 412)
|
self.assertEqual(response.status_code, 412)
|
||||||
response = self.client.get('/condition/etag2/')
|
response = self.client.get('/condition/etag2/')
|
||||||
self.assertFullResponse(response, check_last_modified=False)
|
self.assertEqual(response.status_code, 412)
|
||||||
|
|
||||||
def test_single_condition_head(self):
|
def test_single_condition_head(self):
|
||||||
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
|
||||||
|
@ -204,17 +263,12 @@ class ConditionalGet(SimpleTestCase):
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG
|
||||||
response = self.client.get('/condition/unquoted_etag/')
|
response = self.client.get('/condition/unquoted_etag/')
|
||||||
self.assertNotModified(response)
|
self.assertNotModified(response)
|
||||||
|
response = self.client.put('/condition/unquoted_etag/')
|
||||||
|
self.assertEqual(response.status_code, 412)
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG
|
||||||
response = self.client.get('/condition/unquoted_etag/')
|
response = self.client.get('/condition/unquoted_etag/')
|
||||||
self.assertFullResponse(response, check_last_modified=False)
|
self.assertFullResponse(response, check_last_modified=False)
|
||||||
|
|
||||||
def test_all_if_none_match(self):
|
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = '*'
|
|
||||||
response = self.client.get('/condition/etag/')
|
|
||||||
self.assertNotModified(response)
|
|
||||||
response = self.client.get('/condition/no_etag/')
|
|
||||||
self.assertFullResponse(response, check_last_modified=False, check_etag=False)
|
|
||||||
|
|
||||||
def test_invalid_etag(self):
|
def test_invalid_etag(self):
|
||||||
self.client.defaults['HTTP_IF_NONE_MATCH'] = '"""'
|
self.client.defaults['HTTP_IF_NONE_MATCH'] = '"""'
|
||||||
response = self.client.get('/condition/etag/')
|
response = self.client.get('/condition/etag/')
|
||||||
|
|
|
@ -9,5 +9,6 @@ urlpatterns = [
|
||||||
url('^condition/etag/$', views.etag_view1),
|
url('^condition/etag/$', views.etag_view1),
|
||||||
url('^condition/etag2/$', views.etag_view2),
|
url('^condition/etag2/$', views.etag_view2),
|
||||||
url('^condition/unquoted_etag/$', views.etag_view_unquoted),
|
url('^condition/unquoted_etag/$', views.etag_view_unquoted),
|
||||||
|
url('^condition/weak_etag/$', views.etag_view_weak),
|
||||||
url('^condition/no_etag/$', views.etag_view_none),
|
url('^condition/no_etag/$', views.etag_view_none),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.views.decorators.http import condition, etag, last_modified
|
from django.views.decorators.http import condition, etag, last_modified
|
||||||
|
|
||||||
from .tests import ETAG, FULL_RESPONSE, LAST_MODIFIED
|
from .tests import ETAG, FULL_RESPONSE, LAST_MODIFIED, WEAK_ETAG
|
||||||
|
|
||||||
|
|
||||||
@condition(lambda r: ETAG, lambda r: LAST_MODIFIED)
|
@condition(lambda r: ETAG, lambda r: LAST_MODIFIED)
|
||||||
|
@ -37,6 +37,14 @@ def etag_view_unquoted(request):
|
||||||
return HttpResponse(FULL_RESPONSE)
|
return HttpResponse(FULL_RESPONSE)
|
||||||
|
|
||||||
|
|
||||||
|
@condition(etag_func=lambda r: WEAK_ETAG)
|
||||||
|
def etag_view_weak(request):
|
||||||
|
"""
|
||||||
|
Use an etag_func() that returns a weak ETag.
|
||||||
|
"""
|
||||||
|
return HttpResponse(FULL_RESPONSE)
|
||||||
|
|
||||||
|
|
||||||
@condition(etag_func=lambda r: None)
|
@condition(etag_func=lambda r: None)
|
||||||
def etag_view_none(request):
|
def etag_view_none(request):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue