diff --git a/AUTHORS b/AUTHORS
index 27fe91bcc5..585b1c8db6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -55,6 +55,7 @@ answer newbie questions, and generally made Django that much better:
Niran Babalola
Morten Bagai
Mikaël Barbero
+ Randy Barlow
Scott Barr
Jiri Barton
Ned Batchelder
@@ -136,7 +137,7 @@ answer newbie questions, and generally made Django that much better:
Andrew Durdin
dusk@woofle.net
Andy Dustman
- J. Clifford Dyer
+ J. Clifford Dyer
Clint Ecker
Nick Efford
eibaan@gmail.com
@@ -172,6 +173,7 @@ answer newbie questions, and generally made Django that much better:
Alex Gaynor
Andy Gayton
Idan Gazit
+ geber@datacollect.com
Baishampayan Ghose
Dimitris Glezos
glin@seznam.cz
@@ -267,6 +269,7 @@ answer newbie questions, and generally made Django that much better:
Finn Gruwier Larsen
Lau Bech Lauritzen
Rune Rønde Laursen
+ Mark Lavin
Eugene Lazutkin
lcordier@point45.com
Jeong-Min Lee
@@ -300,6 +303,7 @@ answer newbie questions, and generally made Django that much better:
Jason McBrayer
Kevin McConnell
mccutchen@gmail.com
+ Paul McLanahan
Tobias McNulty
Christian Metts
michael.mcewan@gmail.com
@@ -382,6 +386,7 @@ answer newbie questions, and generally made Django that much better:
Massimo Scamarcia
David Schein
Bernd Schlapsi
+ schwank@gmail.com
scott@staplefish.com
Ilya Semenov
serbaut@gmail.com
@@ -393,6 +398,7 @@ answer newbie questions, and generally made Django that much better:
Jozko Skrablin
Ben Slavin
sloonz
+ Paul Smith
Warren Smith
smurf@smurf.noris.de
Vsevolod Solovyov
diff --git a/README b/README
index 720b6c76df..f4476be925 100644
--- a/README
+++ b/README
@@ -27,7 +27,7 @@ http://code.djangoproject.com/newticket
To get more help:
* Join the #django channel on irc.freenode.net. Lots of helpful people
- hang out there. Read the archives at http://oebfare.com/logger/django/.
+ hang out there. Read the archives at http://botland.oebfare.com/logger/django/.
* Join the django-users mailing list, or read the archives, at
http://groups.google.com/group/django-users.
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 7193beeee8..c055f4ea85 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -692,6 +692,9 @@ class ModelAdmin(BaseModelAdmin):
# perform an action on it, so bail.
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
if not selected:
+ # Reminder that something needs to be selected or nothing will happen
+ msg = "Items must be selected in order to perform actions on them. No items have been changed."
+ self.message_user(request, _(msg))
return None
response = func(self, request, queryset.filter(pk__in=selected))
@@ -703,6 +706,9 @@ class ModelAdmin(BaseModelAdmin):
return response
else:
return HttpResponseRedirect(".")
+ else:
+ msg = "No action selected."
+ self.message_user(request, _(msg))
@csrf_protect
@transaction.commit_on_success
diff --git a/django/contrib/admindocs/models.py b/django/contrib/admindocs/models.py
new file mode 100644
index 0000000000..a9f813a4cb
--- /dev/null
+++ b/django/contrib/admindocs/models.py
@@ -0,0 +1 @@
+# Empty models.py to allow for specifying admindocs as a test label.
diff --git a/django/contrib/admindocs/tests/__init__.py b/django/contrib/admindocs/tests/__init__.py
new file mode 100644
index 0000000000..a091ebe122
--- /dev/null
+++ b/django/contrib/admindocs/tests/__init__.py
@@ -0,0 +1,36 @@
+import unittest
+from django.contrib.admindocs import views
+import fields
+
+from django.db.models import fields as builtin_fields
+
+class TestFieldType(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def test_field_name(self):
+ self.assertRaises(AttributeError,
+ views.get_readable_field_data_type, "NotAField"
+ )
+
+ def test_builtin_fields(self):
+ self.assertEqual(
+ views.get_readable_field_data_type(builtin_fields.BooleanField()),
+ u'Boolean (Either True or False)'
+ )
+
+ def test_custom_fields(self):
+ self.assertEqual(
+ views.get_readable_field_data_type(fields.CustomField()),
+ u'A custom field type'
+ )
+ self.assertEqual(
+ views.get_readable_field_data_type(fields.DocstringLackingField()),
+ u'Field of type: DocstringLackingField'
+ )
+
+ def test_multiline_custom_field_truncation(self):
+ self.assertEqual(
+ views.get_readable_field_data_type(fields.ManyLineDocstringField()),
+ u'Many-line custom field'
+ )
diff --git a/django/contrib/admindocs/tests/fields.py b/django/contrib/admindocs/tests/fields.py
new file mode 100644
index 0000000000..5cab3627c6
--- /dev/null
+++ b/django/contrib/admindocs/tests/fields.py
@@ -0,0 +1,13 @@
+from django.db import models
+
+class CustomField(models.Field):
+ """A custom field type"""
+
+class ManyLineDocstringField(models.Field):
+ """Many-line custom field
+
+ This docstring has many lines. Lorum ipsem etc. etc. Four score
+ and seven years ago, and so on and so forth."""
+
+class DocstringLackingField(models.Field):
+ pass
diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index 571f393ff8..d04030e9f0 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -326,43 +326,20 @@ def get_return_data_type(func_name):
return 'Integer'
return ''
-# Maps Field objects to their human-readable data types, as strings.
-# Column-type strings can contain format strings; they'll be interpolated
-# against the values of Field.__dict__ before being output.
-# If a column type is set to None, it won't be included in the output.
-DATA_TYPE_MAPPING = {
- 'AutoField' : _('Integer'),
- 'BooleanField' : _('Boolean (Either True or False)'),
- 'CharField' : _('String (up to %(max_length)s)'),
- 'CommaSeparatedIntegerField': _('Comma-separated integers'),
- 'DateField' : _('Date (without time)'),
- 'DateTimeField' : _('Date (with time)'),
- 'DecimalField' : _('Decimal number'),
- 'EmailField' : _('E-mail address'),
- 'FileField' : _('File path'),
- 'FilePathField' : _('File path'),
- 'FloatField' : _('Floating point number'),
- 'ForeignKey' : _('Integer'),
- 'ImageField' : _('File path'),
- 'IntegerField' : _('Integer'),
- 'IPAddressField' : _('IP address'),
- 'ManyToManyField' : '',
- 'NullBooleanField' : _('Boolean (Either True, False or None)'),
- 'OneToOneField' : _('Relation to parent model'),
- 'PhoneNumberField' : _('Phone number'),
- 'PositiveIntegerField' : _('Integer'),
- 'PositiveSmallIntegerField' : _('Integer'),
- 'SlugField' : _('String (up to %(max_length)s)'),
- 'SmallIntegerField' : _('Integer'),
- 'TextField' : _('Text'),
- 'TimeField' : _('Time'),
- 'URLField' : _('URL'),
- 'USStateField' : _('U.S. state (two uppercase letters)'),
- 'XMLField' : _('XML text'),
-}
-
def get_readable_field_data_type(field):
- return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
+ """Returns the first line of a doc string for a given field type, if it
+ exists. Fields' docstrings can contain format strings, which will be
+ interpolated against the values of Field.__dict__ before being output.
+ If no docstring is given, a sensible value will be auto-generated from
+ the field's class name."""
+
+ if field.__doc__:
+ doc = field.__doc__.split('\n')[0]
+ return _(doc) % field.__dict__
+ else:
+ return _(u'Field of type: %(field_type)s') % {
+ 'field_type': field.__class__.__name__
+ }
def extract_views_from_urlpatterns(urlpatterns, base=''):
"""
diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py
index 7c5fb14ee9..729b0cf354 100644
--- a/django/contrib/gis/db/models/fields.py
+++ b/django/contrib/gis/db/models/fields.py
@@ -40,8 +40,8 @@ def get_srid_info(srid, connection):
return _srid_cache[name][srid]
-class GeometryField(Field):
- "The base GIS field -- maps to the OpenGIS Specification Geometry type."
+class GeometryField(SpatialBackend.Field):
+ """The base GIS field -- maps to the OpenGIS Specification Geometry type."""
# The OpenGIS Geometry name.
geom_type = 'GEOMETRY'
@@ -285,22 +285,29 @@ class GeometryField(Field):
# The OpenGIS Geometry Type Fields
class PointField(GeometryField):
+ """Point"""
geom_type = 'POINT'
class LineStringField(GeometryField):
+ """Line string"""
geom_type = 'LINESTRING'
class PolygonField(GeometryField):
+ """Polygon"""
geom_type = 'POLYGON'
class MultiPointField(GeometryField):
+ """Multi-point"""
geom_type = 'MULTIPOINT'
class MultiLineStringField(GeometryField):
+ """Multi-line string"""
geom_type = 'MULTILINESTRING'
class MultiPolygonField(GeometryField):
+ """Multi polygon"""
geom_type = 'MULTIPOLYGON'
class GeometryCollectionField(GeometryField):
+ """Geometry collection"""
geom_type = 'GEOMETRYCOLLECTION'
diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py
index 327d938373..542ff40670 100644
--- a/django/contrib/localflavor/ca/forms.py
+++ b/django/contrib/localflavor/ca/forms.py
@@ -12,13 +12,20 @@ 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."""
+ """
+ Canadian postal code field.
+
+ Validates against known invalid characters: D, F, I, O, Q, U
+ Additionally the first character cannot be Z or W.
+ For more info see:
+ http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1402170
+ """
default_error_messages = {
'invalid': _(u'Enter a postal code in the format XXX XXX.'),
}
def __init__(self, *args, **kwargs):
- super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$',
+ super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] \d[ABCEGHJKLMNPRSTVWXYZ]\d$',
max_length=None, min_length=None, *args, **kwargs)
class CAPhoneNumberField(Field):
diff --git a/django/contrib/localflavor/us/models.py b/django/contrib/localflavor/us/models.py
index 209bb9051e..01157d038b 100644
--- a/django/contrib/localflavor/us/models.py
+++ b/django/contrib/localflavor/us/models.py
@@ -1,22 +1,16 @@
-from django.db.models.fields import Field
-
-class USStateField(Field):
- def get_internal_type(self):
- return "USStateField"
-
- def db_type(self, connection):
- if connection.settings_dict['ENGINE'] == 'django.db.backends.oracle':
- return 'CHAR(2)'
- else:
- return 'varchar(2)'
-
- def formfield(self, **kwargs):
- from django.contrib.localflavor.us.forms import USStateSelect
- defaults = {'widget': USStateSelect}
- defaults.update(kwargs)
- return super(USStateField, self).formfield(**defaults)
-
+from django.conf import settings
+from django.db.models.fields import Field, CharField
+from django.contrib.localflavor.us.us_states import STATE_CHOICES
+
+class USStateField(CharField):
+ """U.S. state (two uppercase letters)"""
+ def __init__(self, *args, **kwargs):
+ kwargs['choices'] = STATE_CHOICES
+ kwargs['max_length'] = 2
+ super(USStateField, self).__init__(*args, **kwargs)
+
class PhoneNumberField(Field):
+ """Phone number"""
def get_internal_type(self):
return "PhoneNumberField"
diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py
index 248791d3e7..1ac1afe550 100644
--- a/django/contrib/messages/storage/cookie.py
+++ b/django/contrib/messages/storage/cookie.py
@@ -1,7 +1,7 @@
import hmac
from django.conf import settings
-from django.utils.hashcompat import sha_constructor
+from django.utils.hashcompat import sha_hmac
from django.contrib.messages import constants
from django.contrib.messages.storage.base import BaseStorage, Message
from django.utils import simplejson as json
@@ -41,7 +41,6 @@ class MessageDecoder(json.JSONDecoder):
decoded = super(MessageDecoder, self).decode(s, **kwargs)
return self.process_messages(decoded)
-
class CookieStorage(BaseStorage):
"""
Stores messages in a cookie.
@@ -103,7 +102,7 @@ class CookieStorage(BaseStorage):
SECRET_KEY, modified to make it unique for the present purpose.
"""
key = 'django.contrib.messages' + settings.SECRET_KEY
- return hmac.new(key, value, sha_constructor).hexdigest()
+ return hmac.new(key, value, sha_hmac).hexdigest()
def _encode(self, messages, encode_empty=False):
"""
diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py
index 45e061aedc..6ca9185f62 100644
--- a/django/contrib/messages/tests/base.py
+++ b/django/contrib/messages/tests/base.py
@@ -218,9 +218,9 @@ class BaseTest(TestCase):
response = self.client.post(add_url, data, follow=True)
self.assertRedirects(response, show_url)
self.assertTrue('messages' in response.context)
- self.assertEqual(list(response.context['messages']),
- data['messages'])
+ context_messages = list(response.context['messages'])
for msg in data['messages']:
+ self.assertTrue(msg in context_messages)
self.assertContains(response, msg)
def test_middleware_disabled_anon_user(self):
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index 0ff0da32ae..d5e6394f92 100644
--- a/django/core/cache/backends/memcached.py
+++ b/django/core/cache/backends/memcached.py
@@ -46,7 +46,28 @@ class CacheClass(BaseCache):
self._cache.disconnect_all()
def incr(self, key, delta=1):
- return self._cache.incr(key, delta)
+ try:
+ val = self._cache.incr(key, delta)
+
+ # python-memcache responds to incr on non-existent keys by
+ # raising a ValueError. Cmemcache returns None. In both
+ # cases, we should raise a ValueError though.
+ except ValueError:
+ val = None
+ if val is None:
+ raise ValueError("Key '%s' not found" % key)
+
+ return val
def decr(self, key, delta=1):
- return self._cache.decr(key, delta)
+ try:
+ val = self._cache.decr(key, delta)
+
+ # python-memcache responds to decr on non-existent keys by
+ # raising a ValueError. Cmemcache returns None. In both
+ # cases, we should raise a ValueError though.
+ except ValueError:
+ val = None
+ if val is None:
+ raise ValueError("Key '%s' not found" % key)
+ return val
diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py
index 8ebf3daea6..4fd6ba0c8d 100644
--- a/django/core/management/commands/test.py
+++ b/django/core/management/commands/test.py
@@ -6,6 +6,8 @@ class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--noinput', action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.'),
+ make_option('--failfast', action='store_true', dest='failfast', default=False,
+ help='Tells Django to stop running the test suite after first failed test.')
)
help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.'
args = '[appname ...]'
@@ -15,11 +17,18 @@ class Command(BaseCommand):
def handle(self, *test_labels, **options):
from django.conf import settings
from django.test.utils import get_runner
-
+
verbosity = int(options.get('verbosity', 1))
interactive = options.get('interactive', True)
+ failfast = options.get('failfast', False)
test_runner = get_runner(settings)
- failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
+ # Some custom test runners won't accept the failfast flag, so let's make sure they accept it before passing it to them
+ if 'failfast' in test_runner.func_code.co_varnames:
+ failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive,
+ failfast=failfast)
+ else:
+ failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
+
if failures:
sys.exit(failures)
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index a924afeaf8..c721015d62 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -257,9 +257,8 @@ class RegexURLResolver(object):
def _resolve_special(self, view_type):
callback = getattr(self.urlconf_module, 'handler%s' % view_type)
- mod_name, func_name = get_mod_func(callback)
try:
- return getattr(import_module(mod_name), func_name), {}
+ return get_callable(callback), {}
except (ImportError, AttributeError), e:
raise ViewDoesNotExist, "Tried %s. Error was: %s" % (callback, str(e))
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 97af08612f..8012ade37c 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -3,10 +3,6 @@ import datetime
import os
import re
import time
-try:
- import decimal
-except ImportError:
- from django.utils import _decimal as decimal # for Python 2.3
from django.db import connection
from django.db.models import signals
@@ -50,7 +46,9 @@ class FieldDoesNotExist(Exception):
# getattr(obj, opts.pk.attname)
class Field(object):
+ """Base class for all field types"""
__metaclass__ = LegacyConnection
+
# Designates whether empty strings fundamentally are allowed at the
# database level.
empty_strings_allowed = True
@@ -370,7 +368,10 @@ class Field(object):
return getattr(obj, self.attname)
class AutoField(Field):
+ """Integer"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
kwargs['blank'] = True
@@ -400,7 +401,10 @@ class AutoField(Field):
return None
class BooleanField(Field):
+ """Boolean (Either True or False)"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
kwargs['blank'] = True
if 'default' not in kwargs and not kwargs.get('null'):
@@ -443,6 +447,8 @@ class BooleanField(Field):
return super(BooleanField, self).formfield(**defaults)
class CharField(Field):
+ """String (up to %(max_length)s)"""
+
def get_internal_type(self):
return "CharField"
@@ -464,6 +470,8 @@ class CharField(Field):
# TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField):
+ """Comma-separated integers"""
+
def formfield(self, **kwargs):
defaults = {
'form_class': forms.RegexField,
@@ -479,7 +487,10 @@ class CommaSeparatedIntegerField(CharField):
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
class DateField(Field):
+ """Date (without time)"""
+
empty_strings_allowed = False
+
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
#HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@@ -559,6 +570,8 @@ class DateField(Field):
return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField):
+ """Date (with time)"""
+
def get_internal_type(self):
return "DateTimeField"
@@ -623,7 +636,10 @@ class DateTimeField(DateField):
return super(DateTimeField, self).formfield(**defaults)
class DecimalField(Field):
+ """Decimal number"""
+
empty_strings_allowed = False
+
def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, verbose_name, name, **kwargs)
@@ -677,6 +693,8 @@ class DecimalField(Field):
return super(DecimalField, self).formfield(**defaults)
class EmailField(CharField):
+ """E-mail address"""
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs)
@@ -687,6 +705,8 @@ class EmailField(CharField):
return super(EmailField, self).formfield(**defaults)
class FilePathField(Field):
+ """File path"""
+
def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive
kwargs['max_length'] = kwargs.get('max_length', 100)
@@ -706,6 +726,8 @@ class FilePathField(Field):
return "FilePathField"
class FloatField(Field):
+ """Floating point number"""
+
empty_strings_allowed = False
def get_prep_value(self, value):
@@ -731,8 +753,15 @@ class FloatField(Field):
return super(FloatField, self).formfield(**defaults)
class IntegerField(Field):
+ """Integer"""
+
empty_strings_allowed = False
+<<<<<<< HEAD:django/db/models/fields/__init__.py
def get_prep_value(self, value):
+=======
+
+ def get_db_prep_value(self, value):
+>>>>>>> master:django/db/models/fields/__init__.py
if value is None:
return None
return int(value)
@@ -755,7 +784,10 @@ class IntegerField(Field):
return super(IntegerField, self).formfield(**defaults)
class IPAddressField(Field):
+ """IP address"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs)
@@ -769,7 +801,10 @@ class IPAddressField(Field):
return super(IPAddressField, self).formfield(**defaults)
class NullBooleanField(Field):
+ """Boolean (Either True, False or None)"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
kwargs['null'] = True
Field.__init__(self, *args, **kwargs)
@@ -809,6 +844,8 @@ class NullBooleanField(Field):
return super(NullBooleanField, self).formfield(**defaults)
class PositiveIntegerField(IntegerField):
+ """Integer"""
+
def get_internal_type(self):
return "PositiveIntegerField"
@@ -818,6 +855,8 @@ class PositiveIntegerField(IntegerField):
return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField):
+ """Integer"""
+
def get_internal_type(self):
return "PositiveSmallIntegerField"
@@ -827,6 +866,8 @@ class PositiveSmallIntegerField(IntegerField):
return super(PositiveSmallIntegerField, self).formfield(**defaults)
class SlugField(CharField):
+ """String (up to %(max_length)s)"""
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 50)
# Set db_index=True unless it's been set manually.
@@ -843,10 +884,14 @@ class SlugField(CharField):
return super(SlugField, self).formfield(**defaults)
class SmallIntegerField(IntegerField):
+ """Integer"""
+
def get_internal_type(self):
return "SmallIntegerField"
class TextField(Field):
+ """Text"""
+
def get_internal_type(self):
return "TextField"
@@ -856,7 +901,10 @@ class TextField(Field):
return super(TextField, self).formfield(**defaults)
class TimeField(Field):
+ """Time"""
+
empty_strings_allowed = False
+
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add:
@@ -933,6 +981,8 @@ class TimeField(Field):
return super(TimeField, self).formfield(**defaults)
class URLField(CharField):
+ """URL"""
+
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 200)
self.verify_exists = verify_exists
@@ -944,6 +994,8 @@ class URLField(CharField):
return super(URLField, self).formfield(**defaults)
class XMLField(TextField):
+ """XML text"""
+
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
self.schema_path = schema_path
Field.__init__(self, verbose_name, name, **kwargs)
diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py
index 2eb22271cb..3debb25cb6 100644
--- a/django/db/models/fields/files.py
+++ b/django/db/models/fields/files.py
@@ -209,6 +209,8 @@ class FileDescriptor(object):
instance.__dict__[self.field.name] = value
class FileField(Field):
+ """File path"""
+
# The class to wrap instance attributes in. Accessing the file object off
# the instance will always return an instance of attr_class.
attr_class = FieldFile
@@ -323,6 +325,8 @@ class ImageFieldFile(ImageFile, FieldFile):
super(ImageFieldFile, self).delete(save)
class ImageField(FileField):
+ """File path"""
+
attr_class = ImageFieldFile
descriptor_class = ImageFileDescriptor
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index f8ae5b1f4e..84a0a7a74f 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -708,6 +708,8 @@ class ManyToManyRel(object):
return self.to._meta.pk
class ForeignKey(RelatedField, Field):
+ """Foreign Key (type determined by related field)"""
+
empty_strings_allowed = False
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try:
@@ -806,12 +808,13 @@ class ForeignKey(RelatedField, Field):
return rel_field.db_type(connection=connection)
class OneToOneField(ForeignKey):
- """
+ """One-to-one relationship
+
A OneToOneField is essentially the same as a ForeignKey, with the exception
that always carries a "unique" constraint with it and the reverse relation
always returns the object pointed to (since there will only ever be one),
- rather than returning a list.
- """
+ rather than returning a list."""
+
def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@@ -865,6 +868,8 @@ def create_many_to_many_intermediary_model(field, klass):
})
class ManyToManyField(RelatedField, Field):
+ """Many-to-many relationship"""
+
def __init__(self, to, **kwargs):
try:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 0b7c2e2338..e854de8a7a 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -138,6 +138,8 @@ class BaseForm(StrAndUnicode):
"Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
output, hidden_fields = [], []
+ html_class_attr = ''
+
for name, field in self.fields.items():
bf = BoundField(self, field, name)
bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable.
@@ -146,8 +148,15 @@ class BaseForm(StrAndUnicode):
top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
hidden_fields.append(unicode(bf))
else:
+ # Create a 'class="..."' atribute if the row should have any
+ # CSS classes applied.
+ css_classes = bf.css_classes()
+ if css_classes:
+ html_class_attr = ' class="%s"' % css_classes
+
if errors_on_separate_row and bf_errors:
output.append(error_row % force_unicode(bf_errors))
+
if bf.label:
label = conditional_escape(force_unicode(bf.label))
# Only add the suffix if the label does not end in
@@ -158,13 +167,23 @@ class BaseForm(StrAndUnicode):
label = bf.label_tag(label) or ''
else:
label = ''
+
if field.help_text:
help_text = help_text_html % force_unicode(field.help_text)
else:
help_text = u''
- output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
+
+ output.append(normal_row % {
+ 'errors': force_unicode(bf_errors),
+ 'label': force_unicode(label),
+ 'field': unicode(bf),
+ 'help_text': help_text,
+ 'html_class_attr': html_class_attr
+ })
+
if top_errors:
output.insert(0, error_row % force_unicode(top_errors))
+
if hidden_fields: # Insert any hidden fields in the last row.
str_hidden = u''.join(hidden_fields)
if output:
@@ -176,7 +195,9 @@ class BaseForm(StrAndUnicode):
# that users write): if there are only top errors, we may
# not be able to conscript the last row for our purposes,
# so insert a new, empty row.
- last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''}
+ last_row = (normal_row % {'errors': '', 'label': '',
+ 'field': '', 'help_text':'',
+ 'html_class_attr': html_class_attr})
output.append(last_row)
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
else:
@@ -187,15 +208,30 @@ class BaseForm(StrAndUnicode):
def as_table(self):
"Returns this form rendered as HTML s -- excluding the ."
- return self._html_output(u' %(label)s %(errors)s%(field)s%(help_text)s ', u'%s ', '', u' %s', False)
+ return self._html_output(
+ normal_row = u'%(label)s %(errors)s%(field)s%(help_text)s ',
+ error_row = u'%s ',
+ row_ender = u'',
+ help_text_html = u' %s',
+ errors_on_separate_row = False)
def as_ul(self):
"Returns this form rendered as HTML s -- excluding the ."
- return self._html_output(u' %(errors)s%(label)s %(field)s%(help_text)s ', u'%s ', '', u' %s', False)
+ return self._html_output(
+ normal_row = u'%(errors)s%(label)s %(field)s%(help_text)s ',
+ error_row = u'%s ',
+ row_ender = '',
+ help_text_html = u' %s',
+ errors_on_separate_row = False)
def as_p(self):
"Returns this form rendered as HTML s."
- return self._html_output(u'
%(label)s %(field)s%(help_text)s
', u'%s', '
', u' %s', True)
+ return self._html_output(
+ normal_row = u'%(label)s %(field)s%(help_text)s
',
+ error_row = u'%s',
+ row_ender = '',
+ help_text_html = u' %s',
+ errors_on_separate_row = True)
def non_field_errors(self):
"""
@@ -343,6 +379,7 @@ class BoundField(StrAndUnicode):
self.name = name
self.html_name = form.add_prefix(name)
self.html_initial_name = form.add_initial_prefix(name)
+ self.html_initial_id = form.add_initial_prefix(self.auto_id)
if self.field.label is None:
self.label = pretty_name(name)
else:
@@ -374,7 +411,10 @@ class BoundField(StrAndUnicode):
attrs = attrs or {}
auto_id = self.auto_id
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
- attrs['id'] = auto_id
+ if not only_initial:
+ attrs['id'] = auto_id
+ else:
+ attrs['id'] = self.html_initial_id
if not self.form.is_bound:
data = self.form.initial.get(self.name, self.field.initial)
if callable(data):
@@ -429,6 +469,19 @@ class BoundField(StrAndUnicode):
contents = u'%s ' % (widget.id_for_label(id_), attrs, unicode(contents))
return mark_safe(contents)
+ def css_classes(self, extra_classes=None):
+ """
+ Returns a string of space-separated CSS classes for this field.
+ """
+ if hasattr(extra_classes, 'split'):
+ extra_classes = extra_classes.split()
+ extra_classes = set(extra_classes or [])
+ if self.errors and hasattr(self.form, 'error_css_class'):
+ extra_classes.add(self.form.error_css_class)
+ if self.field.required and hasattr(self.form, 'required_css_class'):
+ extra_classes.add(self.form.required_css_class)
+ return ' '.join(extra_classes)
+
def _is_hidden(self):
"Returns True if this BoundField's widget is hidden."
return self.field.widget.is_hidden
diff --git a/django/forms/models.py b/django/forms/models.py
index 6985448659..81b727111e 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -912,6 +912,9 @@ class ModelChoiceIterator(object):
for obj in self.queryset.all():
yield self.choice(obj)
+ def __len__(self):
+ return len(self.queryset)
+
def choice(self, obj):
if self.field.to_field_name:
key = obj.serializable_value(self.field.to_field_name)
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 683212fcd4..446659b560 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -1,6 +1,6 @@
import os
import re
-from Cookie import SimpleCookie, CookieError
+from Cookie import BaseCookie, SimpleCookie, CookieError
from pprint import pformat
from urllib import urlencode
from urlparse import urljoin
@@ -251,13 +251,15 @@ class QueryDict(MultiValueDict):
def parse_cookie(cookie):
if cookie == '':
return {}
- try:
- c = SimpleCookie()
- c.load(cookie)
- except CookieError:
- # Invalid cookie
- return {}
-
+ if not isinstance(cookie, BaseCookie):
+ try:
+ c = SimpleCookie()
+ c.load(cookie)
+ except CookieError:
+ # Invalid cookie
+ return {}
+ else:
+ c = cookie
cookiedict = {}
for key in c.keys():
cookiedict[key] = c.get(key).value
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 2957c3d045..a8c25670f6 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -249,7 +249,8 @@ stringformat.is_safe = True
def title(value):
"""Converts a string into titlecase."""
- return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
+ t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
+ return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)
title.is_safe = True
title = stringfilter(title)
diff --git a/django/test/simple.py b/django/test/simple.py
index 4c4d7324f9..092ef4bde2 100644
--- a/django/test/simple.py
+++ b/django/test/simple.py
@@ -10,6 +10,26 @@ TEST_MODULE = 'tests'
doctestOutputChecker = OutputChecker()
+class DjangoTestRunner(unittest.TextTestRunner):
+
+ def __init__(self, verbosity=0, failfast=False, **kwargs):
+ super(DjangoTestRunner, self).__init__(verbosity=verbosity, **kwargs)
+ self.failfast = failfast
+
+ def _makeResult(self):
+ result = super(DjangoTestRunner, self)._makeResult()
+ failfast = self.failfast
+
+ def stoptest_override(func):
+ def stoptest(test):
+ if failfast and not result.wasSuccessful():
+ result.stop()
+ func(test)
+ return stoptest
+
+ setattr(result, 'stopTest', stoptest_override(result.stopTest))
+ return result
+
def get_tests(app_module):
try:
app_path = app_module.__name__.split('.')[:-1]
@@ -146,7 +166,7 @@ def reorder_suite(suite, classes):
bins[0].addTests(bins[i+1])
return bins[0]
-def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
+def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=[]):
"""
Run the unit tests for all the test labels in the provided list.
Labels must be of the form:
@@ -186,13 +206,13 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
suite = reorder_suite(suite, (TestCase,))
- old_names = []
from django.db import connections
+ old_names = []
for alias in connections:
connection = connections[alias]
old_names.append((connection, connection.settings_dict['NAME']))
connection.creation.create_test_db(verbosity, autoclobber=not interactive)
- result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
+ result = DjangoTestRunner(verbosity=verbosity, failfast=failfast).run(suite)
for connection, old_name in old_names:
connection.creation.destroy_test_db(old_name, verbosity)
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index 85cdd443f8..2b586d7efe 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -1,3 +1,6 @@
+from copy import deepcopy
+
+
class MergeDict(object):
"""
A simple class for creating new "virtual" dictionaries that actually look
@@ -72,22 +75,20 @@ class SortedDict(dict):
self.keyOrder.append(key)
def __deepcopy__(self, memo):
- from copy import deepcopy
return self.__class__([(key, deepcopy(value, memo))
for key, value in self.iteritems()])
def __setitem__(self, key, value):
- super(SortedDict, self).__setitem__(key, value)
- if key not in self.keyOrder:
+ if key not in self:
self.keyOrder.append(key)
+ super(SortedDict, self).__setitem__(key, value)
def __delitem__(self, key):
super(SortedDict, self).__delitem__(key)
self.keyOrder.remove(key)
def __iter__(self):
- for k in self.keyOrder:
- yield k
+ return iter(self.keyOrder)
def pop(self, k, *args):
result = super(SortedDict, self).pop(k, *args)
@@ -108,7 +109,7 @@ class SortedDict(dict):
def iteritems(self):
for key in self.keyOrder:
- yield key, super(SortedDict, self).__getitem__(key)
+ yield key, self[key]
def keys(self):
return self.keyOrder[:]
@@ -117,18 +118,18 @@ class SortedDict(dict):
return iter(self.keyOrder)
def values(self):
- return map(super(SortedDict, self).__getitem__, self.keyOrder)
+ return map(self.__getitem__, self.keyOrder)
def itervalues(self):
for key in self.keyOrder:
- yield super(SortedDict, self).__getitem__(key)
+ yield self[key]
def update(self, dict_):
- for k, v in dict_.items():
- self.__setitem__(k, v)
+ for k, v in dict_.iteritems():
+ self[k] = v
def setdefault(self, key, default):
- if key not in self.keyOrder:
+ if key not in self:
self.keyOrder.append(key)
return super(SortedDict, self).setdefault(key, default)
@@ -222,18 +223,18 @@ class MultiValueDict(dict):
dict.__setitem__(result, copy.deepcopy(key, memo),
copy.deepcopy(value, memo))
return result
-
+
def __getstate__(self):
obj_dict = self.__dict__.copy()
obj_dict['_data'] = dict([(k, self.getlist(k)) for k in self])
return obj_dict
-
+
def __setstate__(self, obj_dict):
data = obj_dict.pop('_data', {})
for k, v in data.items():
self.setlist(k, v)
self.__dict__.update(obj_dict)
-
+
def get(self, key, default=None):
"""
Returns the last data value for the passed key. If key doesn't exist
@@ -301,12 +302,12 @@ class MultiValueDict(dict):
def values(self):
"""Returns a list of the last value on every key list."""
return [self[key] for key in self.keys()]
-
+
def itervalues(self):
"""Yield the last value on every key list."""
for key in self.iterkeys():
yield self[key]
-
+
def copy(self):
"""Returns a copy of this object."""
return self.__deepcopy__()
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 434b6b76c9..823cda4587 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -277,6 +277,13 @@ class LazyObject(object):
self._setup()
setattr(self._wrapped, name, value)
+ def __delattr__(self, name):
+ if name == "_wrapped":
+ raise TypeError("can't delete _wrapped.")
+ if self._wrapped is None:
+ self._setup()
+ delattr(self._wrapped, name)
+
def _setup(self):
"""
Must be implemented by subclasses to initialise the wrapped object.
diff --git a/django/utils/hashcompat.py b/django/utils/hashcompat.py
index 8880d92646..b1e6021890 100644
--- a/django/utils/hashcompat.py
+++ b/django/utils/hashcompat.py
@@ -8,9 +8,13 @@ available.
try:
import hashlib
md5_constructor = hashlib.md5
+ md5_hmac = md5_constructor
sha_constructor = hashlib.sha1
+ sha_hmac = sha_constructor
except ImportError:
import md5
md5_constructor = md5.new
+ md5_hmac = md5
import sha
sha_constructor = sha.new
+ sha_hmac = sha
diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt
index e1f04fe664..13e4fff36d 100644
--- a/docs/howto/custom-model-fields.txt
+++ b/docs/howto/custom-model-fields.txt
@@ -39,6 +39,8 @@ are traditionally called *north*, *east*, *south* and *west*. Our class looks
something like this::
class Hand(object):
+ """A hand of cards (bridge style)"""
+
def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc)
self.north = north
@@ -163,6 +165,8 @@ behave like any existing field, so we'll subclass directly from
from django.db import models
class HandField(models.Field):
+ """A hand of cards (bridge style)"""
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
@@ -244,6 +248,8 @@ simple: make sure your field subclass uses a special metaclass:
For example::
class HandField(models.Field):
+ """A hand of cards (bridge style)"""
+
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
@@ -252,6 +258,21 @@ For example::
This ensures that the :meth:`to_python` method, documented below, will always be
called when the attribute is initialized.
+
+Documenting your Custom Field
+-----------------------------
+
+As always, you should document your field type, so users will know what it is.
+The best way to do this is to simply provide a docstring for it. This will
+automatically be picked up by ``django.contrib.admindocs``, if you have it
+installed, and the first line of it will show up as the field type in the
+documentation for any model that uses your field. In the above examples, it
+will show up as 'A hand of cards (bridge style)'. Note that if you provide a
+more verbose docstring, only the first line will show up in
+``django.contrib.admindocs``. The full docstring will, of course, still be
+available through ``pydoc`` or the interactive interpreter's ``help()``
+function.
+
Useful methods
--------------
diff --git a/docs/index.txt b/docs/index.txt
index 4b14b74917..e6558a7478 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -28,7 +28,7 @@ Having trouble? We'd like to help!
.. _archives of the django-users mailing list: http://groups.google.com/group/django-users/
.. _post a question: http://groups.google.com/group/django-users/
.. _#django IRC channel: irc://irc.freenode.net/django
-.. _IRC logs: http://oebfare.com/logger/django/
+.. _IRC logs: http://botland.oebfare.com/logger/django/
.. _ticket tracker: http://code.djangoproject.com/
First steps
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index da9bb99224..f83fc37356 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -802,6 +802,14 @@ test
Runs tests for all installed models. See :ref:`topics-testing` for more
information.
+--failfast
+~~~~~~~~~~
+
+.. versionadded:: 1.2
+
+Use the ``--failfast`` option to stop running tests and report the failure
+immediately after a test fails.
+
testserver
--------------------------------
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 7a2341f69b..26934f07a3 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -366,6 +366,36 @@ calls its ``as_table()`` method behind the scenes::
Sender:
Cc myself:
+Styling required or erroneous form rows
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.2
+
+It's pretty common to style form rows and fields that are required or have
+errors. For example, you might want to present required form rows in bold and
+highlight errors in red.
+
+The :class:`Form` class has a couple of hooks you can use to add ``class``
+attributes to required rows or to rows with errors: simple set the
+:attr:`Form.error_css_class` and/or :attr:`Form.required_css_class`
+attributes::
+
+ class ContactForm(Form):
+ error_css_class = 'error'
+ required_css_class = 'required'
+
+ # ... and the rest of your fields here
+
+Once you've done that, rows will be given ``"error"`` and/or ``"required"``
+classes, as needed. The HTML will look something like::
+
+ >>> f = ContactForm(data)
+ >>> print f.as_table()
+ Subject: ...
+ Message: ...
+ Sender: ...
+ Cc myself: ...
+
.. _ref-forms-api-configuring-label:
Configuring HTML ```` tags
diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt
index fd45e79876..0a5d04cdae 100644
--- a/docs/topics/http/urls.txt
+++ b/docs/topics/http/urls.txt
@@ -253,24 +253,30 @@ handler404
.. data:: handler404
-A string representing the full Python import path to the view that should be
-called if none of the URL patterns match.
+A callable, or a string representing the full Python import path to the view
+that should be called if none of the URL patterns match.
By default, this is ``'django.views.defaults.page_not_found'``. That default
value should suffice.
+.. versionchanged:: 1.2
+ Previous versions of Django only accepted strings representing import paths.
+
handler500
----------
.. data:: handler500
-A string representing the full Python import path to the view that should be
-called in case of server errors. Server errors happen when you have runtime
-errors in view code.
+A callable, or a string representing the full Python import path to the view
+that should be called in case of server errors. Server errors happen when you
+have runtime errors in view code.
By default, this is ``'django.views.defaults.server_error'``. That default
value should suffice.
+.. versionchanged:: 1.2
+ Previous versions of Django only accepted strings representing import paths.
+
include
-------
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
index cda3aa9d8e..d6f541bfd5 100644
--- a/tests/modeltests/model_formsets/models.py
+++ b/tests/modeltests/model_formsets/models.py
@@ -885,7 +885,7 @@ False
>>> form = formset.forms[0] # this formset only has one form
>>> now = form.fields['date_joined'].initial
>>> print form.as_p()
-Date joined:
+Date joined:
Karma:
# test for validation with callable defaults. Validations rely on hidden fields
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 167498ac37..3124071503 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -1157,7 +1157,6 @@ class AdminActionsTest(TestCase):
self.assert_('action-checkbox-column' in response.content,
"Expected an action-checkbox-column in response")
-
def test_multiple_actions_form(self):
"""
Test that actions come from the form whose submit button was pressed (#10618).
@@ -1175,6 +1174,35 @@ class AdminActionsTest(TestCase):
self.assertEquals(len(mail.outbox), 1)
self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action')
+ def test_user_message_on_none_selected(self):
+ """
+ User should see a warning when 'Go' is pressed and no items are selected.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [],
+ 'action' : 'delete_selected',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
+ msg = """Items must be selected in order to perform actions on them. No items have been changed."""
+ self.assertContains(response, msg)
+ self.failUnlessEqual(Subscriber.objects.count(), 2)
+
+ def test_user_message_on_no_action(self):
+ """
+ User should see a warning when 'Go' is pressed and no action is selected.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1, 2],
+ 'action' : '',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
+ msg = """No action selected."""
+ self.assertContains(response, msg)
+ self.failUnlessEqual(Subscriber.objects.count(), 2)
+
+
class TestInlineNotEditable(TestCase):
fixtures = ['admin-views-users.xml']
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
index 9d407d9ea7..c9736d38e1 100644
--- a/tests/regressiontests/forms/fields.py
+++ b/tests/regressiontests/forms/fields.py
@@ -1,17 +1,5 @@
# -*- coding: utf-8 -*-
-tests = r"""
->>> from django.forms import *
->>> from django.forms.widgets import RadioFieldRenderer
->>> from django.core.files.uploadedfile import SimpleUploadedFile
->>> import datetime
->>> import time
->>> import re
->>> try:
-... from decimal import Decimal
-... except ImportError:
-... from django.utils._decimal import Decimal
-
-
+"""
##########
# Fields #
##########
@@ -35,1480 +23,825 @@ Each Field's __init__() takes at least these parameters:
Other than that, the Field subclasses have class-specific options for
__init__(). For example, CharField has a max_length option.
-
-# CharField ###################################################################
-
->>> f = CharField()
->>> f.clean(1)
-u'1'
->>> f.clean('hello')
-u'hello'
->>> 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.clean([1, 2, 3])
-u'[1, 2, 3]'
-
->>> f = CharField(required=False)
->>> f.clean(1)
-u'1'
->>> f.clean('hello')
-u'hello'
->>> f.clean(None)
-u''
->>> f.clean('')
-u''
->>> f.clean([1, 2, 3])
-u'[1, 2, 3]'
-
-CharField accepts an optional max_length parameter:
->>> f = CharField(max_length=10, required=False)
->>> f.clean('12345')
-u'12345'
->>> f.clean('1234567890')
-u'1234567890'
->>> f.clean('1234567890a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at most 10 characters (it has 11).']
-
-CharField accepts an optional min_length parameter:
->>> f = CharField(min_length=10, required=False)
->>> f.clean('')
-u''
->>> f.clean('12345')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at least 10 characters (it has 5).']
->>> f.clean('1234567890')
-u'1234567890'
->>> f.clean('1234567890a')
-u'1234567890a'
-
->>> f = CharField(min_length=10, required=True)
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean('12345')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at least 10 characters (it has 5).']
->>> f.clean('1234567890')
-u'1234567890'
->>> f.clean('1234567890a')
-u'1234567890a'
-
-# IntegerField ################################################################
-
->>> f = IntegerField()
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean('1')
-1
->>> isinstance(f.clean('1'), int)
-True
->>> f.clean('23')
-23
->>> f.clean('a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a whole number.']
->>> f.clean(42)
-42
->>> f.clean(3.14)
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a whole number.']
->>> f.clean('1 ')
-1
->>> f.clean(' 1')
-1
->>> f.clean(' 1 ')
-1
->>> f.clean('1a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a whole number.']
-
->>> f = IntegerField(required=False)
->>> f.clean('')
->>> repr(f.clean(''))
-'None'
->>> f.clean(None)
->>> repr(f.clean(None))
-'None'
->>> f.clean('1')
-1
->>> isinstance(f.clean('1'), int)
-True
->>> f.clean('23')
-23
->>> f.clean('a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a whole number.']
->>> f.clean('1 ')
-1
->>> f.clean(' 1')
-1
->>> f.clean(' 1 ')
-1
->>> f.clean('1a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a whole number.']
-
-IntegerField accepts an optional max_value parameter:
->>> f = IntegerField(max_value=10)
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(1)
-1
->>> f.clean(10)
-10
->>> f.clean(11)
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is less than or equal to 10.']
->>> f.clean('10')
-10
->>> f.clean('11')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is less than or equal to 10.']
-
-IntegerField accepts an optional min_value parameter:
->>> f = IntegerField(min_value=10)
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(1)
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is greater than or equal to 10.']
->>> f.clean(10)
-10
->>> f.clean(11)
-11
->>> f.clean('10')
-10
->>> f.clean('11')
-11
-
-min_value and max_value can be used together:
->>> f = IntegerField(min_value=10, max_value=20)
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(1)
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is greater than or equal to 10.']
->>> f.clean(10)
-10
->>> f.clean(11)
-11
->>> f.clean('10')
-10
->>> f.clean('11')
-11
->>> f.clean(20)
-20
->>> f.clean(21)
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is less than or equal to 20.']
-
-# FloatField ##################################################################
-
->>> f = FloatField()
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean('1')
-1.0
->>> isinstance(f.clean('1'), float)
-True
->>> f.clean('23')
-23.0
->>> f.clean('3.14')
-3.1400000000000001
->>> f.clean(3.14)
-3.1400000000000001
->>> f.clean(42)
-42.0
->>> f.clean('a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a number.']
->>> f.clean('1.0 ')
-1.0
->>> f.clean(' 1.0')
-1.0
->>> f.clean(' 1.0 ')
-1.0
->>> f.clean('1.0a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a number.']
-
->>> f = FloatField(required=False)
->>> f.clean('')
-
->>> f.clean(None)
-
->>> f.clean('1')
-1.0
-
-FloatField accepts min_value and max_value just like IntegerField:
->>> f = FloatField(max_value=1.5, min_value=0.5)
-
->>> f.clean('1.6')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is less than or equal to 1.5.']
->>> f.clean('0.4')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
->>> f.clean('1.5')
-1.5
->>> f.clean('0.5')
-0.5
-
-# DecimalField ################################################################
-
->>> f = DecimalField(max_digits=4, decimal_places=2)
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean('1') == Decimal("1")
-True
->>> isinstance(f.clean('1'), Decimal)
-True
->>> f.clean('23') == Decimal("23")
-True
->>> f.clean('3.14') == Decimal("3.14")
-True
->>> f.clean(3.14) == Decimal("3.14")
-True
->>> f.clean(Decimal('3.14')) == Decimal("3.14")
-True
->>> f.clean('a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a number.']
->>> f.clean(u'łąść')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a number.']
->>> f.clean('1.0 ') == Decimal("1.0")
-True
->>> f.clean(' 1.0') == Decimal("1.0")
-True
->>> f.clean(' 1.0 ') == Decimal("1.0")
-True
->>> f.clean('1.0a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a number.']
->>> f.clean('123.45')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 4 digits in total.']
->>> f.clean('1.234')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 2 decimal places.']
->>> f.clean('123.4')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 2 digits before the decimal point.']
->>> f.clean('-12.34') == Decimal("-12.34")
-True
->>> f.clean('-123.45')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 4 digits in total.']
->>> f.clean('-.12') == Decimal("-0.12")
-True
->>> f.clean('-00.12') == Decimal("-0.12")
-True
->>> f.clean('-000.12') == Decimal("-0.12")
-True
->>> f.clean('-000.123')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 2 decimal places.']
->>> f.clean('-000.12345')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 4 digits in total.']
->>> f.clean('--0.12')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a number.']
-
->>> f = DecimalField(max_digits=4, decimal_places=2, required=False)
->>> f.clean('')
-
->>> f.clean(None)
-
->>> f.clean('1') == Decimal("1")
-True
-
-DecimalField accepts min_value and max_value just like IntegerField:
->>> f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
-
->>> f.clean('1.6')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is less than or equal to 1.5.']
->>> f.clean('0.4')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
->>> f.clean('1.5') == Decimal("1.5")
-True
->>> f.clean('0.5') == Decimal("0.5")
-True
->>> f.clean('.5') == Decimal("0.5")
-True
->>> f.clean('00.50') == Decimal("0.50")
-True
-
-
->>> f = DecimalField(decimal_places=2)
->>> f.clean('0.00000001')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 2 decimal places.']
-
-
->>> f = DecimalField(max_digits=3)
-
-# Leading whole zeros "collapse" to one digit.
->>> f.clean('0000000.10') == Decimal("0.1")
-True
-
-# But a leading 0 before the . doesn't count towards max_digits
->>> f.clean('0000000.100') == Decimal("0.100")
-True
-
-# Only leading whole zeros "collapse" to one digit.
->>> f.clean('000000.02') == Decimal('0.02')
-True
->>> f.clean('000000.0002')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 3 digits in total.']
->>> f.clean('.002') == Decimal("0.002")
-True
-
->>> f = DecimalField(max_digits=2, decimal_places=2)
->>> f.clean('.01') == Decimal(".01")
-True
->>> f.clean('1.1')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure that there are no more than 0 digits before the decimal point.']
-
-
-# DateField ###################################################################
-
->>> 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('10/25/2006')
-datetime.date(2006, 10, 25)
->>> f.clean('10/25/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('25/10/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('10/25/2006')
-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.']
-
-# TimeField ###################################################################
-
->>> import datetime
->>> f = TimeField()
->>> f.clean(datetime.time(14, 25))
-datetime.time(14, 25)
->>> f.clean(datetime.time(14, 25, 59))
-datetime.time(14, 25, 59)
->>> f.clean('14:25')
-datetime.time(14, 25)
->>> f.clean('14:25:59')
-datetime.time(14, 25, 59)
->>> f.clean('hello')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid time.']
->>> f.clean('1:24 p.m.')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid time.']
-
-TimeField accepts an optional input_formats parameter:
->>> f = TimeField(input_formats=['%I:%M %p'])
->>> f.clean(datetime.time(14, 25))
-datetime.time(14, 25)
->>> f.clean(datetime.time(14, 25, 59))
-datetime.time(14, 25, 59)
->>> f.clean('4:25 AM')
-datetime.time(4, 25)
->>> f.clean('4:25 PM')
-datetime.time(16, 25)
-
-The input_formats parameter overrides all default input formats,
-so the default formats won't work unless you specify them:
->>> f.clean('14:30:45')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid time.']
-
-# DateTimeField ###############################################################
-
->>> 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('10/25/2006 14:30:45')
-datetime.datetime(2006, 10, 25, 14, 30, 45)
->>> f.clean('10/25/2006 14:30:00')
-datetime.datetime(2006, 10, 25, 14, 30)
->>> f.clean('10/25/2006 14:30')
-datetime.datetime(2006, 10, 25, 14, 30)
->>> f.clean('10/25/2006')
-datetime.datetime(2006, 10, 25, 0, 0)
->>> f.clean('10/25/06 14:30:45')
-datetime.datetime(2006, 10, 25, 14, 30, 45)
->>> f.clean('10/25/06 14:30:00')
-datetime.datetime(2006, 10, 25, 14, 30)
->>> f.clean('10/25/06 14:30')
-datetime.datetime(2006, 10, 25, 14, 30)
->>> f.clean('10/25/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'
-
-# RegexField ##################################################################
-
->>> f = RegexField('^\d[A-F]\d$')
->>> f.clean('2A2')
-u'2A2'
->>> f.clean('3F3')
-u'3F3'
->>> f.clean('3G3')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
->>> f.clean(' 2A2')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
->>> f.clean('2A2 ')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
-
->>> f = RegexField('^\d[A-F]\d$', required=False)
->>> f.clean('2A2')
-u'2A2'
->>> f.clean('3F3')
-u'3F3'
->>> f.clean('3G3')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
->>> f.clean('')
-u''
-
-Alternatively, RegexField can take a compiled regular expression:
->>> f = RegexField(re.compile('^\d[A-F]\d$'))
->>> f.clean('2A2')
-u'2A2'
->>> f.clean('3F3')
-u'3F3'
->>> f.clean('3G3')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
->>> f.clean(' 2A2')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
->>> f.clean('2A2 ')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
-
-RegexField takes an optional error_message argument:
->>> f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.')
->>> f.clean('1234')
-u'1234'
->>> f.clean('123')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a four-digit number.']
->>> f.clean('abcd')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a four-digit number.']
-
-RegexField also access min_length and max_length parameters, for convenience.
->>> f = RegexField('^\d+$', min_length=5, max_length=10)
->>> f.clean('123')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at least 5 characters (it has 3).']
->>> f.clean('abc')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at least 5 characters (it has 3).']
->>> f.clean('12345')
-u'12345'
->>> f.clean('1234567890')
-u'1234567890'
->>> f.clean('12345678901')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at most 10 characters (it has 11).']
->>> f.clean('12345a')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid value.']
-
-# EmailField ##################################################################
-
->>> f = EmailField()
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean('person@example.com')
-u'person@example.com'
->>> f.clean('foo')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('foo@')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('foo@bar')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('example@invalid-.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('example@-invalid.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('example@inv-.alid-.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('example@inv-.-alid.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('example@valid-----hyphens.com')
-u'example@valid-----hyphens.com'
-
->>> f.clean('example@valid-with-hyphens.com')
-u'example@valid-with-hyphens.com'
-
-# Check for runaway regex security problem. This will take for-freeking-ever
-# if the security fix isn't in place.
->>> f.clean('viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058')
-Traceback (most recent call last):
- ...
-ValidationError: [u'Enter a valid e-mail address.']
-
->>> f = EmailField(required=False)
->>> f.clean('')
-u''
->>> f.clean(None)
-u''
->>> f.clean('person@example.com')
-u'person@example.com'
->>> f.clean('foo')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('foo@')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('foo@bar')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
-
-EmailField also access min_length and max_length parameters, for convenience.
->>> f = EmailField(min_length=10, max_length=15)
->>> f.clean('a@foo.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at least 10 characters (it has 9).']
->>> f.clean('alf@foo.com')
-u'alf@foo.com'
->>> f.clean('alf123456788@foo.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at most 15 characters (it has 20).']
-
-# FileField ##################################################################
-
->>> f = FileField()
->>> f.clean('')
-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.clean('', 'files/test1.pdf')
-'files/test1.pdf'
-
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
-
->>> f.clean(None, '')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
-
->>> f.clean(None, 'files/test2.pdf')
-'files/test2.pdf'
-
->>> f.clean(SimpleUploadedFile('', ''))
-Traceback (most recent call last):
-...
-ValidationError: [u'No file was submitted. Check the encoding type on the form.']
-
->>> f.clean(SimpleUploadedFile('', ''), '')
-Traceback (most recent call last):
-...
-ValidationError: [u'No file was submitted. Check the encoding type on the form.']
-
->>> f.clean(None, 'files/test3.pdf')
-'files/test3.pdf'
-
->>> f.clean('some content that is not a file')
-Traceback (most recent call last):
-...
-ValidationError: [u'No file was submitted. Check the encoding type on the form.']
-
->>> f.clean(SimpleUploadedFile('name', None))
-Traceback (most recent call last):
-...
-ValidationError: [u'The submitted file is empty.']
-
->>> f.clean(SimpleUploadedFile('name', ''))
-Traceback (most recent call last):
-...
-ValidationError: [u'The submitted file is empty.']
-
->>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
-
-
->>> type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')))
-
-
->>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
-
-
->>> f = FileField(max_length = 5)
->>> f.clean(SimpleUploadedFile('test_maxlength.txt', 'hello world'))
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this filename has at most 5 characters (it has 18).']
-
->>> f.clean('', 'files/test1.pdf')
-'files/test1.pdf'
-
->>> f.clean(None, 'files/test2.pdf')
-'files/test2.pdf'
-
->>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
-
-
-# URLField ##################################################################
-
->>> f = URLField()
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean('http://localhost')
-u'http://localhost/'
->>> f.clean('http://example.com')
-u'http://example.com/'
->>> f.clean('http://www.example.com')
-u'http://www.example.com/'
->>> f.clean('http://www.example.com:8000/test')
-u'http://www.example.com:8000/test'
->>> f.clean('valid-with-hyphens.com')
-u'http://valid-with-hyphens.com/'
->>> f.clean('subdomain.domain.com')
-u'http://subdomain.domain.com/'
->>> f.clean('http://200.8.9.10')
-u'http://200.8.9.10/'
->>> f.clean('http://200.8.9.10:8000/test')
-u'http://200.8.9.10:8000/test'
->>> f.clean('foo')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://example')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://example.')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://invalid-.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://-invalid.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://inv-.alid-.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://inv-.-alid.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://valid-----hyphens.com')
-u'http://valid-----hyphens.com/'
-
->>> f = URLField(required=False)
->>> f.clean('')
-u''
->>> f.clean(None)
-u''
->>> f.clean('http://example.com')
-u'http://example.com/'
->>> f.clean('http://www.example.com')
-u'http://www.example.com/'
->>> f.clean('foo')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://example')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://example.')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('.')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('com.')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://example.com.')
-u'http://example.com./'
->>> f.clean('example.com.')
-u'http://example.com./'
-
-# hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed
->>> f.clean('http://%s' % ("X"*200,))
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
-
-# a second test, to make sure the problem is really addressed, even on
-# domains that don't fail the domain label length check in the regex
->>> f.clean('http://%s' % ("X"*60,))
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
-
->>> f.clean('http://.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
-
-URLField takes an optional verify_exists parameter, which is False by default.
-This verifies that the URL is live on the Internet and doesn't return a 404 or 500:
->>> f = URLField(verify_exists=True)
->>> f.clean('http://www.google.com') # This will fail if there's no Internet connection
-u'http://www.google.com/'
->>> f.clean('http://example')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
->>> f.clean('http://www.broken.djangoproject.com') # bad domain
-Traceback (most recent call last):
-...
-ValidationError: [u'This URL appears to be a broken link.']
->>> f.clean('http://google.com/we-love-microsoft.html') # good domain, bad page
-Traceback (most recent call last):
-...
-ValidationError: [u'This URL appears to be a broken link.']
->>> f = URLField(verify_exists=True, required=False)
->>> f.clean('')
-u''
->>> f.clean('http://www.google.com') # This will fail if there's no Internet connection
-u'http://www.google.com/'
-
-URLField also access min_length and max_length parameters, for convenience.
->>> f = URLField(min_length=15, max_length=20)
->>> f.clean('http://f.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at least 15 characters (it has 13).']
->>> f.clean('http://example.com')
-u'http://example.com/'
->>> f.clean('http://abcdefghijklmnopqrstuvwxyz.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at most 20 characters (it has 38).']
-
-URLField should prepend 'http://' if no scheme was given
->>> f = URLField(required=False)
->>> f.clean('example.com')
-u'http://example.com/'
->>> f.clean('')
-u''
->>> f.clean('https://example.com')
-u'https://example.com/'
-
-URLField should append '/' if no path was given
->>> f = URLField()
->>> f.clean('http://example.com')
-u'http://example.com/'
-
-URLField shouldn't change the path if it was given
->>> f.clean('http://example.com/test')
-u'http://example.com/test'
-
-# BooleanField ################################################################
-
->>> f = BooleanField()
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(True)
-True
->>> f.clean(False)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(1)
-True
->>> f.clean(0)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean('Django rocks')
-True
-
->>> f.clean('True')
-True
->>> f.clean('False')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
-
->>> f = BooleanField(required=False)
->>> f.clean('')
-False
->>> f.clean(None)
-False
->>> f.clean(True)
-True
->>> f.clean(False)
-False
->>> f.clean(1)
-True
->>> f.clean(0)
-False
->>> f.clean('1')
-True
->>> f.clean('0')
-False
->>> f.clean('Django rocks')
-True
-
-A form's BooleanField with a hidden widget will output the string 'False', so
-that should clean to the boolean value False:
->>> f.clean('False')
-False
-
-# ChoiceField #################################################################
-
->>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')])
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(1)
-u'1'
->>> f.clean('1')
-u'1'
->>> f.clean('3')
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
-
->>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
->>> f.clean('')
-u''
->>> f.clean(None)
-u''
->>> f.clean(1)
-u'1'
->>> f.clean('1')
-u'1'
->>> f.clean('3')
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
-
->>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
->>> f.clean('J')
-u'J'
->>> f.clean('John')
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. John is not one of the available choices.']
-
->>> f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
->>> f.clean(1)
-u'1'
->>> f.clean('1')
-u'1'
->>> f.clean(3)
-u'3'
->>> f.clean('3')
-u'3'
->>> f.clean(5)
-u'5'
->>> f.clean('5')
-u'5'
->>> f.clean('6')
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
-
-# TypedChoiceField ############################################################
-
-# TypedChoiceField is just like ChoiceField, except that coerced types will
-# be returned:
->>> f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
->>> f.clean('1')
-1
->>> f.clean('2')
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 2 is not one of the available choices.']
-
-# Different coercion, same validation.
->>> f.coerce = float
->>> f.clean('1')
-1.0
-
-
-# This can also cause weirdness: be careful (bool(-1) == True, remember)
->>> f.coerce = bool
->>> f.clean('-1')
-True
-
-# Even more weirdness: if you have a valid choice but your coercion function
-# can't coerce, you'll still get a validation error. Don't do this!
->>> f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int)
->>> f.clean('B')
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. B is not one of the available choices.']
-
-# Required fields require values
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
-
-# Non-required fields aren't required
->>> f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False)
->>> f.clean('')
-''
-
-# If you want cleaning an empty value to return a different type, tell the field
->>> f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None)
->>> print f.clean('')
-None
-
-# NullBooleanField ############################################################
-
->>> f = NullBooleanField()
->>> f.clean('')
->>> f.clean(True)
-True
->>> f.clean(False)
-False
->>> f.clean(None)
->>> f.clean('0')
-False
->>> f.clean('1')
-True
->>> f.clean('2')
->>> f.clean('3')
->>> f.clean('hello')
-
-# Make sure that the internal value is preserved if using HiddenInput (#7753)
->>> class HiddenNullBooleanForm(Form):
-... hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True)
-... hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False)
->>> f = HiddenNullBooleanForm()
->>> print f
-
->>> f = HiddenNullBooleanForm({ 'hidden_nullbool1': 'True', 'hidden_nullbool2': 'False' })
->>> f.full_clean()
->>> f.cleaned_data['hidden_nullbool1']
-True
->>> f.cleaned_data['hidden_nullbool2']
-False
-
-# Make sure we're compatible with MySQL, which uses 0 and 1 for its boolean
-# values. (#9609)
->>> NULLBOOL_CHOICES = (('1', 'Yes'), ('0', 'No'), ('', 'Unknown'))
->>> class MySQLNullBooleanForm(Form):
-... nullbool0 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
-... nullbool1 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
-... nullbool2 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
->>> f = MySQLNullBooleanForm({ 'nullbool0': '1', 'nullbool1': '0', 'nullbool2': '' })
->>> f.full_clean()
->>> f.cleaned_data['nullbool0']
-True
->>> f.cleaned_data['nullbool1']
-False
->>> f.cleaned_data['nullbool2']
-
-# MultipleChoiceField #########################################################
-
->>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')])
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean([1])
-[u'1']
->>> f.clean(['1'])
-[u'1']
->>> f.clean(['1', '2'])
-[u'1', u'2']
->>> f.clean([1, '2'])
-[u'1', u'2']
->>> f.clean((1, '2'))
-[u'1', u'2']
->>> f.clean('hello')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a list of values.']
->>> f.clean([])
-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.clean(['3'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
-
->>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
->>> f.clean('')
-[]
->>> f.clean(None)
-[]
->>> f.clean([1])
-[u'1']
->>> f.clean(['1'])
-[u'1']
->>> f.clean(['1', '2'])
-[u'1', u'2']
->>> f.clean([1, '2'])
-[u'1', u'2']
->>> f.clean((1, '2'))
-[u'1', u'2']
->>> f.clean('hello')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a list of values.']
->>> f.clean([])
-[]
->>> f.clean(())
-[]
->>> f.clean(['3'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
-
->>> f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
->>> f.clean([1])
-[u'1']
->>> f.clean(['1'])
-[u'1']
->>> f.clean([1, 5])
-[u'1', u'5']
->>> f.clean([1, '5'])
-[u'1', u'5']
->>> f.clean(['1', 5])
-[u'1', u'5']
->>> f.clean(['1', '5'])
-[u'1', u'5']
->>> f.clean(['6'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
->>> f.clean(['1','6'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
-
-
-# ComboField ##################################################################
-
-ComboField takes a list of fields that should be used to validate a value,
-in that order.
->>> f = ComboField(fields=[CharField(max_length=20), EmailField()])
->>> f.clean('test@example.com')
-u'test@example.com'
->>> f.clean('longemailaddress@example.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at most 20 characters (it has 28).']
->>> f.clean('not an e-mail')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
->>> f.clean(None)
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
-
->>> f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False)
->>> f.clean('test@example.com')
-u'test@example.com'
->>> f.clean('longemailaddress@example.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Ensure this value has at most 20 characters (it has 28).']
->>> f.clean('not an e-mail')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid e-mail address.']
->>> f.clean('')
-u''
->>> f.clean(None)
-u''
-
-# FilePathField ###############################################################
-
->>> def fix_os_paths(x):
-... if isinstance(x, basestring):
-... return x.replace('\\', '/')
-... elif isinstance(x, tuple):
-... return tuple(fix_os_paths(list(x)))
-... elif isinstance(x, list):
-... return [fix_os_paths(y) for y in x]
-... else:
-... return x
-...
->>> import os
->>> from django import forms
->>> path = forms.__file__
->>> path = os.path.dirname(path) + '/'
->>> fix_os_paths(path)
-'.../django/forms/'
->>> f = forms.FilePathField(path=path)
->>> f.choices = [p for p in f.choices if p[0].endswith('.py')]
->>> f.choices.sort()
->>> fix_os_paths(f.choices)
-[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/widgets.py', 'widgets.py')]
->>> f.clean('fields.py')
-Traceback (most recent call last):
-...
-ValidationError: [u'Select a valid choice. fields.py is not one of the available choices.']
->>> fix_os_paths(f.clean(path + 'fields.py'))
-u'.../django/forms/fields.py'
->>> f = forms.FilePathField(path=path, match='^.*?\.py$')
->>> f.choices.sort()
->>> fix_os_paths(f.choices)
-[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/widgets.py', 'widgets.py')]
->>> f = forms.FilePathField(path=path, recursive=True, match='^.*?\.py$')
->>> f.choices.sort()
->>> fix_os_paths(f.choices)
-[('.../django/forms/__init__.py', '__init__.py'), ('.../django/forms/extras/__init__.py', 'extras/__init__.py'), ('.../django/forms/extras/widgets.py', 'extras/widgets.py'), ('.../django/forms/fields.py', 'fields.py'), ('.../django/forms/forms.py', 'forms.py'), ('.../django/forms/models.py', 'models.py'), ('.../django/forms/util.py', 'util.py'), ('.../django/forms/widgets.py', 'widgets.py')]
-
-# SplitDateTimeField ##########################################################
-
->>> f = SplitDateTimeField()
->>> f.widget
->> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])
-datetime.datetime(2006, 1, 10, 7, 30)
->>> 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.clean('hello')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a list of values.']
->>> f.clean(['hello', 'there'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid date.', u'Enter a valid time.']
->>> f.clean(['2006-01-10', 'there'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid time.']
->>> f.clean(['hello', '07:30'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid date.']
-
->>> f = SplitDateTimeField(required=False)
->>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])
-datetime.datetime(2006, 1, 10, 7, 30)
->>> f.clean(['2006-01-10', '07:30'])
-datetime.datetime(2006, 1, 10, 7, 30)
->>> f.clean(None)
->>> f.clean('')
->>> f.clean([''])
->>> f.clean(['', ''])
->>> f.clean('hello')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a list of values.']
->>> f.clean(['hello', 'there'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid date.', u'Enter a valid time.']
->>> f.clean(['2006-01-10', 'there'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid time.']
->>> f.clean(['hello', '07:30'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid date.']
->>> f.clean(['2006-01-10', ''])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid time.']
->>> f.clean(['2006-01-10'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid time.']
->>> f.clean(['', '07:30'])
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid date.']
"""
+import datetime
+import time
+import re
+import os
+
+from unittest import TestCase
+
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.forms import *
+from django.forms.widgets import RadioFieldRenderer
+
+try:
+ from decimal import Decimal
+except ImportError:
+ from django.utils._decimal import Decimal
+
+
+def fix_os_paths(x):
+ if isinstance(x, basestring):
+ return x.replace('\\', '/')
+ elif isinstance(x, tuple):
+ return tuple(fix_os_paths(list(x)))
+ elif isinstance(x, list):
+ return [fix_os_paths(y) for y in x]
+ else:
+ return x
+
+
+class FieldsTests(TestCase):
+
+ def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs):
+ self.assertRaises(error, callable, *args, **kwargs)
+ try:
+ callable(*args, **kwargs)
+ except error, e:
+ self.assertEqual(message, str(e))
+
+ # CharField ###################################################################
+
+ def test_charfield_0(self):
+ f = CharField()
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'hello', f.clean('hello'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3]))
+
+ def test_charfield_1(self):
+ f = CharField(required=False)
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'hello', f.clean('hello'))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'[1, 2, 3]', f.clean([1, 2, 3]))
+
+ def test_charfield_2(self):
+ f = CharField(max_length=10, required=False)
+ self.assertEqual(u'12345', f.clean('12345'))
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '1234567890a')
+
+ def test_charfield_3(self):
+ f = CharField(min_length=10, required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345')
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertEqual(u'1234567890a', f.clean('1234567890a'))
+
+ def test_charfield_4(self):
+ f = CharField(min_length=10, required=True)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 5).']", f.clean, '12345')
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertEqual(u'1234567890a', f.clean('1234567890a'))
+
+ # IntegerField ################################################################
+
+ def test_integerfield_5(self):
+ f = IntegerField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(1, f.clean('1'))
+ self.assertEqual(True, isinstance(f.clean('1'), int))
+ self.assertEqual(23, f.clean('23'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 'a')
+ self.assertEqual(42, f.clean(42))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 3.14)
+ self.assertEqual(1, f.clean('1 '))
+ self.assertEqual(1, f.clean(' 1'))
+ self.assertEqual(1, f.clean(' 1 '))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a')
+
+ def test_integerfield_6(self):
+ f = IntegerField(required=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual('None', repr(f.clean('')))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual('None', repr(f.clean(None)))
+ self.assertEqual(1, f.clean('1'))
+ self.assertEqual(True, isinstance(f.clean('1'), int))
+ self.assertEqual(23, f.clean('23'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, 'a')
+ self.assertEqual(1, f.clean('1 '))
+ self.assertEqual(1, f.clean(' 1'))
+ self.assertEqual(1, f.clean(' 1 '))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a whole number.']", f.clean, '1a')
+
+ def test_integerfield_7(self):
+ f = IntegerField(max_value=10)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(1, f.clean(1))
+ self.assertEqual(10, f.clean(10))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 10.']", f.clean, 11)
+ self.assertEqual(10, f.clean('10'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 10.']", f.clean, '11')
+
+ def test_integerfield_8(self):
+ f = IntegerField(min_value=10)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1)
+ self.assertEqual(10, f.clean(10))
+ self.assertEqual(11, f.clean(11))
+ self.assertEqual(10, f.clean('10'))
+ self.assertEqual(11, f.clean('11'))
+
+ def test_integerfield_9(self):
+ f = IntegerField(min_value=10, max_value=20)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 10.']", f.clean, 1)
+ self.assertEqual(10, f.clean(10))
+ self.assertEqual(11, f.clean(11))
+ self.assertEqual(10, f.clean('10'))
+ self.assertEqual(11, f.clean('11'))
+ self.assertEqual(20, f.clean(20))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 20.']", f.clean, 21)
+
+ # FloatField ##################################################################
+
+ def test_floatfield_10(self):
+ f = FloatField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(1.0, f.clean('1'))
+ self.assertEqual(True, isinstance(f.clean('1'), float))
+ self.assertEqual(23.0, f.clean('23'))
+ self.assertEqual(3.1400000000000001, f.clean('3.14'))
+ self.assertEqual(3.1400000000000001, f.clean(3.14))
+ self.assertEqual(42.0, f.clean(42))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, 'a')
+ self.assertEqual(1.0, f.clean('1.0 '))
+ self.assertEqual(1.0, f.clean(' 1.0'))
+ self.assertEqual(1.0, f.clean(' 1.0 '))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '1.0a')
+
+ def test_floatfield_11(self):
+ f = FloatField(required=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(1.0, f.clean('1'))
+
+ def test_floatfield_12(self):
+ f = FloatField(max_value=1.5, min_value=0.5)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4')
+ self.assertEqual(1.5, f.clean('1.5'))
+ self.assertEqual(0.5, f.clean('0.5'))
+
+ # DecimalField ################################################################
+
+ def test_decimalfield_13(self):
+ f = DecimalField(max_digits=4, decimal_places=2)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(f.clean('1'), Decimal("1"))
+ self.assertEqual(True, isinstance(f.clean('1'), Decimal))
+ self.assertEqual(f.clean('23'), Decimal("23"))
+ self.assertEqual(f.clean('3.14'), Decimal("3.14"))
+ self.assertEqual(f.clean(3.14), Decimal("3.14"))
+ self.assertEqual(f.clean(Decimal('3.14')), Decimal("3.14"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, 'a')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, u'łąść')
+ self.assertEqual(f.clean('1.0 '), Decimal("1.0"))
+ self.assertEqual(f.clean(' 1.0'), Decimal("1.0"))
+ self.assertEqual(f.clean(' 1.0 '), Decimal("1.0"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '1.0a')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '123.45')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '1.234')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 digits before the decimal point.']", f.clean, '123.4')
+ self.assertEqual(f.clean('-12.34'), Decimal("-12.34"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '-123.45')
+ self.assertEqual(f.clean('-.12'), Decimal("-0.12"))
+ self.assertEqual(f.clean('-00.12'), Decimal("-0.12"))
+ self.assertEqual(f.clean('-000.12'), Decimal("-0.12"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '-000.123')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 4 digits in total.']", f.clean, '-000.12345')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a number.']", f.clean, '--0.12')
+
+ def test_decimalfield_14(self):
+ f = DecimalField(max_digits=4, decimal_places=2, required=False)
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(f.clean('1'), Decimal("1"))
+
+ def test_decimalfield_15(self):
+ f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is less than or equal to 1.5.']", f.clean, '1.6')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4')
+ self.assertEqual(f.clean('1.5'), Decimal("1.5"))
+ self.assertEqual(f.clean('0.5'), Decimal("0.5"))
+ self.assertEqual(f.clean('.5'), Decimal("0.5"))
+ self.assertEqual(f.clean('00.50'), Decimal("0.50"))
+
+ def test_decimalfield_16(self):
+ f = DecimalField(decimal_places=2)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 2 decimal places.']", f.clean, '0.00000001')
+
+ def test_decimalfield_17(self):
+ f = DecimalField(max_digits=3)
+ # Leading whole zeros "collapse" to one digit.
+ self.assertEqual(f.clean('0000000.10'), Decimal("0.1"))
+ # But a leading 0 before the . doesn't count towards max_digits
+ self.assertEqual(f.clean('0000000.100'), Decimal("0.100"))
+ # Only leading whole zeros "collapse" to one digit.
+ self.assertEqual(f.clean('000000.02'), Decimal('0.02'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 3 digits in total.']", f.clean, '000000.0002')
+ self.assertEqual(f.clean('.002'), Decimal("0.002"))
+
+ def test_decimalfield_18(self):
+ f = DecimalField(max_digits=2, decimal_places=2)
+ self.assertEqual(f.clean('.01'), Decimal(".01"))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure that there are no more than 0 digits before the decimal point.']", f.clean, '1.1')
+
+ # DateField ###################################################################
+
+ def test_datefield_19(self):
+ f = DateField()
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('2006-10-25'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('10/25/2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('10/25/06'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('Oct 25 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('October 25 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('October 25, 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October 2006'))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October, 2006'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '2006-4-31')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '200a-10-25')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '25/10/06')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+
+ def test_datefield_20(self):
+ f = DateField(required=False)
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual('None', repr(f.clean(None)))
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual('None', repr(f.clean('')))
+
+ def test_datefield_21(self):
+ f = DateField(input_formats=['%Y %m %d'])
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.date(2006, 10, 25), f.clean('2006 10 25'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '2006-10-25')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/2006')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, '10/25/06')
+
+ # TimeField ###################################################################
+
+ def test_timefield_22(self):
+ f = TimeField()
+ self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25)))
+ self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59)))
+ self.assertEqual(datetime.time(14, 25), f.clean('14:25'))
+ self.assertEqual(datetime.time(14, 25, 59), f.clean('14:25:59'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '1:24 p.m.')
+
+ def test_timefield_23(self):
+ f = TimeField(input_formats=['%I:%M %p'])
+ self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25)))
+ self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59)))
+ self.assertEqual(datetime.time(4, 25), f.clean('4:25 AM'))
+ self.assertEqual(datetime.time(16, 25), f.clean('4:25 PM'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, '14:30:45')
+
+ # DateTimeField ###############################################################
+
+ def test_datetimefield_24(self):
+ f = DateTimeField()
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59, 200), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('2006-10-25 14:30:45'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30:00'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('2006-10-25'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/2006 14:30:45'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30:00'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/2006'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/06 14:30:45'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30:00'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30'))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/06'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 4:30 p.m.')
+
+ def test_datetimefield_25(self):
+ f = DateTimeField(input_formats=['%Y %m %d %I:%M %p'])
+ self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 59, 200), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)))
+ self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006 10 25 2:30 PM'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date/time.']", f.clean, '2006-10-25 14:30:45')
+
+ def test_datetimefield_26(self):
+ f = DateTimeField(required=False)
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual('None', repr(f.clean(None)))
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual('None', repr(f.clean('')))
+
+ # RegexField ##################################################################
+
+ def test_regexfield_27(self):
+ f = RegexField('^\d[A-F]\d$')
+ self.assertEqual(u'2A2', f.clean('2A2'))
+ self.assertEqual(u'3F3', f.clean('3F3'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, ' 2A2')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+
+ def test_regexfield_28(self):
+ f = RegexField('^\d[A-F]\d$', required=False)
+ self.assertEqual(u'2A2', f.clean('2A2'))
+ self.assertEqual(u'3F3', f.clean('3F3'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3')
+ self.assertEqual(u'', f.clean(''))
+
+ def test_regexfield_29(self):
+ f = RegexField(re.compile('^\d[A-F]\d$'))
+ self.assertEqual(u'2A2', f.clean('2A2'))
+ self.assertEqual(u'3F3', f.clean('3F3'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '3G3')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, ' 2A2')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '2A2 ')
+
+ def test_regexfield_30(self):
+ f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.')
+ self.assertEqual(u'1234', f.clean('1234'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, '123')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a four-digit number.']", f.clean, 'abcd')
+
+ def test_regexfield_31(self):
+ f = RegexField('^\d+$', min_length=5, max_length=10)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).']", f.clean, '123')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).']", f.clean, 'abc')
+ self.assertEqual(u'12345', f.clean('12345'))
+ self.assertEqual(u'1234567890', f.clean('1234567890'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '12345678901')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid value.']", f.clean, '12345a')
+
+ # EmailField ##################################################################
+
+ def test_emailfield_32(self):
+ f = EmailField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(u'person@example.com', f.clean('person@example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@invalid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@-invalid.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.alid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'example@inv-.-alid.com')
+ self.assertEqual(u'example@valid-----hyphens.com', f.clean('example@valid-----hyphens.com'))
+ self.assertEqual(u'example@valid-with-hyphens.com', f.clean('example@valid-with-hyphens.com'))
+
+ def test_email_regexp_for_performance(self):
+ f = EmailField()
+ # Check for runaway regex security problem. This will take for-freeking-ever
+ # if the security fix isn't in place.
+ self.assertRaisesErrorWithMessage(
+ ValidationError,
+ "[u'Enter a valid e-mail address.']",
+ f.clean,
+ 'viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058'
+ )
+
+ def test_emailfield_33(self):
+ f = EmailField(required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'person@example.com', f.clean('person@example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar')
+
+ def test_emailfield_34(self):
+ f = EmailField(min_length=10, max_length=15)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 10 characters (it has 9).']", f.clean, 'a@foo.com')
+ self.assertEqual(u'alf@foo.com', f.clean('alf@foo.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 15 characters (it has 20).']", f.clean, 'alf123456788@foo.com')
+
+ # FileField ##################################################################
+
+ def test_filefield_35(self):
+ f = FileField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '', '')
+ self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None, '')
+ self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', ''))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, SimpleUploadedFile('', ''), '')
+ self.assertEqual('files/test3.pdf', f.clean(None, 'files/test3.pdf'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'No file was submitted. Check the encoding type on the form.']", f.clean, 'some content that is not a file')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', None))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'The submitted file is empty.']", f.clean, SimpleUploadedFile('name', ''))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'))))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'))))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf')))
+
+ def test_filefield_36(self):
+ f = FileField(max_length = 5)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this filename has at most 5 characters (it has 18).']", f.clean, SimpleUploadedFile('test_maxlength.txt', 'hello world'))
+ self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf'))
+ self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf'))
+ self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', 'Some File Content'))))
+
+ # URLField ##################################################################
+
+ def test_urlfield_37(self):
+ f = URLField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(u'http://localhost/', f.clean('http://localhost'))
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertEqual(u'http://example.com./', f.clean('http://example.com.'))
+ self.assertEqual(u'http://www.example.com/', f.clean('http://www.example.com'))
+ self.assertEqual(u'http://www.example.com:8000/test', f.clean('http://www.example.com:8000/test'))
+ self.assertEqual(u'http://valid-with-hyphens.com/', f.clean('valid-with-hyphens.com'))
+ self.assertEqual(u'http://subdomain.domain.com/', f.clean('subdomain.domain.com'))
+ self.assertEqual(u'http://200.8.9.10/', f.clean('http://200.8.9.10'))
+ self.assertEqual(u'http://200.8.9.10:8000/test', f.clean('http://200.8.9.10:8000/test'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'com.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, '.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://invalid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://-invalid.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.alid-.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.-alid.com')
+ self.assertEqual(u'http://valid-----hyphens.com/', f.clean('http://valid-----hyphens.com'))
+
+ def test_url_regex_ticket11198(self):
+ f = URLField()
+ # hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*200,))
+
+ # a second test, to make sure the problem is really addressed, even on
+ # domains that don't fail the domain label length check in the regex
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*60,))
+
+ def test_urlfield_38(self):
+ f = URLField(required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertEqual(u'http://www.example.com/', f.clean('http://www.example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'foo')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example.')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://.com')
+
+ def test_urlfield_39(self):
+ f = URLField(verify_exists=True)
+ self.assertEqual(u'http://www.google.com/', f.clean('http://www.google.com')) # This will fail if there's no Internet connection
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://example')
+ self.assertRaises(ValidationError, f.clean, 'http://www.broken.djangoproject.com') # bad domain
+ try:
+ f.clean('http://www.broken.djangoproject.com') # bad domain
+ except ValidationError, e:
+ self.assertEqual("[u'This URL appears to be a broken link.']", str(e))
+ self.assertRaises(ValidationError, f.clean, 'http://google.com/we-love-microsoft.html') # good domain, bad page
+ try:
+ f.clean('http://google.com/we-love-microsoft.html') # good domain, bad page
+ except ValidationError, e:
+ self.assertEqual("[u'This URL appears to be a broken link.']", str(e))
+
+ def test_urlfield_40(self):
+ f = URLField(verify_exists=True, required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'http://www.google.com/', f.clean('http://www.google.com')) # This will fail if there's no Internet connection
+
+ def test_urlfield_41(self):
+ f = URLField(min_length=15, max_length=20)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 15 characters (it has 13).']", f.clean, 'http://f.com')
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 38).']", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com')
+
+ def test_urlfield_42(self):
+ f = URLField(required=False)
+ self.assertEqual(u'http://example.com/', f.clean('example.com'))
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'https://example.com/', f.clean('https://example.com'))
+
+ def test_urlfield_43(self):
+ f = URLField()
+ self.assertEqual(u'http://example.com/', f.clean('http://example.com'))
+ self.assertEqual(u'http://example.com/test', f.clean('http://example.com/test'))
+
+ # BooleanField ################################################################
+
+ def test_booleanfield_44(self):
+ f = BooleanField()
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(True, f.clean(True))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, False)
+ self.assertEqual(True, f.clean(1))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, 0)
+ self.assertEqual(True, f.clean('Django rocks'))
+ self.assertEqual(True, f.clean('True'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, 'False')
+
+ def test_booleanfield_45(self):
+ f = BooleanField(required=False)
+ self.assertEqual(False, f.clean(''))
+ self.assertEqual(False, f.clean(None))
+ self.assertEqual(True, f.clean(True))
+ self.assertEqual(False, f.clean(False))
+ self.assertEqual(True, f.clean(1))
+ self.assertEqual(False, f.clean(0))
+ self.assertEqual(True, f.clean('1'))
+ self.assertEqual(False, f.clean('0'))
+ self.assertEqual(True, f.clean('Django rocks'))
+ self.assertEqual(False, f.clean('False'))
+
+ # ChoiceField #################################################################
+
+ def test_choicefield_46(self):
+ f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'1', f.clean('1'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3')
+
+ def test_choicefield_47(self):
+ f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'1', f.clean('1'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, '3')
+
+ def test_choicefield_48(self):
+ f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
+ self.assertEqual(u'J', f.clean('J'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. John is not one of the available choices.']", f.clean, 'John')
+
+ def test_choicefield_49(self):
+ f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
+ self.assertEqual(u'1', f.clean(1))
+ self.assertEqual(u'1', f.clean('1'))
+ self.assertEqual(u'3', f.clean(3))
+ self.assertEqual(u'3', f.clean('3'))
+ self.assertEqual(u'5', f.clean(5))
+ self.assertEqual(u'5', f.clean('5'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, '6')
+
+ # TypedChoiceField ############################################################
+ # TypedChoiceField is just like ChoiceField, except that coerced types will
+ # be returned:
+
+ def test_typedchoicefield_50(self):
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int)
+ self.assertEqual(1, f.clean('1'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 2 is not one of the available choices.']", f.clean, '2')
+
+ def test_typedchoicefield_51(self):
+ # Different coercion, same validation.
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float)
+ self.assertEqual(1.0, f.clean('1'))
+
+ def test_typedchoicefield_52(self):
+ # This can also cause weirdness: be careful (bool(-1) == True, remember)
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool)
+ self.assertEqual(True, f.clean('-1'))
+
+ def test_typedchoicefield_53(self):
+ # Even more weirdness: if you have a valid choice but your coercion function
+ # can't coerce, you'll still get a validation error. Don't do this!
+ f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. B is not one of the available choices.']", f.clean, 'B')
+ # Required fields require values
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+
+ def test_typedchoicefield_54(self):
+ # Non-required fields aren't required
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False)
+ self.assertEqual('', f.clean(''))
+ # If you want cleaning an empty value to return a different type, tell the field
+
+ def test_typedchoicefield_55(self):
+ f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None)
+ self.assertEqual(None, f.clean(''))
+
+ # NullBooleanField ############################################################
+
+ def test_nullbooleanfield_56(self):
+ f = NullBooleanField()
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(True, f.clean(True))
+ self.assertEqual(False, f.clean(False))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(False, f.clean('0'))
+ self.assertEqual(True, f.clean('1'))
+ self.assertEqual(None, f.clean('2'))
+ self.assertEqual(None, f.clean('3'))
+ self.assertEqual(None, f.clean('hello'))
+
+
+ def test_nullbooleanfield_57(self):
+ # Make sure that the internal value is preserved if using HiddenInput (#7753)
+ class HiddenNullBooleanForm(Form):
+ hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True)
+ hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False)
+ f = HiddenNullBooleanForm()
+ self.assertEqual(' ', str(f))
+
+ def test_nullbooleanfield_58(self):
+ class HiddenNullBooleanForm(Form):
+ hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True)
+ hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False)
+ f = HiddenNullBooleanForm({ 'hidden_nullbool1': 'True', 'hidden_nullbool2': 'False' })
+ self.assertEqual(None, f.full_clean())
+ self.assertEqual(True, f.cleaned_data['hidden_nullbool1'])
+ self.assertEqual(False, f.cleaned_data['hidden_nullbool2'])
+
+ def test_nullbooleanfield_59(self):
+ # Make sure we're compatible with MySQL, which uses 0 and 1 for its boolean
+ # values. (#9609)
+ NULLBOOL_CHOICES = (('1', 'Yes'), ('0', 'No'), ('', 'Unknown'))
+ class MySQLNullBooleanForm(Form):
+ nullbool0 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
+ nullbool1 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
+ nullbool2 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES))
+ f = MySQLNullBooleanForm({ 'nullbool0': '1', 'nullbool1': '0', 'nullbool2': '' })
+ self.assertEqual(None, f.full_clean())
+ self.assertEqual(True, f.cleaned_data['nullbool0'])
+ self.assertEqual(False, f.cleaned_data['nullbool1'])
+ self.assertEqual(None, f.cleaned_data['nullbool2'])
+
+ # MultipleChoiceField #########################################################
+
+ def test_multiplechoicefield_60(self):
+ f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertEqual([u'1'], f.clean([1]))
+ self.assertEqual([u'1'], f.clean(['1']))
+ self.assertEqual([u'1', u'2'], f.clean(['1', '2']))
+ self.assertEqual([u'1', u'2'], f.clean([1, '2']))
+ self.assertEqual([u'1', u'2'], f.clean((1, '2')))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, [])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, ())
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3'])
+
+ def test_multiplechoicefield_61(self):
+ f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
+ self.assertEqual([], f.clean(''))
+ self.assertEqual([], f.clean(None))
+ self.assertEqual([u'1'], f.clean([1]))
+ self.assertEqual([u'1'], f.clean(['1']))
+ self.assertEqual([u'1', u'2'], f.clean(['1', '2']))
+ self.assertEqual([u'1', u'2'], f.clean([1, '2']))
+ self.assertEqual([u'1', u'2'], f.clean((1, '2')))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertEqual([], f.clean([]))
+ self.assertEqual([], f.clean(()))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 3 is not one of the available choices.']", f.clean, ['3'])
+
+ def test_multiplechoicefield_62(self):
+ f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
+ self.assertEqual([u'1'], f.clean([1]))
+ self.assertEqual([u'1'], f.clean(['1']))
+ self.assertEqual([u'1', u'5'], f.clean([1, 5]))
+ self.assertEqual([u'1', u'5'], f.clean([1, '5']))
+ self.assertEqual([u'1', u'5'], f.clean(['1', 5]))
+ self.assertEqual([u'1', u'5'], f.clean(['1', '5']))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['6'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. 6 is not one of the available choices.']", f.clean, ['1','6'])
+
+ # ComboField ##################################################################
+
+ def test_combofield_63(self):
+ f = ComboField(fields=[CharField(max_length=20), EmailField()])
+ self.assertEqual(u'test@example.com', f.clean('test@example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'not an e-mail')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+
+ def test_combofield_64(self):
+ f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False)
+ self.assertEqual(u'test@example.com', f.clean('test@example.com'))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 20 characters (it has 28).']", f.clean, 'longemailaddress@example.com')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'not an e-mail')
+ self.assertEqual(u'', f.clean(''))
+ self.assertEqual(u'', f.clean(None))
+
+ # FilePathField ###############################################################
+
+ def test_filepathfield_65(self):
+ path = forms.__file__
+ path = os.path.dirname(path) + '/'
+ assert fix_os_paths(path).endswith('/django/forms/')
+
+ def test_filepathfield_66(self):
+ path = forms.__file__
+ path = os.path.dirname(path) + '/'
+ f = FilePathField(path=path)
+ f.choices = [p for p in f.choices if p[0].endswith('.py')]
+ f.choices.sort()
+ expected = [
+ ('/django/forms/__init__.py', '__init__.py'),
+ ('/django/forms/fields.py', 'fields.py'),
+ ('/django/forms/forms.py', 'forms.py'),
+ ('/django/forms/formsets.py', 'formsets.py'),
+ ('/django/forms/models.py', 'models.py'),
+ ('/django/forms/util.py', 'util.py'),
+ ('/django/forms/widgets.py', 'widgets.py')
+ ]
+ for exp, got in zip(expected, fix_os_paths(f.choices)):
+ self.assertEqual(exp[1], got[1])
+ assert got[0].endswith(exp[0])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. fields.py is not one of the available choices.']", f.clean, 'fields.py')
+ assert fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py')
+
+ def test_filepathfield_67(self):
+ path = forms.__file__
+ path = os.path.dirname(path) + '/'
+ f = FilePathField(path=path, match='^.*?\.py$')
+ f.choices.sort()
+ expected = [
+ ('/django/forms/__init__.py', '__init__.py'),
+ ('/django/forms/fields.py', 'fields.py'),
+ ('/django/forms/forms.py', 'forms.py'),
+ ('/django/forms/formsets.py', 'formsets.py'),
+ ('/django/forms/models.py', 'models.py'),
+ ('/django/forms/util.py', 'util.py'),
+ ('/django/forms/widgets.py', 'widgets.py')
+ ]
+ for exp, got in zip(expected, fix_os_paths(f.choices)):
+ self.assertEqual(exp[1], got[1])
+ assert got[0].endswith(exp[0])
+
+ def test_filepathfield_68(self):
+ path = forms.__file__
+ path = os.path.dirname(path) + '/'
+ f = FilePathField(path=path, recursive=True, match='^.*?\.py$')
+ f.choices.sort()
+ expected = [
+ ('/django/forms/__init__.py', '__init__.py'),
+ ('/django/forms/extras/__init__.py', 'extras/__init__.py'),
+ ('/django/forms/extras/widgets.py', 'extras/widgets.py'),
+ ('/django/forms/fields.py', 'fields.py'),
+ ('/django/forms/forms.py', 'forms.py'),
+ ('/django/forms/formsets.py', 'formsets.py'),
+ ('/django/forms/models.py', 'models.py'),
+ ('/django/forms/util.py', 'util.py'),
+ ('/django/forms/widgets.py', 'widgets.py')
+ ]
+ for exp, got in zip(expected, fix_os_paths(f.choices)):
+ self.assertEqual(exp[1], got[1])
+ assert got[0].endswith(exp[0])
+
+ # SplitDateTimeField ##########################################################
+
+ def test_splitdatetimefield_69(self):
+ from django.forms.widgets import SplitDateTimeWidget
+ f = SplitDateTimeField()
+ assert isinstance(f.widget, SplitDateTimeWidget)
+ self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, None)
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'This field is required.']", f.clean, '')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.', u'Enter a valid time.']", f.clean, ['hello', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['hello', '07:30'])
+
+ def test_splitdatetimefield_70(self):
+ f = SplitDateTimeField(required=False)
+ self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]))
+ self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean(['2006-01-10', '07:30']))
+ self.assertEqual(None, f.clean(None))
+ self.assertEqual(None, f.clean(''))
+ self.assertEqual(None, f.clean(['']))
+ self.assertEqual(None, f.clean(['', '']))
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a list of values.']", f.clean, 'hello')
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.', u'Enter a valid time.']", f.clean, ['hello', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', 'there'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['hello', '07:30'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10', ''])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid time.']", f.clean, ['2006-01-10'])
+ self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid date.']", f.clean, ['', '07:30'])
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
index bf9623fe77..627f50a6fe 100644
--- a/tests/regressiontests/forms/forms.py
+++ b/tests/regressiontests/forms/forms.py
@@ -1807,4 +1807,43 @@ True
>>> [f.name for f in form.visible_fields()]
['artist', 'name']
+# Hidden initial input gets its own unique id ################################
+
+>>> class MyForm(Form):
+... field1 = CharField(max_length=50, show_hidden_initial=True)
+>>> print MyForm()
+Field1:
+
+# The error_html_class and required_html_class attributes ####################
+
+>>> p = Person({})
+>>> p.error_css_class = 'error'
+>>> p.required_css_class = 'required'
+
+>>> print p.as_ul()
+Name:
+Is cool:
+Unknown
+Yes
+No
+
+
+>>> print p.as_p()
+
+Name:
+Is cool:
+Unknown
+Yes
+No
+
+
+>>> print p.as_table()
+Name:
+Is cool:
+Unknown
+Yes
+No
+
+
+
"""
diff --git a/tests/regressiontests/forms/localflavor/ca.py b/tests/regressiontests/forms/localflavor/ca.py
index 48171b0558..7f4b3ac89c 100644
--- a/tests/regressiontests/forms/localflavor/ca.py
+++ b/tests/regressiontests/forms/localflavor/ca.py
@@ -60,6 +60,50 @@ ValidationError: [u'Enter a postal code in the format XXX XXX.']
u''
>>> f.clean('')
u''
+>>> f.clean('W2S 2H3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('T2W 2H7')
+u'T2W 2H7'
+>>> f.clean('T2S 2W7')
+u'T2S 2W7'
+>>> f.clean('Z2S 2H3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('T2Z 2H7')
+u'T2Z 2H7'
+>>> f.clean('T2S 2Z7')
+u'T2S 2Z7'
+>>> f.clean('F2S 2H3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('A2S 2D3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('A2I 2R3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('A2I 2R3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('A2Q 2R3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('U2B 2R3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
+>>> f.clean('O2B 2R3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXX XXX.']
# CAPhoneNumberField ##########################################################
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 6d418fa5a3..6f00a06fdd 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from extra import tests as extra_tests
-from fields import tests as fields_tests
from forms import tests as form_tests
from error_messages import tests as custom_error_message_tests
from localflavor.ar import tests as localflavor_ar_tests
@@ -32,9 +31,10 @@ from widgets import tests as widgets_tests
from formsets import tests as formset_tests
from media import media_tests
+from fields import FieldsTests
+
__test__ = {
'extra_tests': extra_tests,
- 'fields_tests': fields_tests,
'form_tests': form_tests,
'custom_error_message_tests': custom_error_message_tests,
'localflavor_ar_tests': localflavor_ar_tests,
diff --git a/tests/regressiontests/localflavor/__init__.py b/tests/regressiontests/localflavor/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/regressiontests/localflavor/forms.py b/tests/regressiontests/localflavor/forms.py
new file mode 100644
index 0000000000..49635b02fb
--- /dev/null
+++ b/tests/regressiontests/localflavor/forms.py
@@ -0,0 +1,14 @@
+from django.forms import ModelForm
+from models import Place
+
+class PlaceForm(ModelForm):
+ """docstring for PlaceForm"""
+ class Meta:
+ model = Place
+from django.forms import ModelForm
+from models import Place
+
+class PlaceForm(ModelForm):
+ """docstring for PlaceForm"""
+ class Meta:
+ model = Place
diff --git a/tests/regressiontests/localflavor/models.py b/tests/regressiontests/localflavor/models.py
new file mode 100644
index 0000000000..079c7bd982
--- /dev/null
+++ b/tests/regressiontests/localflavor/models.py
@@ -0,0 +1,16 @@
+from django.db import models
+from django.contrib.localflavor.us.models import USStateField
+
+class Place(models.Model):
+ state = USStateField(blank=True)
+ state_req = USStateField()
+ state_default = USStateField(default="CA", blank=True)
+ name = models.CharField(max_length=20)
+from django.db import models
+from django.contrib.localflavor.us.models import USStateField
+
+class Place(models.Model):
+ state = USStateField(blank=True)
+ state_req = USStateField()
+ state_default = USStateField(default="CA", blank=True)
+ name = models.CharField(max_length=20)
diff --git a/tests/regressiontests/localflavor/tests.py b/tests/regressiontests/localflavor/tests.py
new file mode 100644
index 0000000000..61e0e5b5e6
--- /dev/null
+++ b/tests/regressiontests/localflavor/tests.py
@@ -0,0 +1,166 @@
+from django.test import TestCase
+from models import Place
+from forms import PlaceForm
+
+class USLocalflavorTests(TestCase):
+ def setUp(self):
+ self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'})
+
+ def test_get_display_methods(self):
+ """Test that the get_*_display() methods are added to the model instances."""
+ place = self.form.save()
+ self.assertEqual(place.get_state_display(), 'Georgia')
+ self.assertEqual(place.get_state_req_display(), 'North Carolina')
+
+ def test_required(self):
+ """Test that required USStateFields throw appropriate errors."""
+ form = PlaceForm({'state':'GA', 'name':'Place in GA'})
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors['state_req'], [u'This field is required.'])
+
+ def test_field_blank_option(self):
+ """Test that the empty option is there."""
+ state_select_html = """\
+
+---------
+Alabama
+Alaska
+American Samoa
+Arizona
+Arkansas
+California
+Colorado
+Connecticut
+Delaware
+District of Columbia
+Florida
+Georgia
+Guam
+Hawaii
+Idaho
+Illinois
+Indiana
+Iowa
+Kansas
+Kentucky
+Louisiana
+Maine
+Maryland
+Massachusetts
+Michigan
+Minnesota
+Mississippi
+Missouri
+Montana
+Nebraska
+Nevada
+New Hampshire
+New Jersey
+New Mexico
+New York
+North Carolina
+North Dakota
+Northern Mariana Islands
+Ohio
+Oklahoma
+Oregon
+Pennsylvania
+Puerto Rico
+Rhode Island
+South Carolina
+South Dakota
+Tennessee
+Texas
+Utah
+Vermont
+Virgin Islands
+Virginia
+Washington
+West Virginia
+Wisconsin
+Wyoming
+ """
+ self.assertEqual(str(self.form['state']), state_select_html)
+from django.test import TestCase
+from models import Place
+from forms import PlaceForm
+
+class USLocalflavorTests(TestCase):
+ def setUp(self):
+ self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'})
+
+ def test_get_display_methods(self):
+ """Test that the get_*_display() methods are added to the model instances."""
+ place = self.form.save()
+ self.assertEqual(place.get_state_display(), 'Georgia')
+ self.assertEqual(place.get_state_req_display(), 'North Carolina')
+
+ def test_required(self):
+ """Test that required USStateFields throw appropriate errors."""
+ form = PlaceForm({'state':'GA', 'name':'Place in GA'})
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors['state_req'], [u'This field is required.'])
+
+ def test_field_blank_option(self):
+ """Test that the empty option is there."""
+ state_select_html = """\
+
+---------
+Alabama
+Alaska
+American Samoa
+Arizona
+Arkansas
+California
+Colorado
+Connecticut
+Delaware
+District of Columbia
+Florida
+Georgia
+Guam
+Hawaii
+Idaho
+Illinois
+Indiana
+Iowa
+Kansas
+Kentucky
+Louisiana
+Maine
+Maryland
+Massachusetts
+Michigan
+Minnesota
+Mississippi
+Missouri
+Montana
+Nebraska
+Nevada
+New Hampshire
+New Jersey
+New Mexico
+New York
+North Carolina
+North Dakota
+Northern Mariana Islands
+Ohio
+Oklahoma
+Oregon
+Pennsylvania
+Puerto Rico
+Rhode Island
+South Carolina
+South Dakota
+Tennessee
+Texas
+Utah
+Vermont
+Virgin Islands
+Virginia
+Washington
+West Virginia
+Wisconsin
+Wyoming
+ """
+ self.assertEqual(str(self.form['state']), state_select_html)
diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py
index 85e284b639..f8b6511140 100644
--- a/tests/regressiontests/model_forms_regress/tests.py
+++ b/tests/regressiontests/model_forms_regress/tests.py
@@ -100,4 +100,16 @@ class CustomFieldSaveTests(TestCase):
# It's enough that the form saves without error -- the custom save routine will
# generate an AssertionError if it is called more than once during save.
form = CFFForm(data = {'f': None})
- form.save()
\ No newline at end of file
+ form.save()
+
+class ModelChoiceIteratorTests(TestCase):
+ def test_len(self):
+ class Form(forms.ModelForm):
+ class Meta:
+ model = Article
+ fields = ["publications"]
+
+ Publication.objects.create(title="Pravda",
+ date_published=date(1991, 8, 22))
+ f = Form()
+ self.assertEqual(len(f.fields["publications"].choices), 1)
diff --git a/tests/regressiontests/settings_tests/__init__.py b/tests/regressiontests/settings_tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/regressiontests/settings_tests/models.py b/tests/regressiontests/settings_tests/models.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py
new file mode 100644
index 0000000000..fa217b1d79
--- /dev/null
+++ b/tests/regressiontests/settings_tests/tests.py
@@ -0,0 +1,17 @@
+import unittest
+from django.conf import settings
+
+class SettingsTests(unittest.TestCase):
+
+ #
+ # Regression tests for #10130: deleting settings.
+ #
+
+ def test_settings_delete(self):
+ settings.TEST = 'test'
+ self.assertEqual('test', settings.TEST)
+ del settings.TEST
+ self.assertRaises(AttributeError, getattr, settings, 'TEST')
+
+ def test_settings_delete_wrapped(self):
+ self.assertRaises(TypeError, delattr, settings, '_wrapped')
diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py
index 2b448574f7..88266e107b 100644
--- a/tests/regressiontests/templates/filters.py
+++ b/tests/regressiontests/templates/filters.py
@@ -120,13 +120,19 @@ def get_filter_tests():
# Notice that escaping is applied *after* any filters, so the string
# formatting here only needs to deal with pre-escaped characters.
- 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a