[wwwsearch-commits] r17024 - in wwwsearch/ClientForm/trunk: .
examples
jjlee at codespeak.net
jjlee at codespeak.net
Mon Aug 29 00:26:23 CEST 2005
Author: jjlee
Date: Mon Aug 29 00:26:21 2005
New Revision: 17024
Modified:
wwwsearch/ClientForm/trunk/ClientForm.py
wwwsearch/ClientForm/trunk/examples/example.py
wwwsearch/ClientForm/trunk/test.py
Log:
ClientForm version 0.2 starts here: Apply Gary Poster / Benji York's patch (with some changes from me) to add label support &c. and deprecate various methods. Still to resolve: Missing tests for various new features; Missing docstrings and README update for ALL new features; Disabled items invariants are broken; Is ListControl.items_from_label buggy?; Un-deprecate .set()?)
Modified: wwwsearch/ClientForm/trunk/ClientForm.py
==============================================================================
--- wwwsearch/ClientForm/trunk/ClientForm.py (original)
+++ wwwsearch/ClientForm/trunk/ClientForm.py Mon Aug 29 00:26:21 2005
@@ -30,6 +30,9 @@
# Does file upload work when name is missing? Sourceforge tracker form
# doesn't like it. Check standards, and test with Apache. Test
# binary upload with Apache.
+# There have been reports that some servers are very picky about MIME
+# boundaries, so file uploads may fail with those servers. Should
+# copy what IE does religiously.
# Unicode: see Wichert Akkerman's 2004-01-22 message to c.l.py.
# Controls can have name=None (eg. forms constructed partly with
# JavaScript), but find_control can't be told to find a control
@@ -44,9 +47,6 @@
# necessary -- HTML spec. doesn't require it, and Mozilla Firebird 0.6
# doesn't seem to do it.
# Add charset parameter to Content-type headers? How to find value??
-# Add label support for CHECKBOX and RADIO. Actually, I may not bother
-# to fix this, since a discussion with Gisle on libwww-perl list seemed
-# to show that it wouldn't be very useful.
# I'm not going to fix this unless somebody tells me what real servers
# that want this encoding actually expect: If enctype is
# application/x-www-form-urlencoded and there's a FILE control present.
@@ -54,31 +54,15 @@
# 17.13.2), but I send "name=" ATM. What about multiple file upload??
# Get rid of MapBase, AList and MimeWriter.
# Should really use sgmllib, not htmllib.
-# Remove single-selection code: can be special case of multi-selection,
-# with a few variations, I think.
-# Factor out multiple-selection list code? May not be easy. Maybe like
-# this:
-
-# ListControl
-# ^
-# | MultipleListControlMixin
-# | ^
-# SelectControl /
-# ^ /
-# \ /
-# MultiSelectControl
-
-
-# Plan
-# ----
-# Maybe a 0.2.x, cleaned up a bit and with id support for list items?
+
+# Would be nice, but I'm not going to do it myself:
+# -------------------------------------------------
+# Maybe a 0.3.x, cleaned up a bit and with id support for list items?
# Not sure it's worth it...
-# Unify single / multiple selection code.
# action should probably be an absolute URI, like DOMForm.
-# Remove toggle methods.
-# Replace by_label with choice between value / id / label /
-# element contents (see discussion with Gisle about labels on
-# libwww-perl list).
+# Replace by_label with choice between value / id / label / element
+# contents. Moniker / selector concept is clearly the right thing
+# here.
# ...what else?
# Work on DOMForm.
# XForms? Don't know if there's a need here.
@@ -96,7 +80,7 @@
else: return False
import sys, urllib, urllib2, types, string, mimetools, copy, urlparse, \
- htmlentitydefs
+ htmlentitydefs, re
from urlparse import urljoin
from cStringIO import StringIO
try:
@@ -106,10 +90,22 @@
else:
UNICODE = True
-VERSION = "0.1.18"
+try:
+ import warnings
+except ImportError:
+ def deprecation(message):
+ pass
+else:
+ def deprecation(message):
+ warnings.warn(message, DeprecationWarning, stacklevel=2)
+
+VERSION = "0.2.0a"
CHUNK = 1024 # size of chunks fed to parser, in bytes
+_compress_re = re.compile(r"\s+")
+compress_text = lambda text: _compress_re.sub(' ', text.strip())
+
# This version of urlencode is from my Python 1.5.2 back-port of the
# Python 2.1 CVS maintenance branch of urllib. It will accept a sequence
# of pairs instead of a mapping -- the 2.0 version only accepts a mapping.
@@ -179,29 +175,14 @@
l.append(k + '=' + urllib.quote_plus(str(elt)))
return string.join(l, '&')
-# Grabbed from 2.4 xml.sax.saxutils, and modified
-def __dict_replace(s, d):
- """Replace substrings of a string using a dictionary."""
- for key, value in d.items():
- s = string.replace(s, key, value)
- return s
def unescape(data, entities):
- if data is None:
- return None
- do_amp = False
- if entities:
- # must do ampersand last
- ents = entities.copy()
- try:
- del ents["&"]
- except KeyError:
- pass
- else:
- do_amp = True
- data = __dict_replace(data, ents)
- if do_amp:
- data = string.replace(data, "&", "&")
- return data
+ if data is None or '&' not in data:
+ return data
+ def replace_entities(match, entities=entities):
+ ent = match.group()
+ repl = entities.get(ent, ent)
+ return repl
+ return re.sub(r'&\S+?;', replace_entities, data)
def startswith(string, initial):
if len(initial) > len(string): return False
@@ -587,8 +568,11 @@
class ControlNotFoundError(ValueError): pass
-class ItemNotFoundError(ValueError): pass
-class ItemCountError(ValueError): pass
+class ItemError(ValueError): pass
+class ItemNotFoundError(ItemError): pass
+class ItemCountError(ItemError): pass
+class AmbiguityError(ItemError): pass
+
class ParseError(Exception): pass
@@ -603,6 +587,8 @@
self.base = None
self.forms = []
+ self.labels = []
+ self._current_label = None
self._current_form = None
self._select = None
self._optgroup = None
@@ -614,6 +600,12 @@
if key == "href":
self.base = value
+ def end_body(self):
+ if self._current_label is not None:
+ self.end_label()
+ if self._current_form is not None:
+ self.end_form()
+
def start_form(self, attrs):
if self._current_form is not None:
raise ParseError("nested FORMs")
@@ -636,6 +628,8 @@
self._current_form = (name, action, method, enctype), d, controls
def end_form(self):
+ if self._current_label is not None:
+ self.end_label()
if self._current_form is None:
raise ParseError("end of FORM before start")
self.forms.append(self._current_form)
@@ -653,6 +647,7 @@
d[key] = val
self._select = d
+ self._add_label(d)
self._append_select_control({"__select": d})
@@ -728,6 +723,7 @@
d = {}
for key, val in attrs:
d[key] = val
+ self._add_label(d)
self._textarea = d
@@ -741,6 +737,36 @@
controls.append(("textarea", name, self._textarea))
self._textarea = None
+ def start_label(self, attrs):
+ if self._current_label:
+ self.end_label()
+ d = {}
+ for key, val in attrs:
+ d[key] = val
+ taken = bool(d.get('for')) # empty id is invalid
+ d['__text'] = ''
+ d['__taken'] = taken
+ if taken:
+ self.labels.append(d)
+ self._current_label = d
+
+ def end_label(self):
+ label = self._current_label
+ if label is None:
+ # something is ugly in the HTML, but we're ignoring it
+ return
+ self._current_label = None
+ label['__text'] = label['__text']
+ del label['__taken'] # if it is staying around, it is True in all cases
+
+ def _add_label(self, d):
+ if self._current_label is not None:
+ if self._current_label['__taken']:
+ self.end_label() # be fuzzy
+ else:
+ self._current_label['__taken'] = True
+ d['__label'] = self._current_label
+
def handle_data(self, data):
if self._option is not None:
# self._option is a dictionary of the OPTION element's HTML
@@ -752,6 +778,10 @@
elif self._textarea is not None:
map = self._textarea
key = "value"
+ elif self._current_label is not None: # not if within option or
+ # textarea
+ map = self._current_label
+ key = "__text"
else:
return
@@ -776,6 +806,7 @@
# eg. type for BUTTON/RESET is "resetbutton"
# (type for INPUT/RESET is "reset")
type = type+"button"
+ self._add_label(d)
controls.append((type, name, d))
def do_input(self, attrs):
@@ -789,6 +820,7 @@
type = d["type"]
name = d.get("name")
+ self._add_label(d)
controls.append((type, name, d))
def do_isindex(self, attrs):
@@ -799,6 +831,7 @@
d[key] = val
controls = self._current_form[2]
+ self._add_label(d)
# isindex doesn't have type or name HTML attributes
controls.append(("isindex", None, d))
@@ -994,6 +1027,17 @@
if fp.base is not None:
# HTML BASE element takes precedence over document URI
base_uri = fp.base
+ labels = [] # Label(label) for label in fp.labels]
+ id_to_labels = {}
+ for l in fp.labels:
+ label = Label(l)
+ labels.append(label)
+ for_id = l['for']
+ coll = id_to_labels.get(for_id)
+ if coll is None:
+ id_to_labels[for_id] = [label]
+ else:
+ coll.append(label)
forms = []
for (name, action, method, enctype), attrs, controls in fp.forms:
if action is None:
@@ -1003,7 +1047,9 @@
action = fp.unescape_attr_if_required(action)
name = fp.unescape_attr_if_required(name)
attrs = fp.unescape_attrs_if_required(attrs)
- form = HTMLForm(action, method, enctype, name, attrs, request_class)
+ form = HTMLForm( # would be nice to make class (form builder) pluggable
+ action, method, enctype, name, attrs, request_class,
+ forms, labels, id_to_labels)
for type, name, attrs in controls:
attrs = fp.unescape_attrs_if_required(attrs)
name = fp.unescape_attr_if_required(name)
@@ -1013,6 +1059,17 @@
form.fixup()
return forms
+class Label:
+ def __init__(self, attrs):
+ self.id = attrs.get('for')
+ self.text = compress_text(attrs.get('__text'))
+ self.attrs = attrs
+
+def _getLabel(attrs):
+ label = attrs.get('__label')
+ if label is not None:
+ label = Label(label)
+ return label
class Control:
"""An HTML form control.
@@ -1080,6 +1137,7 @@
raise NotImplementedError()
def add_to_form(self, form):
+ self._form = form
form.controls.append(self)
def fixup(self):
@@ -1112,6 +1170,14 @@
def __str__(self):
raise NotImplementedError()
+ def get_labels(self):
+ res = []
+ if self._label:
+ res.append(self._label)
+ if self.id:
+ res.extend(self._form._id_to_labels.get(self.id, ()))
+ return res
+
#---------------------------------------------------
class ScalarControl(Control):
@@ -1127,6 +1193,7 @@
"""
def __init__(self, type, name, attrs):
+ self._label = _getLabel(attrs)
self.__dict__["type"] = string.lower(type)
self.__dict__["name"] = name
self._value = attrs.get("value")
@@ -1408,6 +1475,80 @@
#---------------------------------------------------
+# ListControls
+
+# helpers and subsidiary classes
+
+class Item:
+ def __init__(self, control, attrs):
+ label = _getLabel(attrs)
+ self.__dict__.update({
+ 'name': attrs['value'],
+ '_labels': label and [label] or [],
+ 'attrs': attrs,
+ 'control': control,
+ '_disabled': attrs.has_key("disabled"),
+ '_selected': False,
+ 'id': attrs.get('id'),
+ })
+
+ def get_labels(self):
+ res = []
+ res.extend(self._labels)
+ if self.id:
+ res.extend(self.control._form._id_to_labels.get(self.id, ()))
+ return res
+
+ # selected and disabled properties
+ def __getattr__(self, name):
+ if name=='selected':
+ return self._selected
+ elif name=='disabled':
+ return self._disabled
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ if name == 'selected':
+ if bool(value) != bool(self._selected):
+ self.control._set_selected_state(self, value)
+ elif name == 'disabled':
+ if bool(value) != bool(self._disabled):
+ self.control._set_item_disabled(self, value)
+ else:
+ raise AttributeError(name)
+
+ def __str__(self):
+ res = self.name
+ if self.selected:
+ res = '*' + res
+ if self.disabled:
+ res = '(%s)' % res
+ return res
+
+ def __repr__(self):
+ attrs = [("name", self.name), ("id", self.id)]+self.attrs.items()
+ return "<%s %s>" % (
+ self.__class__.__name__,
+ " ".join(['%s=%r' % (k, v) for k, v in attrs])
+ )
+
+# how to remove items from a list container: delete them as usual
+# ("del control.items[:]", for instance).
+# how to add items to a list container: instantiate Item with control, and add
+# to list ("control.items.append(Item(control, {...attrs...}))", for instance).
+# You never want an item to have an incorrect reference to its control (and
+# thus you never want an item to be in more than one control).
+
+def disambiguate(items, nr, name):
+ if not items:
+ raise ItemNotFoundError(name)
+ if nr is None:
+ if len(items) > 1:
+ raise AmbiguityError(name)
+ return items[0]
+ else:
+ return items[nr]
+
class ListControl(Control):
"""Control representing a sequence of items.
@@ -1435,8 +1576,6 @@
Use the by_label argument, and the set_value_by_label, get_value_by_label
methods.
- XXX RadioControl and CheckboxControl don't implement by_label yet.
-
Note that, rather confusingly, though SELECT controls are represented in
HTML by SELECT elements (which contain OPTION elements, representing
individual list items), CHECKBOXes and RADIOs are not represented by *any*
@@ -1481,6 +1620,8 @@
# (actually, it's much easier just to use ParseFile)
+ _label = None
+
def __init__(self, type, name, attrs={}, select_default=False,
called_as_base_class=False):
"""
@@ -1499,24 +1640,14 @@
self.readonly = False
self.id = attrs.get("id")
- self._attrs = attrs.copy()
- # As Controls are merged in with .merge_control(), self._attrs will
+ # As Controls are merged in with .merge_control(), self.attrs will
# refer to each Control in turn -- always the most recently merged
# control. Each merged-in Control instance corresponds to a single
# list item: see ListControl.__doc__.
- if attrs:
- self._attrs_list = [self._attrs] # extended by .merge_control()
- self._disabled_list = [self._attrs.has_key("disabled")] # ditto
- else:
- self._attrs_list = [] # extended by .merge_control()
- self._disabled_list = [] # ditto
+ self.items = []
self._select_default = select_default
self._clicked = False
- # Some list controls can have their default set only after all items
- # are known. If so, self._value_is_set is false, and the self.fixup
- # method, called after all items have been added, sets the default.
- self._value_is_set = False
def clear(self):
self.value = []
@@ -1531,98 +1662,114 @@
else:
return False
- def _value_from_label(self, label):
- raise NotImplementedError("control '%s' does not yet support "
- "by_label" % self.name)
-
- def toggle(self, name, by_label=False):
- return self._set_selected_state(name, 2, by_label)
- def set(self, selected, name, by_label=False):
- action = int(bool(selected))
- return self._set_selected_state(name, action, by_label)
+ def items_from_label(self, label, exclude_disabled=False):
+ if not isstringlike(label):
+ raise TypeError("item label must be string-like")
+ # check all labels on the items, then if any of the values have
+ # an id, go through all the collected labels on self._form._labels and
+ # see if any of them match.
+ items = [] # order is important
+ mapping = self._form._id_to_labels
+ for o in self.items:
+ if not exclude_disabled or not o.disabled:
+ for l in o.get_labels():
+ if l.text.find(label) >= 0:
+ items.append(o)
+ break
+ return items
+
+ def items_from_name(self, name, exclude_disabled=False):
+ if not isstringlike(name):
+ raise TypeError("item name must be string-like")
+ return [o for o in self.items if
+ o.name == name and (not exclude_disabled or not o.disabled)]
+
+ def get(self, name, by_label=False, nr=None, exclude_disabled=False):
+ if by_label:
+ method = self.items_from_label
+ else:
+ method = self.items_from_name
+ return disambiguate(method(name, exclude_disabled), nr, name)
+
+ def toggle(self, name, by_label=False, nr=None):
+ deprecation(
+ "item = control.get(...); item.selected = not item.selected")
+ o = self.get(name, by_label, nr)
+ self._set_selected_state(o, not o.selected)
- def _set_selected_state(self, name, action, by_label):
+ def set(self, selected, name, by_label=False, nr=None):
+ deprecation(
+ "control.get(...).selected = <boolean>")
+ self._set_selected_state(self.get(name, by_label, nr), selected)
+
+ def _set_selected_state(self, item, action):
"""
- name: item name
+ index: index of item
action:
- 0: clear
- 1: set
- 2: toggle
-
+ bool False: off
+ bool True: on
"""
- if not isstringlike(name):
- raise TypeError("item name must be string-like")
if self.disabled:
raise AttributeError("control '%s' is disabled" % self.name)
if self.readonly:
raise AttributeError("control '%s' is readonly" % self.name)
- if by_label:
- name = self._value_from_label(name)
- try:
- i = self._menu.index(name)
- except ValueError:
- raise ItemNotFoundError("no item named '%s'" % name)
-
- if self.multiple:
- if action == 2:
- action = not self._selected[i]
- if action and self._disabled_list[i]:
- raise AttributeError("item '%s' is disabled" % name)
- self._selected[i] = bool(action)
- else:
- if action == 2:
- if self._selected == name:
- action = 0
+ action == bool(action)
+ if item.disabled:
+ raise AttributeError("item is disabled")
+ elif action != item.selected:
+ if self.multiple:
+ item.__dict__['_selected'] = action
+ else:
+ if not action:
+ item.__dict__['_selected'] = action
else:
- action = 1
- if action == 0 and self._selected == name:
- self._selected = None
- elif action == 1:
- if self._disabled_list[i]:
- raise AttributeError("item '%s' is disabled" % name)
- self._selected = name
-
- def toggle_single(self, by_label=False):
- self._set_single_selected_state(2, by_label)
- def set_single(self, selected, by_label=False):
- action = int(bool(selected))
- self._set_single_selected_state(action, by_label)
-
- def _set_single_selected_state(self, action, by_label):
- if len(self._menu) != 1:
- raise ItemCountError("'%s' is not a single-item control" %
- self.name)
-
- name = self._menu[0]
- if by_label:
- name = self._value_from_label(name)
- self._set_selected_state(name, action, by_label)
+ selected = [o for o in self.items
+ if o.selected and not o.disabled]
+ # disabled items are not changeable but also not
+ # 'successful': their names should not be sent to the
+ # server, so they are effectively invisible, whether or not
+ # the control considers itself to be selected
+ for s in selected:
+ s.__dict__['_selected'] = False
+ item.__dict__['_selected'] = True
+
+ def toggle_single(self, by_label=None):
+ deprecation(
+ "control.items[0].selected = not control.items[0].selected")
+ if len(self.items) != 1:
+ raise ItemCountError(
+ "'%s' is not a single-item control" % self.name)
+ item = self.items[0]
+ self._set_selected_state(item, not item.selected)
+
+ def set_single(self, selected, by_label=None):
+ deprecation(
+ "control.items[0].selected = <boolean>")
+ if len(self.items) != 1:
+ raise ItemCountError(
+ "'%s' is not a single-item control" % self.name)
+ self._set_selected_state(self.items[0], selected)
- def get_item_disabled(self, name, by_label=False):
+ def get_item_disabled(self, name, by_label=False, nr=None):
"""Get disabled state of named list item in a ListControl."""
- if by_label:
- name = self._value_from_label(name)
- try:
- i = self._menu.index(name)
- except ValueError:
- raise ItemNotFoundError()
- else:
- return self._disabled_list[i]
+ deprecation(
+ "control.get(...).disabled")
+ return self.get(name, by_label, nr).disabled
- def set_item_disabled(self, disabled, name, by_label=False):
+ def set_item_disabled(self, disabled, name, by_label=False, nr=None):
"""Set disabled state of named list item in a ListControl.
disabled: boolean disabled state
"""
- if by_label:
- name = self._value_from_label(name)
- try:
- i = self._menu.index(name)
- except ValueError:
- raise ItemNotFoundError()
- else:
- self._disabled_list[i] = bool(disabled)
+ deprecation(
+ "control.get(...).disabled = <boolean>")
+ self.get(name, by_label, nr).disabled = disabled
+
+ def _set_item_disabled(self, item, disabled):
+ if not self.multiple and item.selected and self.value:
+ item.__dict__['_selected'] = False
+ item.__dict__['_disabled'] = bool(disabled)
def set_all_items_disabled(self, disabled):
"""Set disabled state of all list items in a ListControl.
@@ -1630,10 +1777,25 @@
disabled: boolean disabled state
"""
- for i in range(len(self._disabled_list)):
- self._disabled_list[i] = bool(disabled)
+ disabled = bool(disabled)
+ if not self.multiple: # make sure that re-emerging items don't
+ # make single-choice controls insane
+ value = bool(self.value)
+ for o in self.items:
+ if not disabled and o.disabled:
+ o.__dict__['_disabled'] = disabled
+ if not self.multiple and o.selected:
+ if value:
+ o.selected = False
+ else:
+ value = True
+ else:
+ o.__dict__['_disabled'] = disabled
+ else:
+ for o in self.items:
+ o.__dict__['_disabled'] = disabled
- def get_item_attrs(self, name, by_label=False):
+ def get_item_attrs(self, name, by_label=False, nr=None):
"""Return dictionary of HTML attributes for a single ListControl item.
The HTML element types that describe list items are: OPTION for SELECT
@@ -1644,20 +1806,13 @@
The returned dictionary maps HTML attribute names to values. The names
and values are taken from the original HTML.
-
- Note that for SELECT controls, the returned dictionary contains a
- special key "contents" -- see SelectControl.__doc__.
-
"""
- if by_label:
- name = self._value_from_label(name)
- try:
- i = self._menu.index(name)
- except ValueError:
- raise ItemNotFoundError()
- return self._attrs_list[i]
+ deprecation(
+ "control.get(...).attrs")
+ return self.get(name, by_label, nr).attrs
def add_to_form(self, form):
+ self._form = form
try:
control = form.find_control(self.name, self.type)
except ControlNotFoundError:
@@ -1667,17 +1822,8 @@
def merge_control(self, control):
assert bool(control.multiple) == bool(self.multiple)
- assert isinstance(control, self.__class__)
- self._menu.extend(control._menu)
- self._attrs_list.extend(control._attrs_list)
- self._disabled_list.extend(control._disabled_list)
- if control.multiple:
- self._selected.extend(control._selected)
- else:
- if control._value_is_set:
- self._selected = control._selected
- if control._value_is_set:
- self._value_is_set = True
+ #assert isinstance(control, self.__class__)
+ self.items.extend(control.items)
def fixup(self):
"""
@@ -1707,23 +1853,18 @@
# leave SELECT/multiple with nothing selected, in violation of RFC 1866
# (but not in violation of the W3C HTML 4 standard); the same is true
# of RADIO (which *is* in violation of the HTML 4 standard). We follow
- # RFC 1866 if the select_default attribute is set, and Netscape and IE
+ # RFC 1866 if the _select_default attribute is set, and Netscape and IE
# otherwise. RFC 1866 and HTML 4 are always violated insofar as you
# can deselect all items in a RadioControl.
-
- raise NotImplementedError()
+
+ for o in self.items:
+ # set items' controls to self, now that we've merged
+ o.__dict__['control'] = self
def __getattr__(self, name):
if name == "value":
- menu = self._menu
- if self.multiple:
- values = []
- for i in range(len(menu)):
- if self._selected[i]: values.append(menu[i])
- return values
- else:
- if self._selected is None: return []
- else: return [self._selected]
+ return [o.name for o in self.items if
+ not o.disabled and o.selected]
else:
raise AttributeError("%s instance has no attribute '%s'" %
(self.__class__.__name__, name))
@@ -1741,106 +1882,133 @@
self.__dict__[name] = value
def _set_value(self, value):
- if self.multiple:
+ if value is None or isstringlike(value):
+ raise TypeError("ListControl, must set a sequence")
+ if not value:
+ for o in self.items:
+ if not o.disabled:
+ o.selected = False
+ elif self.multiple:
self._multiple_set_value(value)
+ elif len(value) > 1:
+ raise ItemCountError(
+ "single selection list, must set sequence of "
+ "length 0 or 1")
else:
self._single_set_value(value)
- def _single_set_value(self, value):
- if value is None or isstringlike(value):
- raise TypeError("ListControl, must set a sequence")
- nr = len(value)
- if not (0 <= nr <= 1):
- raise ItemCountError("single selection list, must set sequence of "
- "length 0 or 1")
+ def _get_items(self, name, target=1):
+ all_items = self.items_from_name(name)
+ items = [o for o in all_items if not o.disabled]
+ if len(items) < target:
+ if len(all_items) < target:
+ raise ItemNotFoundError(
+ "insufficient items with name %r" % name)
+ else:
+ raise AttributeError('disabled item with name %s' % name)
+ on = []
+ off = []
+ for o in items:
+ if o.selected:
+ on.append(o)
+ else:
+ off.append(o)
+ return on, off
- if nr == 0:
- self._selected = None
- else:
- value = value[0]
- try:
- i = self._menu.index(value)
- except ValueError:
- raise ItemNotFoundError("no item named '%s'" %
- repr(value))
- if self._disabled_list[i]:
- raise AttributeError("item '%s' is disabled" % value)
- self._selected = value
+ def _single_set_value(self, value):
+ on, off = self._get_items(value[0])
+ if not on:
+ off[0].selected = True
def _multiple_set_value(self, value):
- if value is None or isstringlike(value):
- raise TypeError("ListControl, must set a sequence")
+ turn_on = [] # transactional-ish
+ turn_off = [item for item in self.items if item.selected and not item.disabled]
+ names = {}
+ for nn in value:
+ if nn in names.keys():
+ names[nn] += 1
+ else:
+ names[nn] = 1
+ for name, count in names.items():
+ on, off = self._get_items(name, count)
+ for i in range(count):
+ if on:
+ item = on[0]
+ del on[0]
+ del turn_off[turn_off.index(item)]
+ else:
+ item = off[0]
+ del off[0]
+ turn_on.append(item)
+ for item in turn_off:
+ item.selected = False
+ for item in turn_on:
+ item.selected = True
- selected = [False]*len(self._selected)
- menu = self._menu
- disabled_list = self._disabled_list
-
- for v in value:
- found = False
- for i in range(len(menu)):
- item_name = menu[i]
- if v == item_name:
- if disabled_list[i]:
- raise AttributeError("item '%s' is disabled" % value)
- selected[i] = True
- found = True
+ def set_value_by_label(self, value):
+ if isstringlike(value):
+ raise TypeError(value)
+ items = []
+ for nn in value:
+ found = self.items_from_label(nn)
+ if len(found) > 1:
+ # ambiguous labels are fine as long as values are same
+ opt_name = found[0].name
+ if [o for o in found[1:] if o != opt_name]:
+ raise AmbiguityError(nn)
+ for o in found: # for the multiple-item case, we could try to
+ # be smarter, saving them up and trying to resolve, but that's
+ # too much.
+ if o not in items:
+ items.append(o)
break
- if not found:
- raise ItemNotFoundError("no item named '%s'" % repr(v))
- self._selected = selected
+ else: # all of them are used
+ raise ItemNotFoundError(nn)
+ # now we have all the items that should be on
+ # let's just turn everything off and then back on.
+ self.value = []
+ for o in items:
+ o.selected = True
- def set_value_by_label(self, value):
- raise NotImplementedError("control '%s' does not yet support "
- "by_label" % self.name)
def get_value_by_label(self):
- raise NotImplementedError("control '%s' does not yet support "
- "by_label" % self.name)
+ res = []
+ for o in self.items:
+ if not o.disabled and o.selected:
+ for l in o.get_labels():
+ if l.text:
+ res.append(l.text)
+ break
+ else:
+ res.append(None)
+ return res
- def possible_items(self, by_label=False):
+ def possible_items(self, by_label=False): # disabled are not possible
+ deprecation(
+ "[item.name for item in self.items]")
if by_label:
- raise NotImplementedError(
- "control '%s' does not yet support by_label" % self.name)
- return copy.copy(self._menu)
+ res = []
+ for o in self.items:
+ for l in o.get_labels():
+ if l.text:
+ res.append(l.text)
+ break
+ else:
+ res.append(None)
+ return res
+ return [o.name for o in self.items]
def pairs(self):
if self.disabled:
return []
-
- if not self.multiple:
- name = self.name
- value = self._selected
- if name is None or value is None:
- return []
- return [(name, value)]
- else:
- control_name = self.name # usually the name HTML attribute
- pairs = []
- for i in range(len(self._menu)):
- item_name = self._menu[i] # usually the value HTML attribute
- if self._selected[i]:
- pairs.append((control_name, item_name))
- return pairs
-
- def _item_str(self, i):
- item_name = self._menu[i]
- if self.multiple:
- if self._selected[i]:
- item_name = "*"+item_name
- else:
- if self._selected == item_name:
- item_name = "*"+item_name
- if self._disabled_list[i]:
- item_name = "(%s)" % item_name
- return item_name
+ else:
+ return [(self.name, o.name) for o in self.items
+ if o.selected and not o.disabled]
def __str__(self):
name = self.name
if name is None: name = "<None>"
- display = []
- for i in range(len(self._menu)):
- s = self._item_str(i)
- display.append(s)
+ display = [str(o) for o in self.items]
infos = []
if self.disabled: infos.append("disabled")
@@ -1860,26 +2028,29 @@
"""
def __init__(self, type, name, attrs, select_default=False):
+ attrs.setdefault('value', 'on')
ListControl.__init__(self, type, name, attrs, select_default,
called_as_base_class=True)
self.__dict__["multiple"] = False
- value = attrs.get("value", "on")
- self._menu = [value]
- checked = attrs.has_key("checked")
- if checked:
- self._value_is_set = True
- self._selected = value
- else:
- self._selected = None
+ o = Item(self, attrs)
+ o.__dict__['_selected'] = attrs.has_key("checked")
+ self.items.append(o)
def fixup(self):
- if not self._value_is_set:
- # no item explicitly selected
- assert self._selected is None
+ ListControl.fixup(self)
+ found = [o for o in self.items if o.selected and not o.disabled]
+ if not found:
if self._select_default:
- self._selected = self._menu[0]
- self._value_is_set = True
+ for o in self.items:
+ if not o.disabled:
+ o.selected = True
+ break
+ else: # eliminate any duplicate selected. Choose the last one.
+ for o in found[:-1]:
+ o.selected = False
+ def get_labels(self):
+ return []
class CheckboxControl(ListControl):
"""
@@ -1889,19 +2060,16 @@
"""
def __init__(self, type, name, attrs, select_default=False):
+ attrs.setdefault('value', 'on')
ListControl.__init__(self, type, name, attrs, select_default,
called_as_base_class=True)
self.__dict__["multiple"] = True
- value = attrs.get("value", "on")
- self._menu = [value]
- checked = attrs.has_key("checked")
- self._selected = [checked]
- self._value_is_set = True
+ o = Item(self, attrs)
+ o.__dict__['_selected'] = attrs.has_key("checked")
+ self.items.append(o)
- def fixup(self):
- # If no items were explicitly checked in HTML, that's how we must
- # leave it, so we have nothing to do here.
- assert self._value_is_set
+ def get_labels(self):
+ return []
class SelectControl(ListControl):
@@ -1911,7 +2079,7 @@
SELECT (and OPTION)
SELECT control values and labels are subject to some messy defaulting
- rules. For example, if the HTML repreentation of the control is:
+ rules. For example, if the HTML representation of the control is:
<SELECT name=year>
<OPTION value=0 label="2002">current year</OPTION>
@@ -1956,123 +2124,55 @@
# this SelectControl represents an empty SELECT control.
# -Subsequent SelectControls have both OPTION HTML-attribute in attrs and
# the __select dictionary containing the SELECT HTML-attributes.
+
def __init__(self, type, name, attrs, select_default=False):
# fish out the SELECT HTML attributes from the OPTION HTML attributes
# dictionary
self.attrs = attrs["__select"].copy()
+ self.__dict__['_label'] = _getLabel(self.attrs)
+ self.__dict__['id'] = self.attrs.get('id')
+ self.__dict__["multiple"] = self.attrs.has_key("multiple")
+ # the majority of the contents, label, and value dance already happened
+ contents = attrs.get('contents')
attrs = attrs.copy()
del attrs["__select"]
- ListControl.__init__(self, type, name, attrs, select_default,
+ ListControl.__init__(self, type, name, self.attrs, select_default,
called_as_base_class=True)
-
- self._label_map = None
self.disabled = self.attrs.has_key("disabled")
- self.id = self.attrs.get("id")
-
- self._menu = []
- self._selected = []
- self._value_is_set = False
- if self.attrs.has_key("multiple"):
- self.__dict__["multiple"] = True
- self._selected = []
- else:
- self.__dict__["multiple"] = False
- self._selected = None
-
- if attrs: # OPTION item data was provided
- value = attrs["value"]
- self._menu.append(value)
- selected = attrs.has_key("selected")
- if selected:
- self._value_is_set = True
- if self.attrs.has_key("multiple"):
- self._selected.append(selected)
- elif selected:
- self._selected = value
-
- def _build_select_label_map(self):
- """Return an ordered mapping of labels to values.
-
- For example, if the HTML repreentation of the control is as given in
- SelectControl.__doc__, this function will return a mapping like:
-
- {"2002": "0", "2001": "1", "2000": "2000"}
-
- """
- alist = []
- for val in self._menu:
- attrs = self.get_item_attrs(val)
- alist.append((attrs["label"], val))
- return AList(alist)
-
- def _value_from_label(self, label):
- try:
- return self._label_map[label]
- except KeyError:
- raise ItemNotFoundError("no item has label '%s'" % label)
+ self.readonly = self.attrs.has_key("readonly")
+ if attrs.has_key('value'):
+ # otherwise it is a marker 'select started' token
+ o = Item(self, attrs)
+ o.__dict__['_selected'] = attrs.has_key("selected")
+ # add 'label' label and contents label, if different. If both are
+ # provided, the 'label' label is used for display in HTML
+ # 4.0-compliant browsers (and any lower spec? not sure) while the
+ # contents are used for display in older or less-compliant
+ # browsers. We make label objects for both, if the values are
+ # different.
+ label = attrs.get('label')
+ if label:
+ o._labels.append(Label({'__text': label}))
+ if contents and contents != label:
+ o._labels.append(Label({'__text': contents}))
+ elif contents:
+ o._labels.append(Label({'__text': contents}))
+ self.items.append(o)
def fixup(self):
- if not self._value_is_set:
- # No item explicitly selected.
- if len(self._menu) > 0:
- if self.multiple:
- if self._select_default:
- self._selected[0] = True
- else:
- assert self._selected is None
- self._selected = self._menu[0]
- self._value_is_set = True
- self._label_map = self._build_select_label_map()
-
- def _delete_items(self):
- # useful for simulating JavaScript code, but not a stable interface yet
- self._menu = []
- self._value_is_set = False
- if self.multiple:
- self._selected = []
- else:
- self._selected = None
-
- def possible_items(self, by_label=False):
- if not by_label:
- return copy.copy(self._menu)
- else:
- self._label_map.set_inverted(True)
- try:
- r = map(lambda v, self=self: self._label_map[v], self._menu)
- finally:
- self._label_map.set_inverted(False)
- return r
-
- def set_value_by_label(self, value):
- if isstringlike(value):
- raise TypeError("ListControl, must set a sequence, not a string")
- if self.disabled:
- raise AttributeError("control '%s' is disabled" % self.name)
- if self.readonly:
- raise AttributeError("control '%s' is readonly" % self.name)
-
- try:
- value = map(lambda v, self=self: self._label_map[v], value)
- except KeyError, e:
- raise ItemNotFoundError("no item has label '%s'" % e.args[0])
- self._set_value(value)
-
- def get_value_by_label(self):
- menu = self._menu
- self._label_map.set_inverted(True)
- try:
- if self.multiple:
- values = []
- for i in range(len(menu)):
- if self._selected[i]:
- values.append(self._label_map[menu[i]])
- return values
- else:
- return [self._label_map[self._selected]]
- finally:
- self._label_map.set_inverted(False)
+ ListControl.fixup(self)
+ found = [o for o in self.items if o.selected and not o.disabled]
+ if not found:
+ if not self.multiple or self._select_default:
+ for o in self.items:
+ if not o.disabled:
+ o.selected = True
+ break
+ elif not self.multiple: # eliminate any duplicate selected.
+ # Choose the last one.
+ for o in found[:-1]:
+ o.selected = False
#---------------------------------------------------
@@ -2092,6 +2192,13 @@
if self.value is None: self.value = ""
self.readonly = True
+ def get_labels(self):
+ res = []
+ if self.value:
+ res.append(Label({'__text': self.value}))
+ res.extend(ScalarControl.get_labels(self))
+ return res
+
def is_of_kind(self, kind): return kind == "clickable"
def _click(self, form, coord, return_type, request_class=urllib2.Request):
@@ -2133,7 +2240,9 @@
value = self._value
if value:
pairs.append((name, value))
- return pairs
+ return pairs
+
+ get_labels = ScalarControl.get_labels
# aliases, just to make str(control) and str(form) clearer
class PasswordControl(TextControl): pass
@@ -2298,8 +2407,6 @@
AttributeError is raised if a control or item is readonly or disabled and
an attempt is made to alter its value.
- XXX CheckBoxControl and RadioControl don't yet support item access by label
-
Security note: Remember that any passwords you store in HTMLForm instances
will be saved to disk in the clear if you pickle them (directly or
indirectly). The simplest solution to this is to avoid pickling HTMLForm
@@ -2405,7 +2512,8 @@
def __init__(self, action, method="GET",
enctype="application/x-www-form-urlencoded",
name=None, attrs=None,
- request_class=urllib2.Request):
+ request_class=urllib2.Request,
+ forms=None, labels=None, id_to_labels=None):
"""
In the usual case, use ParseResponse (or ParseFile) to create new
HTMLForm objects.
@@ -2427,6 +2535,9 @@
self.attrs = {}
self.controls = []
self._request_class = request_class
+ self._forms = forms # this is a semi-public API!
+ self._labels = labels # this is a semi-public API!
+ self._id_to_labels = id_to_labels # this is a semi-public API!
def new_control(self, type, name, attrs,
ignore_unknown=False, select_default=False):
@@ -2568,33 +2679,33 @@
#---------------------------------------------------
# Form-filling methods applying only to ListControls.
- def possible_items(self,
- name=None, type=None, kind=None, id=None, nr=None,
- by_label=False):
+ def possible_items(self, # deprecated
+ name=None, type=None, kind=None, id=None, label=None,
+ nr=None, by_label=False):
"""Return a list of all values that the specified control can take."""
- c = self._find_list_control(name, type, kind, id, nr)
+ c = self._find_list_control(name, type, kind, id, label, nr)
return c.possible_items(by_label)
- def set(self, selected, item_name,
- name=None, type=None, kind=None, id=None, nr=None,
+ def set(self, selected, item_name, # deprecated
+ name=None, type=None, kind=None, id=None, label=None, nr=None,
by_label=False):
"""Select / deselect named list item.
selected: boolean selected state
"""
- self._find_list_control(name, type, kind, id, nr).set(
+ self._find_list_control(name, type, kind, id, label, nr).set(
selected, item_name, by_label)
- def toggle(self, item_name,
- name=None, type=None, kind=None, id=None, nr=None,
+ def toggle(self, item_name, # deprecated
+ name=None, type=None, kind=None, id=None, label=None, nr=None,
by_label=False):
"""Toggle selected state of named list item."""
- self._find_list_control(name, type, kind, id, nr).toggle(
+ self._find_list_control(name, type, kind, id, label, nr).toggle(
item_name, by_label)
- def set_single(self, selected,
- name=None, type=None, kind=None, id=None, nr=None,
- by_label=False):
+ def set_single(self, selected, # deprecated
+ name=None, type=None, kind=None, id=None, label=None,
+ nr=None, by_label=None):
"""Select / deselect list item in a control having only one item.
If the control has multiple list items, ItemCountError is raised.
@@ -2609,24 +2720,23 @@
control.toggle("on")
control.toggle_single()
- """
- self._find_list_control(name, type, kind, id, nr).set_single(
- selected, by_label)
- def toggle_single(self, name=None, type=None, kind=None, id=None, nr=None,
- by_label=False):
+ """ # by_label ignored and deprecated
+ self._find_list_control(
+ name, type, kind, id, label, nr).set_single(selected)
+ def toggle_single(self, name=None, type=None, kind=None, id=None,
+ label=None, nr=None, by_label=None): # deprecated
"""Toggle selected state of list item in control having only one item.
The rest is as for HTMLForm.set_single.__doc__.
- """
- self._find_list_control(name, type, kind, id, nr).toggle_single(
- by_label)
+ """ # by_label ignored and deprecated
+ self._find_list_control(name, type, kind, id, label, nr).toggle_single()
#---------------------------------------------------
# Form-filling method applying only to FileControls.
def add_file(self, file_object, content_type=None, filename=None,
- name=None, id=None, nr=None):
+ name=None, id=None, label=None, nr=None):
"""Add a file to be uploaded.
file_object: file-like object (with read method) from which to read
@@ -2654,13 +2764,13 @@
maxlength: XXX hint of max content length in bytes?
"""
- self.find_control(name, "file", id=id, nr=nr).add_file(
+ self.find_control(name, "file", id=id, label=label, nr=nr).add_file(
file_object, content_type, filename)
#---------------------------------------------------
# Form submission methods, applying only to clickable controls.
- def click(self, name=None, type=None, id=None, nr=0, coord=(1,1),
+ def click(self, name=None, type=None, id=None, label=None, nr=0, coord=(1,1),
request_class=urllib2.Request):
"""Return request that would result from clicking on a control.
@@ -2683,11 +2793,12 @@
difference if you clicked on an image.
"""
- return self._click(name, type, id, nr, coord, "request",
+ return self._click(name, type, id, label, nr, coord, "request",
self._request_class)
def click_request_data(self,
- name=None, type=None, id=None, nr=0, coord=(1,1),
+ name=None, type=None, id=None, label=None,
+ nr=0, coord=(1,1),
request_class=urllib2.Request):
"""As for click method, but return a tuple (url, data, headers).
@@ -2715,10 +2826,11 @@
r = conn.getresponse()
"""
- return self._click(name, type, id, nr, coord, "request_data",
+ return self._click(name, type, id, label, nr, coord, "request_data",
self._request_class)
- def click_pairs(self, name=None, type=None, id=None, nr=0, coord=(1,1)):
+ def click_pairs(self, name=None, type=None, id=None, label=None,
+ nr=0, coord=(1,1)):
"""As for click_request_data, but returns a list of (key, value) pairs.
You can use this list as an argument to ClientForm.urlencode. This is
@@ -2738,14 +2850,14 @@
instead.
"""
- return self._click(name, type, id, nr, coord, "pairs",
+ return self._click(name, type, id, label, nr, coord, "pairs",
self._request_class)
#---------------------------------------------------
def find_control(self,
- name=None, type=None, kind=None, id=None, predicate=None,
- nr=None):
+ name=None, type=None, kind=None, id=None, label=None,
+ predicate=None, nr=None):
"""Locate and return some specific control within the form.
At least one of the name, type, kind, predicate and nr arguments must
@@ -2778,27 +2890,30 @@
"""
if ((name is None) and (type is None) and (kind is None) and
- (id is None) and (predicate is None) and (nr is None)):
+ (id is None) and (label is None) and (predicate is None) and
+ (nr is None)):
raise ValueError(
"at least one argument must be supplied to specify control")
if nr is None: nr = 0
- return self._find_control(name, type, kind, id, predicate, nr)
+ return self._find_control(name, type, kind, id, label, predicate, nr)
#---------------------------------------------------
# Private methods.
def _find_list_control(self,
- name=None, type=None, kind=None, id=None, nr=None):
+ name=None, type=None, kind=None, id=None,
+ label=None, nr=None):
if ((name is None) and (type is None) and (kind is None) and
- (id is None) and (nr is None)):
+ (id is None) and (label is None) and (nr is None)):
raise ValueError(
"at least one argument must be supplied to specify control")
if nr is None: nr = 0
- return self._find_control(name, type, kind, id, is_listcontrol, nr)
+ return self._find_control(name, type, kind, id, label,
+ is_listcontrol, nr)
- def _find_control(self, name, type, kind, id, predicate, nr):
+ def _find_control(self, name, type, kind, id, label, predicate, nr):
if (name is not None) and not isstringlike(name):
raise TypeError("control name must be string-like")
if (type is not None) and not isstringlike(type):
@@ -2807,6 +2922,8 @@
raise TypeError("control kind must be string-like")
if (id is not None) and not isstringlike(id):
raise TypeError("control id must be string-like")
+ if (label is not None) and not isstringlike(label):
+ raise TypeError("control label must be string-like")
if (predicate is not None) and not callable(predicate):
raise TypeError("control predicate must be callable")
if nr < 0: raise ValueError("control number must be a positive "
@@ -2825,6 +2942,12 @@
continue
if predicate and not predicate(control):
continue
+ if label:
+ for l in control.get_labels():
+ if l.text.find(label) > -1:
+ break
+ else:
+ continue
if nr:
nr = nr - 1
continue
@@ -2835,16 +2958,18 @@
if type is not None: description.append("type '%s'" % type)
if kind is not None: description.append("kind '%s'" % kind)
if id is not None: description.append("id '%s'" % id)
+ if label is not None: description.append("label '%s'" % label)
if predicate is not None:
description.append("predicate %s" % predicate)
if orig_nr: description.append("nr %d" % orig_nr)
description = string.join(description, ", ")
raise ControlNotFoundError("no control matching "+description)
- def _click(self, name, type, id, nr, coord, return_type,
+ def _click(self, name, type, id, label, nr, coord, return_type,
request_class=urllib2.Request):
try:
- control = self._find_control(name, type, "clickable", id, None, nr)
+ control = self._find_control(
+ name, type, "clickable", id, label, None, nr)
except ControlNotFoundError:
if ((name is not None) or (type is not None) or (id is not None) or
(nr != 0)):
Modified: wwwsearch/ClientForm/trunk/examples/example.py
==============================================================================
--- wwwsearch/ClientForm/trunk/examples/example.py (original)
+++ wwwsearch/ClientForm/trunk/examples/example.py Mon Aug 29 00:26:21 2005
@@ -2,7 +2,8 @@
import ClientForm
import urllib2
-request = urllib2.Request("http://wwwsearch.sf.net/ClientForm/example.html")
+request = urllib2.Request("http://localhost/example.html")
+#request = urllib2.Request("http://wwwsearch.sf.net/ClientForm/example.html")
response = urllib2.urlopen(request)
forms = ClientForm.ParseResponse(response)
response.close()
Modified: wwwsearch/ClientForm/trunk/test.py
==============================================================================
--- wwwsearch/ClientForm/trunk/test.py (original)
+++ wwwsearch/ClientForm/trunk/test.py Mon Aug 29 00:26:21 2005
@@ -28,6 +28,37 @@
if expr: return True
else: return False
+try:
+ import warnings
+except ImportError:
+ warnings_imported = False
+ def hide_deprecations():
+ pass
+ def reset_deprecations():
+ pass
+ def raise_deprecations():
+ pass
+else:
+ warnings_imported = True
+ def hide_deprecations():
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
+ def reset_deprecations():
+ warnings.filterwarnings('default', category=DeprecationWarning)
+ def raise_deprecations():
+ try:
+ registry = ClientForm.__warningregistry__
+ except AttributeError:
+ pass
+ else:
+ registry.clear()
+ warnings.filterwarnings('error', category=DeprecationWarning)
+
+class DummyForm:
+ def __init__(self):
+ self._forms = []
+ self._labels = []
+ self._id_to_labels = {}
+
class UnescapeTests(TestCase):
def test_unescape(self):
from ClientForm import unescape
@@ -185,9 +216,13 @@
entity_ctl = form.find_control(type="select")
self.assert_(entity_ctl.name == "foo")
self.assertEqual(entity_ctl.value[0], "Hello testers & &blah; users!")
+
+ hide_deprecations()
opt = entity_ctl.get_item_attrs("Hello testers & &blah; users!")
- self.assert_(opt["value"] == opt["label"] == opt["contents"] ==
- "Hello testers & &blah; users!")
+ reset_deprecations()
+ self.assertEqual(opt["value"], "Hello testers & &blah; users!")
+ self.assertEqual(opt["label"], "Hello testers & &blah; users!")
+ self.assertEqual(opt["contents"], "Hello testers & &blah; users!")
def testButton(self):
file = StringIO(
@@ -378,8 +413,10 @@
""")
forms = ClientForm.ParseFile(file, "http://localhost/")
form = forms[0]
+ hide_deprecations()
self.assert_(form.possible_items("foo") == ["on"])
self.assert_(form.possible_items("bar") == ["on"])
+ reset_deprecations()
#self.assert_(form.possible_items("baz") == [])
self.assert_(form["foo"] == [])
self.assert_(form["bar"] == [])
@@ -405,7 +442,9 @@
""")
forms = ClientForm.ParseFile(file, "http://localhost/")
form = forms[0]
+ hide_deprecations()
self.assert_(form.possible_items("spam") == ["1", "2"])
+ reset_deprecations()
nr_selected = len(form.find_control("spam").pairs())
self.assert_(nr_selected == 1)
@@ -505,62 +544,68 @@
control = get_control("foo")
self.assertRaises(TypeError, control.get_item_disabled)
+ hide_deprecations()
control.set_item_disabled(True, "2")
- self.assert_(str(control) == "<SelectControl(foo="
- "[1, (2), 3, 4, 5, 6, (*7), (8), 9, (10)])>")
+ reset_deprecations()
+ self.assertEqual(
+ str(control),
+ "<SelectControl(foo=[1, (2), 3, 4, 5, 6, (*7), (8), 9, (10)])>")
# list controls only allow assignment to .value if no attempt is
# made to set any disabled item...
# ...multi selection
control = get_control("foo")
- self.assert_(control.value == ["7"])
+ # disabled items are not part of the submitted value, so "7" not
+ # included (they are not "successful":
+ # http://www.w3.org/TR/REC-html40/interact/forms.html#successful-controls
+ # ). This behavior was confirmed in Firefox 1.0.4 at least.
+ self.assertEqual(control.value, [])
control.value = ["1"]
+ self.assertEqual(control.value, ["1"])
control = get_control("foo")
- def assign_8(control=control): control.value = ["8"]
- self.assertRaises(AttributeError, assign_8)
- self.assert_(control.value == ["7"])
- # even though 7 is set already, attempt to set it again fails
- def assign_7(control=control): control.value = ["7"]
- self.assertRaises(AttributeError, assign_7)
+ self.assertRaises(AttributeError, setattr, control, 'value', ['8'])
+ self.assertEqual(control.value, [])
+ # even though 7 is set already, attempt to set it fails
+ self.assertRaises(AttributeError, setattr, control, 'value', ['7'])
control.value = ["1", "3"]
+ self.assertEqual(control.value, ["1", "3"])
control = get_control("foo")
- def assign_multi(control=control): control.value = ["1", "7"]
- self.assertRaises(AttributeError, assign_multi)
+ self.assertRaises(AttributeError, setattr, control, 'value', ['1', '7'])
+ self.assertEqual(control.value, [])
# enable all items
- for item in control.possible_items():
- control.set_item_disabled(False, item)
- assign_multi()
+ control.set_all_items_disabled(False)
+ control.value = ['1', '7']
+ self.assertEqual(control.value, ["1", "7"])
control = get_control("foo")
- for value in 7, 8, 10:
- self.assert_(control.get_item_disabled(str(value)))
- self.assertRaises(AttributeError, control.set, True, str(value))
- control.set(False, str(value))
- self.assert_(str(value) not in control.value)
- control.set(False, str(value))
- self.assert_((str(value) not in control.value))
- self.assertRaises(AttributeError, control.toggle, str(value))
- self.assert_(str(value) not in control.value)
- self.assertRaises(AttributeError, control.set, True, str(value))
- self.assert_(str(value) not in control.value)
+ hide_deprecations()
+ for name in 7, 8, 10:
+ self.assert_(control.get_item_disabled(str(name)))
+ # a disabled option is never "successful" (see above) so never in
+ # value
+ self.assert_(str(name) not in control.value)
+ # a disabled option always is always upset if you try to set it
+ self.assertRaises(AttributeError, control.set, True, str(name))
+ self.assertRaises(AttributeError, control.set, False, str(name))
+ self.assertRaises(AttributeError, control.toggle, str(name))
+ # still not in value
+ self.assert_(str(name) not in control.value)
control = get_control("foo")
- for value in 1, 2, 3, 4, 5, 6, 9:
- self.assert_(not control.get_item_disabled(str(value)))
- control.set(False, str(value))
- self.assert_(str(value) not in control.value)
- control.toggle(str(value))
- self.assert_(str(value) in control.value)
- control.set(True, str(value))
- self.assert_(str(value) in control.value)
- control.toggle(str(value))
- self.assert_(str(value) not in control.value)
+ for name in 1, 2, 3, 4, 5, 6, 9:
+ self.assert_(not control.get_item_disabled(str(name)))
+ control.set(False, str(name))
+ self.assert_(str(name) not in control.value)
+ control.toggle(str(name))
+ self.assert_(str(name) in control.value)
+ control.set(True, str(name))
+ self.assert_(str(name) in control.value)
+ control.toggle(str(name))
+ self.assert_(str(name) not in control.value)
control = get_control("foo")
self.assert_(control.get_item_disabled("7"))
- control.toggle("7") # clearing, not setting, so no problem
- self.assertRaises(AttributeError, control.set, True, "7")
control.set_item_disabled(True, "7")
self.assert_(control.get_item_disabled("7"))
self.assertRaises(AttributeError, control.set, True, "7")
@@ -570,56 +615,59 @@
control.set(False, "7")
control.toggle("7")
control.toggle("7")
+ reset_deprecations()
# ...single-selection
control = get_control("bar")
- self.assert_(control.value == ["7"])
- control.value = ["1"]
+ # 7 is selected but disabled, so 1 is selected by browser
+ self.assertEqual(control.value, ['1'])
+ control.value = ["2"]
+
control = get_control("bar")
def assign_8(control=control): control.value = ["8"]
self.assertRaises(AttributeError, assign_8)
- self.assert_(control.value == ["7"])
- # even though 7 is set already, attempt to set it again fails
+ self.assertEqual(control.value, ['1'])
def assign_7(control=control): control.value = ["7"]
self.assertRaises(AttributeError, assign_7)
# enable all items
- for item in control.possible_items():
- control.set_item_disabled(False, item)
+ control.set_all_items_disabled(False)
assign_7()
+ self.assertEqual(control.value, ['7'])
control = get_control("bar")
- for value in 7, 8, 10:
- self.assert_(control.get_item_disabled(str(value)))
- self.assertRaises(AttributeError, control.set, True, str(value))
- control.set(False, str(value))
- self.assert_(str(value) != control.value)
- control.set(False, str(value))
- self.assert_(str(value) != control.value)
- self.assertRaises(AttributeError, control.toggle, str(value))
- self.assert_(str(value) != control.value)
- self.assertRaises(AttributeError, control.set, True, str(value))
- self.assert_(str(value) != control.value)
+ hide_deprecations()
+ for name in 7, 8, 10:
+ self.assert_(control.get_item_disabled(str(name)))
+ # a disabled option is never "successful" (see above) so never in
+ # value
+ self.assert_(str(name) not in control.value)
+ # a disabled option always is always upset if you try to set it
+ self.assertRaises(AttributeError, control.set, True, str(name))
+ self.assertRaises(AttributeError, control.set, False, str(name))
+ self.assertRaises(AttributeError, control.toggle, str(name))
+ # still not in value
+ self.assert_(str(name) not in control.value)
control = get_control("bar")
- for value in 1, 2, 3, 4, 5, 6, 9:
- self.assert_(not control.get_item_disabled(str(value)))
- control.set(False, str(value))
- self.assert_(str(value) not in control.value)
- control.toggle(str(value))
- self.assert_(str(value) == control.value[0])
- control.set(True, str(value))
- self.assert_(str(value) == control.value[0])
- control.toggle(str(value))
- self.assert_(str(value) not in control.value)
+ for name in 1, 2, 3, 4, 5, 6, 9:
+ self.assert_(not control.get_item_disabled(str(name)))
+ control.set(False, str(name))
+ self.assert_(str(name) not in control.value)
+ control.toggle(str(name))
+ self.assert_(str(name) == control.value[0])
+ control.set(True, str(name))
+ self.assert_(str(name) == control.value[0])
+ control.toggle(str(name))
+ self.assert_(str(name) not in control.value)
control = get_control("bar")
self.assert_(control.get_item_disabled("7"))
- control.toggle("7") # clearing, not setting, so no problem
- self.assertRaises(AttributeError, control.set, True, "7")
control.set_item_disabled(True, "7")
self.assert_(control.get_item_disabled("7"))
self.assertRaises(AttributeError, control.set, True, "7")
+ self.assertEqual(control.value, ['1'])
control.set_item_disabled(False, "7")
+ self.assertEqual(control.value, ['1'])
self.assert_(not control.get_item_disabled("7"))
control.set(True, "7")
control.set(False, "7")
@@ -635,6 +683,7 @@
control.set_all_items_disabled(True)
self.assertRaises(AttributeError, control.set, True, "7")
self.assertRaises(AttributeError, control.set, True, "1")
+ reset_deprecations()
# XXX single select
def testDisabledSelect(self):
@@ -675,36 +724,42 @@
("baz", True, False),
("spam", True, True)]:
control = form.find_control(name)
- self.assert_(bool(control.disabled) == control_disabled)
+ self.assertEqual(bool(control.disabled), control_disabled)
+ hide_deprecations()
item = control.get_item_attrs("2")
- self.assert_(bool(item.has_key("disabled")) == item_disabled)
+ reset_deprecations()
+ self.assertEqual(bool(item.has_key("disabled")), item_disabled)
def bad_assign(value, control=control): control.value = value
+ hide_deprecations()
if control_disabled:
- for value in "1", "2", "3":
- self.assertRaises(AttributeError, control.set, True, value)
- self.assertRaises(AttributeError, bad_assign, [value])
+ for name in "1", "2", "3":
+ self.assertRaises(AttributeError, control.set, True, name)
+ self.assertRaises(AttributeError, bad_assign, [name])
elif item_disabled:
self.assertRaises(AttributeError, control.set, True, "2")
self.assertRaises(AttributeError, bad_assign, ["2"])
- for value in "1", "3":
- control.set(True, value)
+ for name in "1", "3":
+ control.set(True, name)
else:
control.value = ["1", "2", "3"]
+ reset_deprecations()
control = form.find_control("foo")
# missing disabled arg
+ hide_deprecations()
self.assertRaises(TypeError, control.set_item_disabled, "1")
# by_label
self.assert_(not control.get_item_disabled("a", by_label=True))
control.set_item_disabled(True, "a", by_label=True)
self.assert_(control.get_item_disabled("a", by_label=True))
+ reset_deprecations()
def testDisabledCheckbox(self):
file = StringIO(
"""<form action="abc" name="myform">
-<input type="checkbox" name="foo" value="1" label="a"></input>
+<label><input type="checkbox" name="foo" value="1"></input> a</label>
<input type="checkbox" name="foo" value="2"></input>
<input type="checkbox" name="foo" value="3"></input>
@@ -725,6 +780,7 @@
("baz", False, True)]:
control = form.find_control(name)
self.assert_(bool(control.disabled) == control_disabled)
+ hide_deprecations()
item = control.get_item_attrs("2")
self.assert_(bool(item.has_key("disabled")) == item_disabled)
self.assert_(control.get_item_disabled("2") == item_disabled)
@@ -737,19 +793,19 @@
control.set(True, "1")
else:
control.value = ["1", "2", "3"]
+ reset_deprecations()
control = form.find_control("foo")
+ hide_deprecations()
control.set_item_disabled(False, "1")
# missing disabled arg
self.assertRaises(TypeError, control.set_item_disabled, "1")
# by_label
- self.assertRaises(NotImplementedError,
- control.get_item_disabled, "a", by_label=True)
- self.assert_(not control.get_item_disabled("1"))
- self.assertRaises(NotImplementedError,
- control.set_item_disabled, True, "a",
- by_label=True)
+ self.failIf(control.get_item_disabled('a', by_label=True))
self.assert_(not control.get_item_disabled("1"))
+ control.set_item_disabled(True, 'a', by_label=True)
+ self.assert_(control.get_item_disabled("1"))
+ reset_deprecations()
class ControlTests(TestCase):
@@ -1017,7 +1073,9 @@
self.assert_(c.type == "checkbox")
self.assert_(c.name == "name_value")
self.assert_(c.value == [])
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value"])
+ reset_deprecations()
def set_type(c=c): c.type = "sometype"
self.assertRaises(AttributeError, set_type)
self.assert_(c.type == "checkbox")
@@ -1034,28 +1092,34 @@
c.fixup()
self.assert_(str(c) == "<CheckboxControl("
"name_value=[value_value, value_value2])>")
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value", "value_value2"])
attrs = c.get_item_attrs("value_value")
for key in "alt", "name", "value", "type":
self.assert_(attrs.has_key(key))
self.assertRaises(ItemNotFoundError, c.get_item_attrs, "oops")
+ reset_deprecations()
def set_value(value, c=c): c.value = value
c.value = ["value_value", "value_value2"]
self.assert_(c.value == ["value_value", "value_value2"])
c.value = ["value_value"]
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
self.assertRaises(ItemNotFoundError, set_value, ["oops"])
self.assertRaises(TypeError, set_value, "value_value")
c.value = ["value_value2"]
self.assert_(c.value == ["value_value2"])
+ hide_deprecations()
c.toggle("value_value")
self.assert_(c.value == ["value_value", "value_value2"])
c.toggle("value_value2")
+ reset_deprecations()
self.assert_(c.value == ["value_value"])
+ hide_deprecations()
self.assertRaises(ItemNotFoundError, c.toggle, "oops")
+ reset_deprecations()
self.assert_(c.value == ["value_value"])
c.readonly = True
@@ -1065,6 +1129,7 @@
self.assert_(c.value == [])
# set
+ hide_deprecations()
c.set(True, "value_value")
self.assert_(c.value == ["value_value"])
c.set(True, "value_value2")
@@ -1079,6 +1144,7 @@
self.assertRaises(TypeError, c.set, True, ["value_value"])
self.assertRaises(ItemNotFoundError, c.set, False, "oops")
self.assertRaises(TypeError, c.set, False, ["value_value"])
+ reset_deprecations()
self.assert_(str(c) == "<CheckboxControl("
"name_value=[*value_value, value_value2])>")
@@ -1125,7 +1191,9 @@
self.assert_(c.type == "select")
self.assert_(c.name == "select_name")
self.assert_(c.value == [])
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value"])
+ reset_deprecations()
self.assert_(c.attrs.has_key("name"))
self.assert_(c.attrs.has_key("type"))
self.assert_(c.attrs["alt"] == "alt_text")
@@ -1143,10 +1211,12 @@
c.fixup()
self.assert_(str(c) == "<SelectControl("
"select_name=[value_value, value_value2])>")
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value", "value_value2"])
# get_item_attrs
attrs3 = c.get_item_attrs("value_value")
+ reset_deprecations()
self.assert_(attrs3.has_key("alt"))
self.assert_(not attrs3.has_key("multiple"))
# HTML attributes dictionary should have been copied by ListControl
@@ -1155,24 +1225,28 @@
attrs2["new_attr2"] = "new2"
for key in ("new_attr", "new_attr2"):
self.assert_(not attrs3.has_key(key))
+ hide_deprecations()
self.assertRaises(ItemNotFoundError, c.get_item_attrs, "oops")
+ reset_deprecations()
c.value = ["value_value", "value_value2"]
self.assert_(c.value == ["value_value", "value_value2"])
c.value = ["value_value"]
- self.assert_(c.value == ["value_value"])
+ self.assertEqual(c.value, ["value_value"])
def set_value(value, c=c): c.value = value
self.assertRaises(ItemNotFoundError, set_value, ["oops"])
self.assertRaises(TypeError, set_value, "value_value")
self.assertRaises(TypeError, set_value, None)
c.value = ["value_value2"]
self.assert_(c.value == ["value_value2"])
+ hide_deprecations()
c.toggle("value_value")
self.assert_(c.value == ["value_value", "value_value2"])
c.toggle("value_value2")
self.assert_(c.value == ["value_value"])
self.assertRaises(ItemNotFoundError, c.toggle, "oops")
self.assert_(c.value == ["value_value"])
+ reset_deprecations()
c.readonly = True
self.assertRaises(AttributeError, c.clear)
@@ -1184,6 +1258,7 @@
c.value = ["value_value2", "value_value"]
self.assert_(c.value == ["value_value", "value_value2"])
# set
+ hide_deprecations()
c.set(True, "value_value")
self.assert_(c.value == ["value_value", "value_value2"])
c.set(True, "value_value2")
@@ -1196,6 +1271,7 @@
self.assertRaises(TypeError, c.set, True, ["value_value"])
self.assertRaises(ItemNotFoundError, c.set, False, "oops")
self.assertRaises(TypeError, c.set, False, ["value_value"])
+ reset_deprecations()
c.value = []
self.assert_(c.value == [])
@@ -1233,10 +1309,12 @@
c = ClientForm.SelectControl("select", "select_name", attrs)
c2 = ClientForm.SelectControl("select", "select_name", attrs2)
c3 = ClientForm.SelectControl("select", "select_name", attrs3)
+ c._form = DummyForm()
c.merge_control(c2)
c.merge_control(c3)
c.fixup()
+ hide_deprecations()
self.assert_(c.possible_items() == ["0", "1", "2000"])
self.assert_(c.possible_items(by_label=True) ==
["2002", "2001", "2000"])
@@ -1253,6 +1331,7 @@
self.assertRaises(ItemNotFoundError, c.toggle, "blah", by_label=True)
self.assert_(c.value == [])
c.toggle("2000")
+ reset_deprecations()
self.assert_(c.value == ["2000"])
self.assert_(c.get_value_by_label() == ["2000"])
@@ -1262,7 +1341,7 @@
self.assertRaises(TypeError, set_value, None)
self.assert_(c.value == ["2000"])
c.value = ["0"]
- self.assert_(c.value == ["0"])
+ self.assertEqual(c.value, ["0"])
c.value = []
self.assertRaises(TypeError, c.set_value_by_label, "2002")
c.set_value_by_label(["2002"])
@@ -1282,6 +1361,7 @@
self.assert_(c.value == [])
c.set_value_by_label(["2000", "2002"])
+ hide_deprecations()
c.set(False, "2002", by_label=True)
self.assert_(c.get_value_by_label() == c.value == ["2000"])
c.set(False, "2002", by_label=True)
@@ -1299,6 +1379,7 @@
by_label=True)
self.assertRaises(ItemNotFoundError, c.set,
False, "blah", by_label=True)
+ reset_deprecations()
def testSelectControlSingle_label(self):
import ClientForm
@@ -1331,13 +1412,16 @@
c = ClientForm.SelectControl("select", "select_name", attrs)
c2 = ClientForm.SelectControl("select", "select_name", attrs2)
c3 = ClientForm.SelectControl("select", "select_name", attrs3)
+ c._form = DummyForm()
c.merge_control(c2)
c.merge_control(c3)
c.fixup()
+ hide_deprecations()
self.assert_(c.possible_items() == ["0", "1", "2000"])
self.assert_(c.possible_items(by_label=True) ==
["2002", "2001", "2000"])
+ reset_deprecations()
def set_value(value, c=c): c.value = value
self.assertRaises(ItemNotFoundError, set_value, ["2002"])
@@ -1348,6 +1432,7 @@
self.assert_(c.value == [])
c.value = ["0"]
self.assert_(c.value == ["0"])
+
c.value = []
self.assertRaises(TypeError, c.set_value_by_label, "2002")
self.assertRaises(ItemNotFoundError, c.set_value_by_label, ["foo"])
@@ -1379,7 +1464,9 @@
self.assert_(c.type == "select")
self.assert_(c.name == "select_name")
self.assert_(c.value == ["value_value"])
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value"])
+ reset_deprecations()
self.assert_(c.attrs.has_key("name"))
self.assert_(c.attrs.has_key("type"))
self.assert_(c.attrs["alt"] == "alt_text")
@@ -1406,7 +1493,9 @@
self.assert_(c.value == ["value_value"])
self.assert_(str(c) == "<SelectControl("
"select_name=[*value_value, value_value2])>")
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value", "value_value2"])
+ reset_deprecations()
def set_value(value, c=c): c.value = value
self.assertRaises(ItemCountError, set_value,
@@ -1419,9 +1508,11 @@
self.assert_(c.value == ["value_value"])
self.assertRaises(ItemNotFoundError, set_value, ["oops"])
self.assert_(c.value == ["value_value"])
+ hide_deprecations()
c.toggle("value_value")
self.assertRaises(ItemNotFoundError, c.toggle, "oops")
self.assertRaises(TypeError, c.toggle, ["oops"])
+ reset_deprecations()
self.assert_(c.value == [])
c.value = ["value_value"]
self.assert_(c.value == ["value_value"])
@@ -1429,6 +1520,7 @@
c.value = []
self.assert_(c.value == [])
+ hide_deprecations()
c.set(True, "value_value")
self.assert_(c.value == ["value_value"])
c.readonly = True
@@ -1454,6 +1546,7 @@
self.assertRaises(TypeError, c.set, True, ["value_value"])
self.assertRaises(ItemNotFoundError, c.set, False, "oops")
self.assertRaises(TypeError, c.set, False, ["value_value"])
+ reset_deprecations()
def testRadioControl(self):
attrs = {"type": "this is ignored",
@@ -1467,7 +1560,9 @@
self.assert_(c.name == "name_value")
self.assert_(c.id == "blah")
self.assert_(c.value == [])
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value"])
+ reset_deprecations()
# ...and RFC 1866 behaviour
c = ClientForm.RadioControl("radio", "name_value", attrs,
select_default=True)
@@ -1485,7 +1580,9 @@
c.fixup()
self.assert_(str(c) == "<RadioControl("
"name_value=[*value_value, value_value2])>")
+ hide_deprecations()
self.assert_(c.possible_items() == ["value_value", "value_value2"])
+ reset_deprecations()
def set_value(value, c=c): c.value = value
self.assertRaises(ItemCountError, set_value,
@@ -1498,6 +1595,7 @@
self.assert_(c.value == ["value_value"])
self.assertRaises(ItemNotFoundError, set_value, ["oops"])
self.assert_(c.value == ["value_value"])
+ hide_deprecations()
c.toggle("value_value")
self.assert_(c.value == [])
c.toggle("value_value")
@@ -1509,6 +1607,7 @@
self.assert_(c.value == [])
c.set(True, "value_value")
+ reset_deprecations()
self.assert_(c.value == ["value_value"])
c.readonly = True
self.assertRaises(AttributeError, c.clear)
@@ -1517,6 +1616,7 @@
self.assert_(c.value == [])
# set
+ hide_deprecations()
c.set(True, "value_value")
self.assert_(c.value == ["value_value"])
c.set(True, "value_value")
@@ -1533,10 +1633,85 @@
self.assertRaises(TypeError, c.set, True, ["value_value"])
self.assertRaises(ItemNotFoundError, c.set, False, "oops")
self.assertRaises(TypeError, c.set, False, ["value_value"])
-
+ reset_deprecations()
+ # tests for multiple identical values
+
+ attrs = {"type": "this is ignored",
+ "name": "name_value",
+ "value": "value_value",
+ "id": "name_value_1"}
+ c1 = ClientForm.RadioControl("radio", "name_value", attrs)
+ attrs = {"type": "this is ignored",
+ "name": "name_value",
+ "value": "value_value",
+ "id": "name_value_2",
+ "checked": "checked"}
+ c2 = ClientForm.RadioControl("radio", "name_value", attrs)
+ attrs = {"type": "this is ignored",
+ "name": "name_value",
+ "value": "another_value",
+ "id": "name_value_3",
+ "__label": {"__text": "Third Option"}}
+ c3 = ClientForm.RadioControl("radio", "name_value", attrs)
+ form = DummyForm()
+ c1._form = form
+ c1.merge_control(c2)
+ c1.merge_control(c3)
+ c1.fixup()
+ self.assertEqual(c1.value, ['value_value'])
+ hide_deprecations()
+ self.assertEqual(
+ c1.possible_items(),
+ ['value_value', 'value_value', 'another_value'])
+ reset_deprecations()
+ self.assertEqual(c1.value, ['value_value'])
+ self.failIf(c1.items[0].selected)
+ self.failUnless(c1.items[1].selected)
+ self.failIf(c1.items[2].selected)
+ c1.value = ['value_value'] # should be no change
+ self.failUnless(c1.items[1].selected)
+ self.assertEqual(c1.value, ['value_value'])
+ c1.value = ['another_value']
+ self.failUnless(c1.items[2].selected)
+ self.assertEqual(c1.value, ['another_value'])
+ c1.value = ['value_value']
+ self.failUnless(c1.items[0].selected)
+ self.assertEqual(c1.value, ['value_value'])
+
+ # id labels
+ form._id_to_labels['name_value_1'] = [
+ ClientForm.Label({'for': 'name_value_1', '__text':'First Option'})]
+ form._id_to_labels['name_value_2'] = [
+ ClientForm.Label({'for': 'name_value_2',
+ '__text':'Second Option'})]
+ form._id_to_labels['name_value_3'] = [
+ ClientForm.Label({'for': 'name_value_3',
+ '__text':'Last Option'})] # notice __label above
+ self.assertEqual([l.text for l in c1.items[0].get_labels()],
+ ['First Option'])
+ self.assertEqual([l.text for l in c1.items[1].get_labels()],
+ ['Second Option'])
+ self.assertEqual([l.text for l in c1.items[2].get_labels()],
+ ['Third Option', 'Last Option'])
+ self.assertEqual(c1.get_value_by_label(), ['First Option'])
+ c1.set_value_by_label(['Second Option'])
+ self.assertEqual(c1.get_value_by_label(), ['Second Option'])
+ self.assertEqual(c1.value, ['value_value'])
+ c1.set_value_by_label(['Third Option'])
+ self.assertEqual(c1.get_value_by_label(), ['Third Option'])
+ self.assertEqual(c1.value, ['another_value'])
+ c1.items[1].selected = True
+ self.assertEqual(c1.get_value_by_label(), ['Second Option'])
+ self.assertEqual(c1.value, ['value_value'])
+ c1.set_value_by_label(['Last Option']) # by second label
+ self.assertEqual(c1.get_value_by_label(), ['Third Option'])
+ self.assertEqual(c1.value, ['another_value'])
+ c1.set_value_by_label(['irst']) # by substring
+ self.assertEqual(c1.get_value_by_label(), ['First Option'])
class FormTests(TestCase):
base_uri = "http://auth.athensams.net/"
+
def test_click(self):
file = StringIO(
"""<form action="abc" name="myform">
@@ -1658,9 +1833,10 @@
for i in range(len(keys)):
name = keys[i]
type = types[i]
- self.assert_(fc(name).value == form.get_value(name) == values[i])
- self.assert_(fc(name).type == type)
- self.assert_(fc(name, type).name == name)
+ self.assertEqual(fc(name).value, form.get_value(name))
+ self.assertEqual(fc(name).value, values[i])
+ self.assertEqual(fc(name).type, type)
+ self.assertEqual(fc(name, type).name, name)
self.assert_(fc(type="hidden").name == "SID")
self.assert_(fc(type="image").name == "Home")
self.assert_(fc(nr=6).name == "Search")
@@ -1700,6 +1876,7 @@
("doctype", "All document types"), ("Sort", "Latest date"),
("Form", "General"), ("Func", "Search")])
+ hide_deprecations()
pvs = form.possible_items("languagetype")
self.assert_(pvs[0] == "All languages")
self.assert_(len(pvs) == 47)
@@ -1759,6 +1936,7 @@
self.assertRaises(TypeError, form.set, False, ["Relevance"], "Sort")
self.assertRaises(TypeError, form.set, True, "Relevance", ["Sort"])
self.assertRaises(TypeError, form.set, False, "Relevance", ["Sort"])
+ reset_deprecations()
form["Sort"] = ["Relevance"]
self.assert_(form["Sort"] == ["Relevance"])
@@ -1767,13 +1945,154 @@
self.assertRaises(ControlNotFoundError, setitem, "oops", ["blah"])
self.assertRaises(TypeError, setitem, ["Sort"], ["Relevance"])
+ def testSearchByLabel(self):
+ f = StringIO("""\
+<form>
+<table>
+ <tr>
+ <td><label for="form.title">Book Title</label></td>
+ <td><input type="text" id="form.title" name="form.title"
+ value="The Grapes of Wrath" /></tr>
+ </tr>
+ <tr>
+ <td>Quality</td>
+ <td>
+ <div>
+ <label><input type="radio" id="form.quality.good" name="form.quality"
+ value="good" /> Good</label>
+ </div><div>
+ <label><input type="radio" id="form.quality.indifferent"
+ name="form.quality" value="indifferent" />
+ Indifferent</label>
+ </div><div>
+ <label><input type="radio" id="form.quality.bad" name="form.quality"
+ value="bad" /> Bad</label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="form.country" blah="foo">Country of Origin</label></td>
+ <td>
+ <select id="form.country" name="form.country">
+ <option value="albania">Albania</option>
+ <optgroup label="European Union">
+ <option label="GB" value="EU: Great Britain">Great Britain</option>
+ </optgroup>
+ <option value="USA">United States of America</option>
+ <option value="zimbabwe">Zimbabwe</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>Genre</label></td>
+ <td>
+ <div>
+ <label><input type="checkbox" id="form.genre.western" name="form.genre"
+ value="western" /> Western</label>
+ </div><div>
+ <label><input type="checkbox" id="form.genre.sciencefiction"
+ name="form.genre" value="scifi" />
+ Science Fiction</label>
+ </div><div>
+ <label><input type="checkbox" id="form.genre.horror" name="form.genre"
+ value="horror" /> Horror</label>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="form.password">Password</label></td>
+ <td><input type="text" id="form.password" name="form.password"
+ value="123" /></tr>
+ </tr>
+</table>
+<input type="submit" value="Submit" />
+</form>
+""")
+ form = ClientForm.ParseFile(f, "http://localhost/")[0]
+ self.assertEqual(form.find_control(label='Title').value,
+ 'The Grapes of Wrath')
+ self.assertEqual(form.find_control(label='Submit').value,
+ 'Submit')
+ self.assertEqual(
+ form.find_control(label="Country").get(
+ 'Britain', by_label=True).name, 'EU: Great Britain')
+ self.assertEqual(
+ form.find_control(label="Origin").get(
+ 'GB', by_label=True).name, 'EU: Great Britain')
+ self.assertEqual(form.find_control(label='Password').value,
+ '123')
+ self.assertEqual(form.find_control(label='Title').value,
+ 'The Grapes of Wrath')
+
+ # test deprecation
+ if warnings_imported:
+ try:
+ for c, f in (
+ (form.find_control('form.genre'), 'western'),
+ (form.find_control('form.country'), 'zimbabwe'),
+ (form.find_control('form.quality'), 'good')):
+ # warnings are nasty. :-(
+ raise_deprecations() # clear onceregistry
+ try:
+ c.possible_items()
+ except DeprecationWarning:
+ pass
+ else:
+ self.fail('deprecation failed')
+ # warnings are nasty. :-(
+ try:
+ c.toggle_single()
+ except DeprecationWarning:
+ pass
+ else:
+ self.fail('deprecation failed')
+ # warnings are nasty. :-(
+ try:
+ c.set_single(True)
+ except DeprecationWarning:
+ pass
+ else:
+ self.fail('deprecation failed')
+ # warnings are nasty. :-(
+ try:
+ c.toggle(f)
+ except DeprecationWarning:
+ pass
+ else:
+ self.fail('deprecation failed')
+ # warnings are nasty. :-(
+ try:
+ c.get_item_disabled(f)
+ except DeprecationWarning:
+ pass
+ else:
+ self.fail('deprecation failed')
+ # warnings are nasty. :-(
+ try:
+ c.set_item_disabled(True, f)
+ except DeprecationWarning:
+ pass
+ else:
+ self.fail('deprecation failed')
+ # warnings are nasty. :-(
+ try:
+ c.get_item_attrs(True, f)
+ except DeprecationWarning:
+ pass
+ else:
+ self.fail('deprecation failed')
+ finally:
+ reset_deprecations()
+
def testResults(self):
file = open("./testdata/Results.html", "r")
forms = ClientForm.ParseFile(file, self.base_uri)
self.assert_(len(forms) == 1)
form = forms[0]
+ hide_deprecations()
pvs = form.possible_items("marked_list_candidates")
+ reset_deprecations()
self.assert_(pvs == [
"000174872000059/1", "000174858300003/2", "000174827900006/3"])
def bad_setitem(form=form):
@@ -1836,13 +2155,17 @@
def make_form(self):
f = StringIO("""\
<form blah="nonsense" name="formname">
- <input type="checkbox" name="a" value="1" id="1a" blah="spam"></input>
- <input type="checkbox" name="a" value="2" blah="eggs"></input>
+ <label><input type="checkbox" name="a" value="1" id="1a" blah="spam"></input>
+ One</label>
+ <label><input type="checkbox" name="a" value="2" blah="eggs"></input>
+ Two</label>
<input type="checkbox" name="a" value="3" id="3a"></input>
+ <label for="3a">Three</label>
- <input type="radio" name="b" value="1"></input>
- <input type="radio" name="b" value="2" id="2"></input>
+ <label><input type="radio" name="b" value="1"></input> One</label>
+ <label><input type="radio" name="b" value="2" id="2"></input> Two</label>
<input type="radio" name="b" value="3" id="3"></input>
+ <label for="3">Three</label>
<select name="c" id="cselect" blah="foo">
<option id="coption1" blah="bar">1</option>
@@ -1871,8 +2194,14 @@
self.assert_(form.get_value(
"b", "radio", "singlelist", None, 0, False) == [])
- self.assertRaises(NotImplementedError,
- form.set_value, ["1"], "b", by_label=True)
+ form.set_value(["One"], "b", by_label=True)
+ self.assertEqual(
+ form.get_value("b", "radio", "singlelist", None, 0, False),
+ ["1"])
+ form.set_value(["Three"], "b", by_label=True)
+ self.assertEqual(
+ form.get_value("b", "radio", "singlelist", None, 0, False),
+ ["3"])
def test_id(self):
form = self.make_form()
@@ -1890,27 +2219,33 @@
def test_single(self):
form = self.make_form()
+ hide_deprecations()
self.assertRaises(ItemCountError, form.set_single, True, "d")
-
- self.assertRaises(NotImplementedError,
- form.set_single, True, "e", by_label=True)
+ form.set_single(True, 'e', by_label=True)
+ self.assertEqual(form.get_value("e"), ["1"])
+ form.set_single(False, 'e', by_label=True)
+ self.assertEqual(form.get_value("e"), [])
form.toggle_single("e", "checkbox", "list", nr=0)
self.assert_("1" in form.get_value("e"))
form.set_single(False, "e", "checkbox", "list", nr=0)
self.assert_("1" not in form.get_value("e"))
form.set_single(True, "e", "checkbox", "list", nr=0)
self.assert_("1" in form.get_value("e"))
+ reset_deprecations()
def test_possible_items(self):
form = self.make_form()
-
+ hide_deprecations()
self.assert_(form.possible_items("c") == ["1", "2", "3"])
self.assert_(form.possible_items("d", by_label=True) ==
["l1", "l2", "l3"])
self.assert_(form.possible_items("a") == ["1", "2", "3"])
- self.assertRaises(NotImplementedError,
- form.possible_items, "a", by_label=True)
+ self.assertEqual(form.possible_items('e', by_label=True),
+ [None])
+ self.assertEqual(form.possible_items('a', by_label=True),
+ ['One', 'Two', 'Three'])
+ reset_deprecations()
def test_set_all_readonly(self):
form = self.make_form()
@@ -1948,7 +2283,8 @@
self.assert_(form.attrs["name"] == "formname")
a = form.find_control("a")
- self.assert_(not hasattr(a, "attrs"))
+ self.assertRaises(AttributeError, getattr, a, 'attrs')
+ hide_deprecations()
self.assert_(a.get_item_attrs("1")["blah"] == "spam")
self.assert_(a.get_item_attrs("2")["blah"] == "eggs")
self.assert_(not a.get_item_attrs("3").has_key("blah"))
@@ -1958,7 +2294,7 @@
self.assert_(c.get_item_attrs("1")["blah"] == "bar")
self.assert_(c.get_item_attrs("2")["blah"] == "baz")
self.assert_(not c.get_item_attrs("3").has_key("blah"))
-
+ reset_deprecations()
def startswith(string, initial):
if len(initial) > len(string): return False
More information about the wwwsearch-commits
mailing list