QuantityRangeWidget

[email protected]
Newsgroups gmane.comp.web.quixote.user
Message-ID <[email protected]>
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 <optgroup>), 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(" &nbsp; to &nbsp; ")
        tio += self.get_widget('high').render_content()
        tio += htmltext(" &nbsp ")
        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 <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.