This section tackles a number of useful speedups common patterns:
“fields” over an over. Why reconfigure their validators repeatedly? Define them once and re-use them, getting a performance boost at startup time.
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.
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, as part of the “application”. Let ZPT iterate over this to generate the <select> and make sure the validator enforces it.
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
|
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,
)
|
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>
|
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"> </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"> </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>
|