# HG changeset patch # User Sylvain Thénault # Date 1319704696 -7200 # Node ID 805d4e121b656c8c409e32901ee91c1cde0bc2c6 # Parent 58e9bc8a1f2c32564b0170ffb32fa9ae72715f09 [ui lib] facet and form widget for Integer used to store binary mask. Closes #2054771 diff -r 58e9bc8a1f2c -r 805d4e121b65 web/facet.py --- a/web/facet.py Thu Oct 27 10:38:03 2011 +0200 +++ b/web/facet.py Thu Oct 27 10:38:16 2011 +0200 @@ -33,6 +33,7 @@ .. autoclass:: cubicweb.web.facet.RQLPathFacet .. autoclass:: cubicweb.web.facet.RangeFacet .. autoclass:: cubicweb.web.facet.DateRangeFacet +.. autoclass:: cubicweb.web.facet.BitFieldFacet Classes for facets implementor ------------------------------ @@ -977,8 +978,12 @@ cleanup_select(select, filtered_variable) newvar = prepare_vocabulary_select(select, filtered_variable, self.rtype, self.role) _set_orderby(select, newvar, self.sortasc, self.sortfunc) + if self.cw_rset: + args = self.cw_rset.args + else: # vocabulary used for possible_values + args = None try: - rset = self.rqlexec(select.as_string(), self.cw_rset.args) + rset = self.rqlexec(select.as_string(), args) except Exception: self.exception('error while getting vocabulary for %s, rql: %s', self, select.as_string()) @@ -1364,6 +1369,42 @@ self.select.add_relation(var, self.rtype, self.filtered_variable) +class BitFieldFacet(AttributeFacet): + """Base facet class for Int field holding some bit values using binary + masks. + + label / value for each bit should be given using the :attr:`choices` + attribute. + + See also :class:`~cubicweb.web.formwidgets.BitSelect`. + """ + choices = None # to be set on concret class + def add_rql_restrictions(self): + value = self._cw.form.get(self.__regid__) + if not value: + return + if isinstance(value, list): + value = reduce(lambda x, y: int(x) | int(y), value) + attr_var = self.select.make_variable() + self.select.add_relation(self.filtered_variable, self.rtype, attr_var) + comp = nodes.Comparison('=', nodes.Constant(value, 'Int')) + comp.append(nodes.MathExpression('&', nodes.variable_ref(attr_var), + nodes.Constant(value, 'Int'))) + having = self.select.having + if having: + self.select.replace(having[0], nodes.And(having[0], comp)) + else: + self.select.set_having([comp]) + + def rset_vocabulary(self, rset): + mask = reduce(lambda x, y: x | (y[0] or 0), rset, 0) + return sorted([(self._cw._(label), val) for label, val in self.choices + if val & mask]) + + def possible_values(self): + return [unicode(val) for label, val in self.vocabulary()] + + ## html widets ################################################################ _DEFAULT_CONSTANT_VOCAB_WIDGET_HEIGHT = 12 diff -r 58e9bc8a1f2c -r 805d4e121b65 web/formwidgets.py --- a/web/formwidgets.py Thu Oct 27 10:38:03 2011 +0200 +++ b/web/formwidgets.py Thu Oct 27 10:38:16 2011 +0200 @@ -75,6 +75,7 @@ .. autoclass:: cubicweb.web.formwidgets.PasswordInput .. autoclass:: cubicweb.web.formwidgets.IntervalWidget +.. autoclass:: cubicweb.web.formwidgets.BitSelect .. autoclass:: cubicweb.web.formwidgets.HorizontalLayoutWidget .. autoclass:: cubicweb.web.formwidgets.EditableURLWidget @@ -452,7 +453,7 @@ oattrs.setdefault('label', label or '') options.append(u'' % uilib.sgml_attributes(oattrs)) optgroup_opened = True - elif value in curvalues: + elif self.value_selected(value, curvalues): options.append(tags.option(label, value=value, selected='selected', **oattrs)) else: @@ -468,6 +469,36 @@ return tags.select(name=field.input_name(form, self.suffix), multiple=self._multiple, options=options, **attrs) + def value_selected(self, value, curvalues): + return value in curvalues + + +class BitSelect(Select): + """Select widget for IntField using a vocabulary with bit masks as values. + + See also :class:`~cubicweb.web.facet.BitFieldFacet`. + """ + def __init__(self, attrs=None, multiple=True, **kwargs): + super(BitSelect, self).__init__(attrs, multiple=multiple, **kwargs) + + def value_selected(self, value, curvalues): + mask = reduce(lambda x, y: int(x) | int(y), curvalues, 0) + return int(value) & mask + + def process_field_data(self, form, field): + """Return process posted value(s) for widget and return something + understandable by the associated `field`. That value may be correctly + typed or a string that the field may parse. + """ + val = super(BitSelect, self).process_field_data(form, field) + if isinstance(val, list): + val = reduce(lambda x, y: int(x) | int(y), val, 0) + elif val: + val = int(val) + else: + val = 0 + return val + class CheckBox(Input): """Simple , for field having a specific diff -r 58e9bc8a1f2c -r 805d4e121b65 web/test/unittest_facet.py --- a/web/test/unittest_facet.py Thu Oct 27 10:38:03 2011 +0200 +++ b/web/test/unittest_facet.py Thu Oct 27 10:38:16 2011 +0200 @@ -195,6 +195,32 @@ self.assertEqual(f.select.as_string(), "DISTINCT Any WHERE X is CWUser, X login 'admin'") + def test_bitfield(self): + req, rset, rqlst, filtered_variable = self.prepare_rqlst( + 'CWAttribute X WHERE X ordernum XO', + expected_baserql='Any X WHERE X ordernum XO, X is CWAttribute', + expected_preparedrql='DISTINCT Any WHERE X ordernum XO, X is CWAttribute') + f = facet.BitFieldFacet(req, rset=rset, + select=rqlst.children[0], + filtered_variable=filtered_variable) + f.choices = [('un', 1,), ('deux', 2,)] + f.rtype = 'ordernum' + self.assertEqual(f.vocabulary(), + [(u'deux', 2), (u'un', 1)]) + # ensure rqlst is left unmodified + self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X ordernum XO, X is CWAttribute') + #rqlst = rset.syntax_tree() + self.assertEqual(f.possible_values(), + ['2', '1']) + # ensure rqlst is left unmodified + self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X ordernum XO, X is CWAttribute') + req.form[f.__regid__] = '3' + f.add_rql_restrictions() + # selection is cluttered because rqlst has been prepared for facet (it + # is not in real life) + self.assertEqual(f.select.as_string(), + "DISTINCT Any WHERE X ordernum XO, X is CWAttribute, X ordernum C HAVING 3 = (C & 3)") + def test_rql_path_eid(self): req, rset, rqlst, filtered_variable = self.prepare_rqlst() class RPF(facet.RQLPathFacet): diff -r 58e9bc8a1f2c -r 805d4e121b65 web/test/unittest_formwidgets.py --- a/web/test/unittest_formwidgets.py Thu Oct 27 10:38:03 2011 +0200 +++ b/web/test/unittest_formwidgets.py Thu Oct 27 10:38:16 2011 +0200 @@ -32,7 +32,7 @@ class WidgetsTC(TestCase): - def test_state_fields(self): + def test_editableurl_widget(self): field = formfields.guess_field(schema['Bookmark'], schema['path']) widget = formwidgets.EditableURLWidget() req = fake.FakeRequest(form={'path-subjectfqs:A': 'param=value&vid=view'}) @@ -40,5 +40,21 @@ self.assertEqual(widget.process_field_data(form, field), '?param=value%26vid%3Dview') + def test_bitselect_widget(self): + field = formfields.guess_field(schema['CWAttribute'], schema['ordernum']) + field.choices = [('un', '1',), ('deux', '2',)] + widget = formwidgets.BitSelect(settabindex=False) + req = fake.FakeRequest(form={'ordernum-subject:A': ['1', '2']}) + form = mock(_cw=req, formvalues={}, edited_entity=mock(eid='A'), + form_previous_values=()) + self.assertMultiLineEqual(widget._render(form, field, None), + '''\ +''') + self.assertEqual(widget.process_field_data(form, field), + 3) + if __name__ == '__main__': unittest_main()