changeset 1420:100197cb69ab

Convert Flatland forms to contain rendering hints. Form elements are now rendered using the polymorphic forms.render macro. Ref: http://dag-flatland.readthedocs.org/en/latest/patterns/widgets/
author Cheer Xiao <xiaqqaix@gmail.com>
date Fri, 06 Jul 2012 18:50:35 +0800
parents 94dda5c633d2
children 9f36555901db
files MoinMoin/apps/frontend/views.py MoinMoin/constants/forms.py MoinMoin/forms.py MoinMoin/items/__init__.py MoinMoin/search/__init__.py MoinMoin/templates/delete.html MoinMoin/templates/destroy.html MoinMoin/templates/forms.html MoinMoin/templates/index.html MoinMoin/templates/login.html MoinMoin/templates/lookup.html MoinMoin/templates/lostpass.html MoinMoin/templates/modify_applet.html MoinMoin/templates/modify_binary.html MoinMoin/templates/openid_register.html MoinMoin/templates/recoverpass.html MoinMoin/templates/register.html MoinMoin/templates/rename.html MoinMoin/templates/revert.html MoinMoin/templates/search.html MoinMoin/templates/usersettings_forms.html
diffstat 21 files changed, 389 insertions(+), 232 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/apps/frontend/views.py	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/apps/frontend/views.py	Fri Jul 06 18:50:35 2012 +0800
@@ -34,8 +34,8 @@
 from flaskext.babel import format_date
 from flaskext.themes import get_themes_list
 
-from flatland import Form, String, Integer, Boolean, Enum, MultiValue
-from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLValidator, Converted, ValueAtLeast
+from flatland import Form
+from flatland.validation import Validator
 
 from jinja2 import Markup
 
@@ -50,7 +50,8 @@
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.themes import render_template, get_editor_info, contenttype_to_class
 from MoinMoin.apps.frontend import frontend
-from MoinMoin.items import Item, NonExistent
+from MoinMoin.forms import OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox, InlineCheckbox, Select, Tags, Natural, Submit, Hidden
+from MoinMoin.items import BaseChangeForm, Item, NonExistent
 from MoinMoin.items import ROWS_META, COLS, ROWS_DATA
 from MoinMoin import config, user, util
 from MoinMoin.config import CONTENTTYPE_GROUPS
@@ -128,18 +129,18 @@
 
 
 class LookupForm(Form):
-    name = String.using(label=L_('name'), optional=True)
-    name_exact = String.using(label=L_('name_exact'), optional=True)
-    itemid = String.using(label=L_('itemid'), optional=True)
-    revid = String.using(label=L_('revid'), optional=True)
-    userid = String.using(label=L_('userid'), optional=True)
-    language = String.using(label=L_('language'), optional=True)
-    itemlinks = String.using(label=L_('itemlinks'), optional=True)
-    itemtransclusions = String.using(label=L_('itemtransclusions'), optional=True)
-    refs = String.using(label=L_('refs'), optional=True)
-    tags = MultiValue.of(String).using(label=L_('tags'), optional=True)
-    history = Boolean.using(label=L_('search also in non-current revisions'), optional=True)
-    submit = String.using(default=L_('Lookup'), optional=True)
+    name = OptionalText.using(label=L_('name'))
+    name_exact = OptionalText.using(label=L_('name_exact'))
+    itemid = OptionalText.using(label=L_('itemid'))
+    revid = OptionalText.using(label=L_('revid'))
+    userid = OptionalText.using(label=L_('userid'))
+    language = OptionalText.using(label=L_('language'))
+    itemlinks = OptionalText.using(label=L_('itemlinks'))
+    itemtransclusions = OptionalText.using(label=L_('itemtransclusions'))
+    refs = OptionalText.using(label=L_('refs'))
+    tags = Tags.using(optional=True)
+    history = InlineCheckbox.using(label=L_('search also in non-current revisions'))
+    submit = Submit.using(default=L_('Lookup'))
 
 
 @frontend.route('/+lookup', methods=['GET', 'POST'])
@@ -464,35 +465,36 @@
     return item.do_modify(contenttype, template_name)
 
 
-class CommentForm(TextChaizedForm):
-    comment = String.using(label=L_('Comment'), optional=True).with_properties(placeholder=L_("Comment about your change"))
-    submit = String.using(default=L_('OK'), optional=True)
+class TargetChangeForm(BaseChangeForm):
+    target = RequiredText.using(label=L_('Target')).with_properties(placeholder=L_("The name of the target item"))
 
-class TargetCommentForm(CommentForm):
-    target = String.using(label=L_('Target')).with_properties(placeholder=L_("The name of the target item")).validated_by(Present())
-
-class RevertItemForm(CommentForm):
+class RevertItemForm(BaseChangeForm):
     name = 'revert_item'
 
-class DeleteItemForm(CommentForm):
+class DeleteItemForm(BaseChangeForm):
     name = 'delete_item'
 
-class DestroyItemForm(CommentForm):
+class DestroyItemForm(BaseChangeForm):
     name = 'destroy_item'
 
-class RenameItemForm(TargetCommentForm):
+class RenameItemForm(TargetChangeForm):
     name = 'rename_item'
 
 class ContenttypeFilterForm(Form):
     name = 'contenttype_filter'
-    markup_text_items = Boolean.using(label=L_('markup text'), optional=True, default=1)
-    other_text_items = Boolean.using(label=L_('other text'), optional=True, default=1)
-    image_items = Boolean.using(label=L_('image'), optional=True, default=1)
-    audio_items = Boolean.using(label=L_('audio'), optional=True, default=1)
-    video_items = Boolean.using(label=L_('video'), optional=True, default=1)
-    other_items = Boolean.using(label=L_('other'), optional=True, default=1)
-    unknown_items = Boolean.using(label=L_('unknown'), optional=True, default=1)
-    submit = String.using(default=L_('Filter'), optional=True)
+    markup_text_items = InlineCheckbox.using(label=L_('markup text'))
+    other_text_items = InlineCheckbox.using(label=L_('other text'))
+    image_items = InlineCheckbox.using(label=L_('image'))
+    audio_items = InlineCheckbox.using(label=L_('audio'))
+    video_items = InlineCheckbox.using(label=L_('video'))
+    other_items = InlineCheckbox.using(label=L_('other'))
+    unknown_items = InlineCheckbox.using(label=L_('unknown'))
+    submit = Submit.using(default=L_('Filter'))
+
+for gname, contenttypes in CONTENTTYPE_GROUPS:
+    filter_ = ContenttypeFilterForm.field_schema_mapping.get(gname.replace(' ', '_'))
+    if filter_:
+        filter_.properties['helper'] = ", ".join([ctlabel for ctname, ctlabel in contenttypes])
 
 
 @frontend.route('/+revert/+<rev>/<itemname:item_name>', methods=['GET', 'POST'])
@@ -729,10 +731,6 @@
     startswith = request.values.get("startswith")
     index = item.flat_index(startswith, selected_groups)
 
-    ct_groups = [(gname, ", ".join([ctlabel for ctname, ctlabel in contenttypes]))
-                 for gname, contenttypes in CONTENTTYPE_GROUPS]
-    ct_groups = dict(ct_groups)
-
     initials = item.name_initial(item.flat_index())
     initials = [initial.upper() for initial in initials]
     initials = list(set(initials))
@@ -750,7 +748,6 @@
                            index=detailed_index,
                            initials=initials,
                            startswith=startswith,
-                           contenttype_groups=ct_groups,
                            form=form,
                            **args
                           )
@@ -986,31 +983,23 @@
     """a simple user registration form"""
     name = 'register'
 
-    username = String.using(label=L_('Name')).with_properties(placeholder=L_("The login name you want to use")).validated_by(Present())
-    password1 = String.using(label=L_('Password')).with_properties(placeholder=L_("The login password you want to use")).validated_by(Present())
-    password2 = String.using(label=L_('Password')).with_properties(placeholder=L_("Repeat the same password")).validated_by(Present())
-    email = String.using(label=L_('E-Mail')).with_properties(placeholder=L_("Your E-Mail address")).validated_by(IsEmail())
-    openid = String.using(label=L_('OpenID'), optional=True).with_properties(placeholder=L_("Your OpenID address")).validated_by(URLValidator())
-    submit = String.using(default=L_('Register'), optional=True)
+    username = RequiredText.using(label=L_('Name')).with_properties(placeholder=L_("The login name you want to use"))
+    password1 = RequiredPassword.with_properties(placeholder=L_("The login password you want to use"))
+    password2 = RequiredPassword.with_properties(placeholder=L_("Repeat the same password"))
+    email = YourEmail
+    openid = YourOpenID.using(optional=True)
+    submit = Submit.using(default=L_('Register'))
 
     validators = [ValidRegistration()]
 
 
-class OpenIDForm(TextChaizedForm):
+class OpenIDForm(RegistrationForm):
     """
     OpenID registration form, inherited from the simple registration form.
     """
     name = 'openid'
 
-    username = String.using(label=L_('Name')).with_properties(placeholder=L_("The login name you want to use")).validated_by(Present())
-    password1 = String.using(label=L_('Password')).with_properties(placeholder=L_("The login password you want to use")).validated_by(Present())
-    password2 = String.using(label=L_('Password')).with_properties(placeholder=L_("Repeat the same password")).validated_by(Present())
-
-    email = String.using(label=L_('E-Mail')).with_properties(placeholder=L_("Your E-Mail address")).validated_by(IsEmail())
-    openid = String.using(label=L_('OpenID')).with_properties(placeholder=L_("Your OpenID address")).validated_by(URLValidator())
-    submit = String.using(optional=True)
-
-    validators = [ValidRegistration()]
+    openid = YourOpenID
 
 def _using_moin_auth():
     """Check if MoinAuth is being used for authentication.
@@ -1129,9 +1118,9 @@
     """a simple password lost form"""
     name = 'lostpass'
 
-    username = String.using(label=L_('Name'), optional=True).with_properties(placeholder=L_("Your login name"))
-    email = String.using(label=L_('E-Mail'), optional=True).with_properties(placeholder=L_("Your E-Mail address")).validated_by(IsEmail())
-    submit = String.using(default=L_('Recover password'), optional=True)
+    username = OptionalText.using(label=L_('Name')).with_properties(placeholder=L_("Your login name"))
+    email = YourEmail.using(optional=True)
+    submit = Submit.using(default=L_('Recover password'))
 
     validators = [ValidLostPassword()]
 
@@ -1189,11 +1178,11 @@
     """a simple password recovery form"""
     name = 'recoverpass'
 
-    username = String.using(label=L_('Name')).with_properties(placeholder=L_("Your login name")).validated_by(Present())
-    token = String.using(label=L_('Recovery token')).with_properties(placeholder=L_("The recovery token that has been sent to you")).validated_by(Present())
-    password1 = String.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use")).validated_by(Present())
-    password2 = String.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password")).validated_by(Present())
-    submit = String.using(default=L_('Change password'), optional=True)
+    username = RequiredText.using(label=L_('Name')).with_properties(placeholder=L_("Your login name"))
+    token = RequiredText.using(label=L_('Recovery token')).with_properties(placeholder=L_("The recovery token that has been sent to you"))
+    password1 = RequiredPassword.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use"))
+    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password"))
+    submit = Submit.using(default=L_('Change password'))
 
     validators = [ValidPasswordRecovery()]
 
@@ -1256,12 +1245,10 @@
     """
     name = 'login'
 
-    username = String.using(label=L_('Name'), optional=False).with_properties(autofocus=True).validated_by(Present())
-    password = String.using(label=L_('Password'), optional=False).validated_by(Present())
-    openid = String.using(label=L_('OpenID'), optional=True).validated_by(Present(), URLValidator())
-
-    # the submit hidden field
-    submit = String.using(optional=True)
+    username = RequiredText.using(label=L_('Name'), optional=False).with_properties(autofocus=True)
+    password = RequiredPassword
+    openid = YourOpenID.using(optional=True)
+    submit = Submit.using(default=L_('Log in'))
 
     validators = [ValidLogin()]
 
@@ -1335,23 +1322,23 @@
 
 class UserSettingsPasswordForm(Form):
     name = 'usersettings_password'
-    password_current = String.using(label=L_('Current Password')).with_properties(placeholder=L_("Your current login password")).validated_by(Present())
-    password1 = String.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use")).validated_by(Present())
-    password2 = String.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password")).validated_by(Present())
-    submit = String.using(default=L_('Change password'), optional=True)
     validators = [ValidChangePass()]
 
+    password_current = RequiredPassword.using(label=L_('Current Password')).with_properties(placeholder=L_("Your current login password"))
+    password1 = RequiredPassword.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use"))
+    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password"))
+    submit = Submit.using(default=L_('Change password'))
 
 class UserSettingsNotificationForm(Form):
     name = 'usersettings_notification'
-    email = String.using(label=L_('E-Mail')).with_properties(placeholder=L_("Your E-Mail address")).validated_by(IsEmail())
-    submit = String.using(default=L_('Save'), optional=True)
+    email = YourEmail
+    submit = Submit.using(default=L_('Save'))
 
 
 class UserSettingsNavigationForm(Form):
     name = 'usersettings_navigation'
     # TODO: find a good way to handle quicklinks here
-    submit = String.using(default=L_('Save'), optional=True)
+    submit = Submit.using(default=L_('Save'))
 
 
 class UserSettingsOptionsForm(Form):
@@ -1362,11 +1349,11 @@
     # builtin defaults (for some True, for some others False). Makes
     # edit_on_doubleclick malfunctioning (because its default is True).
     name = 'usersettings_options'
-    mailto_author = Boolean.using(label=L_('Publish my email (not my wiki homepage) in author info'), optional=True)
-    edit_on_doubleclick = Boolean.using(label=L_('Open editor on double click'), optional=True)
-    show_comments = Boolean.using(label=L_('Show comment sections'), optional=True)
-    disabled = Boolean.using(label=L_('Disable this account forever'), optional=True)
-    submit = String.using(default=L_('Save'), optional=True)
+    mailto_author = Checkbox.using(label=L_('Publish my email (not my wiki homepage) in author info'))
+    edit_on_doubleclick = Checkbox.using(label=L_('Open editor on double click'))
+    show_comments = Checkbox.using(label=L_('Show comment sections'))
+    disabled = Checkbox.using(label=L_('Disable this account forever'))
+    submit = Submit.using(default=L_('Save'))
 
 
 @frontend.route('/+usersettings', methods=['GET', 'POST'])
@@ -1377,29 +1364,29 @@
     # these forms can't be global because we need app object, which is only available within a request:
     class UserSettingsPersonalForm(Form):
         name = 'usersettings_personal' # "name" is duplicate
-        name = String.using(label=L_('Name')).with_properties(placeholder=L_("The login name you want to use")).validated_by(Present())
-        aliasname = String.using(label=L_('Alias-Name'), optional=True).with_properties(placeholder=L_("Your alias name (informational)"))
-        openid = String.using(label=L_('OpenID'), optional=True).with_properties(placeholder=L_("Your OpenID address")).validated_by(URLValidator())
+        name = RequiredText.using(label=L_('Name')).with_properties(placeholder=L_("The login name you want to use"))
+        aliasname = OptionalText.using(label=L_('Alias-Name')).with_properties(placeholder=L_("Your alias name (informational)"))
+        openid = YourOpenID.using(optional=True)
         #timezones_keys = sorted(Locale('en').time_zones.keys())
         timezones_keys = [unicode(tz) for tz in pytz.common_timezones]
-        timezone = Enum.using(label=L_('Timezone')).valued(*timezones_keys)
+        timezone = Select.using(label=L_('Timezone')).valued(*timezones_keys)
         supported_locales = [Locale('en')] + app.babel_instance.list_translations()
         locales_available = sorted([(unicode(l), l.display_name) for l in supported_locales],
                                    key=lambda x: x[1])
         locales_keys = [l[0] for l in locales_available]
-        locale = Enum.using(label=L_('Locale')).with_properties(labels=dict(locales_available)).valued(*locales_keys)
-        submit = String.using(default=L_('Save'), optional=True)
+        locale = Select.using(label=L_('Locale')).with_properties(labels=dict(locales_available)).valued(*locales_keys)
+        submit = Submit.using(default=L_('Save'))
 
     class UserSettingsUIForm(Form):
         name = 'usersettings_ui'
         themes_available = sorted([(unicode(t.identifier), t.name) for t in get_themes_list()],
                                   key=lambda x: x[1])
         themes_keys = [t[0] for t in themes_available]
-        theme_name = Enum.using(label=L_('Theme name')).with_properties(labels=dict(themes_available)).valued(*themes_keys)
-        css_url = String.using(label=L_('User CSS URL'), optional=True).with_properties(placeholder=L_("Give the URL of your custom CSS (optional)")).validated_by(URLValidator())
-        edit_rows = Integer.using(label=L_('Editor size')).with_properties(placeholder=L_("Editor textarea height (0=auto)")).validated_by(Converted())
-        results_per_page = Integer.using(label=L_('History results per page')).with_properties(placeholder=L_("Number of results per page (0=no paging)")).validated_by(ValueAtLeast(0))
-        submit = String.using(default=L_('Save'), optional=True)
+        theme_name = Select.using(label=L_('Theme name')).with_properties(labels=dict(themes_available)).valued(*themes_keys)
+        css_url = URL.using(label=L_('User CSS URL'), optional=True).with_properties(placeholder=L_("Give the URL of your custom CSS (optional)"))
+        edit_rows = Natural.using(label=L_('Editor size')).with_properties(placeholder=L_("Editor textarea height (0=auto)"))
+        results_per_page = Natural.using(label=L_('History results per page')).with_properties(placeholder=L_("Number of results per page (0=no paging)"))
+        submit = Submit.using(default=L_('Save'))
 
     form_classes = dict(
         personal=UserSettingsPersonalForm,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/constants/forms.py	Fri Jul 06 18:50:35 2012 +0800
@@ -0,0 +1,31 @@
+# Copyright: 2012 MoinMoin:CheerXiao
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - Flatland form related constants
+"""
+
+# Widget types
+
+WIDGET_TEXT = u'text' # single-line text
+WIDGET_MULTILINE_TEXT = u'multiline_text'
+WIDGET_URL = u'url'
+WIDGET_EMAIL = u'email'
+WIDGET_PASSWORD = u'password'
+WIDGET_CHECKBOX = u'checkbox'
+WIDGET_INLINE_CHECKBOX = u'inline_checkbox'
+WIDGET_ANY_INTEGER = u'any_integer'
+WIDGET_SMALL_NATURAL = u'small_natural'
+
+WIDGET_FILE = u'file'
+WIDGET_SEARCH = u'search'
+WIDGET_SUBMIT = u'submit'
+WIDGET_HIDDEN = u'hidden'
+
+WIDGET_SELECT = u'select'
+
+WIDGET_MULTI_COLUMN_FORM = u'multi_column_form'
+WIDGET_TABBED_FORM = u'tabbed_form'
+
+# CSS Classes
+CLASS_BUTTON = u'button'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/forms.py	Fri Jul 06 18:50:35 2012 +0800
@@ -0,0 +1,72 @@
+# Copyright: 2012 MoinMoin:CheerXiao
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+    MoinMoin - Flatland widgets
+
+    General Flatland widgets containing hints for the templates.
+"""
+
+
+from functools import reduce
+from operator import add
+
+from flatland import Element, Form, String, Integer, Boolean, Enum, MultiValue, Dict
+from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLValidator, Converted, ValueAtLeast
+
+from MoinMoin.constants.forms import *
+from MoinMoin.i18n import _, L_, N_
+from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
+from MoinMoin.util.forms import FileStorage
+
+
+Text = String.with_properties(widget=WIDGET_TEXT)
+
+MultilineText = String.with_properties(widget=WIDGET_MULTILINE_TEXT)
+
+OptionalText = Text.using(optional=True)
+
+RequiredText = Text.validated_by(Present())
+
+OptionalMultilineText = MultilineText.using(optional=True)
+
+RequiredMultilineText = MultilineText.validated_by(Present())
+
+URL = String.with_properties(widget=WIDGET_TEXT).validated_by(URLValidator())
+
+OpenID = URL.using(label=L_('OpenID')).with_properties(placeholder=L_("OpenID address"))
+
+YourOpenID = OpenID.with_properties(placeholder=L_("Your OpenID address"))
+
+Email = String.using(label=L_('E-Mail')).with_properties(widget=WIDGET_EMAIL, placeholder=L_("E-Mail address")).validated_by(IsEmail())
+
+YourEmail = Email.with_properties(placeholder=L_("Your E-Mail address"))
+
+Password = Text.with_properties(widget=WIDGET_PASSWORD).using(label=L_('Password'))
+
+RequiredPassword = Password.validated_by(Present())
+
+Checkbox = Boolean.with_properties(widget=WIDGET_CHECKBOX).using(optional=True, default=1)
+
+InlineCheckbox = Checkbox.with_properties(widget=WIDGET_INLINE_CHECKBOX)
+
+Select = Enum.with_properties(widget=WIDGET_SELECT)
+
+# XXX Need a better one than plain text box
+Tags = MultiValue.of(String).with_properties(widget=WIDGET_TEXT).using(label=L_('Tags'), optional=True)
+
+Search = Text.using(default=u'', optional=True).with_properties(widget=WIDGET_SEARCH, placeholder=L_("Search Query"))
+
+_Integer = Integer.validated_by(Converted())
+
+AnyInteger = _Integer.with_properties(widget=WIDGET_ANY_INTEGER)
+
+Natural = AnyInteger.validated_by(ValueAtLeast(0))
+
+SmallNatural = _Integer.with_properties(widget=WIDGET_SMALL_NATURAL)
+
+File = FileStorage.with_properties(widget=WIDGET_FILE)
+
+Submit = String.using(default=L_('OK'), optional=True).with_properties(widget=WIDGET_SUBMIT, class_=CLASS_BUTTON)
+
+Hidden = String.using(optional=True).with_properties(widget=WIDGET_HIDDEN)
--- a/MoinMoin/items/__init__.py	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/items/__init__.py	Fri Jul 06 18:50:35 2012 +0800
@@ -29,7 +29,7 @@
 
 from whoosh.query import Term, And, Prefix
 
-from MoinMoin.util.forms import FileStorage
+from MoinMoin.forms import RequiredText, OptionalText, File, Submit
 
 from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
 from MoinMoin.signalling import item_modified
@@ -631,6 +631,11 @@
         return True
 
 
+class BaseChangeForm(TextChaizedForm):
+    comment = OptionalText.using(label=L_('Comment')).with_properties(placeholder=L_("Comment about your change"))
+    submit = Submit
+
+
 class Binary(Item):
     """ An arbitrary binary item, fallback class for every item mimetype. """
     modify_help = """\
@@ -659,11 +664,10 @@
         revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
         return [rev.meta[NAME] for rev in revs]
 
-    from MoinMoin.apps.frontend.views import CommentForm
-    class ModifyForm(CommentForm):
+    class ModifyForm(BaseChangeForm):
         """Base class for ModifyForm of Binary's subclasses."""
-        meta_text = String.using(optional=False).with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-        data_file = FileStorage.using(optional=True, label=L_('Upload file:'))
+        meta_text = RequiredText.with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
+        data_file = File.using(optional=True, label=L_('Upload file:'))
 
         def _load(self, item):
             self['meta_text'] = item.meta_dict_to_text(item.prepare_meta_for_modify(item.meta))
--- a/MoinMoin/search/__init__.py	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/search/__init__.py	Fri Jul 06 18:50:35 2012 +0800
@@ -6,6 +6,7 @@
 """
 
 from MoinMoin.i18n import L_
+from MoinMoin.forms import Search, InlineCheckbox, Submit
 
 from flatland import Form, String, Boolean
 from flatland.validation import Validator
@@ -24,8 +25,8 @@
         return True
 
 class SearchForm(Form):
-    q = String.using(optional=False, default=u'').with_properties(placeholder=L_("Search Query"))
-    history = Boolean.using(label=L_('search also in non-current revisions'), optional=True)
-    submit = String.using(default=L_('Search'), optional=True)
+    q = Search
+    history = InlineCheckbox.using(label=L_('search also in non-current revisions'))
+    submit = Submit.using(default=L_('Search'))
 
     validators = [ValidSearch()]
--- a/MoinMoin/templates/delete.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/delete.html	Fri Jul 06 18:50:35 2012 +0800
@@ -9,10 +9,10 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.delete_item', item_name=item.name)) }}
   {{ forms.render_errors(form) }}
   <dl>
-    {{ forms.render_field(gen, form['comment'], 'text') }}
+    {{ forms.render(form['comment']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ gen.input(form['submit'], type='submit') }}
+  {{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}
--- a/MoinMoin/templates/destroy.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/destroy.html	Fri Jul 06 18:50:35 2012 +0800
@@ -10,10 +10,10 @@
     {{ gen.form.open(form, method="post", action=url_for('frontend.destroy_item', item_name=item.name)) }}
       {{ forms.render_errors(form) }}
       <dl>
-        {{ forms.render_field(gen, form['comment'], 'text') }}
+        {{ forms.render(form['comment']) }}
         {{ forms.render_textcha(gen, form) }}
       </dl>
-      {{ gen.input(form['submit'], type='submit') }}
+      {{ forms.render(form['submit']) }}
     {{ gen.form.close() }}
     </div>
 {% else %}
@@ -22,10 +22,10 @@
     {{ gen.form.open(form, method="post", action=url_for('frontend.destroy_item', item_name=item.name, rev=rev_id)) }}
       {{ forms.render_errors(form) }}
       <dl>
-        {{ forms.render_field(gen, form['comment'], 'text') }}
+        {{ forms.render(form['comment']) }}
         {{ forms.render_textcha(gen, form) }}
       </dl>
-      {{ gen.input(form['submit'], type='submit') }}
+      {{ forms.render(form['submit']) }}
     {{ gen.form.close() }}
     </div>
 {% endif %}
--- a/MoinMoin/templates/forms.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/forms.html	Fri Jul 06 18:50:35 2012 +0800
@@ -1,7 +1,7 @@
 {#
 Helpers for using flatland with jinja2 to create html forms.
 
-@copyright: Thomas Waldmann, Jason Kirtland, Scott Wilson
+@copyright: Thomas Waldmann, Jason Kirtland, Scott Wilson, Cheer Xiao
 @license: see flatland license
 #}
 
@@ -15,50 +15,6 @@
   {% endif %}
 {% endmacro %}
 
-{% macro render_field(gen, field, field_type) %}
-  <dt>
-    {{ gen.label(field) }}
-  </dt>
-  <dd>
-    {# Use of auto_value=false below prevents flatland from appending _1
-       to the input id attribute ensuring it will match the for attribute
-       in the matching label above.
-    #}
-    {{ gen.input(field, type=field_type, auto_value=False) }}
-    {{ render_errors(field) }}
-  </dd>
-{% endmacro %}
-
-{% macro render_filter_field(gen, field, field_type, helper_text) %}
-  <li>
-    {# Use of auto_value=false below prevents flatland from appending _1
-       to the input id attribute. This is needed so the id will match the for attribute
-       in the matching label generated 2 lines below.
-    #}
-    {{ gen.input(field, type=field_type, auto_value=False, checked="checked") }}
-    {{ render_errors(field) }}
-    {{ gen.label(field) }}
-    <span class="helper-text">
-        {{ helper_text }}
-    </span>
-  </li>
-{% endmacro %}
-
-{% macro render_select(gen, field) %}
-  <dt>
-    {{ gen.label(field) }}
-  </dt>
-  <dd>
-    {{ gen.select.open(field) }}
-    {% set labels = field.properties.get('labels', {}) %}
-    {% for value in field.valid_values %}
-      {{ gen.option(field, value=value, contents=labels.get(value, value)) }}
-    {% endfor %}
-    {{ gen.select.close() }}
-    {{ render_errors(field) }}
-  </dd>
-{% endmacro %}
-
 {% macro render_hidden(name, value) %}
   <input type="hidden" name="{{ name }}" value="{{ value }}" />
 {% endmacro %}
@@ -82,14 +38,116 @@
     {% endif %}
 {% endmacro %}
 
-{% macro render_field_without_markup(gen, field, field_type) %}
-    {# Use of auto_value=false below prevents flatland from appending _1
-       to the input id attribute ensuring it will match the for attribute
-       in the matching label.
-    #}
-    {{ gen.input(field, type=field_type, auto_value=False) }}
+{% macro render(field) %}
+  {%- set macro = {
+      'text': annotated_input,
+      'url': annotated_input,
+      'email': annotated_input,
+      'password': annotated_input,
+      'file': annotated_input,
+      'checkbox': annotated_input,
+      'multiline_text': multiline_text,
+      'inline_checkbox': inline_checkbox,
+      'any_integer': any_integer,
+      'small_natural': small_natural,
+      'search': search,
+      'submit': raw_input,
+      'hidden': raw_input,
+      'select': select,
+  }[field.properties.widget] or stub -%}
+  {{ macro(field, *varargs, **kwargs) }}
+{% endmacro %}
+
+{% macro stub(field) %}
+  <fieldset>
+    STUB: no widget for {{ field.name }} ({{ field.properties }}) yet
+  </fieldset>
+{% endmacro %}
+
+{% macro raw_input(field, type=none) %}
+  {{ gen.input(field, type=type or field.properties.widget, class=field.properties.class_ or '', **kwargs) }}
+{% endmacro %}
+
+{% macro annotated_input(field, type=none) %}
+  <dt>
     {{ gen.label(field) }}
+  </dt>
+  <dd>
+    {{ raw_input(field, type) }}
     {{ render_errors(field) }}
+    {# TODO make the helper more explicit and look better #}
+    {% if field.properties.helper %}
+      <p>{{ field.properties.helper | e }}</p>
+    {% endif %}
+  </dd>
+{% endmacro %}
+
+{% macro multiline_text(field) %}
+  <dt>
+    {{ gen.label(field) }}
+  </dt>
+  <dd>
+    {{ gen.textarea(field, rows=field.properties.rows|string, cols=field.properties.cols|string) }}
+    {{ render_errors(field) }}
+  </dd>
+{% endmacro %}
+
+{% macro _valued_label(field, value, contents=none) %}
+  {# XXX Patch <label>'s `for` attribute with value when element referred to is
+     checkbox or radio. See: https://bitbucket.org/jek/flatland/issue/9 #}
+  {{ gen.label(field, for='f_{0}_{1}'.format(field.flattened_name(), value), contents=contents) }}
+{% endmacro %}
+
+{% macro _checkbox_label(field) %}
+  {{ _valued_label(field, '1') }}
+{% endmacro %}
+
+{% macro _radio_label(field, value) %}
+  {{ _valued_label(field, value, value) }}
+{% endmacro %}
+
+{% macro inline_checkbox(field) %}
+  {{ raw_input(field, 'checkbox') }}
+  {{ _checkbox_label(field) }}
+  {{ render_errors(field) }}
+  {% if field.properties.helper is defined %}
+    <span class="helper-text">
+      {{ field.properties.helper }}
+    </span>
+  {% endif %}
+{% endmacro %}
+
+{% macro any_integer(field) %}
+  {{ annotated_input(field, 'text') }}
+{% endmacro %}
+
+{% macro small_natural(field) %}
+    {{ gen.label(field) }}
+    <dd>
+        {% for i in range(field.properties.lower, field.properties.upper+1) %}
+            {{ gen.input(field, type='radio', value=i|string) }}
+            {{ _radio_label(field, value=i|string) }}
+        {% endfor %}
+    </dd>
+{% endmacro %}
+
+{% macro search(field) %}
+  {{ raw_input(field, id='moin-search-query') }}
+{% endmacro %}
+
+{% macro select(field) %}
+  <dt>
+    {{ gen.label(field) }}
+  </dt>
+  <dd>
+    {{ gen.select.open(field) }}
+    {% set labels = field.properties.get('labels', {}) %}
+    {% for value in field.valid_values %}
+      {{ gen.option(field, value=value, contents=labels.get(value, value)) }}
+    {% endfor %}
+    {{ gen.select.close() }}
+    {{ render_errors(field) }}
+  </dd>
 {% endmacro %}
 
 {% macro render_file_uploader(submit_url) %}
--- a/MoinMoin/templates/index.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/index.html	Fri Jul 06 18:50:35 2012 +0800
@@ -1,5 +1,5 @@
 {% extends theme("layout.html") %}
-{% import "forms.html" as forms %}
+{% import "forms.html" as forms with context %}
 
 {% if item_name %}
     {% set title = _("Index of subitems of '%(item_name)s'", item_name=item_name) %}
@@ -56,15 +56,19 @@
                         <a href="#" class="filter-toggle">&raquo; {{ _("Toggle") }}</a>
                         <a href="#" class="filter-more">&raquo; {{ _("More") }}</a>
                     </li>
-                    {{ forms.render_filter_field(gen, form['markup_text_items'], 'checkbox', contenttype_groups['markup text items']) }}
-                    {{ forms.render_filter_field(gen, form['other_text_items'], 'checkbox', contenttype_groups['other text items']) }}
-                    {{ forms.render_filter_field(gen, form['image_items'], 'checkbox', contenttype_groups['image items']) }}
-                    {{ forms.render_filter_field(gen, form['audio_items'], 'checkbox', contenttype_groups['audio items']) }}
-                    {{ forms.render_filter_field(gen, form['video_items'], 'checkbox', contenttype_groups['video items']) }}
-                    {{ forms.render_filter_field(gen, form['other_items'], 'checkbox', contenttype_groups['other items']) }}
-                    {{ forms.render_filter_field(gen, form['unknown_items'], 'checkbox', unknown_items_label) }}
+                    {% for e in [
+                        'markup_text_items',
+                        'other_text_items',
+                        'image_items',
+                        'audio_items',
+                        'video_items',
+                        'other_items',
+                        'unknown_items',
+                        ] %}
+                        <li>{{ forms.render(form[e]) }}</li>
+                    {% endfor %}
                 </ul>
-                {{ gen.input(form['submit'], type='submit') }}
+                {{ forms.render(form['submit']) }}
                 {{ gen.form.close() }}
             </div>
             </li>
--- a/MoinMoin/templates/login.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/login.html	Fri Jul 06 18:50:35 2012 +0800
@@ -8,16 +8,15 @@
 {% if 'username' in login_inputs or 'password' in login_inputs %}
 <h2>{{ _('Moin login') }}</h2>
 {{ gen.form.open(form, method="post", action=url_for('frontend.login')) }}
-{{ gen.input(form['submit'], type='hidden') }}
 <dl>
   {% if 'username' in login_inputs %}
-  {{ forms.render_field(gen, form['username'], 'text') }}
+  {{ forms.render(form['username']) }}
   {% endif %}
   {% if 'password' in login_inputs %}
-  {{ forms.render_field(gen, form['password'], 'password') }}
+  {{ forms.render(form['password']) }}
   {% endif %}
 </dl>
-<input type="submit" value="{{ _('Log in') }}" />
+{{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 {% endif %}
 
@@ -27,11 +26,9 @@
 
 {{ gen.form.open(form, method="post", action=url_for('frontend.login')) }}
 <dl>
-  {{ gen.input(form['submit'], type='hidden') }}
-
-  {{ forms.render_field(gen, form['openid'], 'text') }}
-  <input type="submit" value="{{ _('Log in') }}" />
+  {{ forms.render(form['openid']) }}
 </dl>
+{{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 {% endif %}
 
--- a/MoinMoin/templates/lookup.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/lookup.html	Fri Jul 06 18:50:35 2012 +0800
@@ -7,17 +7,21 @@
     {{ gen.form.open(lookup_form, id='moin-lookup-form', method='get', action=url_for('frontend.lookup')) }}
         {{ forms.render_errors(lookup_form) }}
         <dl>
-            {{ forms.render_field(gen, lookup_form['name'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['name_exact'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['itemid'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['revid'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['userid'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['language'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['itemlinks'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['itemtransclusions'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['refs'], 'text') }}
-            {{ forms.render_field(gen, lookup_form['tags'], 'text') }}
-            {{ forms.render_field_without_markup(gen, lookup_form['history'], 'checkbox') }}
+            {% for e in [
+                'name',
+                'name_exact',
+                'itemid',
+                'revid',
+                'userid',
+                'language',
+                'itemlinks',
+                'itemtransclusions',
+                'refs',
+                'tags',
+                'history',
+                ] %}
+                {{ forms.render(lookup_form[e]) }}
+            {% endfor %}
         </dl>
         {{ gen.input(lookup_form['submit'], type='submit') }}
     {{ gen.form.close() }}
--- a/MoinMoin/templates/lostpass.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/lostpass.html	Fri Jul 06 18:50:35 2012 +0800
@@ -8,10 +8,10 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.lostpass')) }}
   {{ forms.render_errors(form) }}
   <dl>
-    {{ forms.render_field(gen, form['username'], 'text') }}
-    {{ forms.render_field(gen, form['email'], 'text') }}
+    {{ forms.render(form['username']) }}
+    {{ forms.render(form['email']) }}
   </dl>
-  {{ gen.input(form['submit'], type='submit') }}
+  {{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}
--- a/MoinMoin/templates/modify_applet.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/modify_applet.html	Fri Jul 06 18:50:35 2012 +0800
@@ -11,7 +11,7 @@
     {% block extra_form %}{% endblock %}
     {% block data_editor %}{% endblock %}
     <dl>
-        {{ forms.render_field(gen, form['data_file'], 'file') }}
+        {{ forms.render(form['data_file']) }}
     </dl>
     <pre id="moin-editor-help">{{ help }}</pre>
     {{ gen.textarea(form['meta_text'], lang='en', dir='ltr', rows=rows_meta, cols=cols) }}
--- a/MoinMoin/templates/modify_binary.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/modify_binary.html	Fri Jul 06 18:50:35 2012 +0800
@@ -1,9 +1,9 @@
 {% import "forms.html" as forms %}
 {% extends "modify_applet.html" %}
 {% block extra_form %}
-{{ gen.input(form['submit'], class='button', type='submit') }}
+{{ forms.render(form['submit']) }}
 <dl>
     {{ forms.render_textcha(gen, form) }}
-    {{ forms.render_field(gen, form['comment'], 'text') }}
+    {{ forms.render(form['comment']) }}
 </dl>
 {% endblock %}
--- a/MoinMoin/templates/openid_register.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/openid_register.html	Fri Jul 06 18:50:35 2012 +0800
@@ -7,18 +7,17 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.register')) }}
   {{ forms.render_errors(form) }}
   <dl>
-    {{ gen.input(form['submit'], type='hidden') }}
 
     <dt>{{ gen.label(form['openid']) }}</dt>
     <dd>{{ gen.input(form['openid'], type='text', readonly='1') }}</dd>
 
-    {{ forms.render_field(gen, form['username'], 'text') }}
-    {{ forms.render_field(gen, form['password1'], 'password') }}
-    {{ forms.render_field(gen, form['password2'], 'password') }}
-    {{ forms.render_field(gen, form['email'], 'text') }}
+    {{ forms.render(form['username']) }}
+    {{ forms.render(form['password1']) }}
+    {{ forms.render(form['password2']) }}
+    {{ forms.render(form['email']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  <input type="submit" value="{{ _("Register") }}" />
+  {{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}
--- a/MoinMoin/templates/recoverpass.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/recoverpass.html	Fri Jul 06 18:50:35 2012 +0800
@@ -6,11 +6,11 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.recoverpass')) }}
   {{ forms.render_errors(form) }}
   <dl>
-    {{ forms.render_field(gen, form['username'], 'text') }}
-    {{ forms.render_field(gen, form['token'], 'text') }}
-    {{ forms.render_field(gen, form['password1'], 'password') }}
-    {{ forms.render_field(gen, form['password2'], 'password') }}
+    {{ forms.render(form['username']) }}
+    {{ forms.render(form['token']) }}
+    {{ forms.render(form['password1']) }}
+    {{ forms.render(form['password2']) }}
   </dl>
-  {{ gen.input(form['submit'], type='submit') }}
+  {{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 {% endblock %}
--- a/MoinMoin/templates/register.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/register.html	Fri Jul 06 18:50:35 2012 +0800
@@ -7,14 +7,14 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.register')) }}
   {{ forms.render_errors(form) }}
   <dl>
-    {{ forms.render_field(gen, form['username'], 'text') }}
-    {{ forms.render_field(gen, form['password1'], 'password') }}
-    {{ forms.render_field(gen, form['password2'], 'password') }}
-    {{ forms.render_field(gen, form['email'], 'email') }}
-    {{ forms.render_field(gen, form['openid'], 'url') }}
+    {{ forms.render(form['username']) }}
+    {{ forms.render(form['password1']) }}
+    {{ forms.render(form['password2']) }}
+    {{ forms.render(form['email']) }}
+    {{ forms.render(form['openid']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ gen.input(form['submit'], type='submit') }}
+  {{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}
--- a/MoinMoin/templates/rename.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/rename.html	Fri Jul 06 18:50:35 2012 +0800
@@ -9,11 +9,11 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.rename_item', item_name=item.name)) }}
   {{ forms.render_errors(form) }}
   <dl>
-    {{ forms.render_field(gen, form['target'], 'text') }}
-    {{ forms.render_field(gen, form['comment'], 'text') }}
+    {{ forms.render(form['target']) }}
+    {{ forms.render(form['comment']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ gen.input(form['submit'], type='submit') }}
+  {{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}
--- a/MoinMoin/templates/revert.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/revert.html	Fri Jul 06 18:50:35 2012 +0800
@@ -6,10 +6,10 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.revert_item', item_name=item.name, rev=rev_id)) }}
   {{ forms.render_errors(form) }}
   <dl>
-    {{ forms.render_field(gen, form['comment'], 'text') }}
+    {{ forms.render(form['comment']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ gen.input(form['submit'], type='submit') }}
+  {{ forms.render(form['submit']) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}
--- a/MoinMoin/templates/search.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/search.html	Fri Jul 06 18:50:35 2012 +0800
@@ -18,9 +18,9 @@
     {% endif %}
     {{ gen.form.open(medium_search_form, id='moin-long-searchform', method='get', action=url_for('frontend.search')) }}
         <div>
-            {{ gen.input(medium_search_form['q'], type='search', id='moin-search-query') }}
-            {{ gen.input(medium_search_form['submit'], type='submit') }}
-            {{ forms.render_field_without_markup(gen, medium_search_form['history'], 'checkbox') }}
+            {{ forms.render(medium_search_form['q']) }}
+            {{ forms.render(medium_search_form['submit']) }}
+            {{ forms.render(medium_search_form['history']) }}
             {{ forms.render_errors(medium_search_form) }}
         </div>
     {{ gen.form.close() }}
--- a/MoinMoin/templates/usersettings_forms.html	Fri Jun 29 11:56:28 2012 +0800
+++ b/MoinMoin/templates/usersettings_forms.html	Fri Jul 06 18:50:35 2012 +0800
@@ -4,11 +4,11 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.usersettings')) }}
 {{ forms.render_errors(form) }}
 <dl>
-    {{ forms.render_field(gen, form['name'], 'text') }}
-    {{ forms.render_field(gen, form['aliasname'], 'text') }}
-    {{ forms.render_field(gen, form['openid'], 'url') }}
-    {{ forms.render_select(gen, form['timezone']) }}
-    {{ forms.render_select(gen, form['locale']) }}
+    {{ forms.render(form['name']) }}
+    {{ forms.render(form['aliasname']) }}
+    {{ forms.render(form['openid']) }}
+    {{ forms.render(form['timezone']) }}
+    {{ forms.render(form['locale']) }}
 </dl>
 {{ forms.render_hidden('part', 'personal') }}
 {{ forms.render_button(_("Save")) }}
@@ -20,9 +20,9 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.usersettings')) }}
 {{ forms.render_errors(form) }}
 <dl>
-    {{ forms.render_field(gen, form['password_current'], 'password') }}
-    {{ forms.render_field(gen, form['password1'], 'password') }}
-    {{ forms.render_field(gen, form['password2'], 'password') }}
+    {{ forms.render(form['password_current']) }}
+    {{ forms.render(form['password1']) }}
+    {{ forms.render(form['password2']) }}
 </dl>
 {{ forms.render_hidden('part', 'password') }}
 {{ forms.render_button(_("Change password")) }}
@@ -36,7 +36,7 @@
 {% endif %}
 {{ forms.render_errors(form) }}
 <dl>
-    {{ forms.render_field(gen, form['email'], 'email') }}
+    {{ forms.render(form['email']) }}
 </dl>
 {{ forms.render_hidden('part', 'notification') }}
 {{ forms.render_button(_("Save")) }}
@@ -47,10 +47,10 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.usersettings')) }}
 {{ forms.render_errors(form) }}
 <dl>
-    {{ forms.render_select(gen, form['theme_name']) }}
-    {{ forms.render_field(gen, form['css_url'], 'url') }}
-    {{ forms.render_field(gen, form['edit_rows'], 'text') }}
-    {{ forms.render_field(gen, form['results_per_page'], 'text') }}
+    {{ forms.render(form['theme_name']) }}
+    {{ forms.render(form['css_url']) }}
+    {{ forms.render(form['edit_rows']) }}
+    {{ forms.render(form['results_per_page']) }}
 </dl>
 {{ forms.render_hidden('part', 'ui') }}
 {{ forms.render_button(_("Save")) }}
@@ -72,10 +72,10 @@
 {{ gen.form.open(form, method="post", action=url_for('frontend.usersettings')) }}
 {{ forms.render_errors(form) }}
 <dl>
-    {{ forms.render_field(gen, form['mailto_author'], 'checkbox') }}
-    {{ forms.render_field(gen, form['edit_on_doubleclick'], 'checkbox') }}
-    {{ forms.render_field(gen, form['show_comments'], 'checkbox') }}
-    {{ forms.render_field(gen, form['disabled'], 'checkbox') }}
+    {{ forms.render(form['mailto_author']) }}
+    {{ forms.render(form['edit_on_doubleclick']) }}
+    {{ forms.render(form['show_comments']) }}
+    {{ forms.render(form['disabled']) }}
 </dl>
 {{ forms.render_hidden('part', 'options') }}
 {{ forms.render_button(_("Save")) }}