Ticket #12498: complex-validators.diff
File complex-validators.diff, 15.4 KB (added by , 15 years ago) |
---|
-
django/core/validators.py
diff --git a/django/core/validators.py b/django/core/validators.py index 6cd290f..4515ca7 100644
a b class MaxLengthValidator(BaseValidator): 135 135 message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') 136 136 code = 'max_length' 137 137 138 139 class ComplexValidator(object): 140 def get_value(self, name, all_values, obj): 141 assert all_values or obj, "Either all_values or obj must be supplied" 142 143 if all_values: 144 return all_values.get(name, None) 145 if obj: 146 return getattr(obj, name, None) 147 148 149 def __call__(self, value, all_values={}, obj=None): 150 raise NotImplementedError() 151 152 class RequiredIfOtherFieldBlank(ComplexValidator): 153 def __init__(self, other_field): 154 self.other_field = other_field 155 156 def __call__(self, value, all_values={}, obj=None): 157 if self.get_value(self.other_field, all_values, obj) in EMPTY_VALUES: 158 if value in EMPTY_VALUES: 159 raise ValidationError('This field is required if %s is blank.' % self.other_field) 160 -
django/db/models/base.py
diff --git a/django/db/models/base.py b/django/db/models/base.py index 660c570..eace314 100644
a b class Model(object): 799 799 except ValidationError, e: 800 800 errors[f.name] = e.messages 801 801 802 # run complex validators after the fields have been cleaned since they 803 # need access to model_instance. 804 for f in self._meta.fields: 805 if f.name in errors: 806 continue 807 808 value = getattr(self, f.attname) 809 for v in f.validators: 810 if isinstance(v, validators.ComplexValidator): 811 try: 812 v(value, obj=self) 813 except ValidationError, e: 814 error_list = errors.setdefault(f.name, []) 815 if hasattr(e, 'code') and e.code in f.error_messages: 816 message = f.error_messages[e.code] 817 if e.params: 818 message = message % e.params 819 error_list.append(message) 820 else: 821 error_list.extend(e.messages) 802 822 # Form.clean() is run even if other validation fails, so do the 803 823 # same with Model.validate() for consistency. 804 824 try: -
django/db/models/fields/__init__.py
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 5c36437..96fc36f 100644
a b class Field(object): 176 176 177 177 errors = [] 178 178 for v in self.validators: 179 try: 180 v(value) 181 except exceptions.ValidationError, e: 182 if hasattr(e, 'code') and e.code in self.error_messages: 183 message = self.error_messages[e.code] 184 if e.params: 185 message = message % e.params 186 errors.append(message) 187 else: 188 errors.extend(e.messages) 179 # Don't run complex validators since they need the model instance 180 # and must therefore be run on the model level. 181 if not isinstance(v, validators.ComplexValidator): 182 try: 183 v(value) 184 except exceptions.ValidationError, e: 185 if hasattr(e, 'code') and e.code in self.error_messages: 186 message = self.error_messages[e.code] 187 if e.params: 188 message = message % e.params 189 errors.append(message) 190 else: 191 errors.extend(e.messages) 189 192 if errors: 190 193 raise exceptions.ValidationError(errors) 191 194 -
django/forms/fields.py
diff --git a/django/forms/fields.py b/django/forms/fields.py index 23a88d6..40e3d90 100644
a b class Field(object): 130 130 return 131 131 errors = [] 132 132 for v in self.validators: 133 try: 134 v(value) 135 except ValidationError, e: 136 if hasattr(e, 'code') and e.code in self.error_messages: 137 message = self.error_messages[e.code] 138 if e.params: 139 message = message % e.params 140 errors.append(message) 141 else: 142 errors.extend(e.messages) 133 # don't run complex validators since they need all_values 134 # and must therefore be run on the form level 135 if not isinstance(v, validators.ComplexValidator): 136 try: 137 v(value) 138 except ValidationError, e: 139 if hasattr(e, 'code') and e.code in self.error_messages: 140 message = self.error_messages[e.code] 141 if e.params: 142 message = message % e.params 143 errors.append(message) 144 else: 145 errors.extend(e.messages) 143 146 if errors: 144 147 raise ValidationError(errors) 145 148 -
django/forms/forms.py
diff --git a/django/forms/forms.py b/django/forms/forms.py index d484300..5b8a4d0 100644
a b Form classes 3 3 """ 4 4 5 5 from django.core.exceptions import ValidationError 6 from django.core.validators import ComplexValidator 6 7 from django.utils.copycompat import deepcopy 7 8 from django.utils.datastructures import SortedDict 8 9 from django.utils.html import conditional_escape … … class BaseForm(StrAndUnicode): 282 283 self._errors[name] = self.error_class(e.messages) 283 284 if name in self.cleaned_data: 284 285 del self.cleaned_data[name] 286 287 # Run complex validators after the fields have been cleaned since they 288 # need access to all_values. 289 for name, field in self.fields.items(): 290 if not name in self.cleaned_data: 291 continue 292 failed = False 293 for v in field.validators: 294 # Skip noncomplex validators, they have already been run on the field. 295 if not isinstance(v, ComplexValidator): 296 continue 297 try: 298 v(self.cleaned_data[name], all_values=self.cleaned_data) 299 except ValidationError, e: 300 failed = True 301 error_list = self._errors.setdefault(name, self.error_class()) 302 if hasattr(e, 'code') and e.code in field.error_messages: 303 message = field.error_messages[e.code] 304 if e.params: 305 message = message % e.params 306 error_list.append(message) 307 else: 308 error_list.extend(e.messages) 309 if failed: 310 del self.cleaned_data[name] 285 311 try: 286 312 self.cleaned_data = self.clean() 287 313 except ValidationError, e: -
tests/modeltests/validation/models.py
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py index db5100b..1260417 100644
a b 1 1 from datetime import datetime 2 2 3 3 from django.core.exceptions import ValidationError 4 from django.core.validators import ComplexValidator 4 5 from django.db import models 5 6 from django.test import TestCase 6 7 … … def validate_answer_to_universe(value): 8 9 if value != 42: 9 10 raise ValidationError('This is not the answer to life, universe and everything!', code='not42') 10 11 12 class ValidateFieldNotEqualsOtherField(ComplexValidator): 13 def __init__(self, other_field): 14 self.other_field = other_field 15 16 def __call__(self, value, all_values={}, obj=None): 17 if value == self.get_value(self.other_field, all_values, obj): 18 raise ValidationError("Must not equal to %r's value" % self.other_field, code='not_equal', params=(self.other_field,)) 19 11 20 class ModelToValidate(models.Model): 12 21 name = models.CharField(max_length=100) 13 22 created = models.DateTimeField(default=datetime.now) … … class ModelToValidate(models.Model): 15 24 parent = models.ForeignKey('self', blank=True, null=True) 16 25 email = models.EmailField(blank=True) 17 26 url = models.URLField(blank=True) 18 f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe ])27 f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe, ValidateFieldNotEqualsOtherField('number')]) 19 28 20 29 def validate(self): 21 30 super(ModelToValidate, self).validate() … … class CustomMessagesModel(models.Model): 49 58 other = models.IntegerField(blank=True, null=True) 50 59 number = models.IntegerField( 51 60 error_messages={'null': 'NULL', 'not42': 'AAARGH', 'not_equal': '%s != me'}, 52 validators=[validate_answer_to_universe ]61 validators=[validate_answer_to_universe, ValidateFieldNotEqualsOtherField('other')] 53 62 ) 54 63 55 64 -
tests/modeltests/validation/test_custom_messages.py
diff --git a/tests/modeltests/validation/test_custom_messages.py b/tests/modeltests/validation/test_custom_messages.py index 9a958a0..e543dbd 100644
a b 1 1 from modeltests.validation import ValidationTestCase 2 from models import CustomMessagesModel3 4 2 3 from models import CustomMessagesModel 5 4 class CustomMessagesTest(ValidationTestCase): 5 def test_custom_complex_validator_message(self): 6 cmm = CustomMessagesModel(number=42, other=42) 7 self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['other != me']) 8 6 9 def test_custom_simple_validator_message(self): 7 10 cmm = CustomMessagesModel(number=12) 8 11 self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['AAARGH']) -
tests/modeltests/validation/validators.py
diff --git a/tests/modeltests/validation/validators.py b/tests/modeltests/validation/validators.py index d46d0c5..d54aab6 100644
a b class TestModelsWithValidators(ValidationTestCase): 18 18 [u'This is not the answer to life, universe and everything!'] 19 19 ) 20 20 21 def test_custom_complex_validator_raises_error_for_incorrect_value(self): 22 mtv = ModelToValidate(number=42, name='Some Name', f_with_custom_validator=42) 23 self.assertFailsValidation(mtv.full_validate, ['f_with_custom_validator']) 24 self.assertFieldFailsValidationWithMessage( 25 mtv.full_validate, 26 'f_with_custom_validator', 27 [u"Must not equal to 'number''s value"] 28 ) 29 30 def test_complex_validator_isnt_run_if_field_doesnt_validate(self): 31 mtv = ModelToValidate(number=32, name='Some Name', f_with_custom_validator=32) 32 self.assertFieldFailsValidationWithMessage( 33 mtv.full_validate, 34 'f_with_custom_validator', 35 [u'This is not the answer to life, universe and everything!'] 36 ) -
tests/modeltests/validators/tests.py
diff --git a/tests/modeltests/validators/tests.py b/tests/modeltests/validators/tests.py index 1108ee8..331f6f0 100644
a b from django.core.validators import ( 9 9 validate_integer, validate_email, validate_slug, validate_ipv4_address, 10 10 validate_comma_separated_integer_list, MaxValueValidator, 11 11 MinValueValidator, MaxLengthValidator, MinLengthValidator, 12 URLValidator, BaseValidator, RegexValidator, 12 RequiredIfOtherFieldBlank, URLValidator, BaseValidator, 13 RegexValidator, 13 14 ) 14 15 15 16 now = datetime.now() … … for validator, value, expected in SIMPLE_VALIDATORS_VALUES: 152 153 setattr(TestSimpleValidators, *get_simple_test_func(validator, expected, value, test_counter)) 153 154 test_counter += 1 154 155 156 class TestComplexValidators(TestCase): 157 pass 158 159 COMPLEX_VALIDATORS_VALUES = ( 160 #(validator, value, all_values, obj, expected), 161 (RequiredIfOtherFieldBlank('other'), 'given', {'other': 'given'}, None, None), 162 (RequiredIfOtherFieldBlank('other'), '', {'other': 'given'}, None, None), 163 (RequiredIfOtherFieldBlank('other'), 'given', {}, None, AssertionError), 164 (RequiredIfOtherFieldBlank('other'), '', {}, None, AssertionError), 165 (RequiredIfOtherFieldBlank('other'), '', {'other': ''}, None, ValidationError), 166 ) 167 168 def get_complex_test_func(validator, expected, value, all_values, obj, num): 169 if isinstance(expected, type) and issubclass(expected, Exception): 170 test_mask = 'test_%s_raises_error_%d' 171 def test_func(self): 172 self.assertRaises(expected, validator, value, all_values=all_values, obj=obj) 173 else: 174 test_mask = 'test_%s_%d' 175 def test_func(self): 176 self.assertEqual(expected, validator(value, all_values=all_values, obj=obj)) 177 test_name = test_mask % (validator.__class__.__name__, num) 178 return test_name, test_func 179 180 test_counter = {} 181 for validator, value, all_values, obj, expected in COMPLEX_VALIDATORS_VALUES: 182 num = test_counter[validator.__class__.__name__] = test_counter.setdefault(validator.__class__.__name__, 0) + 1 183 setattr(TestComplexValidators, *get_complex_test_func(validator, expected, value, all_values, obj, num)) 184 -
tests/regressiontests/forms/tests.py
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index db70500..e9ae948 100644
a b from formsets import tests as formset_tests 38 38 from media import media_tests 39 39 40 40 from fields import FieldsTests 41 from validators import TestF ieldWithValidators41 from validators import TestFormWithValidators, TestFieldWithValidators 42 42 43 43 __test__ = { 44 44 'extra_tests': extra_tests, -
tests/regressiontests/forms/validators.py
diff --git a/tests/regressiontests/forms/validators.py b/tests/regressiontests/forms/validators.py index b75a14e..becf4e2 100644
a b from django import forms 4 4 from django.core import validators 5 5 from django.core.exceptions import ValidationError 6 6 7 class AlwaysFailingValidator(validators.ComplexValidator): 8 def __call__(self, value, all_values={}, obj=None): 9 raise ValidationError('AlwaysFailingValidator') 7 10 8 11 class TestFieldWithValidators(TestCase): 9 12 def test_all_errors_get_reported(self): … … class TestFieldWithValidators(TestCase): 16 19 except ValidationError, e: 17 20 self.assertEqual(2, len(e.messages)) 18 21 22 class TestFormWithValidators(TestCase): 23 def test_all_complex_validators_get_run_even_if_they_fail(self): 24 class MyForm(forms.Form): 25 validator_field = forms.CharField( 26 validators=[ 27 AlwaysFailingValidator(), 28 AlwaysFailingValidator(), 29 ] 30 ) 31 form = MyForm({'validator_field': 'some value'}) 32 self.assertFalse(form.is_valid()) 33 self.assertEqual(['validator_field'], form.errors.keys()) 34 self.assertEqual(['AlwaysFailingValidator', 'AlwaysFailingValidator'], form.errors['validator_field'])