Ticket #5791: 5791.diff
File 5791.diff, 9.6 KB (added by , 17 years ago) |
---|
-
django/views/decorators/http.py
2 2 Decorators for views based on HTTP headers. 3 3 """ 4 4 5 from calendar import timegm 6 from datetime import timedelta 7 from email.Utils import formatdate 5 8 from django.utils.decorators import decorator_from_middleware 6 9 from django.middleware.http import ConditionalGetMiddleware 7 from django.http import HttpResponseNotAllowed 10 from django.http import HttpResponseNotAllowed, HttpResponseNotModified 8 11 9 12 conditional_page = decorator_from_middleware(ConditionalGetMiddleware) 10 13 … … 31 34 require_GET.__doc__ = "Decorator to require that a view only accept the GET method." 32 35 33 36 require_POST = require_http_methods(["POST"]) 34 require_POST.__doc__ = "Decorator to require that a view only accept the POST method." 35 No newline at end of file 37 require_POST.__doc__ = "Decorator to require that a view only accept the POST method." 38 39 40 def _none(*args, **kwargs): 41 return None 42 43 def condition(last_modified=_none, etag=_none): 44 """ 45 Decorator to support conditional get for a view. It takes as parameters 46 user-defined functions that calculate etag and/or last modified time. 47 48 Both functions are passed the same parameters as the view itself. "last_modified@ 49 should return a standard datetime value and "etag" should return a string. 50 51 Usage with last_modified:: 52 53 @condition(lambda r, obj_id: MyObject.objects.get(pk=obj_id).update_time) 54 def my_object_view(request, obj_id): 55 # ... 56 57 You can pass last_modified, etag or both of them (if you really need it). 58 """ 59 def decorator(func): 60 def inner(request, *args, **kwargs): 61 62 # _call_* functions are used to format and memoize output from user supplied callables 63 def _call_last_modified(): 64 result = [] 65 def caller(): 66 if not result: 67 dt = last_modified(request, *args, **kwargs) 68 result.append(dt and (formatdate(timegm(dt.utctimetuple()))[:26] + 'GMT')) 69 return result[0] 70 return caller 71 _last_modified = _call_last_modified() 72 73 def _call_etag(): 74 result = [] 75 def caller(): 76 if not result: 77 result.append(etag(request, *args, **kwargs)) 78 return result[0] 79 return caller 80 _etag = _call_etag() 81 82 if request.method not in ('GET', 'HEAD'): 83 return func(request, *args, **kwargs) 84 if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None) 85 if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None) 86 if if_none_match: 87 if_none_match = [e.strip() for e in if_none_match.split(',')] 88 not_modified = (if_modified_since or if_none_match) and \ 89 (not if_modified_since or _last_modified() == if_modified_since) and \ 90 (not if_none_match or _etag() in if_none_match) 91 if not_modified: 92 return HttpResponseNotModified() 93 response = func(request, *args, **kwargs) 94 if _last_modified() and not response.has_header('Last-Modified'): 95 response['Last-Modified'] = _last_modified() 96 if _etag() and not response.has_header('ETag'): 97 response['ETag'] = _etag() 98 return response 99 return inner 100 return decorator 101 No newline at end of file -
tests/regressiontests/conditional_get/views.py
1 # -*- coding:utf-8 -*- 2 from django.views.decorators.http import condition 3 from django.http import HttpResponse 4 5 from models import FULL_RESPONSE, LAST_MODIFIED, ETAG 6 7 @condition(lambda r: LAST_MODIFIED, lambda r: ETAG) 8 def index(request): 9 return HttpResponse(FULL_RESPONSE) 10 11 @condition(last_modified=lambda r: LAST_MODIFIED) 12 def last_modified(request): 13 return HttpResponse(FULL_RESPONSE) 14 15 @condition(etag=lambda r: ETAG) 16 def etag(request): 17 return HttpResponse(FULL_RESPONSE) 18 No newline at end of file -
tests/regressiontests/conditional_get/models.py
1 # -*- coding:utf-8 -*- 2 from datetime import datetime, timedelta 3 from calendar import timegm 4 from django.test import TestCase 5 6 FULL_RESPONSE = 'Test conditional get response' 7 LAST_MODIFIED = datetime(2007, 10, 21, 23, 21, 47) 8 LAST_MODIFIED_STR = 'Sun, 21 Oct 2007 23:21:47 GMT' 9 EXPIRED_LAST_MODIFIED_STR = 'Sat, 20 Oct 2007 23:21:47 GMT' 10 ETAG = '"b4246ffc4f62314ca13147c9d4f76974"' 11 EXPIRED_ETAG = '"7fae4cd4b0f81e7d2914700043aa8ed6"' 12 13 class ConditionalGet(TestCase): 14 def assertFullResponse(self, response, check_last_modified=True, check_etag=True): 15 self.assertEquals(response.status_code, 200) 16 self.assertEquals(response.content, FULL_RESPONSE) 17 if check_last_modified: 18 self.assertEquals(response['Last-Modified'], LAST_MODIFIED_STR) 19 if check_etag: 20 self.assertEquals(response['ETag'], ETAG) 21 22 def assertNotModified(self, response): 23 self.assertEquals(response.status_code, 304) 24 self.assertEquals(response.content, '') 25 26 def testWithoutConditions(self): 27 response = self.client.get('/condition/') 28 self.assertFullResponse(response) 29 30 def testIfModifiedSince(self): 31 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR 32 response = self.client.get('/condition/') 33 self.assertNotModified(response) 34 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR 35 response = self.client.get('/condition/') 36 self.assertFullResponse(response) 37 38 def testIfNoneMatch(self): 39 self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG 40 response = self.client.get('/condition/') 41 self.assertNotModified(response) 42 self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG 43 response = self.client.get('/condition/') 44 self.assertFullResponse(response) 45 46 # Several etags in If-None-Match is a bit exotic but why not? 47 self.client.defaults['HTTP_IF_NONE_MATCH'] = ', '.join([ETAG, EXPIRED_ETAG]) 48 response = self.client.get('/condition/') 49 self.assertNotModified(response) 50 51 def testBothHeaders(self): 52 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR 53 self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG 54 response = self.client.get('/condition/') 55 self.assertNotModified(response) 56 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR 57 self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG 58 response = self.client.get('/condition/') 59 self.assertFullResponse(response) 60 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR 61 self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG 62 response = self.client.get('/condition/') 63 self.assertFullResponse(response) 64 65 def testSingleConditions(self): 66 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR 67 response = self.client.get('/condition/last_modified/') 68 self.assertNotModified(response) 69 response = self.client.get('/condition/etag/') 70 self.assertFullResponse(response, check_last_modified=False) 71 72 del self.client.defaults['HTTP_IF_MODIFIED_SINCE'] 73 self.client.defaults['HTTP_IF_NONE_MATCH'] = ETAG 74 response = self.client.get('/condition/etag/') 75 self.assertNotModified(response) 76 response = self.client.get('/condition/last_modified/') 77 self.assertFullResponse(response, check_etag=False) 78 79 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR 80 self.client.defaults['HTTP_IF_NONE_MATCH'] = EXPIRED_ETAG 81 response = self.client.get('/condition/last_modified/') 82 self.assertFullResponse(response, check_etag=False) 83 response = self.client.get('/condition/etag/') 84 self.assertFullResponse(response, check_last_modified=False) 85 No newline at end of file -
tests/regressiontests/conditional_get/urls.py
1 from django.conf.urls.defaults import * 2 import views 3 4 urlpatterns = patterns('', 5 ('^$', views.index), 6 ('^last_modified/$', views.last_modified), 7 ('^etag/$', views.etag), 8 ) -
tests/urls.py
14 14 15 15 # django built-in views 16 16 (r'^views/', include('regressiontests.views.urls')), 17 18 # conditional get views 19 (r'condition/', include('regressiontests.conditional_get.urls')), 17 20 )