Widgets using Templates and Schema Properties

Unlike utilities more directly focused on processing Web forms, Flatland does not include any concept of “widgets” that render a field. It is however easy enough to employ Flatland’s “properties” and markup generation support to build our own widget system. This also gives us complete control over the rendering.

from flatland import Form, String

Input = String.with_properties(widget='input', type='text')
Password = Input.with_properties(type='password')

class SignInForm(Form):
    username = Input.using(label='Username')
    password = Password.using(label='Password')

Rendering Widgets with Genshi

Macros via Genshi’s py:def directive would be a good way to implement the actual widgets. For example:

<html
    xmlns:form="http://ns.discorporate.us/flatland/genshi"
    xmlns:py="http://genshi.edgewall.org/"
    py:strip=""
  >

  <py:def
      function="widget(field)"
      py:with="macro = value_of(field.properties.widget + '_widget')"
      py:replace="macro(field)"
    />

  <fieldset py:def="input_widget(field)">
    <form:with
        auto-domid="on"
        auto-for="on"
      >

      <label
          form:bind="field"
          py:content="field.label"
        />

      <input
          form:bind="field"
          type="${field.properties.type}"
        />

    </form:with>
  </fieldset>
</html>

Typically we would call the widget macro manually for each field we want rendered, and in the desired order, but for demonstrative purposes we stub out widgets for each field in arbitrary order:

<html
    xmlns:py="http://genshi.edgewall.org/"
    xmlns:xi="http://www.w3.org/2001/XInclude"
  >
  <xi:include href="widgets.html"/>
  <body>
    <form>
      ${widget(form['username'])}
      ${widget(form['password'])}
    </form>
  </body>
</html>

Rendering with Jinja

If you’re not using Genshi you can still benefit from Flatland’s schema-aware markup generating support. With Jinja we might implement the macros as something resembling this:

{% set html = form_generator %}

{% macro widget(field) %}
  {%- set macro = {'input': input}[field.properties.widget] -%}
  {{- macro(field) -}}
{% endmacro %}

{% macro input(field) %}
  {%- do html.begin(auto_domid=true, auto_for=true) %}
<fieldset>
  {{ html.label(field, contents=field.label) }}
  {{ html.input(field, type=field.properties.type) }}
</fieldset>
  {%- do html.end() %}
{% endmacro %}

Then we can simply import the widget macro to form templates:

{% from 'widgets.html' import widget -%}
<html>
  <body>
    <form>
      {{- widget(form['username']) }}
      {{- widget(form['password']) }}
    </form>
  </body>
</html>

Make sure to add a markup generator to the globals of your Jinja environment:

from flatland.out.markup import Generator
jinja_env.globals['form_generator'] = Generator('html')