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