durusmail: quixote-users: QuantityRangeWidget
QuantityRangeWidget
2005-07-18
QuantityRangeWidget
mso@oz.net
2005-07-18
Here's the widget I came up with.  The value is (low, high, unit), aka
(float, float, str) and renders like:
    ____ to ____ [unit]

It works fine, but I ended up duplicating the validation in .set_value()
and ._parse(), and also doing the subwidgets' validation.  I wasn't sure
how to get around that since .set_value() wants to raise ValueError, and
with different messages.  I could call .parse() in .set_value() but that
doesn't sound like a good idea.

The unit list would like to be two-level (HTML ), but that
doesn't seem feasable without rewriting SelectWidget.  Is anybody doing
optgroups?
 http://www.w3.org/TR/REC-html40/interact/forms.html#edef-OPTGROUP


=====
class QuantityRangeWidget(widget.CompositeWidget):
    """A widget that takes a number range and a picklist of units.
       The value is a tuple: (low, high, unit)
       'low' and 'high' are floats, 'unit' is a string.
       Null value is (None, None, anything).

       Validation:
       - Propagate errors from subwidgets (e.g., bad number).
       - If 'low' is present, 'unit' must be present.
       - If 'high' is present, both 'low' and 'unit' must be present.
       - If both 'low' and 'high' are present, 'high' must be greater
         than 'low'.
    """
    def __init__(self, name, value=(None, None, 'gallons'), **kwargs):
        options = [ (x, x, x) for x in VOLUME_UNITS + MASS_UNITS]
        widget.CompositeWidget.__init__(self, name, **kwargs)
        self.add(widget.FloatWidget, 'low', value=value[0])
        self.add(widget.FloatWidget, 'high', value=value[1])
        self.add(widget.SingleSelectWidget, 'unit', value=value[2],
            options=options)

    def render_content(self):
        tio = TemplateIO(html=True)
        tio += self.get_widget('low').render_content()
        tio += htmltext("   to   ")
        tio += self.get_widget('high').render_content()
        tio += htmltext("   ")
        tio += self.get_widget('unit').render_content()
        return tio.getvalue()

    def set_value(self, value):
        if not isinstance(value, (tuple, list)) or len(value) != 3:
            raise TypeError("value must be 3-tuple: (low, high, unit)")
        low, high, unit = value
        def floatify(n, what):
            if n is not None:
                try:
                    n = float(n)
                except TypeError:
                    raise TypeError("%s must be number" % what)
            return n
        low = floatify(low, "low value")
        high = floatify(high, "high value")
        if low is not None and (low == 0.0 or low >= 1.0):
            low = int(low)
        if high is not None and (high == 0.0 or high >= 1.0):
            high = int(high)
        is_low = low is not None
        is_high = high is not None
        is_unit = unit is not None
        if is_high and not is_low:
            raise ValueError("Must provide low number.")
        if (is_low or is_high) and not is_unit:
            raise ValueError("Must provide unit.")
        if (is_low and is_high) and low > high:
            raise ValueError("High number must be higher than low number.")
        self.value = (low, high, unit)
        self.get_widget('low').set_value(low)
        self.get_widget('high').set_value(high)
        self.get_widget('unit').set_value(unit)


    def _parse(self, request):
        low = self['low']
        high = self['high']
        unit = self['unit']
        self.value = (low, high, unit)
        for name in ['low', 'high', 'unit']:
            error = self.get_widget(name).get_error()
            if error:
                what = (name != "unit") and "value" or "control"
                tup = name.capitalize(), what, error
                self.error("%s %s: %s." % tup)
                return
        is_low = low is not None
        is_high = high is not None
        is_unit = unit is not None
        if is_high and not is_low:
            self.error = "Must provide low number."
        elif (is_low or is_high) and not is_unit:
            self.error = "Must provide unit."
        elif (is_low and is_high) and low > high:
            self.error = "Second value must be greater than first value."
=====

--
-- Mike Orr 

reply