SVN: r23720 - trunk/quixote/form2
Neil Schemenauer <nascheme-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Wed, 17 Mar 2004 11:45:59 -0500
| Newsgroups | gmane.comp.web.quixote.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: nascheme
Date: 2004-03-17 11:45:59 -0500 (Wed, 17 Mar 2004)
New Revision: 23720
Modified:
trunk/quixote/form2/__init__.py
trunk/quixote/form2/form.py
trunk/quixote/form2/widget.py
Log:
* Remove FormComponent and related machinery. It's simpler to add
'title', 'hint', and 'required' as optional Widget attributes.
* Allow widgets to control how they are rendered within forms (by
overriding the form_render() method).
* Reorganize rendering code in the form module so that it is easier
to subclass form and provide a different look.
* Change the parsing behavior of widgets. They no longer parse
themselves when __init__ is called. That causes lots of other
problems. Instead, each widget has a parse() method that calls
_parse(), if it hasn't been called already, and returns the widgets
value. Change Form.has_errors() to call parse() on all the widgets
in the form.
* Allow Widget._parse() to raise WidgetValueError in order to signal
a parsing error. Using an exception is often more convenient than
setting Widget.error within the _parse() method.
* Add a CompositeWidget class. It has proven to be useful when
creating widgets that are composed of lower-level widgets.
* Remove Widget.get_value() since parse() seems to be sufficient.
Modified: trunk/quixote/form2/__init__.py
===================================================================
--- trunk/quixote/form2/__init__.py 2004-03-17 15:19:23 UTC (rev 23719)
+++ trunk/quixote/form2/__init__.py 2004-03-17 16:45:59 UTC (rev 23720)
@@ -6,14 +6,13 @@
Application developers will typically create a Form instance each
form in their application; each form object will contain a number
of widget objects. Custom widgets can be created by inheriting
-and/or composing the standard widget classes. More complicated components
-of forms can be built by inheriting from the FormComponent class.
+and/or composing the standard widget classes.
"""
-from quixote.form2.form import Form, FormComponent, FormTokenWidget, \
- WidgetRow, WidgetList
+from quixote.form2.form import Form, FormTokenWidget
from quixote.form2.widget import Widget, StringWidget, FileWidget, \
PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
SingleSelectWidget, SelectWidget, OptionSelectWidget, \
MultipleSelectWidget, SubmitWidget, HiddenWidget, \
- FloatWidget, IntWidget, subname
+ FloatWidget, IntWidget, subname, WidgetValueError, CompositeWidget, \
+ WidgetList
Modified: trunk/quixote/form2/form.py
===================================================================
--- trunk/quixote/form2/form.py 2004-03-17 15:19:23 UTC (rev 23719)
+++ trunk/quixote/form2/form.py 2004-03-17 16:45:59 UTC (rev 23720)
@@ -5,11 +5,12 @@
way of building HTML forms that are composed of Widget objects.
"""
-from types import StringType, ListType
from quixote import get_request, get_session, get_publisher
from quixote.html import url_quote, htmltag, htmltext, nl2br, TemplateIO
-from quixote.form2.widget import Widget, HiddenWidget, StringWidget, \
- ButtonWidget, ResetWidget, SubmitWidget, subname
+from quixote.form2.widget import HiddenWidget, StringWidget, TextWidget, \
+ CheckboxWidget, SingleSelectWidget, RadiobuttonsWidget, \
+ MultipleSelectWidget, ResetWidget, SubmitWidget, FloatWidget, \
+ IntWidget
try:
@@ -21,226 +22,30 @@
return not not v
-class FormComponent:
- """Part of a form. Generally contains one or more than one widgets.
- The standard Form class renders components inside a 'table' tag.
- Some sites may wish to provide their own Form.render() and WidgetRow
- in order to get a different look.
-
- The purpose of this class is to document the FormComponent interface.
-
- Instance attributes: none
- """
-
- def __init__(self, name, *args, **kwargs):
- raise NotImplementedError, 'subclass must implement'
-
- def get_value(self):
- raise NotImplementedError, 'subclass must implement'
-
- def set_error(self, error):
- raise NotImplementedError, 'subclass must implement'
-
- def has_errors(self):
- raise NotImplementedError, 'subclass must implement'
-
- def render(self):
- raise NotImplementedError, 'subclass must implement'
-
-
-def render_error(error):
- if error:
- return htmltext('<font color="red">%s</font><br />') % nl2br(error)
- else:
- return ''
-
-
-def render_hint(hint):
- if hint:
- return htmltext('<em>%s</em>') % hint
- else:
- return ''
-
-
-class WidgetRow(FormComponent):
- """
- Standard wrapper for widgets added to a Form: renders each widget in
- a couple of table rows (one for the widget title, and another for
- the widget itself, along with error and/or hint text).
-
- Instance attribues:
- widget : Widget
- title : string
- hint : string
- required : bool
- """
-
- def __init__(self, name, widget_class, value, title=None, hint=None,
- required=False, **kwargs):
- self.widget = widget_class(name, value, **kwargs)
- self.title = title
- self.hint = hint
- self.required = required
- if required and get_request().form:
- if self.get_value() is None and not self.has_errors():
- self.set_error('value is required')
-
- def get_value(self):
- return self.widget.get_value()
-
- def set_error(self, error):
- self.widget.set_error(error)
-
- def has_errors(self):
- return bool(self.widget.get_error())
-
- def _render_error(self):
- return render_error(self.widget.get_error())
-
- def _render_hint(self):
- return render_hint(self.hint)
-
- def render(self):
- title = self.title or ''
- if title and self.required:
- title = title + htmltext(' *')
- r = TemplateIO(html=True)
- r += htmltext('<tr><th colspan="3" align="left">')
- r += title
- r += htmltext('</th></tr>'
- '<tr><td> </td><td>')
- r += self.widget.render()
- r += htmltext('</td><td>')
- r += self._render_error()
- r += self._render_hint()
- r += htmltext('</td></tr>')
- return r.getvalue()
-
-
-class WidgetList(FormComponent):
- """A variable length list of widgets.
-
- Instance attributes:
- value : [any]
- error : string
- """
-
- def __init__(self,
- name,
- element_type=StringWidget,
- value=None,
- title=None,
- hint=None,
- element_name="row",
- **args):
- assert value is None or type(value) is ListType, (
- "value '%s' not a list: got %r" % (name, value))
- assert type(element_name) in (StringType, htmltext), (
- "value '%s' element_name not a string: "
- "got %r" % (name, element_name))
-
- self.element_type = element_type
- self.title = title
- self.hint = hint
- self.error = None
-
- self.added_elements = HiddenWidget(subname(name, "added_elements"))
- self.add_button = SubmitWidget(subname(name, "add_element"),
- value="Add %s" % element_name)
- self.elements = []
- def add_element(value=None):
- self.elements.append(
- element_type(subname(name, "element%d" % len(self.elements)),
- value=value, **args))
- if value is not None:
- for element_value in value:
- add_element(element_value)
- num_added = int(self.added_elements.get_value() or 1)
- for i in range(num_added):
- add_element()
- if self.add_button.get_value():
- add_element()
- num_added += 1
- self.added_elements.set_value(num_added)
- request = get_request()
- if request.form:
- self.value = []
- for element in self.elements:
- value = element.get_value()
- if value is not None:
- self.value.append(value)
- self.value = value or None
- else:
- self.value = value
-
- def get_value(self):
- return self.value
-
- def set_error(self, error):
- self.error = error
-
- def has_errors(self):
- if self.error:
- return True
- for element in self.elements:
- if element.get_error():
- return True
- return False
-
- def _render_element(self, element, hint=None, error=None):
- r = TemplateIO(html=True)
- r += htmltext('<tr><td> </td><td>')
- r += element.render()
- r += htmltext('</td><td>')
- r += render_error(element.get_error() or error)
- r += render_hint(hint)
- r += htmltext('</td></tr>\n')
- return r.getvalue()
-
- def render(self):
- r = TemplateIO(html=True)
- r += htmltext('\n<tr><th colspan="3" align="left">')
- r += self.title or htmltext(' ')
- r += htmltext('</th></tr>')
- r += self._render_element(self.elements[0], self.hint, self.error)
- for element in self.elements[1:]:
- r += self._render_element(element)
- r += htmltext('<tr><td> </td><td>')
- r += self.add_button.render()
- r += self.added_elements.render()
- r += htmltext('</td></tr>')
- return r.getvalue()
-
-
class FormTokenWidget(HiddenWidget):
def render(self):
self.value = get_session().create_form_token()
return HiddenWidget.render(self)
-JAVASCRIPT_MARKUP = htmltext('''\
-<script type="text/javascript">
-<!--
-%s
-// -->
-</script>
-''')
-
-
class Form:
# XXX needs a nice docstring that explains typical usage!
"""
Instance attributes:
- components : [FormComponent]
- hidden_widgets = [HiddenWidget]
- button_widgets = [ButtonWidget]
- _names : { name:string : Widget|FormComponent }
- names used in the form
+ widgets : [Widget]
+ submit_widgets : [SubmitWidget]
+ hidden_widgets : [HiddenWidget]
+ _names : { name:string : Widget }
+ names used in the form and the widgets associated with them
"""
TOKEN_NAME = "_form_id" # name of hidden token widget
- COMPONENT_CLASS = WidgetRow
+ JAVASCRIPT_MARKUP = htmltext('<script type="text/javascript">\n'
+ '<!--\n'
+ '%s\n'
+ '// -->p\n'
+ '</script>\n')
def __init__(self,
name=None,
@@ -256,9 +61,9 @@
self.name = name
self.method = method
self.action_url = action_url or self._get_default_action_url()
- self.components = []
+ self.widgets = []
+ self.submit_widgets = []
self.hidden_widgets = []
- self.button_widgets = []
self._names = {}
if enctype is not None and enctype not in (
@@ -274,10 +79,9 @@
if config.form_tokens:
# unique token for each form, this prevents many cross-site
# attacks and prevents a form from being submitted twice
- self.add_hidden(self.TOKEN_NAME, None, FormTokenWidget)
+ self.add(FormTokenWidget, self.TOKEN_NAME, value=None)
self.use_form_tokens = True
- self.component_class = component_class or self.COMPONENT_CLASS
def _get_default_action_url(self):
request = get_request()
@@ -287,57 +91,69 @@
action_url += "?" + query
return action_url
+ # -- Form data access methods --------------------------------------
+
def __getitem__(self, name):
"""(name:string) -> any
- Return a component's or widget's value.
+ Return a widget's value.
"""
try:
- return self._names[name].get_value()
+ return self._names[name].parse()
except KeyError:
- raise KeyError, 'no widget or component named %r' % name
+ raise KeyError, 'no widget named %r' % name
def get(self, name):
- component = self._names.get(name)
- if component:
- return component.get_value()
+ widget = self._names.get(name)
+ if widget is not None:
+ return widget.parse()
else:
return None
def get_widget(self, name):
"""(name:string) -> Widget | None
"""
- obj = self._names.get(name)
- if isinstance(obj, WidgetRow):
- return obj.widget
- elif isinstance(obj, Widget):
- return obj
- return None
+ return self._names.get(name)
+ def get_submit_widgets(self):
+ return self.submit_widgets
+
+ def get_all_widgets(self):
+ return self._names.values()
+
+ # -- Form processing and error checking ----------------------------
+
def is_submitted(self):
"""() -> bool
Return true if a form was submitted.
"""
- return bool(get_request().form)
+ request = get_request()
+ if self.method == 'post':
+ if request.get_method() == 'POST':
+ return True
+ else:
+ return False
+ else:
+ return bool(request.form)
- def set_error(self, name, error):
- widget = self._names.get(name)
- if not widget:
- raise KeyError, "unknown name %r" % name
- widget.set_error(error)
-
def has_errors(self):
"""() -> bool
- Return true if form has errors.
+ Ensure that all components of the form have parsed themselves. Return
+ true if any of them have errors.
"""
- for component in self.components:
- if component.has_errors():
- return True
+ request = get_request()
+ if request.form:
+ for widget in self.get_all_widgets():
+ widget.parse(request)
+ for widget in self.get_all_widgets():
+ if widget.get_error():
+ return True
return False
- def get_buttons(self):
- return self.button_widgets
+ def clear_errors(self):
+ for widget in self.get_all_widgets():
+ widget.set_error(None)
def get_submit(self):
"""() -> string | bool
@@ -348,73 +164,69 @@
"""
request = get_request()
- for button in self.button_widgets:
- if request.form.has_key(button.name):
+ for button in self.submit_widgets:
+ if button.parse(request):
return button.name
else:
- if request.form:
+ if self.is_submitted():
return True
else:
return False
+ def set_error(self, name, error):
+ widget = self._names.get(name)
+ if not widget:
+ raise KeyError, "unknown name %r" % name
+ widget.set_error(error)
+
# -- Form population methods ---------------------------------------
- def _add_name(self, name, obj):
+ def add(self, widget_class, name, *args, **kwargs):
if self._names.has_key(name):
- raise ValueError, "form already has '%s' variable" % name
- self._names[name] = obj
+ raise ValueError, "form already has '%s' widget" % name
+ widget = widget_class(name, *args, **kwargs)
+ self._names[name] = widget
+ if isinstance(widget, SubmitWidget):
+ self.submit_widgets.append(widget) # will be rendered at end
+ elif isinstance(widget, HiddenWidget):
+ self.hidden_widgets.append(widget) # will be render at beginning
+ else:
+ self.widgets.append(widget)
- def add_component(self, component_class, name, *args, **kwargs):
- """(FormComponent, name : string, ...)
+ def add_submit(self, name, value=None, **kwargs):
+ self.add(SubmitWidget, name, value, **kwargs)
- Add a form component object to the form.
- """
- if not issubclass(component_class, FormComponent):
- raise TypeError('FormComponent subclass required (got %r)' %
- component_class)
- component = component_class(name, *args, **kwargs)
- self._add_name(name, component)
- self.components.append(component)
+ def add_reset(self, name, value=None, **kwargs):
+ self.add(ResetWidget, name, value, **kwargs)
- def add(self, widget_class, name, value=None,
- title=None, hint=None, required=False, **kwargs):
- """(Widget,
- name : string,
- value : any = None,
- title : string = None,
- hint : string = None,
- required : boolean = False,
- ...)
+ def add_hidden(self, name, value=None, **kwargs):
+ self.add(HiddenWidget, name, value, **kwargs)
- Create a new widget and add it to the form. The expected type of
- 'value' also depends on the widget class. Any extra keyword args are
- passed to the constructor method.
- """
- if issubclass(widget_class, HiddenWidget):
- raise TypeError, "use add_hidden() to add hidden widgets"
- self.add_component(self.component_class, name, widget_class, value,
- title, hint, required, **kwargs)
+ def add_string(self, name, value=None, **kwargs):
+ self.add(StringWidget, name, value, **kwargs)
+ def add_text(self, name, value=None, **kwargs):
+ self.add(TextWidget, name, value, **kwargs)
- def _add_button_widget(self, klass, name, value, **kwargs):
- widget = klass(name, value, **kwargs)
- self._add_name(name, widget)
- self.button_widgets.append(widget)
+ def add_checkbox(self, name, value=None, **kwargs):
+ self.add(CheckboxWidget, name, value, **kwargs)
- def add_button(self, name, value, **kwargs):
- self._add_button_widget(ButtonWidget, name, value, **kwargs)
+ def add_single_select(self, name, value=None, **kwargs):
+ self.add(SingleSelectWidget, name, value, **kwargs)
- def add_reset(self, name, value, **kwargs):
- self._add_button_widget(ResetWidget, name, value, **kwargs)
+ def add_multiple_select(self, name, value=None, **kwargs):
+ self.add(MultipleSelectWidget, name, value, **kwargs)
- def add_submit(self, name, value, **kwargs):
- self._add_button_widget(SubmitWidget, name, value, **kwargs)
+ def add_radiobutton(self, name, value=None, **kwargs):
+ self.add(RadiobuttonsWidget, name, value, **kwargs)
- def add_hidden(self, name, value, klass=HiddenWidget):
- widget = klass(name, value)
- self._add_name(name, widget)
- self.hidden_widgets.append(widget)
+ def add_float(self, name, value=None, **kwargs):
+ self.add(FloatWidget, name, value, **kwargs)
+ def add_int(self, name, value=None, **kwargs):
+ self.add(IntWidget, name, value, **kwargs)
+
+
# -- Layout (rendering) methods ------------------------------------
def render(self):
@@ -437,7 +249,9 @@
def _render_finish(self):
r = TemplateIO(html=True)
r += htmltext('</form>')
- r += self._render_javascript()
+ code = get_request().response.javascript_code
+ if code:
+ r += self._render_javascript(code)
return r.getvalue()
def _render_sep(self, text, line=True):
@@ -445,61 +259,82 @@
'</big></strong></td></tr>') % \
(line and htmltext('<hr>') or '', text)
+ def _render_widgets(self):
+ r = TemplateIO(html=True)
+ for widget in self.widgets:
+ r += widget.form_render(self)
+ return r.getvalue()
+
def _render_hidden_widgets(self):
r = TemplateIO(html=True)
for widget in self.hidden_widgets:
r += widget.render()
return r.getvalue()
- def _render_button_widgets(self, ncols=3):
+ def _render_submit_widgets(self, ncols=3):
r = TemplateIO(html=True)
r += htmltext('<tr><td colspan="%d">\n') % ncols
- for button in self.button_widgets:
- r += button.render()
+ for widget in self.submit_widgets:
+ r += widget.render()
r += htmltext('</td></tr>')
return r.getvalue()
- def _render_components(self):
+ def _render_error_notice(self):
+ return htmltext('<tr><td colspan="3">'
+ '<font color="red"><strong>Warning:</strong></font> '
+ 'there were errors processing your form. '
+ 'See below for details.'
+ '</td></tr>')
+
+ def render_error(self, error):
+ return htmltext('<font color="red">%s</font><br />') % nl2br(error)
+
+ def render_hint(self, hint):
+ return htmltext('<em>%s</em>') % hint
+
+ def render_field(self, title, contents, hint, error, required=False):
+ title = title or ''
+ if title and required:
+ title = title + htmltext(' *')
r = TemplateIO(html=True)
- for component in self.components:
- r += component.render()
+ r += htmltext('<tr><th colspan="3" align="left">')
+ r += title
+ r += htmltext('</th></tr>'
+ '<tr><td> </td><td>')
+ r += contents
+ r += htmltext('</td><td>')
+ if error:
+ r += self.render_error(error)
+ if hint:
+ r += self.render_hint(hint)
+ r += htmltext('</td></tr>')
return r.getvalue()
- def _render_error_notice(self):
- if self.has_errors():
- r = htmltext('<tr><td colspan="3">'
- '<font color="red"><strong>Warning:</strong></font> '
- 'there were errors processing your form. '
- 'See below for details.'
- '</td></tr>')
+ def _render_javascript(self, javascript_code):
+ """Render javacript code for the form. Insert code lexically
+ sorted by code_id.
+ """
+ form_code = []
+ code_ids = javascript_code.keys()
+ code_ids.sort()
+ for code_id in code_ids:
+ code = javascript_code[code_id]
+ if code:
+ form_code.append(code)
+ javascript_code[code_id] = ''
+ if form_code:
+ return self.JAVASCRIPT_MARKUP % htmltext(''.join(form_code))
else:
- r = ''
- return r
+ return ''
def _render_body(self):
r = TemplateIO(html=True)
r += htmltext('<table>')
- r += self._render_error_notice()
- r += self._render_components()
- r += self._render_button_widgets()
+ if self.has_errors():
+ r += self._render_error_notice()
+ r += self._render_widgets()
+ r += self._render_submit_widgets()
r += htmltext('</table>')
return r.getvalue()
- def _render_javascript(self):
- """Render javacript code for the form, if any.
- Insert code lexically sorted by code_id
- """
- javascript_code = get_request().response.javascript_code
- if javascript_code:
- form_code = []
- code_ids = javascript_code.keys()
- code_ids.sort()
- for code_id in code_ids:
- code = javascript_code[code_id]
- if code:
- form_code.append(code)
- javascript_code[code_id] = ''
- if form_code:
- return JAVASCRIPT_MARKUP % htmltext(''.join(form_code))
- return ''
Modified: trunk/quixote/form2/widget.py
===================================================================
--- trunk/quixote/form2/widget.py 2004-03-17 15:19:23 UTC (rev 23719)
+++ trunk/quixote/form2/widget.py 2004-03-17 16:45:59 UTC (rev 23720)
@@ -8,7 +8,7 @@
import struct
from types import FloatType, IntType, ListType, StringType, TupleType
from quixote import get_request
-from quixote.html import htmltext, htmlescape, htmltag, ValuelessAttr
+from quixote.html import htmltext, htmlescape, htmltag, TemplateIO
from quixote.upload import Upload
try:
@@ -40,6 +40,16 @@
return attrs
+class WidgetValueError(Exception):
+ """May be raised a widget has problems parsing its value."""
+
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return str(self.msg)
+
+
class Widget:
"""Abstract base class for web widgets.
@@ -47,22 +57,27 @@
name : string
value : any
error : string
+ title : string
+ hint : string
+ required : bool
attrs : {string: any}
+ _parsed : bool
Feel free to access these directly; to set them, use the 'set_*()'
modifier methods.
"""
- def __init__(self, name, value=None, attrs=None, **kwattrs):
+ def __init__(self, name, value=None, title="", hint="", required=False,
+ attrs=None, **kwattrs):
assert self.__class__ is not Widget, "abstract class"
self.name = name
+ self.value = value
self.error = None
+ self.title = title
+ self.hint = hint
+ self.required = required
self.attrs = merge_attrs(attrs, kwattrs)
- request = get_request()
- if request.form:
- self._parse(request)
- else:
- self.set_value(value)
+ self._parsed = False
def __repr__(self):
return "<%s at %x: %s>" % (self.__class__.__name__,
@@ -75,12 +90,6 @@
def get_name(self):
return self.name
- def set_name(self, name):
- self.name = name
-
- def get_value(self):
- return self.value
-
def set_value(self, value):
self.value = value
@@ -90,6 +99,32 @@
def get_error(self):
return self.error
+ def get_title(self):
+ return self.title
+
+ def get_hint(self):
+ return self.hint
+
+ def is_required(self):
+ return self.required
+
+ def _is_submitted(self, request):
+ return True # subclass should override if it actually knows
+
+ def parse(self, request=None):
+ if not self._parsed:
+ if request is None:
+ request = get_request()
+ if self._is_submitted(request):
+ try:
+ self._parse(request)
+ except WidgetValueError, exc:
+ self.set_error(str(exc))
+ if self.required and self.value is None and not self.error:
+ self.set_error('value is required')
+ self._parsed = True
+ return self.value
+
def _parse(self, request):
# subclasses may override but this is not part of the public API
value = request.form.get(self.name)
@@ -102,7 +137,15 @@
"""render() -> HTML text"""
raise NotImplementedError, 'subclass must implement'
+ def form_render(self, form):
+ return form.render_field(self.title,
+ self.render(),
+ self.hint,
+ self.error,
+ self.required)
+
+
# class Widget
# -- Fundamental widget types ------------------------------------------
@@ -129,6 +172,9 @@
# This lets PasswordWidget be a trivial subclass
HTML_TYPE = "text"
+ def _is_submitted(self, request):
+ return request.form.has_key(self.name)
+
def render(self):
return htmltag("input", xml_end=True,
type=self.HTML_TYPE,
@@ -171,6 +217,9 @@
value : string
"""
+ def _is_submitted(self, request):
+ return request.form.has_key(self.name)
+
def _parse(self, request):
Widget._parse(self, request)
if self.value and self.value.find("\r\n") >= 0:
@@ -199,7 +248,7 @@
type="checkbox",
name=self.name,
value="yes",
- checked=self.value and ValuelessAttr or None,
+ checked=self.value and "checked" or None,
**self.attrs)
@@ -218,14 +267,14 @@
"""
def __init__(self, name, value=None, options=None, sort=True,
- verify_selection=True, attrs=None, **kwattrs):
+ verify_selection=True, **kwargs):
assert self.__class__ is not SelectWidget, "abstract class"
+ Widget.__init__(self, name, value, **kwargs)
self.options = []
if options is not None:
assert options, 'cannot pass empty options list'
self.set_options(options, sort)
self.verify_selection = verify_selection
- Widget.__init__(self, name, value, attrs=attrs, **kwattrs)
def get_allowed_values(self):
return [item[0] for item in self.options]
@@ -359,7 +408,7 @@
tags = [htmltag("select", name=self.name, **self.attrs)]
for object, description, key in self.options:
if self.is_selected(object):
- selected = ValuelessAttr
+ selected = 'selected'
else:
selected = None
if description is None:
@@ -376,7 +425,16 @@
SELECT_TYPE = "single_select"
+ def _is_submitted(self, request):
+ for value, description, key in self.options:
+ if value is None:
+ # empty item is in list so we don't know
+ return True
+ else:
+ return request.form.has_key(self.name)
+
def _parse(self, request):
+ print 'parsing SingleSelectWidget', self.value
parsed_key = request.form.get(self.name)
if parsed_key:
if type(parsed_key) is ListType:
@@ -400,11 +458,9 @@
SELECT_TYPE = "radiobuttons"
- def __init__(self, name, value=None,
- options=None,
- delim=None, attrs=None, **kwattrs):
- SingleSelectWidget.__init__(self, name, value, options,
- attrs=attrs, **kwattrs)
+ def __init__(self, name, value=None, options=None, delim=None, **kwargs):
+ SingleSelectWidget.__init__(self, name, value, options=options,
+ **kwargs)
if delim is None:
self.delim = "\n"
else:
@@ -414,7 +470,7 @@
tags = []
for object, description, key in self.options:
if self.is_selected(object):
- checked = ValuelessAttr
+ checked = 'checked'
else:
checked = None
r = htmltag("input",
@@ -439,7 +495,7 @@
SELECT_TYPE = "multiple_select"
def __init__(self, name, value=None, options=None, **kwargs):
- SelectWidget.__init__(self, name, value, options,
+ SelectWidget.__init__(self, name, value, options=options,
multiple='multiple', **kwargs)
def set_value(self, value):
@@ -481,25 +537,18 @@
HTML_TYPE = "button"
- 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
- # in the request.
+ def __init__(self, name, value=None, **kwargs):
+ Widget.__init__(self, name, value, **kwargs)
self.label = value
- request = get_request()
- if request.form:
- self._parse(request)
- else:
- self.value = False
def set_error(self, error):
return TypeError, 'error not allowed on submit buttons'
def render(self):
+ # slightly different behavior here, we always render the
+ # tag using the 'value' passed in as a parameter. 'self.value'
+ # is a boolean that is true if the button's name appears
+ # in the request.
value = (self.label and htmlescape(self.label) or None)
return htmltag("input", xml_end=True, type=self.HTML_TYPE,
name=self.name, value=value, **self.attrs)
@@ -508,14 +557,13 @@
self.value = request.form.has_key(self.name)
-class ResetWidget(ButtonWidget):
- HTML_TYPE = "reset"
-
-
class SubmitWidget(ButtonWidget):
HTML_TYPE = "submit"
+class ResetWidget(SubmitWidget):
+ HTML_TYPE = "reset"
+
class HiddenWidget(Widget):
"""
Instance attributes:
@@ -552,13 +600,13 @@
TYPE_ERROR = None # human-readable error message
TYPE_CONVERTER = None # eg. int(), float()
- def __init__(self, name, value=None, attrs=None, **kwattrs):
+ def __init__(self, name, value=None, **kwargs):
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, attrs=attrs, **kwattrs)
+ StringWidget.__init__(self, name, value, **kwargs)
def _parse(self, request):
StringWidget._parse(self, request)
@@ -603,7 +651,7 @@
SELECT_TYPE = "option_select"
def __init__(self, name, value=None, options=None, **kwargs):
- SingleSelectWidget.__init__(self, name, value, options,
+ SingleSelectWidget.__init__(self, name, value, options=options,
onchange='submit()', **kwargs)
def render(self):
@@ -612,3 +660,133 @@
'<input type="submit" name="" value="apply" />'
'</noscript>'))
+
+class CompositeWidget(Widget):
+ """
+ Instance attributes:
+ widgets : [Widget]
+ _names : {name:string : Widget}
+ """
+ def __init__(self, name, value=None, **kwargs):
+ Widget.__init__(self, name, value, **kwargs)
+ self.widgets = []
+ self._names = {}
+
+ def _is_submitted(self, request):
+ for widget in self.widgets:
+ if not widget._is_submitted(request):
+ return False
+ return True
+
+ def _parse(self, request):
+ for widget in self.widgets:
+ widget.parse(request)
+
+ def __getitem__(self, name):
+ return self._names[name].parse()
+
+ def get(self, name):
+ widget = self._names.get(name)
+ if widget:
+ return widget.parse()
+ return None
+
+ def get_widget(self, name):
+ return self._names.get(name)
+
+ def get_widgets(self):
+ return self.widgets
+
+ def set_error(self, error):
+ self.error = error
+ if error is None:
+ for widget in self.widgets:
+ widget.set_error(None)
+
+ def set_widget_error(self, name, error):
+ self._names[name].set_error(error)
+
+ def get_error(self):
+ if self.error:
+ return self.error
+ else:
+ for widget in self.widgets:
+ error = widget.get_error()
+ if error:
+ return error
+
+ def add(self, widget_class, name, *args, **kwargs):
+ if self._names.has_key(name):
+ raise ValueError, 'the name %r is already used' % name
+ widget = widget_class(subname(self.name, name), *args, **kwargs)
+ self._names[name] = widget
+ self.widgets.append(widget)
+
+
+class WidgetList(CompositeWidget):
+ """A variable length list of widgets. There is only one
+ title and hint but each element of the list can have its own
+ error. You can also set an error on the WidgetList itself (e.g. as a
+ result of higher-level processing).
+
+ Instance attributes:
+ element_names : string
+ """
+
+ def __init__(self,
+ name,
+ element_type=StringWidget,
+ value=None,
+ element_name="row",
+ title="",
+ hint="",
+ required=False,
+ **kwargs):
+ assert value is None or type(value) is ListType, (
+ "value '%s' not a list: got %r" % (name, value))
+ assert type(element_name) in (StringType, htmltext), (
+ "value '%s' element_name not a string: "
+ "got %r" % (name, element_name))
+ CompositeWidget.__init__(self, name, value, title=title, hint=hint,
+ required=required)
+
+ self.element_names = []
+ def add_element(value=None):
+ name = "element%d" % len(self.element_names)
+ self.add(element_type, name, value=element_value)
+ self.element_names.append(name)
+ if value is not None:
+ for element_value in value:
+ add_element("element%d" % n, value=element_value)
+ self.add(HiddenWidget, "added_elements")
+ self.add(SubmitWidget, "add_element", value="Add %s" % element_name)
+ num_added = int(self["added_elements"] or 1)
+ for i in range(num_added):
+ add_element()
+ if self.add_button.parse():
+ add_element()
+ num_added += 1
+ self.added_elements.set_value(num_added)
+
+ def _parse(self, request):
+ values = []
+ for name in self.element_names:
+ value = element.parse(request)
+ if value is not None:
+ values.append(value)
+ self.value = values or None
+
+ def form_render(self, form):
+ r = TemplateIO(html=True)
+ widget = self.get_widget(self.element_names[0])
+ r += form.render_field(self.title,
+ widget.render(),
+ self.hint,
+ self.error or widget.get_error(),
+ self.required)
+ for name in self.element_names[1:]:
+ widget = self.get_widget(name)
+ r += form.render_field('', widget.render(), '', widget.get_error())
+ r += self.get_widget("add_button").render()
+ r += self.get_widget("added_elements").render()
+ return r.getvalue()