mirror of
				https://github.com/django/django.git
				synced 2025-11-03 21:25:09 +00:00 
			
		
		
		
	git-svn-id: http://code.djangoproject.com/svn/django/trunk@16983 bcc190cf-cafb-0310-a4f2-bffc1f526a37
		
			
				
	
	
		
			197 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
===========================
 | 
						|
Conditional View Processing
 | 
						|
===========================
 | 
						|
 | 
						|
HTTP clients can send a number of headers to tell the server about copies of a
 | 
						|
resource that they have already seen. This is commonly used when retrieving a
 | 
						|
Web page (using an HTTP ``GET`` request) to avoid sending all the data for
 | 
						|
something the client has already retrieved. However, the same headers can be
 | 
						|
used for all HTTP methods (``POST``, ``PUT``, ``DELETE``, etc).
 | 
						|
 | 
						|
For each page (response) that Django sends back from a view, it might provide
 | 
						|
two HTTP headers: the ``ETag`` header and the ``Last-Modified`` header. These
 | 
						|
headers are optional on HTTP responses. They can be set by your view function,
 | 
						|
or you can rely on the :class:`~django.middleware.common.CommonMiddleware`
 | 
						|
middleware to set the ``ETag`` header.
 | 
						|
 | 
						|
When the client next requests the same resource, it might send along a header
 | 
						|
such as `If-modified-since`_, containing the date of the last modification
 | 
						|
time it was sent, or `If-none-match`_, containing the ``ETag`` it was sent.
 | 
						|
If the current version of the page matches the ``ETag`` sent by the client, or
 | 
						|
if the resource has not been modified, a 304 status code can be sent back,
 | 
						|
instead of a full response, telling the client that nothing has changed.
 | 
						|
 | 
						|
.. _If-none-match: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
 | 
						|
.. _If-modified-since: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
 | 
						|
 | 
						|
When you need more fine-grained control you may use per-view conditional
 | 
						|
processing functions.
 | 
						|
 | 
						|
.. conditional-decorators:
 | 
						|
 | 
						|
The ``condition`` decorator
 | 
						|
===========================
 | 
						|
 | 
						|
Sometimes (in fact, quite often) you can create functions to rapidly compute the ETag_
 | 
						|
value or the last-modified time for a resource, **without** needing to do all
 | 
						|
the computations needed to construct the full view. Django can then use these
 | 
						|
functions to provide an "early bailout" option for the view processing.
 | 
						|
Telling the client that the content has not been modified since the last
 | 
						|
request, perhaps.
 | 
						|
 | 
						|
.. _ETag: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
 | 
						|
 | 
						|
These two functions are passed as parameters the
 | 
						|
``django.views.decorators.http.condition`` decorator. This decorator uses
 | 
						|
the two functions (you only need to supply one, if you can't compute both
 | 
						|
quantities easily and quickly) to work out if the headers in the HTTP request
 | 
						|
match those on the resource. If they don't match, a new copy of the resource
 | 
						|
must be computed and your normal view is called.
 | 
						|
 | 
						|
The ``condition`` decorator's signature looks like this::
 | 
						|
 | 
						|
    condition(etag_func=None, last_modified_func=None)
 | 
						|
 | 
						|
The two functions, to compute the ETag and the last modified time, will be
 | 
						|
passed the incoming ``request`` object and the same parameters, in the same
 | 
						|
order, as the view function they are helping to wrap. The function passed
 | 
						|
``last_modified_func`` should return a standard datetime value specifying the
 | 
						|
last time the resource was modified, or ``None`` if the resource doesn't
 | 
						|
exist. The function passed to the ``etag`` decorator should return a string
 | 
						|
representing the `Etag`_ for the resource, or ``None`` if it doesn't exist.
 | 
						|
 | 
						|
Using this feature usefully is probably best explained with an example.
 | 
						|
Suppose you have this pair of models, representing a simple blog system::
 | 
						|
 | 
						|
    import datetime
 | 
						|
    from django.db import models
 | 
						|
 | 
						|
    class Blog(models.Model):
 | 
						|
        ...
 | 
						|
 | 
						|
    class Entry(models.Model):
 | 
						|
        blog = models.ForeignKey(Blog)
 | 
						|
        published = models.DateTimeField(default=datetime.datetime.now)
 | 
						|
        ...
 | 
						|
 | 
						|
If the front page, displaying the latest blog entries, only changes when you
 | 
						|
add a new blog entry, you can compute the last modified time very quickly. You
 | 
						|
need the latest ``published`` date for every entry associated with that blog.
 | 
						|
One way to do this would be::
 | 
						|
 | 
						|
    def latest_entry(request, blog_id):
 | 
						|
        return Entry.objects.filter(blog=blog_id).latest("published").published
 | 
						|
 | 
						|
You can then use this function to provide early detection of an unchanged page
 | 
						|
for your front page view::
 | 
						|
 | 
						|
    from django.views.decorators.http import condition
 | 
						|
 | 
						|
    @condition(last_modified_func=latest_entry)
 | 
						|
    def front_page(request, blog_id):
 | 
						|
        ...
 | 
						|
 | 
						|
Shortcuts for only computing one value
 | 
						|
======================================
 | 
						|
 | 
						|
As a general rule, if you can provide functions to compute *both* the ETag and
 | 
						|
the last modified time, you should do so. You don't know which headers any
 | 
						|
given HTTP client will send you, so be prepared to handle both. However,
 | 
						|
sometimes only one value is easy to compute and Django provides decorators
 | 
						|
that handle only ETag or only last-modified computations.
 | 
						|
 | 
						|
The ``django.views.decorators.http.etag`` and
 | 
						|
``django.views.decorators.http.last_modified`` decorators are passed the same
 | 
						|
type of functions as the ``condition`` decorator. Their signatures are::
 | 
						|
 | 
						|
    etag(etag_func)
 | 
						|
    last_modified(last_modified_func)
 | 
						|
 | 
						|
We could write the earlier example, which only uses a last-modified function,
 | 
						|
using one of these decorators::
 | 
						|
 | 
						|
    @last_modified(latest_entry)
 | 
						|
    def front_page(request, blog_id):
 | 
						|
        ...
 | 
						|
 | 
						|
...or::
 | 
						|
 | 
						|
    def front_page(request, blog_id):
 | 
						|
        ...
 | 
						|
    front_page = last_modified(latest_entry)(front_page)
 | 
						|
 | 
						|
Use ``condition`` when testing both conditions
 | 
						|
------------------------------------------------
 | 
						|
 | 
						|
It might look nicer to some people to try and chain the ``etag`` and
 | 
						|
``last_modified`` decorators if you want to test both preconditions. However,
 | 
						|
this would lead to incorrect behavior.
 | 
						|
 | 
						|
::
 | 
						|
 | 
						|
    # Bad code. Don't do this!
 | 
						|
    @etag(etag_func)
 | 
						|
    @last_modified(last_modified_func)
 | 
						|
    def my_view(request):
 | 
						|
        # ...
 | 
						|
 | 
						|
    # End of bad code.
 | 
						|
 | 
						|
The first decorator doesn't know anything about the second and might
 | 
						|
answer that the response is not modified even if the second decorators would
 | 
						|
determine otherwise. The ``condition`` decorator uses both callback functions
 | 
						|
simultaneously to work out the right action to take.
 | 
						|
 | 
						|
Using the decorators with other HTTP methods
 | 
						|
============================================
 | 
						|
 | 
						|
The ``condition`` decorator is useful for more than only ``GET`` and
 | 
						|
``HEAD`` requests (``HEAD`` requests are the same as ``GET`` in this
 | 
						|
situation). It can be used also to be used to provide checking for ``POST``,
 | 
						|
``PUT`` and ``DELETE`` requests. In these situations, the idea isn't to return
 | 
						|
a "not modified" response, but to tell the client that the resource they are
 | 
						|
trying to change has been altered in the meantime.
 | 
						|
 | 
						|
For example, consider the following exchange between the client and server:
 | 
						|
 | 
						|
1. Client requests ``/foo/``.
 | 
						|
2. Server responds with some content with an ETag of ``"abcd1234"``.
 | 
						|
3. Client sends an HTTP ``PUT`` request to ``/foo/`` to update the
 | 
						|
   resource. It also sends an ``If-Match: "abcd1234"`` header to specify
 | 
						|
   the version it is trying to update.
 | 
						|
4. Server checks to see if the resource has changed, by computing the ETag
 | 
						|
   the same way it does for a ``GET`` request (using the same function).
 | 
						|
   If the resource *has* changed, it will return a 412 status code code,
 | 
						|
   meaning "precondition failed".
 | 
						|
5. Client sends a ``GET`` request to ``/foo/``, after receiving a 412
 | 
						|
   response, to retrieve an updated version of the content before updating
 | 
						|
   it.
 | 
						|
 | 
						|
The important thing this example shows is that the same functions can be used
 | 
						|
to compute the ETag and last modification values in all situations. In fact,
 | 
						|
you **should** use the same functions, so that the same values are returned
 | 
						|
every time.
 | 
						|
 | 
						|
Comparison with middleware conditional processing
 | 
						|
=================================================
 | 
						|
 | 
						|
You may notice that Django already provides simple and straightforward
 | 
						|
conditional ``GET`` handling via the
 | 
						|
:class:`django.middleware.http.ConditionalGetMiddleware` and
 | 
						|
:class:`~django.middleware.common.CommonMiddleware`. Whilst certainly being
 | 
						|
easy to use and suitable for many situations, those pieces of middleware
 | 
						|
functionality have limitations for advanced usage:
 | 
						|
 | 
						|
* They are applied globally to all views in your project
 | 
						|
* They don't save you from generating the response itself, which may be
 | 
						|
  expensive
 | 
						|
* They are only appropriate for HTTP ``GET`` requests.
 | 
						|
 | 
						|
You should choose the most appropriate tool for your particular problem here.
 | 
						|
If you have a way to compute ETags and modification times quickly and if some
 | 
						|
view takes a while to generate the content, you should consider using the
 | 
						|
``condition`` decorator described in this document. If everything already runs
 | 
						|
fairly quickly, stick to using the middleware and the amount of network
 | 
						|
traffic sent back to the clients will still be reduced if the view hasn't
 | 
						|
changed.
 | 
						|
 |