custom field validator is now expected to raise a ValidationError (drop support for returning True/False)
This commit is contained in:
		| @@ -5,6 +5,8 @@ Changelog | |||||||
| Development | Development | ||||||
| =========== | =========== | ||||||
| - Add support for MongoDB 3.6 and Python3.7 in travis | - Add support for MongoDB 3.6 and Python3.7 in travis | ||||||
|  | - BREAKING CHANGE: Changed the custom field validator (i.e `validation` parameter of Field) so that it now requires: | ||||||
|  |     the callable to raise a ValidationError (i.o return True/False). | ||||||
| - Fix querying on List(EmbeddedDocument) subclasses fields #1961 #1492 | - Fix querying on List(EmbeddedDocument) subclasses fields #1961 #1492 | ||||||
| - Fix querying on (Generic)EmbeddedDocument subclasses fields #475 | - Fix querying on (Generic)EmbeddedDocument subclasses fields #475 | ||||||
| - expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all` | - expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all` | ||||||
|   | |||||||
| @@ -11,8 +11,7 @@ from mongoengine.base.common import UPDATE_OPERATORS | |||||||
| from mongoengine.base.datastructures import (BaseDict, BaseList, | from mongoengine.base.datastructures import (BaseDict, BaseList, | ||||||
|                                              EmbeddedDocumentList) |                                              EmbeddedDocumentList) | ||||||
| from mongoengine.common import _import_class | from mongoengine.common import _import_class | ||||||
| from mongoengine.errors import ValidationError | from mongoengine.errors import ValidationError, DeprecatedError | ||||||
|  |  | ||||||
|  |  | ||||||
| __all__ = ('BaseField', 'ComplexBaseField', 'ObjectIdField', | __all__ = ('BaseField', 'ComplexBaseField', 'ObjectIdField', | ||||||
|            'GeoJsonBaseField') |            'GeoJsonBaseField') | ||||||
| @@ -53,8 +52,8 @@ class BaseField(object): | |||||||
|             unique with. |             unique with. | ||||||
|         :param primary_key: Mark this field as the primary key. Defaults to False. |         :param primary_key: Mark this field as the primary key. Defaults to False. | ||||||
|         :param validation: (optional) A callable to validate the value of the |         :param validation: (optional) A callable to validate the value of the | ||||||
|             field.  Generally this is deprecated in favour of the |             field.  The callable takes the value as parameter and should raise | ||||||
|             `FIELD.validate` method |             a ValidationError if validation fails | ||||||
|         :param choices: (optional) The valid choices |         :param choices: (optional) The valid choices | ||||||
|         :param null: (optional) If the field value can be null. If no and there is a default value |         :param null: (optional) If the field value can be null. If no and there is a default value | ||||||
|             then the default value is set |             then the default value is set | ||||||
| @@ -226,10 +225,18 @@ class BaseField(object): | |||||||
|         # check validation argument |         # check validation argument | ||||||
|         if self.validation is not None: |         if self.validation is not None: | ||||||
|             if callable(self.validation): |             if callable(self.validation): | ||||||
|                 if not self.validation(value): |                 try: | ||||||
|                     self.error('Value does not match custom validation method') |                     # breaking change of 0.18 | ||||||
|  |                     # Get rid of True/False-type return for the validation method | ||||||
|  |                     # in favor of having validation raising a ValidationError | ||||||
|  |                     ret = self.validation(value) | ||||||
|  |                     if ret is not None: | ||||||
|  |                         raise DeprecatedError('validation argument for `%s` must not return anything, ' | ||||||
|  |                                               'it should raise a ValidationError if validation fails' % self.name) | ||||||
|  |                 except ValidationError as ex: | ||||||
|  |                     self.error(str(ex)) | ||||||
|             else: |             else: | ||||||
|                 raise ValueError('validation argument for "%s" must be a ' |                 raise ValueError('validation argument for `"%s"` must be a ' | ||||||
|                                  'callable.' % self.name) |                                  'callable.' % self.name) | ||||||
|  |  | ||||||
|         self.validate(value, **kwargs) |         self.validate(value, **kwargs) | ||||||
|   | |||||||
| @@ -142,3 +142,8 @@ class ValidationError(AssertionError): | |||||||
|         for k, v in iteritems(self.to_dict()): |         for k, v in iteritems(self.to_dict()): | ||||||
|             error_dict[generate_key(v)].append(k) |             error_dict[generate_key(v)].append(k) | ||||||
|         return ' '.join(['%s: %s' % (k, v) for k, v in iteritems(error_dict)]) |         return ' '.join(['%s: %s' % (k, v) for k, v in iteritems(error_dict)]) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DeprecatedError(Exception): | ||||||
|  |     """Raise when a user uses a feature that has been Deprecated""" | ||||||
|  |     pass | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ from mongoengine import Document, StringField, IntField, DateTimeField, DateFiel | |||||||
|     FieldDoesNotExist, EmbeddedDocumentListField, MultipleObjectsReturned, NotUniqueError, BooleanField,\ |     FieldDoesNotExist, EmbeddedDocumentListField, MultipleObjectsReturned, NotUniqueError, BooleanField,\ | ||||||
|     ObjectIdField, SortedListField, GenericLazyReferenceField, LazyReferenceField, DynamicDocument |     ObjectIdField, SortedListField, GenericLazyReferenceField, LazyReferenceField, DynamicDocument | ||||||
| from mongoengine.base import (BaseField, EmbeddedDocumentList, _document_registry) | from mongoengine.base import (BaseField, EmbeddedDocumentList, _document_registry) | ||||||
|  | from mongoengine.errors import DeprecatedError | ||||||
|  |  | ||||||
| from tests.utils import MongoDBTestCase | from tests.utils import MongoDBTestCase | ||||||
|  |  | ||||||
| @@ -56,6 +57,48 @@ class FieldTest(MongoDBTestCase): | |||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             data_to_be_saved, ['age', 'created', 'day', 'name', 'userid']) |             data_to_be_saved, ['age', 'created', 'day', 'name', 'userid']) | ||||||
|  |  | ||||||
|  |     def test_custom_field_validation_raise_deprecated_error_when_validation_return_something(self): | ||||||
|  |         # Covers introduction of a breaking change in the validation parameter (0.18) | ||||||
|  |         def _not_empty(z): | ||||||
|  |             return bool(z) | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             name = StringField(validation=_not_empty) | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         error = ("validation argument for `name` must not return anything, " | ||||||
|  |                  "it should raise a ValidationError if validation fails") | ||||||
|  |  | ||||||
|  |         with self.assertRaises(DeprecatedError) as ctx_err: | ||||||
|  |             Person(name="").validate() | ||||||
|  |         self.assertEqual(str(ctx_err.exception), error) | ||||||
|  |  | ||||||
|  |         with self.assertRaises(DeprecatedError) as ctx_err: | ||||||
|  |             Person(name="").save() | ||||||
|  |         self.assertEqual(str(ctx_err.exception), error) | ||||||
|  |  | ||||||
|  |     def test_custom_field_validation_raise_validation_error(self): | ||||||
|  |         def _not_empty(z): | ||||||
|  |             if not z: | ||||||
|  |                 raise ValidationError('cantbeempty') | ||||||
|  |  | ||||||
|  |         class Person(Document): | ||||||
|  |             name = StringField(validation=_not_empty) | ||||||
|  |  | ||||||
|  |         Person.drop_collection() | ||||||
|  |  | ||||||
|  |         with self.assertRaises(ValidationError) as ctx_err: | ||||||
|  |             Person(name="").validate() | ||||||
|  |         self.assertEqual("ValidationError (Person:None) (cantbeempty: ['name'])", str(ctx_err.exception)) | ||||||
|  |  | ||||||
|  |         with self.assertRaises(ValidationError): | ||||||
|  |             Person(name="").save() | ||||||
|  |         self.assertEqual("ValidationError (Person:None) (cantbeempty: ['name'])", str(ctx_err.exception)) | ||||||
|  |  | ||||||
|  |         Person(name="garbage").validate() | ||||||
|  |         Person(name="garbage").save() | ||||||
|  |  | ||||||
|     def test_default_values_set_to_None(self): |     def test_default_values_set_to_None(self): | ||||||
|         """Ensure that default field values are used even when |         """Ensure that default field values are used even when | ||||||
|         we explcitly initialize the doc with None values. |         we explcitly initialize the doc with None values. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user