1. ===========================
    
  2. Conditional View Processing
    
  3. ===========================
    
  4. 
    
  5. HTTP clients can send a number of headers to tell the server about copies of a
    
  6. resource that they have already seen. This is commonly used when retrieving a
    
  7. web page (using an HTTP ``GET`` request) to avoid sending all the data for
    
  8. something the client has already retrieved. However, the same headers can be
    
  9. used for all HTTP methods (``POST``, ``PUT``, ``DELETE``, etc.).
    
  10. 
    
  11. For each page (response) that Django sends back from a view, it might provide
    
  12. two HTTP headers: the ``ETag`` header and the ``Last-Modified`` header. These
    
  13. headers are optional on HTTP responses. They can be set by your view function,
    
  14. or you can rely on the :class:`~django.middleware.http.ConditionalGetMiddleware`
    
  15. middleware to set the ``ETag`` header.
    
  16. 
    
  17. When the client next requests the same resource, it might send along a header
    
  18. such as either :rfc:`If-modified-since <7232#section-3.3>` or
    
  19. :rfc:`If-unmodified-since <7232#section-3.4>`, containing the date of the last
    
  20. modification time it was sent, or either :rfc:`If-match <7232#section-3.1>` or
    
  21. :rfc:`If-none-match <7232#section-3.2>`, containing the last ``ETag`` it was
    
  22. sent. If the current version of the page matches the ``ETag`` sent by the
    
  23. client, or if the resource has not been modified, a 304 status code can be sent
    
  24. back, instead of a full response, telling the client that nothing has changed.
    
  25. Depending on the header, if the page has been modified or does not match the
    
  26. ``ETag`` sent by the client, a 412 status code (Precondition Failed) may be
    
  27. returned.
    
  28. 
    
  29. When you need more fine-grained control you may use per-view conditional
    
  30. processing functions.
    
  31. 
    
  32. .. _conditional-decorators:
    
  33. 
    
  34. The ``condition`` decorator
    
  35. ===========================
    
  36. 
    
  37. Sometimes (in fact, quite often) you can create functions to rapidly compute
    
  38. the :rfc:`ETag <7232#section-2.3>` value or the last-modified time for a
    
  39. resource, **without** needing to do all the computations needed to construct
    
  40. the full view. Django can then use these functions to provide an
    
  41. "early bailout" option for the view processing. Telling the client that the
    
  42. content has not been modified since the last request, perhaps.
    
  43. 
    
  44. These two functions are passed as parameters to the
    
  45. ``django.views.decorators.http.condition`` decorator. This decorator uses
    
  46. the two functions (you only need to supply one, if you can't compute both
    
  47. quantities easily and quickly) to work out if the headers in the HTTP request
    
  48. match those on the resource. If they don't match, a new copy of the resource
    
  49. must be computed and your normal view is called.
    
  50. 
    
  51. The ``condition`` decorator's signature looks like this::
    
  52. 
    
  53.     condition(etag_func=None, last_modified_func=None)
    
  54. 
    
  55. The two functions, to compute the ETag and the last modified time, will be
    
  56. passed the incoming ``request`` object and the same parameters, in the same
    
  57. order, as the view function they are helping to wrap. The function passed
    
  58. ``last_modified_func`` should return a standard datetime value specifying the
    
  59. last time the resource was modified, or ``None`` if the resource doesn't
    
  60. exist. The function passed to the ``etag`` decorator should return a string
    
  61. representing the :rfc:`ETag <7232#section-2.3>` for the resource, or ``None``
    
  62. if it doesn't exist.
    
  63. 
    
  64. The decorator sets the ``ETag`` and ``Last-Modified`` headers on the response
    
  65. if they are not already set by the view and if the request's method is safe
    
  66. (``GET`` or ``HEAD``).
    
  67. 
    
  68. Using this feature usefully is probably best explained with an example.
    
  69. Suppose you have this pair of models, representing a small blog system::
    
  70. 
    
  71.     import datetime
    
  72.     from django.db import models
    
  73. 
    
  74.     class Blog(models.Model):
    
  75.         ...
    
  76. 
    
  77.     class Entry(models.Model):
    
  78.         blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    
  79.         published = models.DateTimeField(default=datetime.datetime.now)
    
  80.         ...
    
  81. 
    
  82. If the front page, displaying the latest blog entries, only changes when you
    
  83. add a new blog entry, you can compute the last modified time very quickly. You
    
  84. need the latest ``published`` date for every entry associated with that blog.
    
  85. One way to do this would be::
    
  86. 
    
  87.     def latest_entry(request, blog_id):
    
  88.         return Entry.objects.filter(blog=blog_id).latest("published").published
    
  89. 
    
  90. You can then use this function to provide early detection of an unchanged page
    
  91. for your front page view::
    
  92. 
    
  93.     from django.views.decorators.http import condition
    
  94. 
    
  95.     @condition(last_modified_func=latest_entry)
    
  96.     def front_page(request, blog_id):
    
  97.         ...
    
  98. 
    
  99. .. admonition:: Be careful with the order of decorators
    
  100. 
    
  101.     When ``condition()`` returns a conditional response, any decorators below
    
  102.     it will be skipped and won't apply to the response. Therefore, any
    
  103.     decorators that need to apply to both the regular view response and a
    
  104.     conditional response must be above ``condition()``. In particular,
    
  105.     :func:`~django.views.decorators.vary.vary_on_cookie`,
    
  106.     :func:`~django.views.decorators.vary.vary_on_headers`, and
    
  107.     :func:`~django.views.decorators.cache.cache_control` should come first
    
  108.     because :rfc:`RFC 7232 <7232#section-4.1>` requires that the headers they
    
  109.     set be present on 304 responses.
    
  110. 
    
  111. Shortcuts for only computing one value
    
  112. ======================================
    
  113. 
    
  114. As a general rule, if you can provide functions to compute *both* the ETag and
    
  115. the last modified time, you should do so. You don't know which headers any
    
  116. given HTTP client will send you, so be prepared to handle both. However,
    
  117. sometimes only one value is easy to compute and Django provides decorators
    
  118. that handle only ETag or only last-modified computations.
    
  119. 
    
  120. The ``django.views.decorators.http.etag`` and
    
  121. ``django.views.decorators.http.last_modified`` decorators are passed the same
    
  122. type of functions as the ``condition`` decorator. Their signatures are::
    
  123. 
    
  124.     etag(etag_func)
    
  125.     last_modified(last_modified_func)
    
  126. 
    
  127. We could write the earlier example, which only uses a last-modified function,
    
  128. using one of these decorators::
    
  129. 
    
  130.     @last_modified(latest_entry)
    
  131.     def front_page(request, blog_id):
    
  132.         ...
    
  133. 
    
  134. ...or::
    
  135. 
    
  136.     def front_page(request, blog_id):
    
  137.         ...
    
  138.     front_page = last_modified(latest_entry)(front_page)
    
  139. 
    
  140. Use ``condition`` when testing both conditions
    
  141. ----------------------------------------------
    
  142. 
    
  143. It might look nicer to some people to try and chain the ``etag`` and
    
  144. ``last_modified`` decorators if you want to test both preconditions. However,
    
  145. this would lead to incorrect behavior.
    
  146. 
    
  147. ::
    
  148. 
    
  149.     # Bad code. Don't do this!
    
  150.     @etag(etag_func)
    
  151.     @last_modified(last_modified_func)
    
  152.     def my_view(request):
    
  153.         # ...
    
  154. 
    
  155.     # End of bad code.
    
  156. 
    
  157. The first decorator doesn't know anything about the second and might
    
  158. answer that the response is not modified even if the second decorators would
    
  159. determine otherwise. The ``condition`` decorator uses both callback functions
    
  160. simultaneously to work out the right action to take.
    
  161. 
    
  162. Using the decorators with other HTTP methods
    
  163. ============================================
    
  164. 
    
  165. The ``condition`` decorator is useful for more than only ``GET`` and
    
  166. ``HEAD`` requests (``HEAD`` requests are the same as ``GET`` in this
    
  167. situation). It can also be used to provide checking for ``POST``,
    
  168. ``PUT`` and ``DELETE`` requests. In these situations, the idea isn't to return
    
  169. a "not modified" response, but to tell the client that the resource they are
    
  170. trying to change has been altered in the meantime.
    
  171. 
    
  172. For example, consider the following exchange between the client and server:
    
  173. 
    
  174. #. Client requests ``/foo/``.
    
  175. #. Server responds with some content with an ETag of ``"abcd1234"``.
    
  176. #. Client sends an HTTP ``PUT`` request to ``/foo/`` to update the
    
  177.    resource. It also sends an ``If-Match: "abcd1234"`` header to specify
    
  178.    the version it is trying to update.
    
  179. #. Server checks to see if the resource has changed, by computing the ETag
    
  180.    the same way it does for a ``GET`` request (using the same function).
    
  181.    If the resource *has* changed, it will return a 412 status code,
    
  182.    meaning "precondition failed".
    
  183. #. Client sends a ``GET`` request to ``/foo/``, after receiving a 412
    
  184.    response, to retrieve an updated version of the content before updating
    
  185.    it.
    
  186. 
    
  187. The important thing this example shows is that the same functions can be used
    
  188. to compute the ETag and last modification values in all situations. In fact,
    
  189. you **should** use the same functions, so that the same values are returned
    
  190. every time.
    
  191. 
    
  192. .. admonition:: Validator headers with non-safe request methods
    
  193. 
    
  194.     The ``condition`` decorator only sets validator headers (``ETag`` and
    
  195.     ``Last-Modified``) for safe HTTP methods, i.e. ``GET`` and ``HEAD``. If you
    
  196.     wish to return them in other cases, set them in your view. See
    
  197.     :rfc:`7231#section-4.3.4` to learn about the distinction between setting a
    
  198.     validator header in response to requests made with ``PUT`` versus ``POST``.
    
  199. 
    
  200. Comparison with middleware conditional processing
    
  201. =================================================
    
  202. 
    
  203. Django provides conditional ``GET`` handling via
    
  204. :class:`django.middleware.http.ConditionalGetMiddleware`. While being suitable
    
  205. for many situations, the middleware has limitations for advanced usage:
    
  206. 
    
  207. * It's applied globally to all views in your project.
    
  208. * It doesn't save you from generating the response, which may be expensive.
    
  209. * It's only appropriate for HTTP ``GET`` requests.
    
  210. 
    
  211. You should choose the most appropriate tool for your particular problem here.
    
  212. If you have a way to compute ETags and modification times quickly and if some
    
  213. view takes a while to generate the content, you should consider using the
    
  214. ``condition`` decorator described in this document. If everything already runs
    
  215. fairly quickly, stick to using the middleware and the amount of network
    
  216. traffic sent back to the clients will still be reduced if the view hasn't
    
  217. changed.