From c9ed9306068db2970f42a6ececbfd26dd783e848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 25 Jun 2019 22:20:47 +0200 Subject: [PATCH] Add a documentation page for validation --- docs/guide/index.rst | 1 + docs/guide/querying.rst | 3 +- docs/guide/validation.rst | 116 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 docs/guide/validation.rst diff --git a/docs/guide/index.rst b/docs/guide/index.rst index 46eb7af2..c94a4eab 100644 --- a/docs/guide/index.rst +++ b/docs/guide/index.rst @@ -10,6 +10,7 @@ User Guide defining-documents document-instances querying + validation gridfs signals text-indexes diff --git a/docs/guide/querying.rst b/docs/guide/querying.rst index 151855a6..6937cf68 100644 --- a/docs/guide/querying.rst +++ b/docs/guide/querying.rst @@ -566,7 +566,8 @@ cannot use the `$` syntax in keyword arguments it has been mapped to `S`:: ['database', 'mongodb'] From MongoDB version 2.6, push operator supports $position value which allows -to push values with index. +to push values with index:: + >>> post = BlogPost(title="Test", tags=["mongo"]) >>> post.save() >>> post.update(push__tags__0=["database", "code"]) diff --git a/docs/guide/validation.rst b/docs/guide/validation.rst new file mode 100644 index 00000000..793a797a --- /dev/null +++ b/docs/guide/validation.rst @@ -0,0 +1,116 @@ +==================== +Document Validation +==================== + +By design, mongoengine strictly validates the documents right before they are inserted in MongoDB +and make sure they are consistent with the fields defined in your models. + +Mongoengine will not validate a document when an object is loaded from the DB into an instance +of your model but this operation will fail under some circumstances (e.g: if there is a field in +the document fetched from the database that is not defined in your model) + + +Builtin validation +================= + +Mongoengine provides different fields that encapsulate the corresponding validation +out of the box. Validation runs when calling `.validate()` or `.save()` + +.. code-block:: python + + from mongoengine import Document, EmailField + + class User(Document): + email = EmailField() + age = IntField(min_value=0, max_value=99) + + user = User(email='invalid@', age=24) + user.validate() # raises ValidationError (Invalid email address: ['email']) + user.save() # raises ValidationError (Invalid email address: ['email']) + + user2 = User(email='john.doe@garbage.com', age=1000) + user2.save() # raises ValidationError (Integer value is too large: ['age']) + +Custom validation +================= + +The following feature can be used to customize the validation: + +* Field `validation` parameter + +.. code-block:: python + + def not_john_doe(name): + if name == 'John Doe': + raise ValidationError("John Doe is not a valid name") + + class Person(Document): + full_name = StringField(validation=not_john_doe) + + Person(full_name='Billy Doe').save() + Person(full_name='John Doe').save() # raises ValidationError (John Doe is not a valid name) + + +* Document `clean` method + +Although not its primary use case, `clean` may be use to do validation that involves multiple fields. +Note that `clean` runs before the validation when you save a Document. + +.. code-block:: python + + class Person(Document): + first_name = StringField() + last_name = StringField() + + def clean(self): + if self.first_name == 'John' and self.last_name == 'Doe': + raise ValidationError('John Doe is not a valid name') + + Person(first_name='Billy', last_name='Doe').save() + Person(first_name='John', last_name='Doe').save() # raises ValidationError (John Doe is not a valid name) + + + +* Adding custom Field classes + +We recommend as much as possible to use the standard field but it is also possible +to subclass a Field and encapsulate some validation by overriding the `validate` method + +.. code-block:: python + + class AgeField(IntField): + + def validate(self, value): + super(AgeField, self).validate(value) # let IntField.validate run first + if value == 60: + self.error('60 is not allowed') + + class Person(Document): + age = AgeField(min_value=0, max_value=99) + + Person(age=20).save() # passes + Person(age=1000).save() # raises ValidationError (Integer value is too large: ['age']) + Person(age=60).save() # raises ValidationError (Person:None) (60 is not allowed: ['age']) + + +.. note:: + + When overriding `validate`, use `self.error("your-custom-error")` instead of raising ValidationError explicitly, + it will provide a better context with the error message + +Disabling validation +==================== + +We do not recommend to do this but if for some reason you need to disable the validation of a document +when you call `.save()`, you can use `.save(validate=False)`. + +.. code-block:: python + + class Person(Document): + age = IntField() + + Person(age='garbage').save() # raises ValidationError (garbage could not be converted to int: ['age']) + + Person(age='garbage').save(validate=False) + person = Person.objects.first() + assert person.age == 'garbage'