Here's another proposed patch. The basis is:
class Widget:
[...]
+ def __init__(self, name, value=None, attrs=None, **kwattrs):
assert self.__class__ is not Widget, "abstract class"
self.name = name
self.error = None
+ self.attrs = merge_attrs(attrs, kwattrs)
request = get_request()
if request.form:
self._parse(request)
So, you can either pass keywords or pass a dictionary. I think we
have concluded that a dictionary is required. I've debated for
quite a while whether to support keywords. Although using a
dictionary is only a little more typing I'ved concluded that they
should be supported as well. For widgets like 'text' and 'string',
you almost always want to provide some attributes. Also, the
merging behavor of having both is handy to have. For example, the
multiple select widget constructor can be:
def __init__(self, name, value=None, options=None, **kwargs):
SingleSelectWidget.__init__(self, name, value, options,
multiple='multiple', **kwargs)
and some funky code can be removed from SelectWidget.render().
practicality-beats-purity-ly y'rs Neil
Index: widget.py
===================================================================
--- widget.py (revision 23231)
+++ widget.py (working copy)
@@ -24,6 +24,22 @@
return "%s$%s" % (prefix, name)
+def merge_attrs(base, overrides):
+ """({string: any}, {string: any}) -> {string: any}
+ """
+ items = []
+ if base:
+ items.extend(base.items())
+ if overrides:
+ items.extend(overrides.items())
+ attrs = {}
+ for name, val in items:
+ if name.endswith('_'):
+ name = name[:-1]
+ attrs[name] = val
+ return attrs
+
+
class Widget:
"""Abstract base class for web widgets.
@@ -31,15 +47,17 @@
name : string
value : any
error : string
+ attrs : {string: any}
Feel free to access these directly; to set them, use the 'set_*()'
modifier methods.
"""
- def __init__(self, name, value=None):
+ def __init__(self, name, value=None, attrs=None, **kwattrs):
assert self.__class__ is not Widget, "abstract class"
self.name = name
self.error = None
+ self.attrs = merge_attrs(attrs, kwattrs)
request = get_request()
if request.form:
self._parse(request)
@@ -106,27 +124,17 @@
Instance attributes:
value : string
- size : int
- maxlength : int
"""
# This lets PasswordWidget be a trivial subclass
HTML_TYPE = "text"
- def __init__(self, name, value=None,
- size=None, maxlength=None):
- Widget.__init__(self, name, value)
- self.size = size
- self.maxlength = maxlength
-
- def render(self, **attributes):
+ def render(self):
return htmltag("input", xml_end=True,
type=self.HTML_TYPE,
name=self.name,
- size=self.size,
- maxlength=self.maxlength,
value=self.value,
- **attributes)
+ **self.attrs)
class FileWidget(StringWidget):
@@ -161,28 +169,15 @@
Instance attributes:
value : string
- cols : int
- rows : int
- wrap : string
- (see an HTML book for details on text widget wrap options)
"""
- def __init__(self, name, value=None, cols=None, rows=None, wrap=None):
- Widget.__init__(self, name, value)
- self.cols = cols
- self.rows = rows
- self.wrap = wrap
-
def _parse(self, request):
Widget._parse(self, request)
if self.value and self.value.find("\r\n") >= 0:
self.value = self.value.replace("\r\n", "\n")
def render(self):
- return (htmltag("textarea", name=self.name,
- cols=self.cols,
- rows=self.rows,
- wrap=self.wrap) +
+ return (htmltag("textarea", name=self.name, **self.attrs) +
htmlescape(self.value or "") +
htmltext(""))
@@ -204,7 +199,8 @@
type="checkbox",
name=self.name,
value="yes",
- checked=self.value and ValuelessAttr or None)
+ checked=self.value and ValuelessAttr or None,
+ **self.attrs)
@@ -219,23 +215,17 @@
options : [ (value:any, description:any, key:string) ]
value : any
The value is None or an element of dict(options.values()).
- size : int
- The number of options that should be presented without scrolling.
"""
- def __init__(self, name, value=None,
- options=None,
- size=None,
- sort=True,
- verify_selection=True):
+ def __init__(self, name, value=None, options=None, sort=True,
+ verify_selection=True, attrs=None, **kwattrs):
assert self.__class__ is not SelectWidget, "abstract class"
self.options = []
if options is not None:
assert options, 'cannot pass empty options list'
self.set_options(options, sort)
self.verify_selection = verify_selection
- self.size = size
- Widget.__init__(self, name, value)
+ Widget.__init__(self, name, value, attrs=attrs, **kwattrs)
def get_allowed_values(self):
return [item[0] for item in self.options]
@@ -366,17 +356,7 @@
return value == self.value
def render(self):
- if self.SELECT_TYPE == "multiple_select":
- multiple = ValuelessAttr
- else:
- multiple = None
- if self.SELECT_TYPE == "option_select":
- onchange = "submit()"
- else:
- onchange = None
- tags = [htmltag("select", name=self.name,
- multiple=multiple, onchange=onchange,
- size=self.size)]
+ tags = [htmltag("select", name=self.name, **self.attrs)]
for object, description, key in self.options:
if self.is_selected(object):
selected = ValuelessAttr
@@ -422,8 +402,9 @@
def __init__(self, name, value=None,
options=None,
- delim=None):
- SingleSelectWidget.__init__(self, name, value, options)
+ delim=None, attrs=None, **kwattrs):
+ SingleSelectWidget.__init__(self, name, value, options,
+ attrs=attrs, **kwattrs)
if delim is None:
self.delim = "\n"
else:
@@ -440,7 +421,8 @@
type="radio",
name=self.name,
value=key,
- checked=checked)
+ checked=checked,
+ **self.attrs)
tags.append(r + htmlescape(description) + htmltext(''))
return htmlescape(self.delim).join(tags)
@@ -456,6 +438,10 @@
SELECT_TYPE = "multiple_select"
+ def __init__(self, name, value=None, options=None, **kwargs):
+ SingleSelectWidget.__init__(self, name, value, options,
+ multiple='multiple', **kwargs)
+
def set_value(self, value):
allowed_values = self.get_allowed_values()
if value in allowed_values:
@@ -495,9 +481,10 @@
HTML_TYPE = "button"
- def __init__(self, name, value=None):
+ def __init__(self, name, value=None, attrs=None, **kwattrs):
self.name = name
self.error = None
+ self.attrs = merge_attrs(attrs, kwattrs)
# slightly different behavior here, we always render the
# tag using the 'value' passed in as a parameter. The 'value'
# attribute is a boolean that is true if the button's name appears
@@ -546,7 +533,8 @@
return htmltag("input", xml_end=True,
type="hidden",
name=self.name,
- value=value)
+ value=value,
+ **self.attrs)
# -- Derived widget types ----------------------------------------------
@@ -564,15 +552,13 @@
TYPE_ERROR = None # human-readable error message
TYPE_CONVERTER = None # eg. int(), float()
- def __init__(self, name,
- value=None,
- size=None, maxlength=None):
+ def __init__(self, name, value=None, attrs=None, **kwattrs)
assert self.__class__ is not NumberWidget, "abstract class"
assert value is None or type(value) is self.TYPE_OBJECT, (
"form value '%s' not a %s: got %r" % (name,
self.TYPE_OBJECT,
value))
- StringWidget.__init__(self, name, value, size, maxlength)
+ StringWidget.__init__(self, name, value, attrs=attrs, **kwattrs)
def _parse(self, request):
StringWidget._parse(self, request)
@@ -616,6 +602,10 @@
SELECT_TYPE = "option_select"
+ def __init__(self, name, value=None, options=None, **kwargs):
+ SingleSelectWidget.__init__(self, name, value, options,
+ onchange='submit()', **kwargs)
+
def render(self):
return (SingleSelectWidget.render(self) +
htmltext('