1
0
mirror of https://github.com/django/django.git synced 2025-10-24 14:16:09 +00:00

[soc2009/model-validation] Added custom messages to models

No tests yet and it broke queryset pickling

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11398 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Honza Král
2009-08-05 01:28:21 +00:00
parent 82c76b98cb
commit a604fc39d0
3 changed files with 73 additions and 34 deletions

View File

@@ -19,7 +19,7 @@ from django.utils.datastructures import DictWrapper
from django.utils.functional import curry from django.utils.functional import curry
from django.utils.itercompat import tee from django.utils.itercompat import tee
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, ugettext as _ from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils.encoding import smart_unicode, force_unicode, smart_str from django.utils.encoding import smart_unicode, force_unicode, smart_str
from django.utils import datetime_safe from django.utils import datetime_safe
@@ -58,14 +58,20 @@ class Field(object):
# creates, creation_counter is used for all user-specified fields. # creates, creation_counter is used for all user-specified fields.
creation_counter = 0 creation_counter = 0
auto_creation_counter = -1 auto_creation_counter = -1
default_validators = [] default_validators = [] # Default set of validators
default_error_messages = {
'invalid_choice': _(u'Value %r is not a valid choice.'),
'null': _(u'This field cannot be null.'),
'blank': _(u'This field cannot be blank.'),
}
def __init__(self, verbose_name=None, name=None, primary_key=False, def __init__(self, verbose_name=None, name=None, primary_key=False,
max_length=None, unique=False, blank=False, null=False, max_length=None, unique=False, blank=False, null=False,
db_index=False, rel=None, default=NOT_PROVIDED, editable=True, db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
serialize=True, unique_for_date=None, unique_for_month=None, serialize=True, unique_for_date=None, unique_for_month=None,
unique_for_year=None, choices=None, help_text='', db_column=None, unique_for_year=None, choices=None, help_text='', db_column=None,
db_tablespace=None, auto_created=False, validators=[]): db_tablespace=None, auto_created=False, validators=[],
error_messages=None):
self.name = name self.name = name
self.verbose_name = verbose_name self.verbose_name = verbose_name
self.primary_key = primary_key self.primary_key = primary_key
@@ -100,6 +106,12 @@ class Field(object):
self.validators = self.default_validators + validators self.validators = self.default_validators + validators
messages = {}
for c in reversed(self.__class__.__mro__):
messages.update(getattr(c, 'default_error_messages', {}))
messages.update(error_messages or {})
self.error_messages = messages
def __cmp__(self, other): def __cmp__(self, other):
# This is needed because bisect does not take a comparison function. # This is needed because bisect does not take a comparison function.
return cmp(self.creation_counter, other.creation_counter) return cmp(self.creation_counter, other.creation_counter)
@@ -147,16 +159,14 @@ class Field(object):
return return
if self._choices and value: if self._choices and value:
if not value in dict(self.choices): if not value in dict(self.choices):
raise exceptions.ValidationError(_('Value %r is not a valid choice.') % value) raise exceptions.ValidationError(self.error_messages['invalid_choice'] % value)
if value is None and not self.null: if value is None and not self.null:
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['null'])
ugettext_lazy("This field cannot be null."))
# cannot do if not value because of 0 passed to integer fields # cannot do if not value because of 0 passed to integer fields
if not self.blank and value in validators.EMPTY_VALUES: if not self.blank and value in validators.EMPTY_VALUES:
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['blank'])
ugettext_lazy("This field cannot be blank."))
@@ -394,6 +404,9 @@ class Field(object):
class AutoField(Field): class AutoField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _(u'This value must be an integer.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__ assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
kwargs['blank'] = True kwargs['blank'] = True
@@ -405,8 +418,7 @@ class AutoField(Field):
try: try:
return int(value) return int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_("This value must be an integer."))
def validate(self, value, model_instance): def validate(self, value, model_instance):
pass pass
@@ -427,6 +439,9 @@ class AutoField(Field):
class BooleanField(Field): class BooleanField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _(u'This value must be either True or False.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['blank'] = True kwargs['blank'] = True
if 'default' not in kwargs and not kwargs.get('null'): if 'default' not in kwargs and not kwargs.get('null'):
@@ -440,8 +455,7 @@ class BooleanField(Field):
if value in (True, False): return value if value in (True, False): return value
if value in ('t', 'True', '1'): return True if value in ('t', 'True', '1'): return True
if value in ('f', 'False', '0'): return False if value in ('f', 'False', '0'): return False
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_("This value must be either True or False."))
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
# Special-case handling for filters coming from a web request (e.g. the # Special-case handling for filters coming from a web request (e.g. the
@@ -505,6 +519,10 @@ ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
class DateField(Field): class DateField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _('Enter a valid date in YYYY-MM-DD format.'),
'invalid_date': _('Invalid date: %s'),
}
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): 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 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. #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@@ -525,8 +543,7 @@ class DateField(Field):
return value return value
if not ansi_date_re.search(value): if not ansi_date_re.search(value):
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_('Enter a valid date in YYYY-MM-DD format.'))
# Now that we have the date string in YYYY-MM-DD format, check to make # Now that we have the date string in YYYY-MM-DD format, check to make
# sure it's a valid date. # sure it's a valid date.
# We could use time.strptime here and catch errors, but datetime.date # We could use time.strptime here and catch errors, but datetime.date
@@ -535,7 +552,7 @@ class DateField(Field):
try: try:
return datetime.date(year, month, day) return datetime.date(year, month, day)
except ValueError, e: except ValueError, e:
msg = _('Invalid date: %s') % _(str(e)) msg = self.error_messages['invalid_date'] % _(str(e))
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
def pre_save(self, model_instance, add): def pre_save(self, model_instance, add):
@@ -579,6 +596,9 @@ class DateField(Field):
return super(DateField, self).formfield(**defaults) return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField): class DateTimeField(DateField):
default_error_messages = {
'invalid': _(u'Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'),
}
def get_internal_type(self): def get_internal_type(self):
return "DateTimeField" return "DateTimeField"
@@ -598,8 +618,7 @@ class DateTimeField(DateField):
value, usecs = value.split('.') value, usecs = value.split('.')
usecs = int(usecs) usecs = int(usecs)
except ValueError: except ValueError:
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
else: else:
usecs = 0 usecs = 0
kwargs = {'microsecond': usecs} kwargs = {'microsecond': usecs}
@@ -616,8 +635,7 @@ class DateTimeField(DateField):
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3], return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
**kwargs) **kwargs)
except ValueError: except ValueError:
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
def get_db_prep_value(self, value): def get_db_prep_value(self, value):
# Casts dates into the format expected by the backend # Casts dates into the format expected by the backend
@@ -639,6 +657,9 @@ class DateTimeField(DateField):
class DecimalField(Field): class DecimalField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _(u'This value must be a decimal number.'),
}
def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): 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 self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
@@ -652,8 +673,7 @@ class DecimalField(Field):
try: try:
return decimal.Decimal(value) return decimal.Decimal(value)
except decimal.InvalidOperation: except decimal.InvalidOperation:
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_("This value must be a decimal number."))
def _format(self, value): def _format(self, value):
if isinstance(value, basestring) or value is None: if isinstance(value, basestring) or value is None:
@@ -718,6 +738,9 @@ class FilePathField(Field):
class FloatField(Field): class FloatField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _("This value must be a float."),
}
def get_db_prep_value(self, value): def get_db_prep_value(self, value):
if value is None: if value is None:
@@ -733,8 +756,7 @@ class FloatField(Field):
try: try:
return float(value) return float(value)
except (TypeError, ValueError): except (TypeError, ValueError):
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_("This value must be a float."))
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'form_class': forms.FloatField} defaults = {'form_class': forms.FloatField}
@@ -743,6 +765,9 @@ class FloatField(Field):
class IntegerField(Field): class IntegerField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _("This value must be a float."),
}
def get_db_prep_value(self, value): def get_db_prep_value(self, value):
if value is None: if value is None:
return None return None
@@ -757,8 +782,7 @@ class IntegerField(Field):
try: try:
return int(value) return int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_("This value must be an integer."))
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'form_class': forms.IntegerField} defaults = {'form_class': forms.IntegerField}
@@ -781,6 +805,9 @@ class IPAddressField(Field):
class NullBooleanField(Field): class NullBooleanField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _("This value must be either None, True or False."),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['null'] = True kwargs['null'] = True
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
@@ -793,8 +820,7 @@ class NullBooleanField(Field):
if value in ('None',): return None if value in ('None',): return None
if value in ('t', 'True', '1'): return True if value in ('t', 'True', '1'): return True
if value in ('f', 'False', '0'): return False if value in ('f', 'False', '0'): return False
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_("This value must be either None, True or False."))
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
# Special-case handling for filters coming from a web request (e.g. the # Special-case handling for filters coming from a web request (e.g. the
@@ -868,6 +894,9 @@ class TextField(Field):
class TimeField(Field): class TimeField(Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'),
}
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): 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 self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add: if auto_now or auto_now_add:
@@ -896,8 +925,7 @@ class TimeField(Field):
value, usecs = value.split('.') value, usecs = value.split('.')
usecs = int(usecs) usecs = int(usecs)
except ValueError: except ValueError:
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'))
else: else:
usecs = 0 usecs = 0
kwargs = {'microsecond': usecs} kwargs = {'microsecond': usecs}
@@ -910,8 +938,7 @@ class TimeField(Field):
return datetime.time(*time.strptime(value, '%H:%M')[3:5], return datetime.time(*time.strptime(value, '%H:%M')[3:5],
**kwargs) **kwargs)
except ValueError: except ValueError:
raise exceptions.ValidationError( raise exceptions.ValidationError(self.error_messages['invalid'])
_('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'))
def pre_save(self, model_instance, add): def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add): if self.auto_now or (self.auto_now_add and add):

View File

@@ -6,7 +6,7 @@ from django.db.models.related import RelatedObject
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.models.query_utils import QueryWrapper from django.db.models.query_utils import QueryWrapper
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext
from django.utils.functional import curry from django.utils.functional import curry
from django.core import exceptions from django.core import exceptions
from django import forms from django import forms
@@ -683,6 +683,9 @@ class ManyToManyRel(object):
class ForeignKey(RelatedField, Field): class ForeignKey(RelatedField, Field):
empty_strings_allowed = False empty_strings_allowed = False
default_error_messages = {
'invalid': _('Model %(model)s with pk %(pk)r does not exist.')
}
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try: try:
to_name = to._meta.object_name.lower() to_name = to._meta.object_name.lower()
@@ -711,7 +714,8 @@ class ForeignKey(RelatedField, Field):
try: try:
self.rel.to._default_manager.get(**{self.rel.field_name:value}) self.rel.to._default_manager.get(**{self.rel.field_name:value})
except self.rel.to.DoesNotExist, e: except self.rel.to.DoesNotExist, e:
raise exceptions.ValidationError('Model %s with pk %r does not exist.' % (self.rel.to._meta.verbose_name, value)) raise exceptions.ValidationError(
self.error_messages['invalid'] % {'model': self.rel.to._meta.verbose_name, 'pk': value})
def get_attname(self): def get_attname(self):
return '%s_id' % self.name return '%s_id' % self.name
@@ -832,7 +836,7 @@ class ManyToManyField(RelatedField, Field):
Field.__init__(self, **kwargs) Field.__init__(self, **kwargs)
msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat(self.help_text, ' ', msg) self.help_text = string_concat(self.help_text, ' ', msg)
def get_choices_default(self): def get_choices_default(self):

View File

@@ -0,0 +1,8 @@
import unittest
from django.db import models
class CustomMessagesTests(unittest.TestCase):
def test_custom_message_is_used_when_present(self):
pass