Ticket #14722: 14722-2.diff

File 14722-2.diff, 8.6 KB (added by Claude Paroz, 13 years ago)

Refactoring, tests and docs

  • django/http/__init__.py

    diff --git a/django/http/__init__.py b/django/http/__init__.py
    index 60a92d5..c48be9f 100644
    a b  
    11from __future__ import absolute_import
    22
    33import datetime
     4import hashlib
    45import os
    56import re
    67import sys
    class HttpResponse(object):  
    575576                    self._charset)
    576577        self.content = content
    577578        self.cookies = SimpleCookie()
     579        self.not_modified_checked = False
    578580        if status:
    579581            self.status_code = status
    580582
    class HttpResponse(object):  
    682684        self.set_cookie(key, max_age=0, path=path, domain=domain,
    683685                        expires='Thu, 01-Jan-1970 00:00:00 GMT')
    684686
     687    def set_etag(self):
     688        """
     689        Compute the ETag header (if not already set)
     690        """
     691        if settings.USE_ETAGS and not self.has_header('ETag'):
     692            self['ETag'] = '"%s"' % hashlib.md5(self.content).hexdigest()
     693
    685694    def _get_content(self):
    686695        if self.has_header('Content-Encoding'):
    687696            return ''.join([str(e) for e in self._container])
  • django/middleware/common.py

    diff --git a/django/middleware/common.py b/django/middleware/common.py
    index d894ec8..abe97c8 100644
    a b  
    1 import hashlib
    21import re
    32
    43from django.conf import settings
    class CommonMiddleware(object):  
    109108                return response
    110109
    111110        # Use ETags, if requested.
    112         if settings.USE_ETAGS:
    113             if response.has_header('ETag'):
    114                 etag = response['ETag']
    115             else:
    116                 etag = '"%s"' % hashlib.md5(response.content).hexdigest()
    117             if response.status_code >= 200 and response.status_code < 300 and request.META.get('HTTP_IF_NONE_MATCH') == etag:
     111        if settings.USE_ETAGS and response.not_modified_checked is not True:
     112            response.set_etag()
     113            if response.status_code >= 200 and response.status_code < 300 and \
     114                    request.META.get('HTTP_IF_NONE_MATCH') == response['ETag']:
    118115                cookies = response.cookies
    119116                response = http.HttpResponseNotModified()
    120117                response.cookies = cookies
    121             else:
    122                 response['ETag'] = etag
    123118
    124119        return response
    125120
  • django/middleware/http.py

    diff --git a/django/middleware/http.py b/django/middleware/http.py
    index 86e46ce..97c805b 100644
    a b class ConditionalGetMiddleware(object):  
    1313        if not response.has_header('Content-Length'):
    1414            response['Content-Length'] = str(len(response.content))
    1515
     16        if response.status_code == 304 or response.not_modified_checked:
     17            # Do not check Not Modified again
     18            return response
     19
    1620        if response.has_header('ETag'):
    1721            if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
    1822            if if_none_match == response['ETag']:
  • django/template/response.py

    diff --git a/django/template/response.py b/django/template/response.py
    index bb0b9cb..6ed7509 100644
    a b class SimpleTemplateResponse(HttpResponse):  
    132132
    133133    content = property(_get_content, _set_content)
    134134
     135    def set_etag(self):
     136        if not self._is_rendered:
     137            self.add_post_render_callback(super(SimpleTemplateResponse, self).set_etag.__func__)
     138        else:
     139            super(SimpleTemplateResponse, self).set_etag()
     140
    135141
    136142class TemplateResponse(SimpleTemplateResponse):
    137143    rendering_attrs = SimpleTemplateResponse.rendering_attrs + \
  • django/utils/cache.py

    diff --git a/django/utils/cache.py b/django/utils/cache.py
    index 7ef3680..6603bfa 100644
    a b def get_max_age(response):  
    9393        except (ValueError, TypeError):
    9494            pass
    9595
    96 def _set_response_etag(response):
    97     response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest()
    98     return response
    99 
    10096def patch_response_headers(response, cache_timeout=None):
    10197    """
    10298    Adds some useful headers to the given HttpResponse object:
    def patch_response_headers(response, cache_timeout=None):  
    111107        cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
    112108    if cache_timeout < 0:
    113109        cache_timeout = 0 # Can't have max-age negative
    114     if settings.USE_ETAGS and not response.has_header('ETag'):
    115         if hasattr(response, 'render') and callable(response.render):
    116             response.add_post_render_callback(_set_response_etag)
    117         else:
    118             response = _set_response_etag(response)
     110    response.set_etag()
    119111    if not response.has_header('Last-Modified'):
    120112        response['Last-Modified'] = http_date()
    121113    if not response.has_header('Expires'):
  • django/views/decorators/http.py

    diff --git a/django/views/decorators/http.py b/django/views/decorators/http.py
    index d5c4bff..37a9943 100644
    a b def condition(etag_func=None, last_modified_func=None):  
    138138                        }
    139139                    )
    140140                    response = HttpResponse(status=412)
    141                 elif (not if_none_match and request.method == "GET" and
     141                elif (not (if_none_match and res_etag) and request.method == "GET" and
    142142                        res_last_modified and if_modified_since and
    143143                        res_last_modified <= if_modified_since):
    144144                    response = HttpResponseNotModified()
    def condition(etag_func=None, last_modified_func=None):  
    151151                response['Last-Modified'] = http_date(res_last_modified)
    152152            if res_etag and not response.has_header('ETag'):
    153153                response['ETag'] = quote_etag(res_etag)
     154            # Flag to signal middlewares to not check again
     155            response.not_modified_checked = True
    154156
    155157            return response
    156158
  • docs/ref/request-response.txt

    diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
    index c0ae73e..338f4a5 100644
    a b Methods  
    707707    values you used in ``set_cookie()`` -- otherwise the cookie may not be
    708708    deleted.
    709709
     710.. method:: HttpResponse.set_etag()
     711
     712    .. versionadded:: 1.5
     713
     714    Compute and set the ETag header from the response content.
     715
    710716.. method:: HttpResponse.write(content)
    711717
    712718    This method makes an :class:`HttpResponse` instance a file-like object.
  • docs/topics/conditional-view-processing.txt

    diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt
    index 1979e89..5e570d1 100644
    a b for your front page view::  
    9191    def front_page(request, blog_id):
    9292        ...
    9393
     94.. admonition:: Relation with the USE_ETAGS setting
     95
     96    Using the ``condition`` decorator on a view is not dependant on the
     97    :setting:`USE_ETAGS` setting. However, if a response has been generated by
     98    a view decorated with the ``condition`` decorator, the ETag and Last-Modified
     99    headers will not be checked a second time in
     100    :class:`~django.middleware.common.CommonMiddleware` or
     101    :class:`~django.middleware.http.ConditionalGetMiddleware`.
     102
     103
    94104Shortcuts for only computing one value
    95105======================================
    96106
  • tests/regressiontests/conditional_processing/models.py

    diff --git a/tests/regressiontests/conditional_processing/models.py b/tests/regressiontests/conditional_processing/models.py
    index f7f48bc..4739ba6 100644
    a b  
    22from datetime import datetime
    33
    44from django.test import TestCase
     5from django.test.utils import override_settings
    56from django.utils import unittest
    67from django.utils.http import parse_etags, quote_etag, parse_http_date
    78
    class ConditionalGet(TestCase):  
    8485        self.client.defaults['HTTP_IF_NONE_MATCH'] = '"%s"' % EXPIRED_ETAG
    8586        response = self.client.get('/condition/')
    8687        self.assertFullResponse(response)
     88        # If-None-Match header should not disturb condition on last_modify only
     89        # See #14722
     90        response = self.client.get('/condition/last_modified/')
     91        self.assertNotModified(response)
    8792
    8893    def testSingleCondition1(self):
    8994        self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
    class ConditionalGet(TestCase):  
    128133        response = self.client.get('/condition/etag/')
    129134        self.assertFullResponse(response, check_last_modified=False)
    130135
     136ConditionalGetWithUseEtags = override_settings(
     137    USE_ETAGS = True,
     138)(ConditionalGet)
    131139
    132140class ETagProcessing(unittest.TestCase):
    133141    def testParsing(self):
Back to Top