Monday 30 April 2012

WTForms - validating values against other others

Validating form values against each other

Again very basic - I wish to make sure that the value of a field entered is greater than another field. To do this I needed to create a custom validator. This custom validator was based on the EqualTo validator. I called it's class GreaterThan.

I could edit the wtforms/validators.py file and add a new validator in there how ever this would not make library upgrades easy. Instead I created a mywtforms directory within which I created a __init__.py file which when

 import mywtforms 

is used will load the code in this file. The contents of the __init__.py file look like:


#need help figuring out what to import here I've creating so methods that inherit from wtforms and its structure
#TODO re-read : http://docs.python.org/tutorial/modules.html
from wtforms import validators, widgets
from wtforms.widgets import *
from wtforms.fields import *
from wtforms.form import Form
from wtforms.validators import ValidationError


class GreaterThan(object):
    """
    Compares the value of two fields the value of self is to be greater than the supplied field.

    :param fieldname:
        The name of the other field to compare to.
    :param message:
        Error message to raise in case of a validation error. Can be
        interpolated with `%(other_label)s` and `%(other_name)s` to provide a
        more helpful error.
    """
    def __init__(self, fieldname, message=None):
        self.fieldname = fieldname
        self.message = message

    def __call__(self, form, field):
        try:
            other = form[self.fieldname]
        except KeyError:
            raise ValidationError(field.gettext(u"Invalid field name '%s'.") % self.fieldname)
        if field.data != '' and field.data < other.data:
            d = {
                'other_label': hasattr(other, 'label') and other.label.text or self.fieldname,
                'other_name': self.fieldname
            }
            if self.message is None:
                self.message = field.gettext(u'Field must be greater than %(other_name)s.')

            raise ValidationError(self.message % d)

Later I plan to split up __init__.py and place the code into directories mirroring those in the wtforms directory

To use the validator I could create a form such as:


import re

def filter_field(value):
   return re.sub('\d{2}$','00',value)

class MyForm(Form):
   field_one = TextField('Field 1',validators=[validators.Regexp('^[0-9]*)',
                             message='Must be an integer')],
                             filters=[lambda x: x*100, filter_field])
   field_two = TextField('Field 1',validators=[validators.Regexp('^[0-9]*)',
                             message='Must be an integer'),GreaterThann('field_one','field two must be greater than field one')],
                             filters=[lambda x: x*100, filter_field])

WTForms - filtering values

WTForms - using filters on Field items

Pretty basic- the WTForms documentation shows that field elements can be supplied filter methods these can be defined and used as in the example below:
Note: The filter will multiple the value by 100 then replace the last two digits with zeros:
import re

def filter_field(value):
   return re.sub('\d{2}$','00',value)

class MyForm(Form):
   field_one = TextField('Field 1',validators=[validators.Regexp('^[0-9]*)',
                             message='Must be an integer')],
                             filters=[lambda x: x*100, filter_field])

When a MyForm object is instantiated and processed the filters will be run in order they appear in the argument list. Note the list of filters contains two different types an 'inline' function defined in Python using 'lambda' and a defined function.
The filters will be applied when processing the form:
j_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))

class IndexPage(webapp2.RequestHandler):  
   def post(self):
       aforminstance = MyForm(formdata=self.request.POST)

app = webapp2.WSGIApplication( [ ('/', IndexPage) ], debug=True )