Per Erik Strandberg /cv /kurser /blog

In My First Minimal Plone Content Type we creates a simple content type, let us now see how we can verify that a user inserts something meaningful in the fields. If we for example have a field called email we'd like the system to make sure that we enter something that looks like an email. A nice reference when it comes to Archetypes in Plone is the Archetypes Developer Manual, here is one chapter about fields: [1]

Some old (apparently) documentation can also be found here: [2]

I'd like to demonstrate two things when it comes to validators:

I will illustrate the first one by having a content type that only contains an email field and a way to validate that a "valid" email is inserted (hopefully this validation also takes care of strange Irish emails like "o'connor@o'connor.com").

The second one we will implement from scratch, starting with a minimal validator that always fails to a simple validator that really does some validation.

How to use a standard validator

It is really easy to use ready made validators (for a comprehensive list see the Archetypes Developer Manual [3] ), you just add a key word argument in the call to the field-constructor, pretty much like this:

schema = BaseSchema.copy() + Schema((
    StringField('email',
                 required = 1,
                 validators = ('isEmail'),
              ),
    ))

Trying to add a bad email now results in something like this:
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_1.png

And adding a nice regular email results in what you would have expected - a regular page:
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_2.png

This version of MyMessage is available here: [4]

How to write your own validator

Looking in the Archetypes Developer Manual we see the wonderful phrase - so often encountered in the open source community - See source for details, so let us do exactly that (remember: Use the source Luke).

Source Diving

In my Plone installation I find some interesting validators in the folder ~/Plone-3.0/zeocluster/Products/validation/validators and one of them is RangeValidator.py, a condensed version of it follows:

try:
    from Products.validation.interfaces.IValidator import IValidator
except ImportError:
    import sys, os
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
    from interfaces.IValidator import IValidator
    del sys, os

class RangeValidator:
    __implements__ = IValidator

    def __init__(self, name, ...):
        self.name = name
        # ...

    def __call__(self, value, *args, **kwargs):
        # ...
        try:
            nval = float(value)
        except ValueError:
            return ("Validation failed [...]") # [wrong type]
        if minval <= nval < maxval:
            return 1

        return ("Validation failed [...]")     # [not in range]

Even if this does not make much sense in this condensed version you should notice some important parts of this code:

  1. import IValidator: apparently it isn't trivial to find IValidator so the authors use a dirty trick to find it if something fails.
  2. the class RangeValidator that "__implements__" IValidator: this it yet another appraoch to object oriented programming and interfaces. Instead of implementing inheritance of an interface (like in C# and I guess also in Java) the authors of the framework sets the member __implements__ to IValidator (meaning what in C# would be an inheritance of an interface). I do not really like this approach but I can live with it. The point is however that we claim to implement an interface called IValidator.
  3. the method __call__(self, value, *args, **kwargs) seems important.

Given this information we could easily implement our own validator - we will just dig a little more in the source code (Use the source Luke). We really should take a look at the interface we implement - IValidator, it's short and neat:

#~/Plone-3.0/zeocluster/Products/validation/interfaces/IValidator.py
from interface import Interface, Attribute

class IValidator(Interface):

    name = Attribute("name of the validator")
    title = Attribute("title or name of the validator")
    description = Attribute("description of the validator")

    def __call__(value, *args, **kwargs):
        """return True if valid, error string if not"""

# ...

As we can see a Validator (something that implements IValidator) has the three members name, title and description. One method (__call__) is implemented that is well documented: return True if valid, error string if not.

Cut to the chase

Just to test the validators we implement a validator that always indicates an error - a very evil validator. I put the following code in a file called validator.py:

try:
    from Products.validation.interfaces.IValidator import IValidator
except ImportError:
    import sys, os
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
    from interfaces.IValidator import IValidator
    del sys, os

class EvilValidator:
    __implements__ = IValidator

    def __init__(self,
                 name,
                 title='Evil validator',
                 description='You will fail'):
        self.name = name
        self.title = title or name
        self.description = description

    def __call__(self, value, *args, **kwargs):
        return('Moahahahaha - you FAIL!')

I am not sure the strange try/catch is really needed, but let's keep just to make sure for now. As you can see the init-method does nothing really important and the call-method always returns an error string.

Of course we need to update our __init__.py-file, I added the following to the top of it:

from Products.validation import validation
from validator import EvilValidator
validation.register(EvilValidator('evilness'))


Here I give the evil validator the name evilness and register it as a validator.

Our schema needs to be updated in the following way (in message.py):

schema = BaseSchema.copy() + Schema((
    StringField('email',
                 required = 1,
                 validators = ('evilness',),
              ),
    ))

And that should be it - any item entered in the field should now result in something like this:
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_custom_1.png

You can find this version of my message here: [5]

my Fibonacci validator

I want to make a validator that checks is a value is a Fibonacci number. It is perhaps more complex that it should be but bear with me - it is not that bad really:

from Products.validation.interfaces.IValidator import IValidator

class FibonacciValidator:
    __implements__ = IValidator

    def __init__(self, name, title=, description=):
        self.name = name
        self.title = title or name
        self.description = description

    def __call__(self, value, *args, **kwargs):

        # value must be integer
        try:
            value = int(value)
        except:
            return("'%s' is not an integer (illegal value)" % str(value))

        # deal with negative and trivial cases first
        if value < 0:
            return("'%s' is negative (illegal value)" % str(value))
        elif value==0 or value==1:
            return 1
        else:
            low = 0
            high = 1

            # stop if the largest fibonacci is bigger than value
            while value > high:
                # do you remember how fibonacci numbers are defined?
                # hint: F(n+1) = F(n) + F(n-1)
                (low, high) = (high, low + high)

            if value==low or value==high:
                return 1
                    
            # we got this far - return error message
            return("'%d' is not a fibonacci number - try '%d'") % (value, high)

        # If we get this far something really bad happened
        # perhaps integer overflow or something else reallt annoying
        return "Congratulations, you entered a really bad number [:)]-|--<"

Similar changes as in the example above are needed (see download below if you want the code).

Adding numbers that are not Fibonacci numbers is bad.
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_custom_2.png

But fibonacci numbers work fine:
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_custom_3.png

To my surprise even quite large numbers are detected as bad...
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_custom_4.png

...or good...
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_custom_5.png

Negative values are filtered out:
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_custom_7.png

Non-integers are also filtered out:
http://www.pererikstrandberg.se/blog/plone/mymessage_validator_custom_8.png

Download

The source code of my message including a Fibonacci validator is available right here: [6]


This page belongs in Kategori Programmering.
This page is part of a series of tutorials on Plone Cms.