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 "
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 "
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.
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.
+1, dude… +1
Thanks — it took a long time for me to figure that out and I hope I saved somebody else some time.
Thank you very much – I was just trying to work out how to do this! Thanks for posting!
Wonderful! Glad it helped.
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::
You have to define MyScrubber like this::
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::
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.