PageRenderTime 143ms CodeModel.GetById 60ms app.highlight 8ms RepoModel.GetById 72ms app.codeStats 1ms

/docs/topics/conditional-view-processing.txt

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