Speedup, Edit, Lookup

This section tackles a number of useful speedups common patterns:

  • Speedup with pre-built fields. Lots of sites use the same

    “fields” over an over. Why reconfigure their validators repeatedly? Define them once and re-use them, getting a performance boost at startup time.

  • Macros for “fields”. Similarly, why make a “name” field in all

    your ZPTs when it is the same <fieldset> over and over? ZPT has a facility for sharing (macros), so let’s put it to use.

  • Edit existing data. Add some links in the sidebar that allow

    editing existing data. Makes the view more clumsy, as we’d prefer an edit view registered against an instance’s interface. Instead, we put more branching in our view.

    We’ll fix this in the next step.

  • Vocabulary lookup. Add a field for Country that comes from a

    vocabulary, as part of the “application”. Let ZPT iterate over this to generate the <select> and make sure the validator enforces it.

The Files

  1. The BaseForm at t3/baseform.py:

     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
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    """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
    
    # Simulate getting some vocabularies
    vocabularies = {
        'countries': [
            ('AT', 'Austria'),
            ('BE', 'Belgium'),
            ('CA', 'Canada'),
            ('DE', 'Germany'),
            ('ES', 'Estonia'),
            ('FI', 'Finland'),
            ('GB', 'Great Britain'),
            ],
        }
    
    # Make some re-usable fields
    from formencode import validators
    name = validators.UnicodeString(not_empty=True)
    age = validators.Int(not_empty=True, max=200, default=93)
    country = validators.OneOf([i[0] for i in vocabularies['countries']])
    
    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
    
    • Make a mapping of some vocabularies.
    • Create some “fields” at startup time that are pre-configured and can be used in multiple schemas. One of these fields (country) is a controlled vocabulary. Values provided are checked against the vocabulary.
  2. The view at t3/views.py handles more cases:

     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
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    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 baseform import name
    from baseform import age
    from baseform import country
    from baseform import vocabularies
    
    from formencode import Invalid
    
    static_view = static('templates/static')
    
    # Define a form schema, make an instance
    class MyInfo(BaseForm):
        name = name
        age = age
        country = country
    myinfo = MyInfo
    
    # Simulate editing exisiting model data
    records = {
        '1': {'name': 'Rec 1', 'age': 27},
        '2': {'name': 'Rec 2', 'age': 94},
    }
    
    def my_view(context, request):
        layout = get_template('templates/layout.pt')
        formfields = get_template('templates/formfields.pt')
        form = myinfo()
    
        form_message = None
        rec_id = request.GET.get('id', None)
    
        if form.is_submitted(request):
            if rec_id is None:
                fieldvalues = form.combine_dicts(form.defaults, 
                                                 request.POST)
            else:
                # Edit existing
                fieldvalues = form.combine_dicts(records[rec_id], 
                                                 request.POST)
    
            # 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:
                fielderrors = e.error_dict
    
        else:
            if rec_id is None:
                fieldvalues = form.defaults
            else:
                fieldvalues = records[rec_id]
            fielderrors = {}
    
        # Render the form and shove some default values in
        form_html = render_template(
            'templates/myform.pt',
            request=request,
            formfields=formfields,
            fielderrors=fielderrors,
            form_message=form_message,
            vocabularies=vocabularies,
            )
        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,
            )
    
    • Use the three pre-configured fields in the MyInfo form.
    • Make some data at records simulating some model data
    • In the view, see if we were handed an id as GET data.
    • Do some tests to whether to grab existing data.
    • Get the vocabulary data from the other module and pass it into the ZPT for the form.
  3. The form template is now really small at t3/templates/myform.pt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <html xmlns:tal="http://xml.zope.org/namespaces/tal"
          xmlns:metal="http://xml.zope.org/namespaces/metal">
    
      <form method="post" action="${request.url}">
    
        <div metal:use-macro="formfields.macros['form-message']"/>
        <fieldset metal:use-macro="formfields.macros['name-field']"/>
        <fieldset metal:use-macro="formfields.macros['age-field']"/>
        <fieldset metal:use-macro="formfields.macros['country-field']"/>
        <fieldset metal:use-macro="formfields.macros['form-submit']"/>
      </form>
    
    </html>
    
    • Point at macros in formfields.pt for all the form fields.
  4. Common fields are shared as ZPT macros in t3/templates/formfields.pt:

     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
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    	  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:tal="http://xml.zope.org/namespaces/tal"
          xmlns:metal="http://xml.zope.org/namespaces/metal">
     <head>
       <title>KARL3 Forms Macros</title>
     </head>
     <body>
    
    
        <div metal:define-macro="form-message" tal:condition="form_message"
        class="form_message">${form_message}</div>
    
        <fieldset metal:define-macro="name-field"
    	      tal:define="error fielderrors['name']|None">
          <label>Name <span class="required">&nbsp;</span></label>
          <input name="name"/>
          <div tal:condition="error" class="error">${error}</div>
        </fieldset>
    
        <fieldset metal:define-macro="age-field"
    	      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 metal:define-macro="country-field"
    	      tal:define="error fielderrors['country']|None">
          <label>Country</label>
          <select name="country" size="3">
    	<option value="${c[0]}" 
    		tal:repeat="c vocabularies['countries']">${c[1]}</option>
          </select>
          <div tal:condition="error" class="error">${error}</div>
        </fieldset>
    
        <fieldset metal:define-macro="form-submit">
          <input type="submit" name="form.submitted"/>
        </fieldset>
    
     </body>
    </html>
    
    • A macro for each field, plus the message and the buttons.
    • The country field iterates over the vocabulary, making <option> nodes.

Table Of Contents

Previous topic

Getting Closer to FormEncode

Next topic

Refactor Into Models

This Page