diff --git a/AUTHORS b/AUTHORS
index 1e56bd3412..e1966c5af5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -232,6 +232,7 @@ answer newbie questions, and generally made Django that much better:
phil@produxion.net
phil.h.smith@gmail.com
Gustavo Picon
+ pigletto
Luke Plant
plisk
Daniel Poelzleithner
@@ -262,6 +263,7 @@ answer newbie questions, and generally made Django that much better:
SmileyChris
smurf@smurf.noris.de
sopel
+ Leo Soto
Wiliam Alves de Souza
Georgi Stanojevski
Vasiliy Stavenko
@@ -274,6 +276,7 @@ answer newbie questions, and generally made Django that much better:
Swaroop C H
Aaron Swartz
Ville Säävuori
+ Tyler Tarabula
Tyson Tate
Frank Tegtmeyer
thebjorn
diff --git a/django/contrib/admin/media/css/widgets.css b/django/contrib/admin/media/css/widgets.css
index bf526bfd66..67d9662f3e 100644
--- a/django/contrib/admin/media/css/widgets.css
+++ b/django/contrib/admin/media/css/widgets.css
@@ -43,7 +43,7 @@ p.file-upload { line-height:20px; margin:0; padding:0; color:#666; font-size:11p
/* CALENDARS & CLOCKS */
.calendarbox, .clockbox { margin:5px auto; font-size:11px; width:16em; text-align:center; background:white; position:relative; }
-.clockbox { width:9em; }
+.clockbox { width:auto; }
.calendar { margin:0; padding: 0; }
.calendar table { margin:0; padding:0; border-collapse:collapse; background:white; width:99%; }
.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
diff --git a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
index b1504fc819..4682a6841a 100644
--- a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
+++ b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
@@ -195,6 +195,19 @@ var DateTimeShortcuts = {
openCalendar: function(num) {
var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num)
var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num)
+ var inp = DateTimeShortcuts.calendarInputs[num];
+
+ // Determine if the current value in the input has a valid date.
+ // If so, draw the calendar with that date's year and month.
+ if (inp.value) {
+ var date_parts = inp.value.split('-');
+ var year = date_parts[0];
+ var month = parseFloat(date_parts[1]);
+ if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
+ DateTimeShortcuts.calendars[num].drawDate(month, year);
+ }
+ }
+
// Recalculate the clockbox position
// is it left-to-right or right-to-left layout ?
diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
index 2fb4a6f510..de0dc21c5d 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -2,7 +2,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect
from urllib import quote
-def user_passes_test(test_func, login_url=None):
+def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
@@ -15,20 +15,25 @@ def user_passes_test(test_func, login_url=None):
def _checklogin(request, *args, **kwargs):
if test_func(request.user):
return view_func(request, *args, **kwargs)
- return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path())))
+ return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, quote(request.get_full_path())))
_checklogin.__doc__ = view_func.__doc__
_checklogin.__dict__ = view_func.__dict__
return _checklogin
return _dec
-login_required = user_passes_test(lambda u: u.is_authenticated())
-login_required.__doc__ = (
+def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
+ actual_decorator = user_passes_test(
+ lambda u: u.is_authenticated(),
+ redirect_field_name=redirect_field_name
)
+ if function:
+ return actual_decorator(function)
+ return actual_decorator
def permission_required(perm, login_url=None):
"""
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index f1129379d6..d3d8b4ccb7 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -9,10 +9,10 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.utils.translation import ugettext as _
-def login(request, template_name='registration/login.html'):
+def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
"Displays the login form and handles the login action."
manipulator = AuthenticationForm(request)
- redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, '')
+ redirect_to = request.REQUEST.get(redirect_field_name, '')
if request.POST:
errors = manipulator.get_validation_errors(request.POST)
if not errors:
@@ -35,7 +35,7 @@ def login(request, template_name='registration/login.html'):
return render_to_response(template_name, {
'form': oldforms.FormWrapper(manipulator, request.POST, errors),
- REDIRECT_FIELD_NAME: redirect_to,
+ redirect_field_name: redirect_to,
'site_name': current_site.name,
}, context_instance=RequestContext(request))
@@ -56,12 +56,12 @@ def logout_then_login(request, login_url=None):
login_url = settings.LOGIN_URL
return logout(request, login_url)
-def redirect_to_login(next, login_url=None):
+def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"Redirects the user to the login page, passing the given 'next' page"
if not login_url:
from django.conf import settings
login_url = settings.LOGIN_URL
- return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, next))
+ return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, next))
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html'):
diff --git a/django/contrib/localflavor/ca/__init__.py b/django/contrib/localflavor/ca/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/contrib/localflavor/ca/ca_provinces.py b/django/contrib/localflavor/ca/ca_provinces.py
new file mode 100644
index 0000000000..072159ad57
--- /dev/null
+++ b/django/contrib/localflavor/ca/ca_provinces.py
@@ -0,0 +1,57 @@
+"""
+An alphabetical list of provinces and territories for use as `choices`
+in a formfield., and a mapping of province misspellings/abbreviations to
+normalized abbreviations
+
+Source: http://www.canada.gc.ca/othergov/prov_e.html
+
+This exists in this standalone file so that it's only imported into memory
+when explicitly needed.
+"""
+
+PROVINCE_CHOICES = (
+ ('AB', 'Alberta'),
+ ('BC', 'British Columbia'),
+ ('MB', 'Manitoba'),
+ ('NB', 'New Brunswick'),
+ ('NF', 'Newfoundland and Labrador'),
+ ('NT', 'Northwest Territories'),
+ ('NS', 'Nova Scotia'),
+ ('NU', 'Nunavut'),
+ ('ON', 'Ontario'),
+ ('PE', 'Prince Edward Island'),
+ ('QC', 'Quebec'),
+ ('SK', 'Saskatchewan'),
+ ('YK', 'Yukon')
+)
+
+PROVINCES_NORMALIZED = {
+ 'ab': 'AB',
+ 'alberta': 'AB',
+ 'bc': 'BC',
+ 'b.c.': 'BC',
+ 'british columbia': 'BC',
+ 'mb': 'MB',
+ 'manitoba': 'MB',
+ 'nf': 'NF',
+ 'newfoundland': 'NF',
+ 'newfoundland and labrador': 'NF',
+ 'nt': 'NT',
+ 'northwest territories': 'NT',
+ 'ns': 'NS',
+ 'nova scotia': 'NS',
+ 'nu': 'NU',
+ 'nunavut': 'NU',
+ 'on': 'ON',
+ 'ontario': 'ON',
+ 'pe': 'PE',
+ 'pei': 'PE',
+ 'p.e.i.': 'PE',
+ 'prince edward island': 'PE',
+ 'qc': 'QC',
+ 'quebec': 'QC',
+ 'sk': 'SK',
+ 'saskatchewan': 'SK',
+ 'yk': 'YK',
+ 'yukon': 'YK',
+}
\ No newline at end of file
diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py
new file mode 100644
index 0000000000..98f65a5c6c
--- /dev/null
+++ b/django/contrib/localflavor/ca/forms.py
@@ -0,0 +1,112 @@
+"""
+Canada-specific Form helpers
+"""
+
+from django.newforms import ValidationError
+from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
+from django.newforms.util import smart_unicode
+from django.utils.translation import gettext, ugettext
+import re
+
+phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
+sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$")
+
+class CAPostalCodeField(RegexField):
+ """Canadian postal code field."""
+ def __init__(self, *args, **kwargs):
+ super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$',
+ max_length=None, min_length=None,
+ error_message=gettext(u'Enter a postal code in the format XXX XXX.'),
+ *args, **kwargs)
+
+class CAPhoneNumberField(Field):
+ """Canadian phone number field."""
+ def clean(self, value):
+ """Validate a phone number.
+ """
+ super(CAPhoneNumberField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
+ m = phone_digits_re.search(value)
+ if m:
+ return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
+ raise ValidationError(u'Phone numbers must be in XXX-XXX-XXXX format.')
+
+class CAProvinceField(Field):
+ """
+ A form field that validates its input is a Canadian province name or abbreviation.
+ It normalizes the input to the standard two-leter postal service
+ abbreviation for the given province.
+ """
+ def clean(self, value):
+ from ca_provinces import PROVINCES_NORMALIZED
+ super(CAProvinceField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ try:
+ value = value.strip().lower()
+ except AttributeError:
+ pass
+ else:
+ try:
+ return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii')
+ except KeyError:
+ pass
+ raise ValidationError(u'Enter a Canadian province or territory.')
+
+class CAProvinceSelect(Select):
+ """
+ A Select widget that uses a list of Canadian provinces and
+ territories as its choices.
+ """
+ def __init__(self, attrs=None):
+ from ca_provinces import PROVINCE_CHOICES # relative import
+ super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
+
+class CASocialInsuranceNumberField(Field):
+ """
+ A Canadian Social Insurance Number (SIN).
+
+ Checks the following rules to determine whether the number is valid:
+
+ * Conforms to the XXX-XXX-XXXX format.
+ * Passes the check digit process "Luhn Algorithm"
+ See: http://en.wikipedia.org/wiki/Social_Insurance_Number
+ """
+ def clean(self, value):
+ super(CASocialInsuranceNumberField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ msg = ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.')
+ match = re.match(sin_re, value)
+ if not match:
+ raise ValidationError(msg)
+
+ number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3))
+ check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3))
+ if not self.luhn_checksum_is_valid(check_number):
+ raise ValidationError(msg)
+ return number
+
+ def luhn_checksum_is_valid(self, number):
+ """
+ Checks to make sure that the SIN passes a luhn mod-10 checksum
+ See: http://en.wikipedia.org/wiki/Luhn_algorithm
+ """
+
+ sum = 0
+ num_digits = len(number)
+ oddeven = num_digits & 1
+
+ for count in range(0, num_digits):
+ digit = int(number[count])
+
+ if not (( count & 1 ) ^ oddeven ):
+ digit = digit * 2
+ if digit > 9:
+ digit = digit - 9
+
+ sum = sum + digit
+
+ return ( (sum % 10) == 0 )
\ No newline at end of file
diff --git a/django/contrib/localflavor/generic/__init__.py b/django/contrib/localflavor/generic/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/contrib/localflavor/generic/forms.py b/django/contrib/localflavor/generic/forms.py
new file mode 100644
index 0000000000..a6d813723c
--- /dev/null
+++ b/django/contrib/localflavor/generic/forms.py
@@ -0,0 +1,38 @@
+from django import newforms as forms
+
+DEFAULT_DATE_INPUT_FORMATS = (
+ '%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y', # '2006-10-25', '25/10/2006', '25/10/06'
+ '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
+ '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
+ '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
+ '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
+)
+
+DEFAULT_DATETIME_INPUT_FORMATS = (
+ '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
+ '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
+ '%Y-%m-%d', # '2006-10-25'
+ '%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
+ '%d/%m/%Y %H:%M', # '25/10/2006 14:30'
+ '%d/%m/%Y', # '25/10/2006'
+ '%d/%m/%y %H:%M:%S', # '25/10/06 14:30:59'
+ '%d/%m/%y %H:%M', # '25/10/06 14:30'
+ '%d/%m/%y', # '25/10/06'
+)
+
+class DateField(forms.DateField):
+ """
+ A date input field which uses non-US date input formats by default.
+ """
+ def __init__(self, input_formats=None, *args, **kwargs):
+ input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS
+ super(DateField, self).__init__(input_formats=input_formats, *args, **kwargs)
+
+class DateTimeField(forms.DateTimeField):
+ """
+ A date and time input field which uses non-US date and time input formats
+ by default.
+ """
+ def __init__(self, input_formats=None, *args, **kwargs):
+ input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS
+ super(DateTimeField, self).__init__(input_formats=input_formats, *args, **kwargs)
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 768fc14b00..21737f682f 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -142,7 +142,7 @@ def fix_location_header(request, response):
Code constructing response objects is free to insert relative paths and
this function converts them to absolute paths.
"""
- if 'Location' in response.headers and http.get_host(request):
+ if 'location' in response.headers and http.get_host(request):
response['Location'] = request.build_absolute_uri(response['Location'])
return response
diff --git a/django/core/management/commands/adminindex.py b/django/core/management/commands/adminindex.py
index e3dd493fd3..4f389136ca 100644
--- a/django/core/management/commands/adminindex.py
+++ b/django/core/management/commands/adminindex.py
@@ -1,4 +1,5 @@
from django.core.management.base import AppCommand
+from django.utils.encoding import force_unicode
from django.utils.text import capfirst
MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%}
@@ -24,7 +25,7 @@ class Command(AppCommand):
output.append(MODULE_TEMPLATE % {
'app': app_label,
'mod': model._meta.module_name,
- 'name': capfirst(model._meta.verbose_name_plural),
+ 'name': force_unicode(capfirst(model._meta.verbose_name_plural)),
'addperm': model._meta.get_add_permission(),
'changeperm': model._meta.get_change_permission(),
})
diff --git a/django/core/management/commands/testserver.py b/django/core/management/commands/testserver.py
index 50a10a12bc..9b169d3d9b 100644
--- a/django/core/management/commands/testserver.py
+++ b/django/core/management/commands/testserver.py
@@ -7,6 +7,9 @@ class Command(BaseCommand):
make_option('--verbosity', action='store', dest='verbosity', default='1',
type='choice', choices=['0', '1', '2'],
help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
+ make_option('--addrport', action='store', dest='addrport',
+ type='string', default='',
+ help='port number or ipaddr:port to run the server on'),
)
help = 'Runs a development server with data from the given fixture(s).'
args = '[fixture ...]'
@@ -19,6 +22,7 @@ class Command(BaseCommand):
from django.test.utils import create_test_db
verbosity = int(options.get('verbosity', 1))
+ addrport = options.get('addrport')
# Create a test database.
db_name = create_test_db(verbosity=verbosity)
@@ -30,4 +34,4 @@ class Command(BaseCommand):
# a strange error -- it causes this handle() method to be called
# multiple times.
shutdown_message = '\nServer stopped.\nNote that the test database, %r, has not been deleted. You can explore it on your own.' % db_name
- call_command('runserver', shutdown_message=shutdown_message, use_reloader=False)
+ call_command('runserver', addrport=addrport, shutdown_message=shutdown_message, use_reloader=False)
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index aa4ef47c2f..6528a34f29 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -1,5 +1,6 @@
import sys
from django.core.management.color import color_style
+from django.utils.itercompat import is_iterable
class ModelErrorCollection:
def __init__(self, outfile=sys.stdout):
@@ -51,7 +52,8 @@ def get_validation_errors(outfile, app=None):
if f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple):
e.add(opts, '"%s": prepopulate_from should be a list or tuple.' % f.name)
if f.choices:
- if not hasattr(f.choices, '__iter__'):
+ if isinstance(f.choices, basestring) or \
+ not is_iterable(f.choices):
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
else:
for c in f.choices:
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 76e5743539..dbb1d032eb 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -438,21 +438,6 @@ class FormatStylePlaceholderCursor(Database.Cursor):
"""
charset = 'utf-8'
- def _rewrite_args(self, query, params=None):
- if params is None:
- params = []
- else:
- params = self._format_params(params)
- args = [(':arg%d' % i) for i in range(len(params))]
- query = smart_str(query, self.charset) % tuple(args)
- # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
- # it does want a trailing ';' but not a trailing '/'. However, these
- # characters must be included in the original query in case the query
- # is being passed to SQL*Plus.
- if query.endswith(';') or query.endswith('/'):
- query = query[:-1]
- return query, params
-
def _format_params(self, params):
if isinstance(params, dict):
result = {}
@@ -464,12 +449,35 @@ class FormatStylePlaceholderCursor(Database.Cursor):
return tuple([smart_str(p, self.charset, True) for p in params])
def execute(self, query, params=None):
- query, params = self._rewrite_args(query, params)
+ if params is None:
+ params = []
+ else:
+ params = self._format_params(params)
+ args = [(':arg%d' % i) for i in range(len(params))]
+ # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
+ # it does want a trailing ';' but not a trailing '/'. However, these
+ # characters must be included in the original query in case the query
+ # is being passed to SQL*Plus.
+ if query.endswith(';') or query.endswith('/'):
+ query = query[:-1]
+ query = smart_str(query, self.charset) % tuple(args)
return Database.Cursor.execute(self, query, params)
def executemany(self, query, params=None):
- query, params = self._rewrite_args(query, params)
- return Database.Cursor.executemany(self, query, params)
+ try:
+ args = [(':arg%d' % i) for i in range(len(params[0]))]
+ except (IndexError, TypeError):
+ # No params given, nothing to do
+ return None
+ # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
+ # it does want a trailing ';' but not a trailing '/'. However, these
+ # characters must be included in the original query in case the query
+ # is being passed to SQL*Plus.
+ if query.endswith(';') or query.endswith('/'):
+ query = query[:-1]
+ query = smart_str(query, self.charset) % tuple(args)
+ new_param_list = [self._format_params(i) for i in params]
+ return Database.Cursor.executemany(self, query, new_param_list)
def fetchone(self):
return to_unicode(Database.Cursor.fetchone(self))
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index a482a240cf..b4b445cd16 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -133,8 +133,12 @@ class SQLiteCursorWrapper(Database.Cursor):
return Database.Cursor.execute(self, query, params)
def executemany(self, query, param_list):
- query = self.convert_query(query, len(param_list[0]))
- return Database.Cursor.executemany(self, query, param_list)
+ try:
+ query = self.convert_query(query, len(param_list[0]))
+ return Database.Cursor.executemany(self, query, param_list)
+ except (IndexError,TypeError):
+ # No parameter list provided
+ return None
def convert_query(self, query, num_params):
return query % tuple("?" * num_params)
diff --git a/django/db/models/base.py b/django/db/models/base.py
index beb413fc4c..b728150a40 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -12,7 +12,7 @@ from django.db.models.loading import register_models, get_model
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
from django.utils.functional import curry
-from django.utils.encoding import smart_str, force_unicode
+from django.utils.encoding import smart_str, force_unicode, smart_unicode
from django.conf import settings
from itertools import izip
import types
@@ -213,7 +213,7 @@ class Model(object):
pk_val = self._get_pk_val()
# Note: the comparison with '' is required for compatibility with
# oldforms-style model creation.
- pk_set = pk_val is not None and pk_val != u''
+ pk_set = pk_val is not None and smart_unicode(pk_val) != u''
record_exists = True
if pk_set:
# Determine whether a record with the primary key already exists.
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 7e6a7cd258..8fa091dd76 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -693,7 +693,8 @@ class DecimalField(Field):
class EmailField(CharField):
def __init__(self, *args, **kwargs):
- kwargs['max_length'] = 75
+ if 'max_length' not in kwargs:
+ kwargs['max_length'] = 75
CharField.__init__(self, *args, **kwargs)
def get_internal_type(self):
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 502cbc4a65..788d1c80de 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -52,9 +52,19 @@ class Options(object):
del meta_attrs['__doc__']
for attr_name in DEFAULT_NAMES:
setattr(self, attr_name, meta_attrs.pop(attr_name, getattr(self, attr_name)))
+
+ # unique_together can be either a tuple of tuples, or a single
+ # tuple of two strings. Normalize it to a tuple of tuples, so that
+ # calling code can uniformly expect that.
+ ut = meta_attrs.pop('unique_together', getattr(self, 'unique_together'))
+ if ut and not isinstance(ut[0], (tuple, list)):
+ ut = (ut,)
+ setattr(self, 'unique_together', ut)
+
# verbose_name_plural is a special case because it uses a 's'
# by default.
setattr(self, 'verbose_name_plural', meta_attrs.pop('verbose_name_plural', string_concat(self.verbose_name, 's')))
+
# Any leftover attributes must be invalid.
if meta_attrs != {}:
raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 2b68a6243a..1bb1621d77 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -246,7 +246,7 @@ class HttpResponse(object):
else:
self._container = [content]
self._is_string = True
- self.headers = {'Content-Type': content_type}
+ self.headers = {'content-type': content_type}
self.cookies = SimpleCookie()
if status:
self.status_code = status
@@ -258,24 +258,20 @@ class HttpResponse(object):
+ '\n\n' + self.content
def __setitem__(self, header, value):
- self.headers[header] = value
+ self.headers[header.lower()] = value
def __delitem__(self, header):
try:
- del self.headers[header]
+ del self.headers[header.lower()]
except KeyError:
pass
def __getitem__(self, header):
- return self.headers[header]
+ return self.headers[header.lower()]
def has_header(self, header):
"Case-insensitive check for a header"
- header = header.lower()
- for key in self.headers.keys():
- if key.lower() == header:
- return True
- return False
+ return self.headers.has_key(header.lower())
def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
self.cookies[key] = value
@@ -304,7 +300,7 @@ class HttpResponse(object):
content = property(_get_content, _set_content)
def __iter__(self):
- self._iterator = self._container.__iter__()
+ self._iterator = iter(self._container)
return self
def next(self):
diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
index dfb0c28abd..56a2b2634a 100644
--- a/django/shortcuts/__init__.py
+++ b/django/shortcuts/__init__.py
@@ -14,7 +14,8 @@ def render_to_response(*args, **kwargs):
Returns a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
"""
- return HttpResponse(loader.render_to_string(*args, **kwargs))
+ httpresponse_kwargs = {'mimetype': kwargs.pop('mimetype')}
+ return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs)
load_and_render = render_to_response # For backwards compatibility.
def _get_queryset(klass):
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 6880fd5997..de8591ac5c 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -58,6 +58,7 @@ import re
from inspect import getargspec
from django.conf import settings
from django.template.context import Context, RequestContext, ContextPopException
+from django.utils.itercompat import is_iterable
from django.utils.functional import curry, Promise
from django.utils.text import smart_split
from django.utils.encoding import smart_unicode, force_unicode
@@ -900,7 +901,7 @@ class Library(object):
if not getattr(self, 'nodelist', False):
from django.template.loader import get_template, select_template
- if hasattr(file_name, '__iter__'):
+ if not isinstance(file_name, basestring) and is_iterable(file_name):
t = select_template(file_name)
else:
t = get_template(file_name)
diff --git a/django/test/client.py b/django/test/client.py
index c3e221554f..faacc5bf9e 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -16,6 +16,7 @@ from django.test import signals
from django.utils.functional import curry
from django.utils.encoding import smart_str
from django.utils.http import urlencode
+from django.utils.itercompat import is_iterable
BOUNDARY = 'BoUnDaRyStRiNg'
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
@@ -74,21 +75,22 @@ def encode_multipart(boundary, data):
'',
value.read()
])
- elif hasattr(value, '__iter__'):
- for item in value:
+ else:
+ if not isinstance(value, basestring) and is_iterable(value):
+ for item in value:
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % to_str(key),
+ '',
+ to_str(item)
+ ])
+ else:
lines.extend([
'--' + boundary,
'Content-Disposition: form-data; name="%s"' % to_str(key),
'',
- to_str(item)
+ to_str(value)
])
- else:
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % to_str(key),
- '',
- to_str(value)
- ])
lines.extend([
'--' + boundary + '--',
diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py
index 0de1b6cbe2..3742d6c5d8 100644
--- a/django/utils/itercompat.py
+++ b/django/utils/itercompat.py
@@ -57,3 +57,13 @@ else:
tee = compat_tee
if hasattr(itertools, 'groupby'):
groupby = itertools.groupby
+
+def is_iterable(x):
+ "A implementation independent way of checking for iterables"
+ try:
+ iter(x)
+ except TypeError:
+ return False
+ else:
+ return True
+
diff --git a/docs/authentication.txt b/docs/authentication.txt
index 131a8930b5..820aff2712 100644
--- a/docs/authentication.txt
+++ b/docs/authentication.txt
@@ -402,11 +402,29 @@ introduced in Python 2.4::
def my_view(request):
# ...
+In the Django development version, ``login_required`` also takes an optional
+``redirect_field_name`` parameter. Example::
+
+ from django.contrib.auth.decorators import login_required
+
+ def my_view(request):
+ # ...
+ my_view = login_required(redirect_field_name='redirect_to')(my_view)
+
+Again, an equivalent example of the more compact decorator syntax introduced in Python 2.4::
+
+ from django.contrib.auth.decorators import login_required
+
+ @login_required(redirect_field_name='redirect_to')
+ def my_view(request):
+ # ...
+
``login_required`` does the following:
* If the user isn't logged in, redirect to ``settings.LOGIN_URL``
(``/accounts/login/`` by default), passing the current absolute URL
- in the query string as ``next``. For example:
+ in the query string as ``next`` or the value of ``redirect_field_name``.
+ For example:
``/accounts/login/?next=/polls/3/``.
* If the user is logged in, execute the view normally. The view code is
free to assume the user is logged in.
diff --git a/docs/databrowse.txt b/docs/databrowse.txt
index 81e9e8e83b..72e1c71720 100644
--- a/docs/databrowse.txt
+++ b/docs/databrowse.txt
@@ -58,4 +58,29 @@ How to use Databrowse
4. Run the Django server and visit ``/databrowse/`` in your browser.
+Requiring user login
+====================
+
+You can restrict access to logged-in users with only a few extra lines of
+code. Simply add the following import to your URLconf::
+
+ from django.contrib.auth.decorators import login_required
+
+Then modify the URLconf so that the ``databrowse.site.root`` view is decorated
+with ``login_required``::
+
+ (r'^databrowse/(.*)', login_required(databrowse.site.root)),
+
+If you haven't already added support for user logins to your URLconf, as
+described in the `user authentication docs`_, then you will need to do so
+now with the following mapping::
+
+ (r'^accounts/login/$', 'django.contrib.auth.views.login'),
+
+The final step is to create the login form required by
+``django.contrib.auth.views.login``. The `user authentication docs`_
+provide full details and a sample template that can be used for this
+purpose.
+
.. _template loader docs: ../templates_python/#loader-types
+.. _user authentication docs: ../authentication/
diff --git a/docs/django-admin.txt b/docs/django-admin.txt
index cdd31a2c48..68fcad24fe 100644
--- a/docs/django-admin.txt
+++ b/docs/django-admin.txt
@@ -627,14 +627,34 @@ This is useful in a number of ways:
in any way, knowing that whatever data changes you're making are only
being made to a test database.
-Note that this server can only run on the default port on localhost; it does
-not yet accept a ``host`` or ``port`` parameter.
-
-Also note that it does *not* automatically detect changes to your Python source
-code (as ``runserver`` does). It does, however, detect changes to templates.
+Note that this server does *not* automatically detect changes to your Python
+source code (as ``runserver`` does). It does, however, detect changes to
+templates.
.. _unit tests: ../testing/
+--addrport [port number or ipaddr:port]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use ``--addrport`` to specify a different port, or IP address and port, from
+the default of 127.0.0.1:8000. This value follows exactly the same format and
+serves exactly the same function as the argument to the ``runserver`` subcommand.
+
+Examples:
+
+To run the test server on port 7000 with ``fixture1`` and ``fixture2``::
+
+ django-admin.py testserver --addrport 7000 fixture1 fixture2
+ django-admin.py testserver fixture1 fixture2 --addrport 8080
+
+(The above statements are equivalent. We include both of them to demonstrate
+that it doesn't matter whether the options come before or after the
+``testserver`` command.)
+
+To run on 1.2.3.4:7000 with a `test` fixture::
+
+ django-admin.py testserver --addrport 1.2.3.4:7000 test
+
--verbosity
~~~~~~~~~~~
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 27207aab6f..a8af05f676 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -221,8 +221,10 @@ The admin represents this as an ```` (a single-line input).
~~~~~~~~~~~~~~
A ``CharField`` that checks that the value is a valid e-mail address.
-This doesn't accept ``max_length``; its ``max_length`` is automatically set to
-75.
+
+In Django 0.96, this doesn't accept ``max_length``; its ``max_length`` is
+automatically set to 75. In the Django development version, ``max_length`` is
+set to 75 by default, but you can specify it to override default behavior.
``FileField``
~~~~~~~~~~~~~
@@ -1224,6 +1226,13 @@ together. It's used in the Django admin and is enforced at the database
level (i.e., the appropriate ``UNIQUE`` statements are included in the
``CREATE TABLE`` statement).
+**New in Django development version**
+
+For convenience, unique_together can be a single list when dealing
+with a single set of fields::
+
+ unique_together = ("driver", "restaurant")
+
``verbose_name``
----------------
diff --git a/docs/shortcuts.txt b/docs/shortcuts.txt
new file mode 100644
index 0000000000..2c0dbb5663
--- /dev/null
+++ b/docs/shortcuts.txt
@@ -0,0 +1,41 @@
+=========================
+Django shortcut functions
+=========================
+
+The package ``django.shortcuts`` collects helper functions and classes that
+"span" multiple levels of MVC. In other words, these functions/classes
+introduce controlled coupling for convenience's sake.
+
+``render_to_response``
+======================
+
+``django.shortcuts.render_to_response`` renders a given template with a given
+context dictionary and returns an ``HttpResponse`` object with that rendered
+text.
+
+Example::
+
+ from django.shortcuts import render_to_response
+ r = render_to_response('myapp/template.html', {'foo': 'bar'})
+
+This example is equivalent to::
+
+ from django.http import HttpResponse
+ from django.template import Context, loader
+ t = loader.get_template('myapp/template.html')
+ c = Context({'foo': 'bar'})
+ r = HttpResponse(t.render(c))
+
+``get_object_or_404``
+=====================
+
+``django.shortcuts.get_object_or_404`` calls ``get()`` on a given model
+manager, but it raises ``django.http.Http404`` instead of the model's
+``DoesNotExist`` exception.
+
+``get_list_or_404``
+===================
+
+``django.shortcuts.get_list_or_404`` returns the result of ``filter()`` on a
+given model manager, raising ``django.http.Http404`` if the resulting list is
+empty.
diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
index 53bbadbfd4..375859c897 100644
--- a/tests/modeltests/custom_pk/models.py
+++ b/tests/modeltests/custom_pk/models.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
14. Using a custom primary key
@@ -92,4 +93,8 @@ DoesNotExist: Employee matching query does not exist.
>>> Business.objects.filter(employees__first_name__startswith='Fran')
[]
+# Primary key may be unicode string
+>>> emp = Employee(employee_code='jaźń')
+>>> emp.save()
+
"""}
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index 32647552eb..2df5d3cf77 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -250,6 +250,22 @@ class ClientTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
+ def test_view_with_login_and_custom_redirect(self):
+ "Request a page that is protected with @login_required(redirect_field_name='redirect_to')"
+
+ # Get the page without logging in. Should result in 302.
+ response = self.client.get('/test_client/login_protected_view_custom_redirect/')
+ self.assertRedirects(response, 'http://testserver/accounts/login/?redirect_to=/test_client/login_protected_view_custom_redirect/')
+
+ # Log in
+ login = self.client.login(username='testclient', password='password')
+ self.failUnless(login, 'Could not log in')
+
+ # Request a page that requires a login
+ response = self.client.get('/test_client/login_protected_view_custom_redirect/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+
def test_view_with_bad_login(self):
"Request a page that is protected with @login, but use bad credentials"
diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py
index 538c0e4b43..3779a0ecd1 100644
--- a/tests/modeltests/test_client/urls.py
+++ b/tests/modeltests/test_client/urls.py
@@ -13,6 +13,7 @@ urlpatterns = patterns('',
(r'^form_view/$', views.form_view),
(r'^form_view_with_template/$', views.form_view_with_template),
(r'^login_protected_view/$', views.login_protected_view),
+ (r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect),
(r'^session_view/$', views.session_view),
(r'^broken_view/$', views.broken_view),
(r'^mail_sending_view/$', views.mail_sending_view),
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index e2a9081fb2..c406e17d30 100644
--- a/tests/modeltests/test_client/views.py
+++ b/tests/modeltests/test_client/views.py
@@ -122,6 +122,14 @@ def login_protected_view(request):
return HttpResponse(t.render(c))
login_protected_view = login_required(login_protected_view)
+def login_protected_view_changed_redirect(request):
+ "A simple view that is login protected with a custom redirect field set"
+ t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
+ c = Context({'user': request.user})
+
+ return HttpResponse(t.render(c))
+login_protected_view_changed_redirect = login_required(redirect_field_name="redirect_to")(login_protected_view_changed_redirect)
+
def session_view(request):
"A view that modifies the session"
request.session['tobacconist'] = 'hovercraft'
diff --git a/tests/regressiontests/backends/__init__.py b/tests/regressiontests/backends/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py
new file mode 100644
index 0000000000..50a628a22e
--- /dev/null
+++ b/tests/regressiontests/backends/models.py
@@ -0,0 +1,29 @@
+from django.db import models
+
+class Square(models.Model):
+ root = models.IntegerField()
+ square = models.PositiveIntegerField()
+
+ def __unicode__(self):
+ return "%s ** 2 == %s" % (self.root, self.square)
+
+__test__ = {'API_TESTS': """
+
+#4896: Test cursor.executemany
+>>> from django.db import connection
+>>> cursor = connection.cursor()
+>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)',
+... [(i, i**2) for i in range(-5, 6)]) and None or None
+>>> Square.objects.order_by('root')
+[, , , , , , , , , , ]
+
+#4765: executemany with params=None or params=[] does nothing
+>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)', None) and None or None
+>>> Square.objects.count()
+11
+
+>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)', []) and None or None
+>>> Square.objects.count()
+11
+
+"""}
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py
index 1d46adfc0b..2fe15847c7 100644
--- a/tests/regressiontests/forms/localflavor.py
+++ b/tests/regressiontests/forms/localflavor.py
@@ -1818,4 +1818,380 @@ u''
u''
>>> f.clean(u'')
u''
+
+# CAPostalCodeField ##############################################################
+
+CAPostalCodeField validates that the data is a six-character Canadian postal code.
+>>> from django.contrib.localflavor.ca.forms import CAPostalCodeField
+>>> f = CAPostalCodeField()
+>>> f.clean('T2S 2H7')
+u'T2S 2H7'
+>>> f.clean('T2S 2H')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('2T6 H8I')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('T2S2H')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean(90210)
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f = CAPostalCodeField(required=False)
+>>> f.clean('T2S 2H7')
+u'T2S 2H7'
+>>> f.clean('T2S2H7')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('T2S 2H')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('2T6 H8I')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('T2S2H')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean(90210)
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# CAPhoneNumberField ##########################################################
+
+CAPhoneNumberField validates that the data is a valid Canadian phone number,
+including the area code. It's normalized to XXX-XXX-XXXX format.
+Note: This test is exactly the same as the USPhoneNumberField except using a real
+Candian area code
+
+>>> from django.contrib.localflavor.ca.forms import CAPhoneNumberField
+>>> f = CAPhoneNumberField()
+>>> f.clean('403-555-1212')
+u'403-555-1212'
+>>> f.clean('4035551212')
+u'403-555-1212'
+>>> f.clean('403 555-1212')
+u'403-555-1212'
+>>> f.clean('(403) 555-1212')
+u'403-555-1212'
+>>> f.clean('403 555 1212')
+u'403-555-1212'
+>>> f.clean('403.555.1212')
+u'403-555-1212'
+>>> f.clean('403.555-1212')
+u'403-555-1212'
+>>> f.clean(' (403) 555.1212 ')
+u'403-555-1212'
+>>> f.clean('555-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> f.clean('403-55-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = CAPhoneNumberField(required=False)
+>>> f.clean('403-555-1212')
+u'403-555-1212'
+>>> f.clean('4035551212')
+u'403-555-1212'
+>>> f.clean('403 555-1212')
+u'403-555-1212'
+>>> f.clean('(403) 555-1212')
+u'403-555-1212'
+>>> f.clean('403 555 1212')
+u'403-555-1212'
+>>> f.clean('403.555.1212')
+u'403-555-1212'
+>>> f.clean('403.555-1212')
+u'403-555-1212'
+>>> f.clean(' (403) 555.1212 ')
+u'403-555-1212'
+>>> f.clean('555-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> f.clean('403-55-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# CAProvinceField ################################################################
+
+CAProvinceField validates that the data is either an abbreviation or name of a
+Canadian province.
+>>> from django.contrib.localflavor.ca.forms import CAProvinceField
+>>> f = CAProvinceField()
+>>> f.clean('ab')
+u'AB'
+>>> f.clean('BC')
+u'BC'
+>>> f.clean('nova scotia')
+u'NS'
+>>> f.clean(' manitoba ')
+u'MB'
+>>> f.clean('T2S 2H7')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a Canadian province or territory.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = CAProvinceField(required=False)
+>>> f.clean('ab')
+u'AB'
+>>> f.clean('BC')
+u'BC'
+>>> f.clean('nova scotia')
+u'NS'
+>>> f.clean(' manitoba ')
+u'MB'
+>>> f.clean('T2S 2H7')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a Canadian province or territory.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# CAProvinceSelect ###############################################################
+
+CAProvinceSelect is a Select widget that uses a list of Canadian provinces/territories
+as its choices.
+>>> from django.contrib.localflavor.ca.forms import CAProvinceSelect
+>>> w = CAProvinceSelect()
+>>> print w.render('province', 'AB')
+
+
+# CASocialInsuranceNumberField #################################################
+>>> from django.contrib.localflavor.ca.forms import CASocialInsuranceNumberField
+>>> f = CASocialInsuranceNumberField()
+>>> f.clean('046-454-286')
+u'046-454-286'
+>>> f.clean('046-454-287')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.']
+>>> f.clean('046 454 286')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.']
+>>> f.clean('046-44-286')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.']
+
+## Generic DateField ##########################################################
+
+>>> from django.contrib.localflavor.generic.forms import *
+
+A DateField that uses generic dd/mm/yy dates instead of mm/dd/yy where
+appropriate.
+
+>>> import datetime
+>>> f = DateField()
+>>> f.clean(datetime.date(2006, 10, 25))
+datetime.date(2006, 10, 25)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
+datetime.date(2006, 10, 25)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
+datetime.date(2006, 10, 25)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
+datetime.date(2006, 10, 25)
+>>> f.clean('2006-10-25')
+datetime.date(2006, 10, 25)
+>>> f.clean('25/10/2006')
+datetime.date(2006, 10, 25)
+>>> f.clean('25/10/06')
+datetime.date(2006, 10, 25)
+>>> f.clean('Oct 25 2006')
+datetime.date(2006, 10, 25)
+>>> f.clean('October 25 2006')
+datetime.date(2006, 10, 25)
+>>> f.clean('October 25, 2006')
+datetime.date(2006, 10, 25)
+>>> f.clean('25 October 2006')
+datetime.date(2006, 10, 25)
+>>> f.clean('25 October, 2006')
+datetime.date(2006, 10, 25)
+>>> f.clean('2006-4-31')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+>>> f.clean('200a-10-25')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+>>> f.clean('10/25/06')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = DateField(required=False)
+>>> f.clean(None)
+>>> repr(f.clean(None))
+'None'
+>>> f.clean('')
+>>> repr(f.clean(''))
+'None'
+
+DateField accepts an optional input_formats parameter:
+>>> f = DateField(input_formats=['%Y %m %d'])
+>>> f.clean(datetime.date(2006, 10, 25))
+datetime.date(2006, 10, 25)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
+datetime.date(2006, 10, 25)
+>>> f.clean('2006 10 25')
+datetime.date(2006, 10, 25)
+
+The input_formats parameter overrides all default input formats,
+so the default formats won't work unless you specify them:
+>>> f.clean('2006-10-25')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+>>> f.clean('25/10/2006')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+>>> f.clean('25/10/06')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+
+## Generic DateTimeField ######################################################
+
+A DateField that uses generic dd/mm/yy dates instead of mm/dd/yy where
+appropriate.
+
+>>> import datetime
+>>> f = DateTimeField()
+>>> f.clean(datetime.date(2006, 10, 25))
+datetime.datetime(2006, 10, 25, 0, 0)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
+datetime.datetime(2006, 10, 25, 14, 30, 59)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
+datetime.datetime(2006, 10, 25, 14, 30, 59, 200)
+>>> f.clean('2006-10-25 14:30:45')
+datetime.datetime(2006, 10, 25, 14, 30, 45)
+>>> f.clean('2006-10-25 14:30:00')
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean('2006-10-25 14:30')
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean('2006-10-25')
+datetime.datetime(2006, 10, 25, 0, 0)
+>>> f.clean('25/10/2006 14:30:45')
+datetime.datetime(2006, 10, 25, 14, 30, 45)
+>>> f.clean('25/10/2006 14:30:00')
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean('25/10/2006 14:30')
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean('25/10/2006')
+datetime.datetime(2006, 10, 25, 0, 0)
+>>> f.clean('25/10/06 14:30:45')
+datetime.datetime(2006, 10, 25, 14, 30, 45)
+>>> f.clean('25/10/06 14:30:00')
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean('25/10/06 14:30')
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean('25/10/06')
+datetime.datetime(2006, 10, 25, 0, 0)
+>>> f.clean('hello')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date/time.']
+>>> f.clean('2006-10-25 4:30 p.m.')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date/time.']
+
+DateField accepts an optional input_formats parameter:
+>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
+>>> f.clean(datetime.date(2006, 10, 25))
+datetime.datetime(2006, 10, 25, 0, 0)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30))
+datetime.datetime(2006, 10, 25, 14, 30)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))
+datetime.datetime(2006, 10, 25, 14, 30, 59)
+>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))
+datetime.datetime(2006, 10, 25, 14, 30, 59, 200)
+>>> f.clean('2006 10 25 2:30 PM')
+datetime.datetime(2006, 10, 25, 14, 30)
+
+The input_formats parameter overrides all default input formats,
+so the default formats won't work unless you specify them:
+>>> f.clean('2006-10-25 14:30:45')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date/time.']
+
+>>> f = DateTimeField(required=False)
+>>> f.clean(None)
+>>> repr(f.clean(None))
+'None'
+>>> f.clean('')
+>>> repr(f.clean(''))
+'None'
+
"""