From b59bfbd128a882778f4d96ac9a29880791f728b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kr=C3=A1l?= Date: Mon, 1 Jun 2009 15:38:34 +0000 Subject: [PATCH] [soc2009/model-validation] Implemented some basic validation on model Fields including basic tests. git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@10868 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/__init__.py | 37 ++++++++++++++---- django/db/models/fields/related.py | 13 +++++++ tests/regressiontests/model_fields/tests.py | 42 +++++++++++++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index a3007a7f66..327a236123 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -117,6 +117,32 @@ class Field(object): Returns the converted value. Subclasses should override this. """ return value + + def validate(self, value, model_instance): + """ + Validates value and throws ValidationError. Subclasses should override + this to provide validation logic. + """ + if not self.editable: + # skip validation for non-editable fields + return + if self._choices and value: + if not value in dict(self.choices): + raise exceptions.ValidationError(_('Value %r is not a valid choice.') % value) + + if value is None and not self.null: + raise exceptions.ValidationError( + ugettext_lazy("This field cannot be null.")) + + def clean(self, value, model_instance): + """ + Convert the value's type and wun validation. Validation errors from to_python + and validate are propagated. The correct value is returned if no error is + raised. + """ + value = self.to_python(value) + self.validate(value, model_instance) + return value def db_type(self): """ @@ -354,6 +380,9 @@ class AutoField(Field): except (TypeError, ValueError): raise exceptions.ValidationError( _("This value must be an integer.")) + + def validate(self, value, model_instance): + pass def get_db_prep_value(self, value): if value is None: @@ -417,14 +446,8 @@ class CharField(Field): return "CharField" def to_python(self, value): - if isinstance(value, basestring): + if isinstance(value, basestring) or value is None: return value - if value is None: - if self.null: - return value - else: - raise exceptions.ValidationError( - ugettext_lazy("This field cannot be null.")) return smart_unicode(value) def formfield(self, **kwargs): diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 419695b74b..d4b921bc1d 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -678,6 +678,11 @@ class ForeignKey(RelatedField, Field): self.db_index = True + def validate(self, value, model_instance): + if self.rel.parent_link: + return + super(ForeignKey, self).validate(value, model_instance) + def get_attname(self): return '%s_id' % self.name @@ -766,6 +771,14 @@ class OneToOneField(ForeignKey): return None return super(OneToOneField, self).formfield(**kwargs) + def save_form_data(self, instance, data): + # FIXME: is this a hack, or what? it works, but I don't really know why + if isinstance(data, self.rel.to): + setattr(instance, self.name, data) + else: + setattr(instance, self.attname, data) + + class ManyToManyField(RelatedField, Field): def __init__(self, to, **kwargs): try: diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index 0815d9ef3c..7335723978 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -132,3 +132,45 @@ class SlugFieldTests(django.test.TestCase): bs = BigS.objects.get(pk=bs.pk) self.assertEqual(bs.s, 'slug'*50) +class ValidationTest(django.test.TestCase): + def test_charfield_cleans_empty_string(self): + f = models.CharField() + self.assertEqual('', f.clean('', None)) + + def test_integerfield_cleans_valid_string(self): + f = models.IntegerField() + self.assertEqual(2, f.clean('2', None)) + + def test_integerfield_raises_error_on_invalid_intput(self): + f = models.IntegerField() + self.assertRaises(ValidationError, f.clean, "a", None) + + def test_charfield_with_choices_cleans_valid_choice(self): + f = models.CharField(choices=[('a','A'), ('b','B')]) + self.assertEqual('a', f.clean('a', None)) + + def test_charfield_with_choices_raises_error_on_invalid_choice(self): + f = models.CharField(choices=[('a','A'), ('b','B')]) + self.assertRaises(ValidationError, f.clean, "not a", None) + + def test_nullable_integerfield_cleans_none(self): + f = models.IntegerField(null=True) + self.assertEqual(None, f.clean(None, None)) + + def test_integerfield_raises_error_on_empty_input(self): + f = models.IntegerField(null=False) + self.assertRaises(ValidationError, f.clean, None, None) + self.assertRaises(ValidationError, f.clean, '', None) + + def test_charfield_raises_error_on_empty_input(self): + f = models.CharField(null=False) + self.assertRaises(ValidationError, f.clean, None, None) + + def test_datefield_cleans_date(self): + f = models.DateField() + self.assertEqual(datetime.date(2008, 10, 10), f.clean('2008-10-10', None)) + + def test_boolean_field_doesnt_accept_empty_input(self): + f = models.BooleanField() + self.assertRaises(ValidationError, f.clean, None, None) +