Use FormEncode to verify one date precedes another

I have a form on my site that lets people choose a start date and a stop date. Then I show statistics for that date range. I wrote a FormEncode schema to verify that the start date is before the stop date.

The documentation on schema validators is fairly sparse, so I’m publishing this because it might help somebody else out.

Code


# This is in a file named formencodefun.py
from formencode import Schema
from formencode.validators import DateConverter, FancyValidator, Invalid

class DateCompare(FancyValidator):
messages = dict(invalid="Start date must be before stop date")

def validate_python(self, field_dict, state):

start_date = field_dict['start_date']
stop_date = field_dict['stop_date']

if start_date > stop_date:
msg = self.message('invalid', state)

raise Invalid(msg, field_dict, state,
error_dict=dict(stop_date=msg))

class MySchema(Schema):
start_date = DateConverter()
stop_date = DateConverter()

chained_validators = [DateCompare()]

Usage


>>> from formencodefun import MySchema
>>> s = MySchema()
>>> d1 = {'start_date':'11-02-2008', 'stop_date':'11-15-2008'}
>>> d2 = {'start_date':'11-15-2008', 'stop_date':'11-02-2008'}
>>> s.to_python(d1)
{'stop_date': datetime.date(2008, 11, 15), 'start_date': datetime.date(2008, 11, 2)}
>>> s.to_python(d2)
------------------------------------------------------------
Traceback (most recent call last):
File "", line 1, in
File "/home/matt/virtualenvs/scratch/lib/python2.5/site-packages/FormEncode-1.1-py2.5.egg/formencode/api.py", line 400, in to_python
value = tp(value, state)
File "/home/matt/virtualenvs/scratch/lib/python2.5/site-packages/FormEncode-1.1-py2.5.egg/formencode/schema.py", line 200, in _to_python
new = validator.to_python(new, state)
File "/home/matt/virtualenvs/scratch/lib/python2.5/site-packages/FormEncode-1.1-py2.5.egg/formencode/api.py", line 403, in to_python
vp(value, state)
File "formencodefun.py", line 18, in validate_python
error_dict=dict(stop_date=msg))
Invalid: Start date must be before stop date

How it works

Notice when I run s.to_python(d1), I get a dictionary back with the the values for start_date and stop_date replaced with datetime.date objects.

Then when I run my schema on d2, where the start_date is after the stop_date, my schema raises an Invalid exception. In a web framework like TurboGears, there is some exception handler that will catch that exception and take that error dictionary and redraw the form and print my error message.

Notice that the DateConverters first take my strings and turn them into datetime.date objects before the test in DateCompare. FormEncode runs the chained validators after it runs the individual validators.

In this case, I just want to make sure that the start date precedes the stop date. I have written other validators that add extra keys into the field dict or change the values, but I want to keep this example simple.

If the first DateConverters fail, then the chained validators never run:

>>> s.to_python({'start_date':'UNPARSEABLE', 'stop_date':'11-20-2008'})
------------------------------------------------------------
Traceback (most recent call last):
File "", line 1, in
File "/home/matt/virtualenvs/scratch/lib/python2.5/site-packages/FormEncode-1.1-py2.5.egg/formencode/api.py", line 400, in to_python
value = tp(value, state)
File "/home/matt/virtualenvs/scratch/lib/python2.5/site-packages/FormEncode-1.1-py2.5.egg/formencode/schema.py", line 197, in _to_python
error_dict=errors)
Invalid: start_date: Please enter the date in the form mm/dd/yyyy

When my DateCompare validator is run, I can be confident that the objects with the keys start_date and stop_date in the field_dict have already been converted to datetime.date objects.

In summary, FormEncode is awesome, but I have spent a lot of time beating my head against the wall trying to learn how to use it.

Published by

matt

My name is Matt Wilson and I live in Cleveland Heights, Ohio. I love random emails from strangers, so get in touch! matt@tplus1.com.

  • FormEncode is some good stuff. Definitely worth diving into.

  • Yeah, it is really nice to keep all the type-conversion code separate
    from the code that really does something. However, it is really a
    little too fancy for my tastes. At some point, I want to study
    validino more, because it is just a lot simpler to read.

  • FormEncode is really cool and underrated, IMHO. The documentation is really basic and not organized very well, though, so you need to read the source and experiment at lot, to understand how all the combinations of different validators, compound validators and validation schemas work and which tricks there are to write your own validators. Also, the set of standard validators provided with the FE distribution is lacking in many respects (min/max for Integers anyone?) or plain buggy (DateTimeConverter).

    I have a sizable collection of re-usable, custom validators for my TurboGears apps now, and I hope I'll find the time soon, to publish them somewhere.

  • Yeah, FE is a fantastic idea, but like many great ideas, the first
    stab may not be the best possible implementation.

    I think a big list of examples would go a long way to make it FE more popular.

  • from the code that really does something. However, it is really a
    little too fancy for my tastes.I think a big list of examples would go a long way to make it FE more popular.I hope I'll find the time soon, to publish them somewhere

  • Then when I run my schema on d2, where the start_date is after the stop_date, my schema raises an Invalid exception. In a web framework like TurboGears, there is some exception handler that will catch that exception and take that error dictionary and redraw the form and print my error message.

  • David Noyes

    +1, dude… +1

  • Thanks — it took a long time for me to figure that out and I hope I saved somebody else some time.

  • Dave H

    Thank you very much – I was just trying to work out how to do this! Thanks for posting!

  • Wonderful! Glad it helped.

  • Lancer

    Thanks! Good one!

  • you're welcome

  • Thank you Matt,

    this is indeed exactly what I was in search for myself!

    Another way of doing this would include malforming the controller to raise a ValidationError in case end lies before start, but yours is a more elegant way. It is a pity, most of the framework documentation covers mostly the standard cases. At http://tinyurl.com/bngms2t the answer is given, but you will not find this by obvious searching terms for the date problem.

    Again, RTFM is the answer, but your helpful pointer saved a couple of hours ­čÖé

    Regards from Germany

  • Hey, I'm so happy this helped you out!

    I think formencode is a great idea because it is very important to examine all the inputs coming in to our programs.

    But the formencode implementation is very hard to use! Especially when you want to do more complex validation. For example, you may want to verify some input corresponds to an allowed value stored in a database, or stored in a config file. It is not elegant to do this!

    It is also not elegant to coalesce values from several sources, for example, sometimes I want to draw an HTML form and fill in some of the fields to save the user time. I want to grab those values from the query string first, but if that fails, then inspect the some data stored in a session database table. This is possible, but not elegant, and difficult to explain to another programmer.

    I'm working on a replacement to FormEncode, called Scrubber. It doesn't have any of the cute syntax tricks because it doesn't use metaclasses. At some point I will write up a description, but you use the class like this::


    s = MyScrubber(wsgi_environ,
    database_connection,
    config_file_wrapper)

    errors, values = s.make_errors_and_values()

    if errors:
    # re-draw the form, using the errors dictionary
    # to draw error messages, and using the values
    # dictionary to pre-populate fields.

    else:
    # do whatever you want.

    You have to define MyScrubber like this::


    import scrubber

    class MyScrubber(scrubber.Scrubber):

    # Every subclass must define this
    def make_errors_and_values(self):
    errors, values = dict(), dict()
    self.extract_date1(errors, values)
    self.extract_date2(errors, values)
    self.verify_date1_precedes_date2(errors, values)
    return errors, values

    There is no magic introspection involved. You have to write out the definitions of extract_date1 and extract_date2 and verify_date1_precedes_date2 yourself. Each method gets a reference to the same errors and values dictionary, so if one of the extract_… methods failed, then the later method can check for the relevant error, and quit early.

    Here is what extract_date1 might look like::


    import datetime
    def extract_date1(self, errors, values):

    # If you are using something like webob or django
    # requests, this part will look different.
    parsed_QS = urlparse.parse_qs(
    self.wsgi_environ['QUERY_STRING'])

    if 'date1' in parsed_QS:
    raw_date1 = parsed_QS['date1']
    try:
    values['date1'] = datetime.datetime.strptime(
    raw_date1, '%Y-%m-%d')
    except ValueError as ex:
    log.debug(ex, exc_info=1)
    values['date1'] = raw_date1

    else:
    errors['date1'] = 'This is required field!'

    return errors, values

    This pattern works really well for me. I know some people might complain about the amount of redundant code, but I don't care any more. I prefer an obvious verbose solution that works 100% of the time over a fancy solution that ignores weird corner cases.

    Anyhow, thanks so much for writing. I'm glad to know it helped you out.