Getting Closer to FormEncode

Let’s get rid of the hard-wired fields and validators and jump straight into FormEncode.schema.

This isn’t a complete jump: we’re still using our own baseform.validate method. We’ll get more FormEncode-onic in following steps.

Still, we can see from the request/second in the footer that this thing is pretty fast.

Basing on Schema

  1. The``BaseForm`` at t3/baseform.py is now a subclass of Schema:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    """Base class for KARL3 forms.
    """
    
    from lxml.html import document_fromstring
    from lxml.html import tostring
    from time import time
    from formencode import Schema
    from formencode import Invalid
    
    
    class BaseForm(Schema):
    
        submit_name = 'form.submitted'
        allow_extra_fields = True
        filter_extra_fields = True
    
        def __init__(self):
            Schema.__init__(self)
            self.defaults = {}
            for name, value in self.fields.items():
                default = getattr(value, 'default', None)
                if default is not None:
                    self.defaults[name] = value.default
                
    
        def is_submitted(self, request):
            self.start_time = time()
            return request.POST.get('form.submitted', False)
    
    
        def validate(self, fieldvalues):
            fielderrors = {}
            try:
                fieldvalues = self.to_python(fieldvalues)
            except Invalid, e:
                fielderrors = e.error_dict
    
            return fieldvalues, fielderrors
    
    
        def merge(self, htmlstring, fieldvalues):
            # Merge in field values, either from the default or from what
            # they typed in.
    
            form_tree = document_fromstring(htmlstring)
            form = form_tree.forms[0]
    
            for field_name, field_value in fieldvalues.items():
                form.fields[field_name] = unicode(field_value)
            merged_form_html = tostring(form)
    
            # Record the elapsed time
            self.elapsed = str(1 / (time() - self.start_time))[0:7]
    
            return merged_form_html
    
        def combine_dicts(self, dict1, dict2):
            # We often have multiple sources for field values.  For
            # example, the default defined into the field versus the value
            # typed into the request.params.  Combine these, with the
            # latter taking presence over the former.
    
            fieldvalues = dict1
            for key, value in dict2.items():
                fieldvalues[key] = value
    
            return fieldvalues
    
    • The validate method tells FormEncode to do most of the work. We use the error messages it produces. This method can probably go away when we get more FormEncode-onic.
  2. The t3/views.py module gets more realistic:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    from repoze.bfg.chameleon_zpt import render_template_to_response
    from repoze.bfg.chameleon_zpt import get_template
    from repoze.bfg.chameleon_zpt import render_template
    from repoze.bfg.view import static
    
    from baseform import BaseForm
    from formencode import validators
    from formencode import Invalid
    
    static_view = static('templates/static')
    
    # Define a form schema, make an instance
    class MyAge(BaseForm):
        age = validators.Int(not_empty=True, max=200, default=93)
    myage = MyAge
    
    
    def my_view(context, request):
        layout = get_template('templates/layout.pt')
        form = myage()
    
        form_message = None
    
        if form.is_submitted(request):
            # We were submitted. Start by combining data from
            # request.params and form.defaults.
            fieldvalues = form.combine_dicts(form.defaults, request.params)
    
            # Now validate
            try:
                fieldvalues = form.to_python(fieldvalues)
                fielderrors = {}
    
                # Go ahead and do the work to add content, etc.
                form_message = "Saved the data."
    
            except Invalid, e:
                # Form data wasn't valid.
                fielderrors = e.error_dict
    
        else:
            # Set default values and no errors
            fieldvalues = form.defaults
            fielderrors = {}
    
        # Render the form and shove some default values in
        form_html = render_template(
            'templates/myform.pt',
            request=request,
            fielderrors=fielderrors,
            form_message=form_message,
            )
        rendered_form = form.merge(form_html, fieldvalues)
    
        return render_template_to_response(
            'templates/mytemplate.pt',
            request = request,
            layout=layout,
            form_html=rendered_form,
            elapsed=form.elapsed,
            )
    
    • We make a form MyAge with a FormEncode validator, then make a module-level instance we can re-use between requests.
    • Checking is_submitted sets the start_time for measurement. This breaks the no-state rule.
    • Validation returns converted values and field-level error messages.
    • If there were no errors, we can do the part where we create/update content.
    • If the form wasn’t submitted, set some default values for the field values. We could use FormEncode for this, but then the most common case (the first GET) would trigger the complete validation machinery, breaking one of our goals.
  3. The t3/templates/myform.pt form grows a place to show a form message:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <html xmlns:tal="http://xml.zope.org/namespaces/tal">
    
      <form method="post" action="${request.url}">
    
        <div tal:condition="form_message"
        class="form_message">${form_message}</div>
    
        <fieldset tal:define="error fielderrors['age']|None">
          <label>Age <span class="required">&nbsp;</span></label>
          <input name="age"/>
          <div tal:condition="error" class="error">${error}</div>
        </fieldset>
        <fieldset>
          <input type="submit" name="form.submitted"/>
        </fieldset>
      </form>
    
    </html>
    

Notes

  • As you can see, there is no state that changes inside a KarlForm after it is constructed. We pass data in and out (primarily, the field values and the errors.)

Table Of Contents

Previous topic

First Base Class

Next topic

Speedup, Edit, Lookup

This Page