Permalink
Browse files

etag decorator that works with arbitrary objects created by views.

See https://docs.djangoproject.com/en/dev/topics/conditional-view-processing/ for details about why/how this might be a good idea.

This allows for shortcutting running the actual view function if we have a way of generating the Etag, and comparing.

This can also be used for conditional PUT/POST/DELETE requests.
  • Loading branch information...
1 parent d714901 commit cc3a88edc6be21347a9b35929d158b8831ba9bd3 @schinckel committed Jun 29, 2011
Showing with 41 additions and 1 deletion.
  1. +37 −0 djangorestframework/decorators.py
  2. +4 −1 djangorestframework/mixins.py
@@ -0,0 +1,37 @@
+import re
+
+def etag(etag_generator):
+ def decorate_method(method):
+ def wrapper(self, request, *args, **kwargs):
+ value = etag_generator(request, *args, **kwargs)
+ self._ETAG = '"%s"' % value
+ if request.method in ["GET", "HEAD"]:
+ etags = re.split(r'\W*,\W*', request.META.get('HTTP_IF_NONE_MATCH', ''))
+ if value in etags or '*' in etags:
+ raise ErrorResponse(status=304)
+ elif request.method in ["PUT", "POST", "DELETE"]:
+ etags = re.split(r'\W*,\W*', request.META.get('HTTP_IF_MATCH', ''))
+ if value in etags or '*' in etags:
+ raise ErrorResponse(status=412)
+
+ return method(self, request, *args, **kwargs)
+ return wrapper
+ return decorate_method
+
+# Intended use:
+
+# def etag_generator(request, pk):
+# return Foo.objects.get(pk=pk).updated_at
+#
+
+# class MyView(View):
+# @etag(etag_generator)
+# def get(self, request, pk):
+# # Complicated/lengthy processing which can be ignored if the
+# # most desired Foo has not changed/been edited.
+# return Foo.objects.get(pk=pk)
+#
+# @etag(etag_generator)
+# def put(self, request, pk):
+# # This will only run if the object has not changed.
+# Foo.objects.filter(pk=pk).update(**request.DATA)
@@ -226,7 +226,8 @@ class ResponseMixin(object):
_ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
_IGNORE_IE_ACCEPT_HEADER = True
-
+ _ETAG = None
+
"""
The set of response renderers that the view can handle.
@@ -262,6 +263,8 @@ def render(self, response):
# Build the HTTP Response
resp = HttpResponse(content, mimetype=response.media_type, status=response.status)
+ if self._ETAG:
+ resp['Etag'] = self._ETAG
for (key, val) in response.headers.items():
resp[key] = val

0 comments on commit cc3a88e

Please sign in to comment.