From 5d85a5147b55cbe7940df2321e73c48ca265f649 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sun, 18 Nov 2007 12:07:25 +0000 Subject: [PATCH] queryset-refactor: Fixed up a few problems from the previous merge from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6693 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/newforms/fields.py | 99 ++++++++++++++++-------------- django/newforms/util.py | 10 ++- django/templatetags/cache.py | 114 +++++++++++++++++------------------ django/test/testcases.py | 2 +- docs/newforms.txt | 51 +++++++++++++++- 5 files changed, 167 insertions(+), 109 deletions(-) diff --git a/django/newforms/fields.py b/django/newforms/fields.py index efd3004f75..9bb2ced583 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -49,7 +49,8 @@ class Field(object): # Tracks each time a Field instance is created. Used to retain order. creation_counter = 0 - def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None): + def __init__(self, required=True, widget=None, label=None, initial=None, + help_text=None, error_messages=None): # required -- Boolean that specifies whether the field is required. # True by default. # widget -- A Widget class, or instance of a Widget class, that should @@ -106,7 +107,7 @@ class Field(object): Raises ValidationError for any errors. """ if self.required and value in EMPTY_VALUES: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) return value def widget_attrs(self, widget): @@ -141,9 +142,9 @@ class CharField(Field): value = smart_unicode(value) value_length = len(value) if self.max_length is not None and value_length > self.max_length: - raise ValidationError(ugettext(u'Ensure this value has at most %(max)d characters (it has %(length)d).') % {'max': self.max_length, 'length': value_length}) + raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) if self.min_length is not None and value_length < self.min_length: - raise ValidationError(ugettext(u'Ensure this value has at least %(min)d characters (it has %(length)d).') % {'min': self.min_length, 'length': value_length}) + raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) return value def widget_attrs(self, widget): @@ -173,11 +174,11 @@ class IntegerField(Field): try: value = int(str(value)) except (ValueError, TypeError): - raise ValidationError(ugettext(u'Enter a whole number.')) + raise ValidationError(self.error_messages['invalid']) if self.max_value is not None and value > self.max_value: - raise ValidationError(ugettext(u'Ensure this value is less than or equal to %s.') % self.max_value) + raise ValidationError(self.error_messages['max_value'] % self.max_value) if self.min_value is not None and value < self.min_value: - raise ValidationError(ugettext(u'Ensure this value is greater than or equal to %s.') % self.min_value) + raise ValidationError(self.error_messages['min_value'] % self.min_value) return value class FloatField(Field): @@ -202,11 +203,11 @@ class FloatField(Field): try: value = float(value) except (ValueError, TypeError): - raise ValidationError(ugettext('Enter a number.')) + raise ValidationError(self.error_messages['invalid']) if self.max_value is not None and value > self.max_value: - raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value) + raise ValidationError(self.error_messages['max_value'] % self.max_value) if self.min_value is not None and value < self.min_value: - raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value) + raise ValidationError(self.error_messages['min_value'] % self.min_value) return value class DecimalField(Field): @@ -238,20 +239,20 @@ class DecimalField(Field): try: value = Decimal(value) except DecimalException: - raise ValidationError(ugettext('Enter a number.')) + raise ValidationError(self.error_messages['invalid']) pieces = str(value).lstrip("-").split('.') decimals = (len(pieces) == 2) and len(pieces[1]) or 0 digits = len(pieces[0]) if self.max_value is not None and value > self.max_value: - raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value) + raise ValidationError(self.error_messages['max_value'] % self.max_value) if self.min_value is not None and value < self.min_value: - raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value) + raise ValidationError(self.error_messages['min_value'] % self.min_value) if self.max_digits is not None and (digits + decimals) > self.max_digits: - raise ValidationError(ugettext('Ensure that there are no more than %s digits in total.') % self.max_digits) + raise ValidationError(self.error_messages['max_digits'] % self.max_digits) if self.decimal_places is not None and decimals > self.decimal_places: - raise ValidationError(ugettext('Ensure that there are no more than %s decimal places.') % self.decimal_places) + raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): - raise ValidationError(ugettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places)) + raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) return value DEFAULT_DATE_INPUT_FORMATS = ( @@ -288,7 +289,7 @@ class DateField(Field): return datetime.date(*time.strptime(value, format)[:3]) except ValueError: continue - raise ValidationError(ugettext(u'Enter a valid date.')) + raise ValidationError(self.error_messages['invalid']) DEFAULT_TIME_INPUT_FORMATS = ( '%H:%M:%S', # '14:30:59' @@ -319,7 +320,7 @@ class TimeField(Field): return datetime.time(*time.strptime(value, format)[3:6]) except ValueError: continue - raise ValidationError(ugettext(u'Enter a valid time.')) + raise ValidationError(self.error_messages['invalid']) DEFAULT_DATETIME_INPUT_FORMATS = ( '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' @@ -359,14 +360,14 @@ class DateTimeField(Field): # Input comes from a SplitDateTimeWidget, for example. So, it's two # components: date and time. if len(value) != 2: - raise ValidationError(ugettext(u'Enter a valid date/time.')) + raise ValidationError(self.error_messages['invalid']) value = '%s %s' % tuple(value) for format in self.input_formats: try: return datetime.datetime(*time.strptime(value, format)[:6]) except ValueError: continue - raise ValidationError(ugettext(u'Enter a valid date/time.')) + raise ValidationError(self.error_messages['invalid']) class RegexField(CharField): def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): @@ -375,11 +376,15 @@ class RegexField(CharField): error_message is an optional error message to use, if 'Enter a valid value' is too generic for you. """ + # error_message is just kept for backwards compatibility: + if error_message: + error_messages = kwargs.get('error_messages') or {} + error_messages['invalid'] = error_message + kwargs['error_messages'] = error_messages super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) if isinstance(regex, basestring): regex = re.compile(regex) self.regex = regex - self.error_message = error_message or ugettext(u'Enter a valid value.') def clean(self, value): """ @@ -390,7 +395,7 @@ class RegexField(CharField): if value == u'': return value if not self.regex.search(value): - raise ValidationError(self.error_message) + raise ValidationError(self.error_messages['invalid']) return value email_re = re.compile( @@ -404,8 +409,8 @@ class EmailField(RegexField): } def __init__(self, max_length=None, min_length=None, *args, **kwargs): - RegexField.__init__(self, email_re, max_length, min_length, - ugettext(u'Enter a valid e-mail address.'), *args, **kwargs) + RegexField.__init__(self, email_re, max_length, min_length, *args, + **kwargs) try: from django.conf import settings @@ -445,11 +450,11 @@ class FileField(Field): try: f = UploadedFile(data['filename'], data['content']) except TypeError: - raise ValidationError(ugettext(u"No file was submitted. Check the encoding type on the form.")) + raise ValidationError(self.error_messages['invalid']) except KeyError: - raise ValidationError(ugettext(u"No file was submitted.")) + raise ValidationError(self.error_messages['missing']) if not f.content: - raise ValidationError(ugettext(u"The submitted file is empty.")) + raise ValidationError(self.error_messages['empty']) return f class ImageField(FileField): @@ -477,7 +482,7 @@ class ImageField(FileField): trial_image = Image.open(StringIO(f.content)) trial_image.verify() except Exception: # Python Imaging Library doesn't recognize it as an image - raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image.")) + raise ValidationError(self.error_messages['invalid_image']) return f url_re = re.compile( @@ -496,7 +501,8 @@ class URLField(RegexField): def __init__(self, max_length=None, min_length=None, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): - super(URLField, self).__init__(url_re, max_length, min_length, ugettext(u'Enter a valid URL.'), *args, **kwargs) + super(URLField, self).__init__(url_re, max_length, min_length, *args, + **kwargs) self.verify_exists = verify_exists self.user_agent = validator_user_agent @@ -521,9 +527,9 @@ class URLField(RegexField): req = urllib2.Request(value, None, headers) u = urllib2.urlopen(req) except ValueError: - raise ValidationError(ugettext(u'Enter a valid URL.')) + raise ValidationError(self.error_messages['invalid']) except: # urllib2.URLError, httplib.InvalidURL, etc. - raise ValidationError(ugettext(u'This URL appears to be a broken link.')) + raise ValidationError(self.error_messages['invalid_link']) return value class BooleanField(Field): @@ -583,7 +589,7 @@ class ChoiceField(Field): return value valid_values = set([smart_unicode(k) for k, v in self.choices]) if value not in valid_values: - raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.')) + raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) return value class MultipleChoiceField(ChoiceField): @@ -599,17 +605,17 @@ class MultipleChoiceField(ChoiceField): Validates that the input is a list or tuple. """ if self.required and not value: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) elif not self.required and not value: return [] if not isinstance(value, (list, tuple)): - raise ValidationError(ugettext(u'Enter a list of values.')) + raise ValidationError(self.error_messages['invalid_list']) new_value = [smart_unicode(val) for val in value] # Validate that each value in the value list is in self.choices. valid_values = set([smart_unicode(k) for k, v in self.choices]) for val in new_value: if val not in valid_values: - raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val) + raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) return new_value class ComboField(Field): @@ -679,18 +685,18 @@ class MultiValueField(Field): if not value or isinstance(value, (list, tuple)): if not value or not [v for v in value if v not in EMPTY_VALUES]: if self.required: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) else: return self.compress([]) else: - raise ValidationError(ugettext(u'Enter a list of values.')) + raise ValidationError(self.error_messages['invalid']) for i, field in enumerate(self.fields): try: field_value = value[i] except IndexError: field_value = None if self.required and field_value in EMPTY_VALUES: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) try: clean_data.append(field.clean(field_value)) except ValidationError, e: @@ -720,7 +726,13 @@ class SplitDateTimeField(MultiValueField): } def __init__(self, *args, **kwargs): - fields = (DateField(), TimeField()) + errors = self.default_error_messages.copy() + if 'error_messages' in kwargs: + errors.update(kwargs['error_messages']) + fields = ( + DateField(error_messages={'invalid': errors['invalid_date']}), + TimeField(error_messages={'invalid': errors['invalid_time']}), + ) super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): @@ -728,9 +740,9 @@ class SplitDateTimeField(MultiValueField): # Raise a validation error if time or date is empty # (possible if SplitDateTimeField has required=False). if data_list[0] in EMPTY_VALUES: - raise ValidationError(ugettext(u'Enter a valid date.')) + raise ValidationError(self.error_messages['invalid_date']) if data_list[1] in EMPTY_VALUES: - raise ValidationError(ugettext(u'Enter a valid time.')) + raise ValidationError(self.error_messages['invalid_time']) return datetime.datetime.combine(*data_list) return None @@ -742,7 +754,4 @@ class IPAddressField(RegexField): } def __init__(self, *args, **kwargs): - RegexField.__init__(self, ipv4_re, - error_message=ugettext(u'Enter a valid IPv4 address.'), - *args, **kwargs) - + super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) diff --git a/django/newforms/util.py b/django/newforms/util.py index 701c774927..b3edf41adf 100644 --- a/django/newforms/util.py +++ b/django/newforms/util.py @@ -46,13 +46,18 @@ class ErrorList(list, StrAndUnicode): if not self: return u'' return u'\n'.join([u'* %s' % force_unicode(e) for e in self]) + def __repr__(self): + return repr([force_unicode(e) for e in self]) + class ValidationError(Exception): def __init__(self, message): - "ValidationError can be passed a string or a list." + """ + ValidationError can be passed any object that can be printed (usually + a string) or a list of objects. + """ if isinstance(message, list): self.messages = ErrorList([smart_unicode(msg) for msg in message]) else: - assert isinstance(message, (basestring, Promise)), ("%s should be a basestring or lazy translation" % repr(message)) message = smart_unicode(message) self.messages = ErrorList([message]) @@ -62,4 +67,3 @@ class ValidationError(Exception): # AttributeError: ValidationError instance has no attribute 'args' # See http://www.python.org/doc/current/tut/node10.html#handling return repr(self.messages) - diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index 845be36cd8..4436b15d6e 100644 --- a/django/templatetags/cache.py +++ b/django/templatetags/cache.py @@ -1,57 +1,57 @@ -from django.template import Library, Node, TemplateSyntaxError -from django.template import resolve_variable -from django.core.cache import cache -from django.utils.encoding import force_unicode - -register = Library() - -class CacheNode(Node): - def __init__(self, nodelist, expire_time, fragment_name, vary_on): - self.nodelist = nodelist - self.expire_time = expire_time - self.fragment_name = fragment_name - self.vary_on = vary_on - - def render(self, context): - # Build a unicode key for this fragment and all vary-on's. - cache_key = u':'.join([self.fragment_name] + \ - [force_unicode(resolve_variable(var, context)) for var in self.vary_on]) - value = cache.get(cache_key) - if value is None: - value = self.nodelist.render(context) - cache.set(cache_key, value, self.expire_time) - return value - -def do_cache(parser, token): - """ - This will cache the contents of a template fragment for a given amount - of time. - - Usage:: - - {% load cache %} - {% cache [expire_time] [fragment_name] %} - .. some expensive processing .. - {% endcache %} - - This tag also supports varying by a list of arguments:: - - {% load cache %} - {% cache [expire_time] [fragment_name] [var1] [var2] .. %} - .. some expensive processing .. - {% endcache %} - - Each unique set of arguments will result in a unique cache entry. - """ - nodelist = parser.parse(('endcache',)) - parser.delete_first_token() - tokens = token.contents.split() - if len(tokens) < 3: - raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) - try: - expire_time = int(tokens[1]) - except ValueError: - raise TemplateSyntaxError(u"First argument to '%r' must be an integer (got '%s')." % (tokens[0], tokens[1])) - return CacheNode(nodelist, expire_time, tokens[2], tokens[3:]) - -register.tag('cache', do_cache) +from django.template import Library, Node, TemplateSyntaxError +from django.template import resolve_variable +from django.core.cache import cache +from django.utils.encoding import force_unicode + +register = Library() + +class CacheNode(Node): + def __init__(self, nodelist, expire_time, fragment_name, vary_on): + self.nodelist = nodelist + self.expire_time = expire_time + self.fragment_name = fragment_name + self.vary_on = vary_on + + def render(self, context): + # Build a unicode key for this fragment and all vary-on's. + cache_key = u':'.join([self.fragment_name] + \ + [force_unicode(resolve_variable(var, context)) for var in self.vary_on]) + value = cache.get(cache_key) + if value is None: + value = self.nodelist.render(context) + cache.set(cache_key, value, self.expire_time) + return value + +def do_cache(parser, token): + """ + This will cache the contents of a template fragment for a given amount + of time. + + Usage:: + + {% load cache %} + {% cache [expire_time] [fragment_name] %} + .. some expensive processing .. + {% endcache %} + + This tag also supports varying by a list of arguments:: + + {% load cache %} + {% cache [expire_time] [fragment_name] [var1] [var2] .. %} + .. some expensive processing .. + {% endcache %} + + Each unique set of arguments will result in a unique cache entry. + """ + nodelist = parser.parse(('endcache',)) + parser.delete_first_token() + tokens = token.contents.split() + if len(tokens) < 3: + raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) + try: + expire_time = int(tokens[1]) + except ValueError: + raise TemplateSyntaxError(u"First argument to '%r' must be an integer (got '%s')." % (tokens[0], tokens[1])) + return CacheNode(nodelist, expire_time, tokens[2], tokens[3:]) + +register.tag('cache', do_cache) diff --git a/django/test/testcases.py b/django/test/testcases.py index c08ee425ce..2aa0a0783d 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -150,7 +150,7 @@ class TestCase(unittest.TestCase): " context %d does not contain the" " error '%s' (actual errors: %s)" % (field, form, i, err, - list(field_errors))) + repr(field_errors))) elif field in context[form].fields: self.fail("The field '%s' on form '%s' in context %d" " contains no errors" % (field, form, i)) diff --git a/docs/newforms.txt b/docs/newforms.txt index 5e33a478ee..593e9216d7 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -1078,6 +1078,30 @@ fields. We've specified ``auto_id=False`` to simplify the output::

Sender: A valid e-mail address, please.

Cc myself:

+``error_messages`` +~~~~~~~~~~~~~~~~~~ + +**New in Django development version** + +The ``error_messages`` argument lets you override the default messages which the +field will raise. Pass in a dictionary with keys matching the error messages you +want to override. For example:: + + >>> generic = forms.CharField() + >>> generic.clean('') + Traceback (most recent call last): + ... + ValidationError: [u'This field is required.'] + + >>> name = forms.CharField(error_messages={'required': 'Please enter your name'}) + >>> name.clean('') + Traceback (most recent call last): + ... + ValidationError: [u'Please enter your name'] + +In the `built-in Field classes`_ section below, each Field defines the error +message keys it uses. + Dynamic initial values ---------------------- @@ -1143,6 +1167,7 @@ For each field, we describe the default widget used if you don't specify * Normalizes to: A Python ``True`` or ``False`` value. * Validates that the check box is checked (i.e. the value is ``True``) if the field has ``required=True``. + * Error message keys: ``required`` **New in Django development version:** The empty value for a ``CheckboxInput`` (and hence the standard ``BooleanField``) has changed to return ``False`` @@ -1162,6 +1187,7 @@ instead of ``None`` in the development version. * Normalizes to: A Unicode object. * Validates ``max_length`` or ``min_length``, if they are provided. Otherwise, all inputs are valid. + * Error message keys: ``required``, ``max_length``, ``min_length`` Has two optional arguments for validation, ``max_length`` and ``min_length``. If provided, these arguments ensure that the string is at most or at least the @@ -1174,6 +1200,7 @@ given length. * Empty value: ``''`` (an empty string) * Normalizes to: A Unicode object. * Validates that the given value exists in the list of choices. + * Error message keys: ``required``, ``invalid_choice`` Takes one extra argument, ``choices``, which is an iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. @@ -1186,6 +1213,7 @@ tuple) of 2-tuples to use as choices for this field. * Normalizes to: A Python ``datetime.date`` object. * Validates that the given value is either a ``datetime.date``, ``datetime.datetime`` or string formatted in a particular date format. + * Error message keys: ``required``, ``invalid`` Takes one optional argument, ``input_formats``, which is a list of formats used to attempt to convert a string to a valid ``datetime.date`` object. @@ -1206,6 +1234,7 @@ If no ``input_formats`` argument is provided, the default input formats are:: * Normalizes to: A Python ``datetime.datetime`` object. * Validates that the given value is either a ``datetime.datetime``, ``datetime.date`` or string formatted in a particular datetime format. + * Error message keys: ``required``, ``invalid`` Takes one optional argument, ``input_formats``, which is a list of formats used to attempt to convert a string to a valid ``datetime.datetime`` object. @@ -1235,6 +1264,9 @@ If no ``input_formats`` argument is provided, the default input formats are:: * Normalizes to: A Python ``decimal``. * Validates that the given value is a decimal. Leading and trailing whitespace is ignored. + * Error message keys: ``required``, ``invalid``, ``max_value``, + ``min_value``, ``max_digits``, ``max_decimal_places``, + ``max_whole_digits`` Takes four optional arguments: ``max_value``, ``min_value``, ``max_digits``, and ``decimal_places``. The first two define the limits for the fields value. @@ -1251,6 +1283,7 @@ decimal places permitted. * Normalizes to: A Unicode object. * Validates that the given value is a valid e-mail address, using a moderately complex regular expression. + * Error message keys: ``required``, ``invalid`` Has two optional arguments for validation, ``max_length`` and ``min_length``. If provided, these arguments ensure that the string is at most or at least the @@ -1266,6 +1299,7 @@ given length. * Normalizes to: An ``UploadedFile`` object that wraps the file content and file name into a single object. * Validates that non-empty file data has been bound to the form. + * Error message keys: ``required``, ``invalid``, ``missing``, ``empty`` An ``UploadedFile`` object has two attributes: @@ -1296,6 +1330,8 @@ When you use a ``FileField`` on a form, you must also remember to and file name into a single object. * Validates that file data has been bound to the form, and that the file is of an image format understood by PIL. + * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``, + ``invalid_image`` Using an ImageField requires that the `Python Imaging Library`_ is installed. @@ -1312,6 +1348,8 @@ When you use a ``FileField`` on a form, you must also remember to * Normalizes to: A Python integer or long integer. * Validates that the given value is an integer. Leading and trailing whitespace is allowed, as in Python's ``int()`` function. + * Error message keys: ``required``, ``invalid``, ``max_value``, + ``min_value`` Takes two optional arguments for validation, ``max_value`` and ``min_value``. These control the range of values permitted in the field. @@ -1324,6 +1362,7 @@ These control the range of values permitted in the field. * Normalizes to: A Unicode object. * Validates that the given value is a valid IPv4 address, using a regular expression. + * Error message keys: ``required``, ``invalid`` ``MultipleChoiceField`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1333,6 +1372,7 @@ These control the range of values permitted in the field. * Normalizes to: A list of Unicode objects. * Validates that every value in the given list of values exists in the list of choices. + * Error message keys: ``required``, ``invalid_choice``, ``invalid_list`` Takes one extra argument, ``choices``, which is an iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. @@ -1353,6 +1393,7 @@ tuple) of 2-tuples to use as choices for this field. * Normalizes to: A Unicode object. * Validates that the given value matches against a certain regular expression. + * Error message keys: ``required``, ``invalid`` Takes one required argument, ``regex``, which is a regular expression specified either as a string or a compiled regular expression object. @@ -1364,11 +1405,13 @@ Also takes the following optional arguments: ====================== ===================================================== ``max_length`` Ensures the string has at most this many characters. ``min_length`` Ensures the string has at least this many characters. - ``error_message`` Error message to return for failed validation. If no - message is provided, a generic error message will be - used. ====================== ===================================================== +The optional argument ``error_message`` is also accepted for backwards +compatibility. The preferred way to provide an error message is to use the +``error_messages`` argument, passing a dictionary with ``'invalid'`` as a key +and the error message as the value. + ``TimeField`` ~~~~~~~~~~~~~ @@ -1377,6 +1420,7 @@ Also takes the following optional arguments: * Normalizes to: A Python ``datetime.time`` object. * Validates that the given value is either a ``datetime.time`` or string formatted in a particular time format. + * Error message keys: ``required``, ``invalid`` Takes one optional argument, ``input_formats``, which is a list of formats used to attempt to convert a string to a valid ``datetime.time`` object. @@ -1393,6 +1437,7 @@ If no ``input_formats`` argument is provided, the default input formats are:: * Empty value: ``''`` (an empty string) * Normalizes to: A Unicode object. * Validates that the given value is a valid URL. + * Error message keys: ``required``, ``invalid``, ``invalid_link`` Takes the following optional arguments: