diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 7b6c826805..2af2312e76 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -1,8 +1,8 @@ +import time + from django.conf import settings from django.utils.cache import patch_vary_headers -from email.Utils import formatdate -import datetime -import time +from django.utils.http import cookie_date TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_VALUE = 'worked' @@ -10,8 +10,9 @@ TEST_COOKIE_VALUE = 'worked' class SessionMiddleware(object): def process_request(self, request): - engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) - request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)) + engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) + session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None) + request.session = engine.SessionStore(session_key) def process_response(self, request, response): # If request.session was modified, or if response.session was set, save @@ -30,13 +31,8 @@ class SessionMiddleware(object): expires = None else: max_age = settings.SESSION_COOKIE_AGE - rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE) - - # Fixed length date must have '-' separation in the format - # DD-MMM-YYYY for compliance with Netscape cookie standard - expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \ - datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT") - + expires_time = time.time() + settings.SESSION_COOKIE_AGE + expires = cookie_date(expires_time) # Save the seesion data and refresh the client cookie. request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 411068b2f5..380e571b00 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -9,14 +9,14 @@ been reviewed for security issues. Don't use it for production use. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from types import ListType, StringType -from email.Utils import formatdate import mimetypes import os import re import sys -import time import urllib +from django.utils.http import http_date + __version__ = "0.1" __all__ = ['WSGIServer','WSGIRequestHandler','demo_app'] @@ -376,7 +376,7 @@ class ServerHandler(object): self._write('HTTP/%s %s\r\n' % (self.http_version,self.status)) if 'Date' not in self.headers: self._write( - 'Date: %s\r\n' % (formatdate()[:26] + "GMT") + 'Date: %s\r\n' % http_date() ) if self.server_software and 'Server' not in self.headers: self._write('Server: %s\r\n' % self.server_software) diff --git a/django/middleware/http.py b/django/middleware/http.py index 71cdf7aa5d..d080ebcf0f 100644 --- a/django/middleware/http.py +++ b/django/middleware/http.py @@ -1,4 +1,4 @@ -from email.Utils import formatdate +from django.utils.http import http_date class ConditionalGetMiddleware(object): """ @@ -11,7 +11,7 @@ class ConditionalGetMiddleware(object): Also sets the Date and Content-Length response-headers. """ def process_response(self, request, response): - response['Date'] = formatdate()[:26] + "GMT" + response['Date'] = http_date() if not response.has_header('Content-Length'): response['Content-Length'] = str(len(response.content)) @@ -23,7 +23,6 @@ class ConditionalGetMiddleware(object): response['Content-Length'] = '0' if response.has_header('Last-Modified'): - last_mod = response['Last-Modified'] if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None) if if_modified_since == response['Last-Modified']: response.status_code = 304 diff --git a/django/newforms/models.py b/django/newforms/models.py index 247a0eea6b..00a23573d8 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -6,7 +6,6 @@ and database field objects. from django.utils.translation import ugettext from django.utils.encoding import smart_unicode - from util import ValidationError from forms import BaseForm, SortedDictFromList from fields import Field, ChoiceField @@ -17,7 +16,8 @@ __all__ = ( 'ModelChoiceField', 'ModelMultipleChoiceField' ) -def save_instance(form, instance, fields=None, fail_message='saved', commit=True): +def save_instance(form, instance, fields=None, fail_message='saved', + commit=True): """ Saves bound Form ``form``'s cleaned_data into model instance ``instance``. @@ -27,15 +27,17 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True from django.db import models opts = instance.__class__._meta if form.errors: - raise ValueError("The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message)) + raise ValueError("The %s could not be %s because the data didn't" + " validate." % (opts.object_name, fail_message)) cleaned_data = form.cleaned_data for f in opts.fields: - if not f.editable or isinstance(f, models.AutoField) or not f.name in cleaned_data: + if not f.editable or isinstance(f, models.AutoField) \ + or not f.name in cleaned_data: continue if fields and f.name not in fields: continue - f.save_form_data(instance, cleaned_data[f.name]) - # Wrap up the saving of m2m data as a function + f.save_form_data(instance, cleaned_data[f.name]) + # Wrap up the saving of m2m data as a function. def save_m2m(): opts = instance.__class__._meta cleaned_data = form.cleaned_data @@ -45,28 +47,29 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True if f.name in cleaned_data: f.save_form_data(instance, cleaned_data[f.name]) if commit: - # If we are committing, save the instance and the m2m data immediately + # If we are committing, save the instance and the m2m data immediately. instance.save() save_m2m() else: - # We're not committing. Add a method to the form to allow deferred - # saving of m2m data + # We're not committing. Add a method to the form to allow deferred + # saving of m2m data. form.save_m2m = save_m2m return instance def make_model_save(model, fields, fail_message): - "Returns the save() method for a Form." + """Returns the save() method for a Form.""" def save(self, commit=True): return save_instance(self, model(), fields, fail_message, commit) return save - + def make_instance_save(instance, fields, fail_message): - "Returns the save() method for a Form." + """Returns the save() method for a Form.""" def save(self, commit=True): return save_instance(self, instance, fields, fail_message, commit) return save -def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda f: f.formfield()): +def form_for_model(model, form=BaseForm, fields=None, + formfield_callback=lambda f: f.formfield()): """ Returns a Form class for the given Django model class. @@ -87,10 +90,12 @@ def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda if formfield: field_list.append((f.name, formfield)) base_fields = SortedDictFromList(field_list) - return type(opts.object_name + 'Form', (form,), - {'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')}) + return type(opts.object_name + 'Form', (form,), + {'base_fields': base_fields, '_model': model, + 'save': make_model_save(model, fields, 'created')}) -def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): +def form_for_instance(instance, form=BaseForm, fields=None, + formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): """ Returns a Form class for the given Django model instance. @@ -115,16 +120,22 @@ def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=l field_list.append((f.name, formfield)) base_fields = SortedDictFromList(field_list) return type(opts.object_name + 'InstanceForm', (form,), - {'base_fields': base_fields, '_model': model, 'save': make_instance_save(instance, fields, 'changed')}) + {'base_fields': base_fields, '_model': model, + 'save': make_instance_save(instance, fields, 'changed')}) def form_for_fields(field_list): - "Returns a Form class for the given list of Django database field instances." - fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable]) + """ + Returns a Form class for the given list of Django database field instances. + """ + fields = SortedDictFromList([(f.name, f.formfield()) + for f in field_list if f.editable]) return type('FormForFields', (BaseForm,), {'base_fields': fields}) class QuerySetIterator(object): def __init__(self, queryset, empty_label, cache_choices): - self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices + self.queryset = queryset + self.empty_label = empty_label + self.cache_choices = cache_choices def __iter__(self): if self.empty_label is not None: @@ -136,11 +147,13 @@ class QuerySetIterator(object): self.queryset._result_cache = None class ModelChoiceField(ChoiceField): - "A ChoiceField whose choices are a model QuerySet." + """A ChoiceField whose choices are a model QuerySet.""" # This class is a subclass of ChoiceField for purity, but it doesn't # actually use any of ChoiceField's implementation. + def __init__(self, queryset, empty_label=u"---------", cache_choices=False, - required=True, widget=Select, label=None, initial=None, help_text=None): + required=True, widget=Select, label=None, initial=None, + help_text=None): self.queryset = queryset self.empty_label = empty_label self.cache_choices = cache_choices @@ -160,7 +173,8 @@ class ModelChoiceField(ChoiceField): # *each* time _get_choices() is called (and, thus, each time # self.choices is accessed) so that we can ensure the QuerySet has not # been consumed. - return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices) + return QuerySetIterator(self.queryset, self.empty_label, + self.cache_choices) def _set_choices(self, value): # This method is copied from ChoiceField._set_choices(). It's necessary @@ -177,16 +191,20 @@ class ModelChoiceField(ChoiceField): try: value = self.queryset.model._default_manager.get(pk=value) except self.queryset.model.DoesNotExist: - raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.')) + raise ValidationError(ugettext(u'Select a valid choice. That' + u' choice is not one of the' + u' available choices.')) return value class ModelMultipleChoiceField(ModelChoiceField): - "A MultipleChoiceField whose choices are a model QuerySet." + """A MultipleChoiceField whose choices are a model QuerySet.""" hidden_widget = MultipleHiddenInput + def __init__(self, queryset, cache_choices=False, required=True, - widget=SelectMultiple, label=None, initial=None, help_text=None): - super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices, - required, widget, label, initial, help_text) + widget=SelectMultiple, label=None, initial=None, + help_text=None): + super(ModelMultipleChoiceField, self).__init__(queryset, None, + cache_choices, required, widget, label, initial, help_text) def clean(self, value): if self.required and not value: @@ -200,7 +218,9 @@ class ModelMultipleChoiceField(ModelChoiceField): try: obj = self.queryset.model._default_manager.get(pk=val) except self.queryset.model.DoesNotExist: - raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val) + raise ValidationError(ugettext(u'Select a valid choice. %s is' + u' not one of the available' + u' choices.') % val) else: final_values.append(obj) return final_values diff --git a/django/utils/cache.py b/django/utils/cache.py index f192e1115c..ae4de6dd87 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -13,17 +13,18 @@ into account when building its cache key. Requests with the same path but different header content for headers named in "Vary" need to get different cache keys to prevent delivery of wrong content. -A example: i18n middleware would need to distinguish caches by the +An example: i18n middleware would need to distinguish caches by the "Accept-language" header. """ import md5 import re import time -from email.Utils import formatdate + from django.conf import settings from django.core.cache import cache from django.utils.encoding import smart_str, iri_to_uri +from django.utils.http import http_date cc_delim_re = re.compile(r'\s*,\s*') @@ -40,7 +41,7 @@ def patch_cache_control(response, **kwargs): str() to it. """ def dictitem(s): - t = s.split('=',1) + t = s.split('=', 1) if len(t) > 1: return (t[0].lower(), t[1]) else: @@ -64,7 +65,7 @@ def patch_cache_control(response, **kwargs): if 'max-age' in cc and 'max_age' in kwargs: kwargs['max_age'] = min(cc['max-age'], kwargs['max_age']) - for (k,v) in kwargs.items(): + for (k, v) in kwargs.items(): cc[k.replace('_', '-')] = v cc = ', '.join([dictvalue(el) for el in cc.items()]) response['Cache-Control'] = cc @@ -88,15 +89,14 @@ def patch_response_headers(response, cache_timeout=None): if not response.has_header('ETag'): response['ETag'] = md5.new(response.content).hexdigest() if not response.has_header('Last-Modified'): - response['Last-Modified'] = formatdate()[:26] + "GMT" + response['Last-Modified'] = http_date() if not response.has_header('Expires'): - response['Expires'] = formatdate(time.time() + cache_timeout)[:26] + "GMT" + response['Expires'] = http_date(time.time() + cache_timeout) patch_cache_control(response, max_age=cache_timeout) def add_never_cache_headers(response): """ - Add headers to a response to indicate that - a page should never be cached. + Adds headers to a response to indicate that a page should never be cached. """ patch_response_headers(response, cache_timeout=-1) @@ -119,13 +119,14 @@ def patch_vary_headers(response, newheaders): response['Vary'] = ', '.join(vary) def _generate_cache_key(request, headerlist, key_prefix): - "Returns a cache key from the headers given in the header list." + """Returns a cache key from the headers given in the header list.""" ctx = md5.new() for header in headerlist: value = request.META.get(header, None) if value is not None: ctx.update(value) - return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, iri_to_uri(request.path), ctx.hexdigest()) + return 'views.decorators.cache.cache_page.%s.%s.%s' % ( + key_prefix, iri_to_uri(request.path), ctx.hexdigest()) def get_cache_key(request, key_prefix=None): """ @@ -139,7 +140,8 @@ def get_cache_key(request, key_prefix=None): """ if key_prefix is None: key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX - cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) + cache_key = 'views.decorators.cache.cache_header.%s.%s' % ( + key_prefix, iri_to_uri(request.path)) headerlist = cache.get(cache_key, None) if headerlist is not None: return _generate_cache_key(request, headerlist, key_prefix) @@ -163,9 +165,11 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None): key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX if cache_timeout is None: cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS - cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) + cache_key = 'views.decorators.cache.cache_header.%s.%s' % ( + key_prefix, iri_to_uri(request.path)) if response.has_header('Vary'): - headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])] + headerlist = ['HTTP_'+header.upper().replace('-', '_') + for header in vary_delim_re.split(response['Vary'])] cache.set(cache_key, headerlist, cache_timeout) return _generate_cache_key(request, headerlist, key_prefix) else: diff --git a/django/utils/http.py b/django/utils/http.py index 4912c9c46a..5ec6e92d28 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -1,4 +1,6 @@ import urllib +from email.Utils import formatdate + from django.utils.encoding import smart_str, force_unicode from django.utils.functional import allow_lazy @@ -37,3 +39,29 @@ def urlencode(query, doseq=0): for k, v in query], doseq) +def cookie_date(epoch_seconds=None): + """ + Formats the time to ensure compatibility with Netscape's cookie standard. + + Accepts a floating point number expressed in seconds since the epoch, in + UTC - such as that outputted by time.time(). If set to None, defaults to + the current time. + + Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'. + """ + rfcdate = formatdate(epoch_seconds) + return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25]) + +def http_date(epoch_seconds=None): + """ + Formats the time to match the RFC1123 date format as specified by HTTP + RFC2616 section 3.3.1. + + Accepts a floating point number expressed in seconds since the epoch, in + UTC - such as that outputted by time.time(). If set to None, defaults to + the current time. + + Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'. + """ + rfcdate = formatdate(epoch_seconds) + return '%s GMT' % rfcdate[:25] diff --git a/django/views/static.py b/django/views/static.py index dce45d914d..b556c60ca6 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -7,13 +7,14 @@ import mimetypes import os import posixpath import re -import rfc822 import stat import urllib +from email.Utils import parsedate_tz, mktime_tz from django.template import loader from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified from django.template import Template, Context, TemplateDoesNotExist +from django.utils.http import http_date def serve(request, path, document_root=None, show_indexes=False): """ @@ -60,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False): mimetype = mimetypes.guess_type(fullpath)[0] contents = open(fullpath, 'rb').read() response = HttpResponse(contents, mimetype=mimetype) - response["Last-Modified"] = rfc822.formatdate(statobj[stat.ST_MTIME]) + response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) return response DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ @@ -119,8 +120,7 @@ def was_modified_since(header=None, mtime=0, size=0): raise ValueError matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, re.IGNORECASE) - header_mtime = rfc822.mktime_tz(rfc822.parsedate_tz( - matches.group(1))) + header_mtime = mktime_tz(parsedate_tz(matches.group(1))) header_len = matches.group(3) if header_len and int(header_len) != size: raise ValueError diff --git a/docs/cache.txt b/docs/cache.txt index d598915d1a..4f177b8c07 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -291,13 +291,15 @@ minutes. Template fragment caching ========================= +**New in development version**. + If you're after even more control, you can also cache template fragments using -the ``cache`` template tag. To give your template access to this tag, put ``{% -load cache %}`` near the top of your template. +the ``cache`` template tag. To give your template access to this tag, put +``{% load cache %}`` near the top of your template. The ``{% cache %}`` template tag caches the contents of the block for a given -amount of time. It takes at least two arguments: the cache timeout, in -seconds, and the name to give the cache fragment. For example:: +amount of time. It takes at least two arguments: the cache timeout, in seconds, +and the name to give the cache fragment. For example:: {% load cache %} {% cache 500 sidebar %} diff --git a/docs/email.txt b/docs/email.txt index effc5e24cf..55b91b1935 100644 --- a/docs/email.txt +++ b/docs/email.txt @@ -275,7 +275,7 @@ The class has the following methods: There are two ways to call ``attach()``: * You can pass it a single argument that is an - ``email.MIMBase.MIMEBase`` instance. This will be inserted directly + ``email.MIMEBase.MIMEBase`` instance. This will be inserted directly into the resulting message. * Alternatively, you can pass ``attach()`` three arguments: diff --git a/docs/form_preview.txt b/docs/form_preview.txt index 4be7b07a74..e6f9b05f25 100644 --- a/docs/form_preview.txt +++ b/docs/form_preview.txt @@ -45,7 +45,7 @@ How to use ``FormPreview`` 2. Create a ``FormPreview`` subclass that overrides the ``done()`` method:: - from django.contrib.formtools import FormPreview + from django.contrib.formtools.preview import FormPreview from myapp.models import SomeModel class SomeModelFormPreview(FormPreview): diff --git a/docs/modpython.txt b/docs/modpython.txt index 5b20046168..c739997ce4 100644 --- a/docs/modpython.txt +++ b/docs/modpython.txt @@ -150,7 +150,7 @@ mess things up. Use the ``PythonInterpreter`` directive to give different SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings - PythonInterpreter mysite_other + PythonInterpreter othersite diff --git a/docs/testing.txt b/docs/testing.txt index 04c999cda8..7705380eff 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -721,7 +721,6 @@ This means, instead of instantiating a ``Client`` in each test:: ...you can just refer to ``self.client``, like so:: from django.test import TestCase - from django.test.client import Client class SimpleTest(TestCase): def test_details(self): diff --git a/tests/regressiontests/text/tests.py b/tests/regressiontests/text/tests.py index 962a30ef19..7cfe44517a 100644 --- a/tests/regressiontests/text/tests.py +++ b/tests/regressiontests/text/tests.py @@ -27,6 +27,14 @@ u'Paris+%26+Orl%C3%A9ans' >>> urlquote_plus(u'Paris & Orl\xe9ans', safe="&") u'Paris+&+Orl%C3%A9ans' +### cookie_date, http_date ############################################### +>>> from django.utils.http import cookie_date, http_date +>>> t = 1167616461.0 +>>> cookie_date(t) +'Mon, 01-Jan-2007 01:54:21 GMT' +>>> http_date(t) +'Mon, 01 Jan 2007 01:54:21 GMT' + ### iri_to_uri ########################################################### >>> from django.utils.encoding import iri_to_uri >>> iri_to_uri(u'red%09ros\xe9#red')