wiki:FormValidationWithWidgets
Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Version 5 (modified by anonymous, 13 years ago) (diff)

reverted due to spam

Form Validation With Widgets

Widgets are great and widgets are fine, but there are still some API-ish issues that haunt my sleep at nights. Here is how I overcame my fear and grew to love (or maybe loathe) some of the finer points of widget wrangling.

The simple form that I was coding up was a search form. It would have four fields, two text entry, and two select fields. There was a requirement that at least two of the fields have valid entry data associated with them.

I could have done all of this logic in the controller, but I didn't, because it didn't smell right.

If anyone devises a finer solution to this problem, please update this page in the way you see fit.

Here is the code to make the form work. It should be valid unless someone dramatically changes the widget code. I wrote it with SVN 485.

Things of note:

  • It's near impossible (or at least very difficult) to extend a template in an existing widget. I've tried several things, and in the end the easiest thing to do was just copy the existing template from the parent widget.
  • It'd sure be nice if something like this, or formencode.Schema support was attached to forms.
  • Don't eat soap.
import turbogears.widgets as w
import turbogears.validators as v
import cherrypy

# these are just all of the option lists in a separate module, because
# I don't like them dirtying up the other ones.
from myapp.forms.option_select_lists import states,units

_first_name_field = w.TextField("firstName", labeltext="First Name")
_last_name_field = w.TextField("lastName", labeltext="Last Name")
_location_field = w.SelectField("state", states, labeltext="Location")
_unit_field = w.SelectField("unit", units, labeltext="Unit")

class RequireNFields(v.FancyValidator):
    num_fields = 2

    messages = {
        'notEnough': "Please submit at least %(num_fields)s fields"
        }

    def validate_python(self, values, state):
        num_fields_submitted = len([val for val in values.values() if val != ''])
        if self.num_fields > num_fields_submitted:
            raise v.Invalid(self.message('notEnough', state, num_fields=self.num_fields), values, state)

class ValidatingForm(w.Form):
    def create_dict(self, *args, **kwargs):
        dict = w.Form.create_dict(self, *args, **kwargs)
        if not 'name' in dict:
            dict['name'] = self.name
        return dict
        
    def input(self, values):
        # validate the items and then validate the whole shebang
        w.Form.input(self,values)
        if self.validator:
            try:
                self.validator.to_python(values)
            except v.Invalid, error:
                cherrypy.request.form_errors[self.name] = error


# hmm need to hack the template here...
# this would be a lot easier if I could figure out how to get the 
# kid template, take it apart, and jam what I want in there
class ValidatingTableForm(ValidatingForm, w.TableForm):
    template = """
<form xmlns:py="http://purl.org/kid/ns#" name="${widget.name}" 
      action="${getattr(self, 'action', None)}" method="${getattr(self, 'method', 'post')}"
      enctype="${getattr(self, 'enctype', None)}">
    <div py:for="widget in widgets" py:if="widget.hidden" py:strip="True">
       ${widget.insert(getattr(self.widget_value, widget.name, None), input_values, widget_error.get(widget.name, None))}
    </div>
    <table border="0">
    <tr py:if="widget_error">
        <td>
            <span class="field_error">${str(widget_error[name])}</span>
        </td>
    </tr>
    <tr py:for="widget in widgets" py:if="not widget.hidden">
            <td>
                <span py:if="widget.label and widget.labeltext" 
                      py:replace="widget.label.insert(widget.labeltext)"/>
                <span py:if="not widget.label or not widget.labeltext" py:strip="True">&#160;</span>
            </td>
            <td>${widget.insert(getattr(self.widget_value, widget.name, None), input_values, widget_error.get(widget.name, None))}</td>
        </tr>
         <tr>
            <td>&#160;</td>
            <td>${submit.insert(submittext)}</td>
        </tr>
    </table>
</form>
"""


search_form = ValidatingTableForm(widgets= \
        [_first_name_field, _last_name_field, 
         _location_field, _unit_field],
        validator=RequireNFields(),
        name="searchform")