HTML Forms and Markup¶
Flatland is not explicitly a form library, although it handles that task handily with powerful type conversion and validation error reporting. Dedicated form libraries often provide sophisticated “widget” or “control” features to render data fields as complex HTML markup with CSS and JavaScript support. The full expression of these features is outside Flatland’s scope.
However! Properly generating HTML form tags and filling them with processed data is tedious and a common need, so Flatland ships with a minimalistic yet powerful toolset for tag generation. These tools are both highly usable as-is and also a solid base for constructing higher-level widgeting systems.
Markup Generation¶
The generation formula is simple: a desired tag (such as input
) plus a
flatland Element equals a complete HTML tag with attributes like name
and
value
filled in using the element’s state.
Operating on Element
rather than the raw HTTP input or the
polished final value provides a huge amount of expressive power. For example,
- Elements carry their validation errors- place these directly next to the
form fields, or roll them up- your choice. Highlight failing fields with a
class="error"
CSS attribute right on the input element. - (Re)populate form fields with exactly what the user typed, or the normalized version.
- Leverage the structure of the schema in template markup- if a form contains a list of 1 or more email addresses, loop over that list using your template language and render fields.
- Directly access Element properties and metadata, translation functions, and cross-element relations to implement complex view problems simply.
Flatland ships with two generator front-ends, both supporting the same
features via a shared backend. The first,
Generator
, is for use in straight Python code,
Jinja2, Mako, Genshi, or any other templating system. The second is a plugin
for the Genshi templating library that integrates Flatland element binding
directly and naturally into your existing <input/> tags.
Basic DWIM Binding¶
>>> from flatland.out.markup import Generator
>>> from flatland import Form, String
>>> html = Generator()
>>> class Login(Form):
... username = String
... password = String
...
>>> form = Login({'username': 'jek'})
Basic “Do What I Mean” form binding:
>>> print(html.input(form['username']))
<input name="username" value="jek" />
Likewise with Genshi.
<input form:bind="form.username"/>
and Genshi generates:
<input name="username" value="jek"/>
Attributes Too¶
Any HTML attribute can be included. Generated attributes can be overridden, too.
This time, the Generator is used in a Jinja2 template.
>>> from jinja2 import Template
>>> template = Template("""\
... {{ html.input(form.username, name="other", class_="custom") }}
... """)
>>> print(template.render(html=html, form=form))
<input name="other" value="jek" class="custom" />
These features are very similar in Genshi, too.
<input form:bind="form.username" name="other" class="custom"/>
Which generates the same output:
<input name="other" value="jek" class="custom"/>
Many Python templating systems allow you to replace the indexing operator
(form['username']
) with the attribute operator (form.username
) to
improve readability in templates. As shown above, this kind of rewriting
trickery is generally not a problem for Flatland. Just keep name collisions
in mind- if your form has a String field called name
, is form.name
the
value of your form’s name attribute or is it the String field? When writing
macros or reusable functions, using the explicit form[...]
index syntax is
a good choice to protect against unexpected mangling by the template system no
matter what the fields are named.
And More¶
The tag and attribute generation behavior can be configured and even post-processed just as you like it, affecting all of your tags, just one template, a block, or even individual tags.
Controlling Attribute Transformations¶
Out of the box, generation will do everything required for form element
rendering and repopulation: filling <textarea>s
, checking checkboxes, etc.
Flatland can also generate some useful optional attributes, such as id=
and for=
linking for <label>s
. Generation of attributes is controlled
with markup options at several levels:
- Global:
- Everything generated with a Generator instance or within a Genshi rendering operation.
- Block:
- Options can be overridden within the scope of a block, reverting to their previous value at the end of the block.
- Tag:
- Options can overridden on a per-tag basis.
- Default:
- Finally, each tag has a set of sane default behaviors.
Boolean options may be True, or False, “on” or “off”, or set to “auto” to revert to the transformation’s built-in default setting.
Transformations¶
Most transforms require a Flatland element for context, such as setting an
input
tag’s value=
to the element’s Unicode value. These tags can be
said to be “bound” to the element.
Tags need not be bound, however. Here an unbound textarea
can still
participate in tabindex=
generation.
>>> html = Generator(tabindex=100)
>>> print(html.textarea())
<textarea></textarea>
>>> print(html.textarea(auto_tabindex=True))
<textarea tabindex="100"></textarea>
>>> html.set(auto_tabindex=True)
u''
>>> print(html.textarea())
<textarea tabindex="101"></textarea>
Setting a boolean option to “on” or True on the tag itself will always attempt to apply the transform, allowing the transform to be applied to arbitrary tags that normally would not be transformed.
>>> print(html.tag('squiznart', auto_tabindex=True))
<squiznart tabindex="102" />
The Python APIs and the Generator tags use “_”-separated transform names (valid Python identifiers) as shown below, however please note that Genshi uses XML-friendly “-“-separated attribute names in markup.
-
auto-name
Default: on Tags: button, form, input, select, textarea Sets the tag
name=
to the bound element’s.name
. Takes no action if the tag already contains aname=
attribute, unless forced.Receives a
name=
attribute:>>> print(html.input(form['username'], type="text")) <input type="text" name="username" value="jek" />
Uses the explicitly provided
name="foo"
:>>> print(html.input(form['username'], type="text", name='foo')) <input type="text" name="foo" value="jek" />
Replaces
name="foo"
with the element’s name:>>> print(html.input(form['username'], type="text", name='foo', auto_name=True)) <input type="text" name="username" value="jek" />
-
auto-value
Default: on Tags: button, input, select, textarea Uses the bound element’s
.u
Unicode value for the tag’s value. The semantics of “value” vary by tag.<input>
types text, hidden, button, submit and reset:Sets the
value=""
attribute of the tag, or omits the attribute if.u
is the empty string.Receives a
value=
attribute:>>> print(html.input(form['username'], type="text")) <input type="text" name="username" value="jek" />
Uses the explicitly provided
value="quux"
:>>> print(html.input(form['username'], type="text", value='quux')) <input type="text" name="username" value="quux" />
<input>
types password, image and file:No value is added unless forced by setting auto_value on the tag.
>>> print(html.input(form['password'], type="password")) <input type="password" name="password" />
But this behavior can be forced:
>>> print(html.input(form['password'], type="password", auto_value=True)) <input type="password" name="password" value="secret" />
<input>
type radio:Radio buttons will add a
checked="checked"
attribute if the literalvalue=
matches the element’s value. Or, if the bind is aContainer
,value=
will be compared against the.u
of each of the container’s children until a match is found.If the tag lacks a
value=
attribute, no action is taken.>>> print(form['username'].u) jek >>> print(html.input(form['username'], type="radio", value="quux")) <input type="radio" name="username" value="quux" /> >>> print(html.input(form['username'], type="radio", value="jek")) <input type="radio" name="username" value="jek" checked="checked" />
<input>
type checkbox:Check boxes will add a
checked="checked"
attribute if the literalvalue=
matches the element’s value.>>> print(form['username'].u) jek >>> print(html.input(form['username'], type="checkbox", value="quux")) <input type="checkbox" name="username" value="quux" /> >>> print(html.input(form['username'], type="checkbox", value="jek")) <input type="checkbox" name="username" value="jek" checked="checked" />
Or, if the bind is a
Container
,value=
will be compared against the.u
of each of the container’s children until a match is found.>>> from flatland import Array >>> Bag = Array.named('bag').of(String) >>> bag = Bag(['a', 'c']) >>> for value in 'a', 'b', 'c': ... print(html.input(bag, type="checkbox", value=value)) ... <input type="checkbox" name="bag" value="a" checked="checked" /> <input type="checkbox" name="bag" value="b" /> <input type="checkbox" name="bag" value="c" checked="checked" />
If the tag lacks a
value=
attribute, no action is taken, unless the bind is a Boolean. The missingvalue=
will be added using the schema’sBoolean.true
value.>>> print(html.input(form['username'], type="checkbox")) <input type="checkbox" name="username" /> >>> from flatland import Boolean >>> toggle = Boolean.named('toggle')() >>> print(html.input(toggle, type="checkbox")) <input type="checkbox" name="toggle" value="1" /> >>> toggle.set(True) True >>> print(html.input(toggle, type="checkbox")) <input type="checkbox" name="toggle" value="1" checked="checked" /> >>> toggle.true = "yes"
<input>
types unknown:For types unknown to flatland, no value is set unless forced by settingform:auto-value="on"
on the tag.<textarea>
:Textareas will insert the
Element.u
inside the tag pair. Content supplied withcontents=
for Generators or between Genshi tags will be preferred unless forced.>>> print(html.textarea(form['username'])) <textarea name="username">jek</textarea> >>> print(html.textarea(form['username'], contents="quux")) <textarea name="username">quux</textarea>
Note that in Genshi, these two forms are equivalent.
<!-- these: --> <textarea form:bind="form.username"/> <textarea form:bind="form.username"></textarea> <!-- will both render as --> <textarea name="username">jek</textarea>
<select>
:Select tags apply a
selected="selected"
attribute to their<option>
tags that match theElement.u
or, if the bind is aContainer
, the.u
of one of its children.For this matching to work, the
<option>
tags must have a literal value set in the markup. The value may an explicitvalue=
attribute, or it may be the text of the tag. Leading and trailing whitespace will be stripped when considering the text of the tag as the value.The below will emit
selected="selected"
if form.field is equal to any of “a”, “b”, “c”, and “d”.<select form:bind="form.field"> <option>a</option> <option value="b"/> <option value="c">label</option> <option> d </option> </select>
<button/>
and<button value=""/>
:Regular
<button/>
tags will insert theElement.u
inside the<button></button>
tag pair. The output will not be XML-escaped, allowing any markup in the.u
to render properly.If the tag contains a literal
value=
attribute and a value override is forced by settingform:auto-value="on"
, the.u
will be placed in thevalue=
attribute, replacing the existing content. The value is escaped in this case.<!-- set or replace the inner *markup* --> <button form:bind="form.field"/> <button form:bind="form.field" form:auto-value="on">xyz</button> <!-- set the value, retaining the value= style used in the original --> <button form:bind="form.field" value="xyz" form:auto-value="on"/>
-
auto-domid
Default: off Tags: button, input, select, textarea Sets the
id=
attribute of the tag. Takes no action if the markup already contains aid=
unless forced by settingform:auto-domid="on"
.The id is generated by combining the bound element’s
flattened_name
with thedomid-format
in the current scope. The default format is f_%s.
-
auto-for
Default: on Tags: label Sets the
for=
attribute of the tag to the id of the bound element. The id is generated using the same process as auto-domid. No consistency checks are performed on the generated id value.Defaults to “on”, and will only apply if auto-domid is also “on”. Takes no action if the markup already contains a
id=
unless forced by settingform:auto-for="on"
.<form:with auto-domid="on"> <fieldset py:with="field=form.field"> <label form:bind="field">${field.label.x}</label> <input type="text" form:bind="field"/> </fieldset> </form:with>
-
auto-tabindex
Default: off Tags: button, input, select, textarea Sets the
tabindex
attribute of tags with an incrementing integer.Numbering starts at the scope’s
tabindex
, which has no default. Assigning a value fortabindex
will set the value for the next tabindex assignment, and subsequent assignments will increment by one.A
tabindex
value of 0 will block the assignment of a tabindex and will not be incremented.Takes no action if the markup already contains a
tabindex=
unless forced by settingform:auto-tabindex="on"
.<form:with auto-tabindex="on" tabindex="1"> <!-- assigns tabindex="1" --> <input type="text" form:bind="form.field"/> <!-- leaves existing tabindex in place --> <input type="text" tabindex="-1" form:bind="form.field"/> <!-- assigns tabindex="2" --> <a href="#" form:auto-tabindex="on"/> </form:with>
Generator¶
-
class
Generator
(markup=u'xhtml', **settings)¶ General XML/HTML tag generator
Create a generator.
Accepts any Transformations, as well as the following:
Parameters: - markup – tag output style:
'xml'
,'xhtml'
or'html'
- ordered_attributes – if True (default), output markup attributes in a predictable order. Useful for tests and generally a little more pleasant to read.
-
begin
(**settings)¶ Begin a new Transformations context.
Puts **settings into effect until a matching
end()
is called. Each setting specified will mask the current value, reverting whenend()
is called.
-
end
()¶ End a Transformations context.
Restores the settings that were in effect before
begin()
.
-
set
(**settings)¶ Change the Transformations in effect.
Change the **settings in the current scope. Changes remain in effect until another
set()
or aend()
ends the current scope.
-
form
¶ Generate a
<form/>
tag.Parameters: - bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
If provided with a bind, form tags can generate the name attribute.
-
input
¶ Generate an
<input/>
tag.Parameters: - bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
If provided with a bind, input tags can generate the name, value and id attributes. Input tags support tabindex attributes.
-
textarea
¶ Generate a
<textarea/>
tag.Parameters: - bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
If provided with a bind, textarea tags can generate the name and id attributes. If the bind has a value, it will be used as the tag body. Textarea tags support tabindex attributes. To provide an alternate tag body, either supply contents or use the
open()
andclose()
method of the returned tag.
Generate a
<button/>
tag.Parameters: - bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
If provided with a bind, button tags can generate the name, value, and id attributes. Button tags support tabindex attributes.
-
select
¶ Generate a
<select/>
tag.Parameters: - bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
If provided with a bind, select tags can generate the name and id attributes. Select tags support tabindex attributes.
-
option
¶ Generate an
<option/>
tag.Parameters: - bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
If provided with a bind, option tags can generate the value attribute. To provide tag body, either supply contents or use the
open()
andclose()
method of the returned tag:print(generator.option.open(style='bold')) print('<strong>contents</strong>') print(generator.option.close())
-
label
¶ Generate a
<label/>
tag.Parameters: - bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
If provided with a bind, label tags can generate the for attribute and fill in the tag body with the element’s
label
, if present.
-
tag
(tagname, bind=None, **attributes)¶ Generate any tag.
Parameters: - tagname – the name of the tag.
- bind – optional, a flatland element.
- **attributes – any desired XML/HTML attributes.
Returns: a printable
Tag
The attribute rules appropriate for tagname will be applied. For example,
tag('input')
is equivalent toinput()
.
- markup – tag output style:
-
class
Tag
(tagname, context, dangle, paired)¶ A printable markup tag.
Tags are generated by
Generator
and are usually called immediately, returning a fully formed markup string:print(generator.textarea(contents="hello!"))
For more fine-tuned control over your markup, you may instead choose to use the
open()
andclose()
methods of the tag:print(generator.textarea.open()) print("hello!") print(generator.textarea.close())
-
open
(bind=None, **attributes)¶ Return the opening half of the tag, e.g.
<p>
.Parameters: - bind – optional, a flatland element.
- **attributes – any desired tag attributes.
-
close
()¶ Return the closing half of the tag, e.g.
</p>
.
-
Genshi Directives¶
http://ns.discorporate.us/flatland/genshi