Skip to content
Browse files

[soc2009/model-validation] Added clean() and validate() methods to Mo…

…del.

This also called for ValidationError to also accept dicts as message.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@10869 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent b59bfbd commit 6152164303b6b8ac7705873fbebac72177efcb39 @HonzaKral HonzaKral committed Jun 1, 2009
View
12 django/core/exceptions.py
@@ -32,23 +32,33 @@ class FieldError(Exception):
"""Some kind of problem with a model field."""
pass
+NON_FIELD_ERRORS = '__all__'
class ValidationError(Exception):
"""An error while validating data."""
def __init__(self, message):
+ import operator
from django.utils.encoding import force_unicode
"""
ValidationError can be passed any object that can be printed (usually
- a string) or a list of objects.
+ a string), a list of objects or a dictionary.
"""
+ if isinstance(message, dict):
+ self.message_dict = message
+ message = reduce(operator.add, message.values())
+
if isinstance(message, list):
self.messages = [force_unicode(msg) for msg in message]
else:
message = force_unicode(message)
self.messages = [message]
+
+
def __str__(self):
# This is needed because, without a __str__(), printing an exception
# instance would result in this:
# AttributeError: ValidationError instance has no attribute 'args'
# See http://www.python.org/doc/current/tut/node10.html#handling
+ if hasattr(self, 'message_dict'):
+ return repr(self.message_dict)
return repr(self.messages)
View
32 django/db/models/base.py
@@ -9,7 +9,7 @@
from sets import Set as set # Python 2.3 fallback.
import django.db.models.manager # Imported to register signal handler.
-from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
from django.db.models.query import delete_objects, Q
@@ -597,6 +597,36 @@ def _get_next_or_previous_in_order(self, is_next):
def prepare_database_save(self, unused):
return self.pk
+ def validate(self):
+ """
+ Hook for doing any extra model-wide validation after Model.clean() been
+ called on every field. Any ValidationError raised by this method will
+ not be associated with a particular field; it will have a special-case
+ association with the field named '__all__'.
+ """
+ pass
+
+ def clean(self):
+ """
+ Cleans all fields and raises ValidationError containing message_dict of
+ all validation errors if any occur.
+ """
+ errors = {}
+ for f in self._meta.fields:
+ try:
+ # TODO: is the [sg]etattr correct?
+ setattr(self, f.attname, f.clean(getattr(self, f.attname), self))
+ except ValidationError, e:
+ errors[f.name] = e.messages
+ try:
+ # TODO: run this only if not errors??
+ self.validate()
+ except ValidationError, e:
+ errors[NON_FIELD_ERRORS] = e.messages
+
+ if errors:
+ raise ValidationError(errors)
+
############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
View
0 tests/modeltests/validation/__init__.py
No changes.
View
28 tests/modeltests/validation/models.py
@@ -0,0 +1,28 @@
+from datetime import datetime
+
+from django.core.exceptions import ValidationError
+from django.db import models
+from django.test import TestCase
+
+class ModelToValidate(models.Model):
+ name = models.CharField(max_length=100)
+ created = models.DateTimeField(default=datetime.now)
+ number = models.IntegerField()
+
+ def validate(self):
+ super(ModelToValidate, self).validate()
+ if self.number == 11:
+ raise ValidationError('Invalid number supplied!')
+
+class BaseModelValidationTests(TestCase):
+ def test_missing_required_field_raises_error(self):
+ mtv = ModelToValidate()
+ self.assertRaises(ValidationError, mtv.clean)
+
+ def test_with_correct_value_model_validates(self):
+ mtv = ModelToValidate(number=10)
+ self.assertEqual(None, mtv.clean())
+
+ def test_custom_validate_method_is_called(self):
+ mtv = ModelToValidate(number=11)
+ self.assertRaises(ValidationError, mtv.clean)

0 comments on commit 6152164

Please sign in to comment.
Something went wrong with that request. Please try again.