16.4. Widgets and ValidationWe briefly saw how validation works in Chapter 5, but there's a lot more power and flexibility in the validation library TurboGears uses than we've had a chance to cover. Not only that, widgets have some special built-in features that help make form validation even easier and more painless. Widget-based forms automatically handle validation errors, display error messages, and translate data from Python objects into the strings used in the form, and back again when the form is processed. We've already seen most of this in action in Chapter 5, so you probably have a general idea of what happens. But in the next few pages we're going to peel the covers back a bit and take a look at how all of this works and how you can customize things to get exactly the behavior you want from your forms. 16.4.1. How FormEncode Validators Integrate into Form WidgetsAs we mentioned, nearly every web application is going to include forms and nearly every set of forms has required fields or other input validation associated with them. Not only that, nearly every form gets some data from the user that ought to be converted from a string (all form results come from the browser as strings) into a proper Python object. You want age to be an int, dates to be datetime objects, etc. Coding all of this to work by hand isn't rocket science, but it's tedious and repetitive. So, TurboGears needed a way to get rid of all the repetitive stuff for you. Because the TurboGears philosophy is to use the best existing libraries wherever possible, it provides easy access to Ian Bicking's FormEncode package. From day one TurboGears provided convenient access to Form Encode Validators via controller decorators (see Chapter 17, "CherryPy and TurboGears Decorators," for details). Widgets take this one step further and automate error display. FormEncode is designed to convert and validate incoming data and produce validation error messages when necessary. The TurboGears validation mechanism ties directly into FormEncode to preprocess incoming form data so your controller objects get form data in the proper Python type. If there are errors, the error_handler appends the validation error messages to the cherrypy.request object (more about all of this in Chapter 17, and sends the user to another controller method which can handle the errors. Form widgets take this one step further, integrating FormEncode into the widget declaration/display process. This means that you can tell a form to act as its own error_handler, and it will take care of displaying validation errors for you. We've already seen that widgets display input_value and validation error messages when their contents fail to validate, but let's take a quick look at how this all works. The built-in form widgets know how to display their own errors. We didn't mention it earlier for the sake of keeping things simple, but TurboGears form widgets also check the cherrypy.request object to see if there are validation errors present and if errors are present, displays them next to the proper form element. At the same time, form widgets know how to pull the input_value for each form element off the request object. The input_value contains whatever value was submitted by the user when she filled out the form. Because TableForm knows how to check input_value and validation_errors and display them properly, all you have to do is set validators on the field elements, and set up the save method's error_handler to point to a controller method that uses the same form widget to display validation errors. 16.4.2. More ValidatorsSometimes you need more than an Int() validator. Fortunately, FormEncode provides a number of built-in validators that you can use. Not only that, it provides you with a simple mechanism for creating your own validators. More often than not one of the built-in validators can give you what you needthe regex validator is particularly flexible. If you want to check that the user entered a valid number, a valid date, a valid URL, or a valid e-mail address, there are built-in validators to help you. The most-up-to date list of built-in validators is available at www.turbogears.org/docs/api/formencode.validators-module.htm. Here's a list of the most commonly used validators:
Sometimes you want more than any one of these validators can give you. You want to assure that a form field is a valid URL, and it's at least 10 characters long. That's where compound validators come inFormEncode provides the any and all validators, which do pretty much what they say. If you write: mywidget=widgets.TextField(name = "int_or_plan_text", validator=validators.any(validators.PlainText(), validators.Int()) The mywidget widget defined above will accept input as valid if either the value entered by the user is plain text, or an integer. If, on the other hand, you wanted to check that the returned URL string didn't have any special characters, you can write something like this: mywidget=widgets.TextTield(name= "URL_and_more_than_10_characters"), validators.all(validators.plaintext(), validators.MinLength(15)) As we mentioned earlier, the Regex validator can handle most of your string validation needs. But, since the purpose of validators is not just to specify rules about the contents of user values, it is also to convert user entered values into usable Python objects, it is often advantageous to create your own validators. Fortunately that's actually pretty simple to do. Creating your own validator is a three-to-four step process. Validators need to know how to convert Python object strings, how to convert strings to the right kind of Python object, and what kind of messages to display when something goes wrong. To create a new validator class:
Don't let the fact that this is a four-step process fool you into thinking that it's complicated. Here's the code for a simple validator: class isWikiWord(validators.FancyValidator): wiki_regex = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") messages = {'non_wiki': 'Your Page Name must be a WikiWord'} def _to_python(self, value, state): return value.strip() def validate_python(self, value, state): if not self.wiki_regex.match(value): raise validators.Invalid(self.message("non_wiki"), value, state) If you have more than one failure mode, you just define the additional failure messages in your messages dictionary, and add additional raise Invalid exceptions to your _to_python or validate_python methods. The FancyValidator class gives your validator several options which can be used when creating a new instance.
16.4.3. Basics of Schema ValidationSometimes you can't validate a piece of user input in isolation. Password verification fields are a common example. You want the data in each of those two fields to match. This is where schema validation comes in. Schemas are pretty simple as long as you don't have deeply nested compound widgets to match up with your schema. If you have two password fields by themselves in the change_password form, you could create a schema that looks like this: class UserFields(widgets.WidgetsDeclaration): passwd = widgets.PasswordField(validator=validators.NotEmpty()) passwd2 = widgets.PasswordField(validator=validators.UnicodeString()) class UserSchema(formencode.schema.Schema): chained_validators = [validators.FieldsMatch('passwd', 'passwd2')] form = TableForm(fields=UserFields(), validator=UserSchema) The UserSchema class uses chained_validators, which are validators run on the dictionary of form responses after all the other validators are done. Since the schema is just another validator, you can create nested validation schemas, to do chain validation on nested dictionaries. In general you'll find your life is easier if you don't create too many nested dictionaries in need of validation (after all in Python "flat is better than nested"). There's another built-in validator designed especially for schema validation:
|