From 3fca3739de906772cb2090ec0c465576fe550953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 18 Oct 2020 21:11:16 +0200 Subject: [PATCH] rework validation documentation based on review --- docs/guide/defining-documents.rst | 13 -------- docs/guide/document-instances.rst | 29 ------------------ docs/guide/validation.rst | 49 ++++++++++++++++++------------- mongoengine/base/document.py | 3 +- 4 files changed, 30 insertions(+), 64 deletions(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 6dc35c30..f5c70728 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -426,19 +426,6 @@ either a single field name, or a list or tuple of field names:: first_name = StringField() last_name = StringField(unique_with='first_name') -Skipping Document validation on save ------------------------------------- -You can also skip the whole document validation process by setting -``validate=False`` when calling the :meth:`~mongoengine.document.Document.save` -method:: - - class Recipient(Document): - name = StringField() - email = EmailField() - - recipient = Recipient(name='admin', email='root@localhost') - recipient.save() # will raise a ValidationError while - recipient.save(validate=False) # won't Document collections ==================== diff --git a/docs/guide/document-instances.rst b/docs/guide/document-instances.rst index 64f17c08..5dacc0dd 100644 --- a/docs/guide/document-instances.rst +++ b/docs/guide/document-instances.rst @@ -41,35 +41,6 @@ already exist, then any changes will be updated atomically. For example:: .. seealso:: :ref:`guide-atomic-updates` -Pre save data validation and cleaning -------------------------------------- -MongoEngine allows you to create custom cleaning rules for your documents when -calling :meth:`~mongoengine.Document.save`. By providing a custom -:meth:`~mongoengine.Document.clean` method you can do any pre validation / data -cleaning. - -This might be useful if you want to ensure a default value based on other -document values for example:: - - class Essay(Document): - status = StringField(choices=('Published', 'Draft'), required=True) - pub_date = DateTimeField() - - def clean(self): - """Ensures that only published essays have a `pub_date` and - automatically sets `pub_date` if essay is published and `pub_date` - is not set""" - if self.status == 'Draft' and self.pub_date is not None: - msg = 'Draft entries should not have a publication date.' - raise ValidationError(msg) - # Set the pub_date for published items if not set. - if self.status == 'Published' and self.pub_date is None: - self.pub_date = datetime.now() - -.. note:: - Cleaning is only called if validation is turned on and when calling - :meth:`~mongoengine.Document.save`. - Cascading Saves --------------- If your document contains :class:`~mongoengine.fields.ReferenceField` or diff --git a/docs/guide/validation.rst b/docs/guide/validation.rst index 5f0346bd..e5a70d34 100644 --- a/docs/guide/validation.rst +++ b/docs/guide/validation.rst @@ -3,10 +3,11 @@ 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. +and makes 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 +MongoEngine makes the assumption that the documents that exists in the DB are compliant with the schema. +This means that Mongoengine will not validate a document when an object is loaded from the DB into an instance +of your model but this operation may 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). @@ -53,23 +54,28 @@ The following feature can be used to customize the validation: * 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. +This method is called as part of :meth:`~mongoengine.document.Document.save` and should be used to provide +custom model validation and/or to modify some of the field values prior to validation. +For instance, you could use it to automatically provide a value for a field, or to do validation +that requires access to more than a single field. .. code-block:: python - class Person(Document): - first_name = StringField() - last_name = StringField() + class Essay(Document): + status = StringField(choices=('Published', 'Draft'), required=True) + pub_date = DateTimeField() 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) - + # Validate that only published essays have a `pub_date` + if self.status == 'Draft' and self.pub_date is not None: + raise ValidationError('Draft entries should not have a publication date.') + # Set the pub_date for published items if not set. + if self.status == 'Published' and self.pub_date is None: + self.pub_date = datetime.now() +.. note:: + Cleaning is only called if validation is turned on and when calling + :meth:`~mongoengine.Document.save`. * Adding custom Field classes @@ -98,19 +104,20 @@ to subclass a Field and encapsulate some validation by overriding the `validate` 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 +Skipping 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)`. +Although discouraged as it allows to violate fields constraints, if for some reason you need to disable +the validation and cleaning of a document when you call `.save()`, you can use `.save(validate=False)`. .. code-block:: python class Person(Document): - age = IntField() + age = IntField(max_value=100) - Person(age='garbage').save() # raises ValidationError (garbage could not be converted to int: ['age']) + Person(age=1000).save() # raises ValidationError (Integer value is too large) - Person(age='garbage').save(validate=False) + Person(age=1000).save(validate=False) person = Person.objects.first() - assert person.age == 'garbage' + assert person.age == 1000 + diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index e697fe40..0bab2886 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -314,7 +314,8 @@ class BaseDocument: def clean(self): """ - Hook for doing document level data cleaning before validation is run. + Hook for doing document level data cleaning (usually validation or assignment) + before validation is run. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the