# HG changeset patch
# User Paul Boddie <paul@boddie.org.uk>
# Date 1299526770 -3600
# Node ID 45714fde847dbbd79fb2d0189da374cc320c9d9b
# Parent  2585f7e2ec044c295d8f825e6717849601fb45a3
Changed the login form to group fields by authentication method, showing each
method's hint below the fields. Each group of fields is within its own HTML
form, so that only relevant fields are submitted.
Made the form identifier unique.
Added more HTML attributes to the HTML widget classes.

diff -r 2585f7e2ec04 -r 45714fde847d MoinMoin/userform/login.py
--- a/MoinMoin/userform/login.py	Sun Mar 06 16:18:22 2011 +0100
+++ b/MoinMoin/userform/login.py	Mon Mar 07 20:39:30 2011 +0100
@@ -4,6 +4,7 @@
 
     @copyright: 2001-2004 Juergen Hermann <jh@web.de>,
                 2003-2007 MoinMoin:ThomasWaldmann
+                2011 Paul Boddie <paul@boddie.org.uk>
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -28,89 +29,116 @@
             html.TD().extend(cell),
         ]))
 
-
     def asHTML(self):
         """ Create the complete HTML form code. """
         _ = self._
         request = self.request
+        cfg = self.cfg
         action = "%s%s" % (request.script_root, request.path)
-        hints = []
-        for authm in request.cfg.auth:
-            hint = authm.login_hint(request)
-            if hint:
-                hints.append(hint)
-        self._form = html.FORM(action=action, name="loginform", id="loginform")
-        self._table = html.TABLE(border="0")
 
-        # Use the user interface language and direction
-        lang_attr = request.theme.ui_lang_attr()
-        self._form.append(html.Raw('<div class="userpref"%s>' % lang_attr))
+        # Modules should really provide label and type details.
 
-        self._form.append(html.INPUT(type="hidden", name="action", value="login"))
-        self._form.append(self._table)
-        for hint in hints:
-            self._form.append(html.P().append(html.Raw(hint)))
-        self._form.append(html.Raw("</div>"))
+        field_name_mapping = {"username" : "name"}
+        field_type_mapping = {"password" : "password"}
+        field_label_mapping = {"username" : _('Name'), "password" : _('Password'), "openid_identifier" : _('OpenID')}
+        field_id_mapping = {"openid_identifier" : "openididentifier"}
 
-        cfg = request.cfg
-        if 'username' in cfg.auth_login_inputs:
-            self.make_row(_('Name'), [
+        # Restrict type of input available for certain fields - for example,
+        # OpenID input - based on wiki configuration.
+
+        field_value_mapping = {"openid_identifier" : cfg.openidrp_allowed_op}
+
+        # Generate a collection of login forms, one per module.
+
+        self._forms = html.DIV()
+        self._forms.append(html.P().append(html.Text(_('This site supports the following login methods:'))))
+
+        for formnumber, authm in enumerate(cfg.auth):
+
+            # If there are no inputs, don't bother generating a form.
+
+            if not authm.login_inputs:
+                continue
+
+            self._form = html.FORM(action=action, name="loginform%d" % formnumber)
+            self._forms.append(self._form)
+
+            # Use the user interface language and direction.
+
+            lang_attr = request.theme.ui_lang_attr()
+            self._form.append(html.Raw('<div class="userpref"%s>' % lang_attr))
+
+            self._form.append(html.INPUT(type="hidden", name="action", value="login"))
+
+            # Put fields in a table.
+
+            self._table = html.TABLE(border="0", style="table-layout: fixed", width="100%")
+            self._form.append(self._table)
+
+            # Generate the module's fields.
+
+            for field in authm.login_inputs:
+                name = field_name_mapping.get(field, field)
+                type = field_type_mapping.get(field, "text")
+                label = field_label_mapping.get(field, field)
+                kw = {}
+                if field_id_mapping.has_key(field):
+                    kw["id"] = field_id_mapping[field]
+
+                # Restrict type of input based on suggested values.
+                values = field_value_mapping.get(field, [])
+
+                if len(values) == 1:
+                    # Single values are automatically chosen and hidden.
+                    self.make_row(label, [
+                        html.INPUT(
+                            type="hidden", name=name,
+                            value=values[0]
+                        ),
+                        html.Text(values[0]),
+                    ], width="20%")
+                elif len(values) > 1:
+                    # Many values are offered as a selection menu.
+                    op_select = html.SELECT(name=name, **kw)
+                    for op_value in values:
+                        op_select.append(html.OPTION(value=op_value).append(
+                            html.Text(op_value)))
+
+                    self.make_row(label, [op_select, ], width="20%")
+                else:
+                    # No suggested values causes a text entry field to appear.
+                    self.make_row(label, [
+                        html.INPUT(
+                            type=type, size="32", name=name, **kw
+                        ),
+                    ], width="20%")
+
+            # Need both hidden field and submit values for auto-submit to work
+            self.make_row('', [
+                html.INPUT(type="hidden", name="login", value=_('Login')),
                 html.INPUT(
-                    type="text", size="32", name="name",
+                    type="submit", name='login', value=_('Login')
                 ),
             ])
 
-        if 'password' in cfg.auth_login_inputs:
-            self.make_row(_('Password'), [
-                html.INPUT(
-                    type="password", size="32", name="password",
-                ),
-            ])
-
-        # Restrict type of input available for OpenID input
-        # based on wiki configuration.
-        if 'openid_identifier' in cfg.auth_login_inputs:
-            if len(cfg.openidrp_allowed_op) == 1:
-                self.make_row(_('OpenID'), [
-                     html.INPUT(
-                         type="hidden", name="openid_identifier",
-                         value=cfg.openidrp_allowed_op[0]
-                     ),
-                ])
-            elif len(cfg.openidrp_allowed_op) > 1:
-                op_select = html.SELECT(name="openid_identifier",
-                    id="openididentifier")
-                for op_uri in cfg.openidrp_allowed_op:
-                    op_select.append(html.OPTION(value=op_uri).append(
-                        html.Raw(op_uri)))
-
-                self.make_row(_('OpenID'), [op_select, ])
-            else:
-                self.make_row(_('OpenID'), [
-                    html.INPUT(
-                        type="text", size="32", name="openid_identifier",
-                        id="openididentifier"
-                    ),
-                ])
-
-        # Need both hidden field and submit values for auto-submit to work
-        self.make_row('', [
-            html.INPUT(type="hidden", name="login", value=_('Login')),
-            html.INPUT(
-                type="submit", name='login', value=_('Login')
-            ),
-        ])
-
-        # Automatically submit the form if only a single OpenID Provider is allowed
-        if 'openid_identifier' in cfg.auth_login_inputs and len(cfg.openidrp_allowed_op) == 1:
-            self._form.append("""<script type="text/javascript">
+            # Automatically submit the form if only a single OpenID Provider is
+            # provided
+            if 'openid_identifier' in authm.login_inputs and len(cfg.openidrp_allowed_op) == 1:
+                self._form.append("""<script type="text/javascript">
 <!--//
-document.getElementById("loginform").submit();
+document.getElementById("loginform%d").submit();
 //-->
 </script>
-""")
+""" % formnumber)
 
-        return unicode(self._form)
+            # Finish the form with a hint.
+
+            hint = authm.login_hint(request)
+            if hint:
+                self._form.append(html.P().append(html.Raw(hint)))
+            self._form.append(html.Raw("</div>"))
+
+        return unicode(self._forms)
 
 def getLogin(request):
     """ Return HTML code for the login. """
diff -r 2585f7e2ec04 -r 45714fde847d MoinMoin/widget/html.py
--- a/MoinMoin/widget/html.py	Sun Mar 06 16:18:22 2011 +0100
+++ b/MoinMoin/widget/html.py	Mon Mar 07 20:39:30 2011 +0100
@@ -148,6 +148,7 @@
         'rel': None,
         'rev': None,
         'shape': None,
+        'style': None,
         'tabindex': None,
         'type': None,
     }
@@ -156,18 +157,21 @@
     "abbreviated form (e.g., WWW, HTTP, etc.)"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class ACRONYM(CompositeElement):
     "acronyms"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class ADDRESS(CompositeElement):
     "information on author"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class AREA(EmptyElement):
@@ -177,12 +181,14 @@
         'class': None,
         'href': None,
         'shape': None,
+        'style': None,
     }
 
 class B(CompositeElement):
     "bold text style"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class BASE(EmptyElement):
@@ -194,18 +200,21 @@
     "I18N BiDi over-ride"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class BIG(CompositeElement):
     "large text style"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class BLOCKQUOTE(CompositeElement):
     "long quotation"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class BODY(CompositeElement):
@@ -218,6 +227,7 @@
         'link': None,
         'onload': None,
         'onunload': None,
+        'style': None,
         'text': None,
         'vlink': None,
     }
@@ -226,48 +236,56 @@
     "forced line break"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class BUTTON(CompositeElement):
     "push button"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class CAPTION(CompositeElement):
     "table caption"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class CITE(CompositeElement):
     "citation"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class CODE(CompositeElement):
     "computer code fragment"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class DD(CompositeElement):
     "definition description"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class DEL(CompositeElement):
     "deleted text"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class DFN(CompositeElement):
     "instance definition"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class DIV(CompositeElement):
@@ -275,24 +293,28 @@
     _ATTRS = {
         'id': None,
         'class': None,
+        'style': None,
     }
 
 class DL(CompositeElement):
     "definition list"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class DT(CompositeElement):
     "definition term"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class EM(CompositeElement):
     "emphasis"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class FORM(CompositeElement):
@@ -307,6 +329,7 @@
         'name': None,
         'onreset': None,
         'onsubmit': None,
+        'style': None,
         'target': None,
         'id': None,
     }
@@ -318,36 +341,42 @@
     "heading"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class H2(CompositeElement):
     "heading"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class H3(CompositeElement):
     "heading"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class H4(CompositeElement):
     "heading"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class H5(CompositeElement):
     "heading"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class H6(CompositeElement):
     "heading"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class HEAD(CompositeElement):
@@ -359,6 +388,7 @@
     "horizontal rule"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class HTML(CompositeElement):
@@ -371,12 +401,14 @@
     "italic text style"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class IFRAME(CompositeElement):
     "inline subwindow"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class IMG(EmptyElement):
@@ -386,6 +418,7 @@
         'alt': None,
         'border': None,
         'class': None,
+        'style': None,
         'vspace': None,
     }
 
@@ -410,6 +443,7 @@
         'readonly': None,
         'size': None,
         'src': None,
+        'style': None,
         'tabindex': None,
         'type': None,
         'usemap': None,
@@ -420,12 +454,14 @@
     "inserted text"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class KBD(CompositeElement):
     "text to be entered by the user"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class LABEL(CompositeElement):
@@ -433,6 +469,7 @@
     _ATTRS = {
         'class': None,
         'for_': None,
+        'style': None,
     }
 
     def _openingtag(self):
@@ -456,6 +493,7 @@
     "list item"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class LINK(EmptyElement):
@@ -468,6 +506,7 @@
         'media': None,
         'rel': None,
         'rev': None,
+        'style': None,
         'target': None,
         'type': None,
     }
@@ -476,6 +515,7 @@
     "client-side image map"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class META(EmptyElement):
@@ -487,18 +527,21 @@
     "alternate content container for non script-based rendering"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class OL(CompositeElement):
     "ordered list"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class OPTGROUP(CompositeElement):
     "option group"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class OPTION(CompositeElement):
@@ -508,6 +551,7 @@
         'disabled': None,
         'label': None,
         'selected': None,
+        'style': None,
         'value': None,
     }
 
@@ -515,24 +559,28 @@
     "paragraph"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class PRE(CompositeElement):
     "preformatted text"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class Q(CompositeElement):
     "short inline quotation"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class SAMP(CompositeElement):
     "sample program output, scripts, etc."
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class SCRIPT(CompositeElement):
@@ -551,6 +599,7 @@
         'onchange': None,
         'onfocus': None,
         'size': None,
+        'style': None,
         'tabindex': None,
         'id': None,
     }
@@ -559,18 +608,21 @@
     "small text style"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class SPAN(CompositeElement):
     "generic language/style container"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class STRONG(CompositeElement):
     "strong emphasis"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class STYLE(CompositeElement):
@@ -582,12 +634,14 @@
     "subscript"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class SUP(CompositeElement):
     "superscript"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class TABLE(CompositeElement):
@@ -602,6 +656,7 @@
         'frame': None,
         'rules': None,
         'summary': None,
+        'style': None,
         'width': None,
     }
 
@@ -610,6 +665,7 @@
     _ATTRS = {
         'align': None,
         'class': None,
+        'style': None,
     }
 
 class TD(CompositeElement):
@@ -618,7 +674,9 @@
         'abbr': None,
         'align': None,
         'class': None,
+        'style': None,
         'valign': None,
+        'width': None,
         'colspan': None,
         'rowspan': None,
     }
@@ -630,6 +688,7 @@
         'cols': None,
         'name': None,
         'rows': None,
+        'style': None,
     }
 
 class TFOOT(CompositeElement):
@@ -637,6 +696,7 @@
     _ATTRS = {
         'align': None,
         'class': None,
+        'style': None,
     }
 
 class TH(CompositeElement):
@@ -645,6 +705,8 @@
         'abbr': None,
         'align': None,
         'class': None,
+        'style': None,
+        'width': None,
     }
 
 class THEAD(CompositeElement):
@@ -652,6 +714,7 @@
     _ATTRS = {
         'align': None,
         'class': None,
+        'style': None,
     }
 
 class TITLE(CompositeElement):
@@ -664,24 +727,28 @@
     _ATTRS = {
         'align': None,
         'class': None,
+        'style': None,
     }
 
 class TT(CompositeElement):
     "teletype or monospaced text style"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class UL(CompositeElement):
     "unordered list"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 class VAR(CompositeElement):
     "instance of a variable or program argument"
     _ATTRS = {
         'class': None,
+        'style': None,
     }
 
 
