From 3a331970b45af217bd69b04f3b105ecbebb5bb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kr=C3=A1l?= Date: Thu, 18 Jun 2009 00:59:07 +0000 Subject: [PATCH] [soc2009/model-validation] Added validators to DbFields ComplexValidators are not yet run on the model and custom message overring is still not in place git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11037 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/__init__.py | 29 +++++++++++++++---- tests/modeltests/validation/models.py | 6 ++++ tests/modeltests/validation/tests.py | 41 +++++++++++++++++---------- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index f6044d9ccf..f60ef4cc25 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -58,13 +58,14 @@ class Field(object): # creates, creation_counter is used for all user-specified fields. creation_counter = 0 auto_creation_counter = -1 + default_validators = [] def __init__(self, verbose_name=None, name=None, primary_key=False, max_length=None, unique=False, blank=False, null=False, db_index=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, unique_for_date=None, unique_for_month=None, unique_for_year=None, choices=None, help_text='', db_column=None, - db_tablespace=None, auto_created=False): + db_tablespace=None, auto_created=False, validators=[]): self.name = name self.verbose_name = verbose_name self.primary_key = primary_key @@ -97,6 +98,8 @@ class Field(object): self.creation_counter = Field.creation_counter Field.creation_counter += 1 + self.validators = self.default_validators + validators + def __cmp__(self, other): # This is needed because bisect does not take a comparison function. return cmp(self.creation_counter, other.creation_counter) @@ -117,6 +120,22 @@ class Field(object): Returns the converted value. Subclasses should override this. """ return value + + def run_validators(self, value): + if value in validators.EMPTY_VALUES: + return + + errors = [] + for v in self.validators: + # don't run complex validators since they need model_instance + # and must therefore be run on the model level + if not isinstance(v, validators.ComplexValidator): + try: + v(value) + except exceptions.ValidationError, e: + errors.extend(e.messages) + if errors: + raise exceptions.ValidationError(errors) def validate(self, value, model_instance): """ @@ -140,6 +159,7 @@ class Field(object): ugettext_lazy("This field cannot be blank.")) + def clean(self, value, model_instance): """ Convert the value's type and wun validation. Validation errors from to_python @@ -148,6 +168,7 @@ class Field(object): """ value = self.to_python(value) self.validate(value, model_instance) + self.run_validators(value) return value def db_type(self): @@ -666,15 +687,11 @@ class DecimalField(Field): return super(DecimalField, self).formfield(**defaults) class EmailField(CharField): + default_validators = [validators.validate_email] def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 75) CharField.__init__(self, *args, **kwargs) - def formfield(self, **kwargs): - defaults = {'form_class': forms.EmailField} - defaults.update(kwargs) - return super(EmailField, self).formfield(**defaults) - class FilePathField(Field): def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): self.path, self.match, self.recursive = path, match, recursive diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py index e1bbaa6a39..e41fc3b332 100644 --- a/tests/modeltests/validation/models.py +++ b/tests/modeltests/validation/models.py @@ -4,11 +4,17 @@ from django.core.exceptions import ValidationError from django.db import models from django.test import TestCase +def validate_answer_to_universe(value): + if value != 42: + raise ValidationError('This is not the answer to life, universe and everything!') + class ModelToValidate(models.Model): name = models.CharField(max_length=100) created = models.DateTimeField(default=datetime.now) number = models.IntegerField() parent = models.ForeignKey('self', blank=True, null=True) + email = models.EmailField(blank=True) + f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe]) def validate(self): super(ModelToValidate, self).validate() diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py index f5944c3649..c395fb26ab 100644 --- a/tests/modeltests/validation/tests.py +++ b/tests/modeltests/validation/tests.py @@ -7,13 +7,16 @@ from django.db import models from models import * class BaseModelValidationTests(TestCase): + def assertFailsValidation(self, clean, failed_fields): + self.assertRaises(ValidationError, clean) + try: + clean() + except ValidationError, e: + self.assertEquals(sorted(failed_fields), sorted(e.message_dict.keys())) + def test_missing_required_field_raises_error(self): mtv = ModelToValidate() - self.assertRaises(ValidationError, mtv.clean) - try: - mtv.clean() - except ValidationError, e: - self.assertEquals(['name', 'number'], sorted(e.message_dict.keys())) + self.assertFailsValidation(mtv.clean, ['name', 'number']) def test_with_correct_value_model_validates(self): mtv = ModelToValidate(number=10, name='Some Name') @@ -21,25 +24,33 @@ class BaseModelValidationTests(TestCase): def test_custom_validate_method_is_called(self): mtv = ModelToValidate(number=11) - self.assertRaises(ValidationError, mtv.clean) - try: - mtv.clean() - except ValidationError, e: - self.assertEquals(sorted([NON_FIELD_ERRORS, 'name']), sorted(e.message_dict.keys())) + self.assertFailsValidation(mtv.clean, [NON_FIELD_ERRORS, 'name']) def test_wrong_FK_value_raises_error(self): mtv=ModelToValidate(number=10, name='Some Name', parent_id=3) - self.assertRaises(ValidationError, mtv.clean) - try: - mtv.clean() - except ValidationError, e: - self.assertEquals(['parent'], e.message_dict.keys()) + self.assertFailsValidation(mtv.clean, ['parent']) def test_correct_FK_value_cleans(self): parent = ModelToValidate.objects.create(number=10, name='Some Name') mtv=ModelToValidate(number=10, name='Some Name', parent_id=parent.pk) self.assertEqual(None, mtv.clean()) + def test_wrong_email_value_raises_error(self): + mtv = ModelToValidate(number=10, name='Some Name', email='not-an-email') + self.assertFailsValidation(mtv.clean, ['email']) + + def test_correct_email_value_passes(self): + mtv = ModelToValidate(number=10, name='Some Name', email='valid@email.com') + self.assertEqual(None, mtv.clean()) + + def test_custom_validator_passes_for_correct_value(self): + mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42) + self.assertEqual(None, mtv.clean()) + + def test_custom_validator_raises_error_for_incorrect_value(self): + mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=12) + self.assertFailsValidation(mtv.clean, ['f_with_custom_validator']) + class GetUniqueCheckTests(unittest.TestCase): def test_unique_fields_get_collected(self): m = UniqueFieldsModel()