Mongoengine: Custom validation of fields

Created on 14 Oct 2016  路  5Comments  路  Source: MongoEngine/mongoengine

How could it be easier to integration custom field validation with current field validation ?

Implementing it with keyword validation on Field does not enable to customize ValidationError object.

Are there good practices about it with the current V of mongoengine ?

Cheers,

EDIT:

I ended up subclassing mongoengine fields and overwriting the validate methods by first calling the parents validate within the redefinition.

Would be happy to have a shared experience on this though,

Most helpful comment

@JWDobken it's a bit dangerous to implement the validate method like that.

You're overriding the StringField validate method completely and name_validation is not calling the StringField.validate method with super. This means that you can add a name bigger than 255 characters because you completely ignored that check. If you want to try it for yourself go ahead and do mydoc = MyDoc(name="a"*300) it runs and saves when it shouldn't.

I found the clean method approach enough for my use case. Your example would look like this and now it also won't allow for names bigger than 255 characters and it should. I know this is not a specific field check but you can still encapsulate the checking of each field in a method and put them all on the clean method.

from mongoengine import Document, StringField, ValidationError

class MyDoc(Document):
    name = StringField(min_length=3, max_length=255, required=True)

    @staticmethod
    def name_validation(name):
        if not name.startswith("a"):
            raise ValidationError("name does not start with 'a'")


    def clean(self):
        self.name_validation(self.name)

mydoc = MyDoc(name="aaa"*256)

# will throw ValidationError: ValidationError (MyDoc:None) 
# (String value is too long: ['name'])
mydoc.save()  

# ValidationError: ValidationError (MyDoc:None) 
# (name does not start with 'a': ['__all__'])
mydoc = MyDoc(name="aaa"*256).save()

I tried making it work with super but I wasn't sure how to access StringField validate, the closest I got was this but it isn't working, and I'm not willing to keep trying to make it work as the clean method works for me now.

I hope this helps as I was also a bit lost when trying to find a good way to do my custom checks before saving a document. If there's a better way or someone can explain how to properly override a Field validate method please do it.

All 5 comments

hey @BenCoDev , can you share your strategy for this kind of case?

Here is my implementation example:

class MyDoc(Document):

    def name_validation(name):
        assert name.startswith("a"), "name does not start with 'a'"

    name = StringField(max_length=255, required=True)

    name.validate = name_validation

mydoc = MyDoc(name="123")

I would like to know yours :)

@JWDobken it's a bit dangerous to implement the validate method like that.

You're overriding the StringField validate method completely and name_validation is not calling the StringField.validate method with super. This means that you can add a name bigger than 255 characters because you completely ignored that check. If you want to try it for yourself go ahead and do mydoc = MyDoc(name="a"*300) it runs and saves when it shouldn't.

I found the clean method approach enough for my use case. Your example would look like this and now it also won't allow for names bigger than 255 characters and it should. I know this is not a specific field check but you can still encapsulate the checking of each field in a method and put them all on the clean method.

from mongoengine import Document, StringField, ValidationError

class MyDoc(Document):
    name = StringField(min_length=3, max_length=255, required=True)

    @staticmethod
    def name_validation(name):
        if not name.startswith("a"):
            raise ValidationError("name does not start with 'a'")


    def clean(self):
        self.name_validation(self.name)

mydoc = MyDoc(name="aaa"*256)

# will throw ValidationError: ValidationError (MyDoc:None) 
# (String value is too long: ['name'])
mydoc.save()  

# ValidationError: ValidationError (MyDoc:None) 
# (name does not start with 'a': ['__all__'])
mydoc = MyDoc(name="aaa"*256).save()

I tried making it work with super but I wasn't sure how to access StringField validate, the closest I got was this but it isn't working, and I'm not willing to keep trying to make it work as the clean method works for me now.

I hope this helps as I was also a bit lost when trying to find a good way to do my custom checks before saving a document. If there's a better way or someone can explain how to properly override a Field validate method please do it.

The feature isn't well know nor documented (to be fixed) and the online doc isn't up to date (due to a bug) but any fields can take a validation parameter which is a callable
E.g:

        def _not_empty(z):
            if not z:
                raise ValidationError('cantbeempty')

        class Person(Document):
            name = StringField(validation=_not_empty)

Note that:

  • mongoengine>=0.18 expects the callable to raise a ValidationError
  • mongoengine < 0.18 expects it to return a boolean (without possibility to customize the error message, its a generic one)

Thanks for that merge request, it will be very helpful. I didn't try the validation arg because I didn't get how it was supposed to be used, also reading this "Generally this is deprecated in favor of the FIELD.validate method" confused me and led me to find another option like clean.

Was this page helpful?
0 / 5 - 0 ratings