[predicates] ExpectedValuePredicate now accepts a dict parameter
authorAdrien Di Mascio <Adrien.DiMascio@logilab.fr>
Wed, 01 Jul 2015 21:00:13 +0200
changeset 10476 62251bfdfd79
parent 10475 a1e8dbb7215b
child 10477 ee21c559f94f
[predicates] ExpectedValuePredicate now accepts a dict parameter ``match_form_params`` and ``match_kwargs`` benefit from that. For instance, the following statements are valid:: __select__ = match_form_params('vid', 'subvid') __select__ = match_form_params(vid='list', subvid='incontext') __select__ = match_form_params(vid=('list', 'tsearch')) In the latter cases, not only the parameters (``vid``/``subvid``) must be in the request form but their corresponding value must also match the expected values (or one of possible choices if `values` is a sequence). closes #5484070
predicates.py
test/unittest_predicates.py
--- a/predicates.py	Thu Jun 18 18:26:02 2015 +0200
+++ b/predicates.py	Wed Jul 01 21:00:13 2015 +0200
@@ -394,7 +394,7 @@
     """
     def __init__(self, *expected, **kwargs):
         assert expected, self
-        if len(expected) == 1 and isinstance(expected[0], set):
+        if len(expected) == 1 and isinstance(expected[0], (set, dict)):
             self.expected = expected[0]
         else:
             self.expected = frozenset(expected)
@@ -409,7 +409,21 @@
 
     def __call__(self, cls, req, **kwargs):
         values = self._values_set(cls, req, **kwargs)
-        matching = len(values & self.expected)
+        if isinstance(values, dict):
+            if isinstance(self.expected, dict):
+                matching = 0
+                for key, expected_value in self.expected.items():
+                    if key in values:
+                        if (isinstance(expected_value, (list, tuple, frozenset, set))
+                            and values[key] in expected_value):
+                            matching += 1
+                        elif values[key] == expected_value:
+                            matching += 1
+            if isinstance(self.expected, (set, frozenset)):
+                values = frozenset(values)
+                matching = len(values & self.expected)
+        else:
+            matching = len(values & self.expected)
         if self.once_is_enough:
             return matching
         if matching == len(self.expected):
@@ -438,7 +452,7 @@
     """
 
     def _values_set(self, cls, req, **kwargs):
-        return frozenset(kwargs)
+        return kwargs
 
 
 class appobject_selectable(Predicate):
@@ -1435,8 +1449,23 @@
     in which case a single matching parameter is enough.
     """
 
+    def __init__(self, *expected, **kwargs):
+        """override default __init__ to allow either named or positional
+        parameters.
+        """
+        if kwargs and expected:
+            raise ValueError("match_form_params() can't be called with both "
+                             "positional and named arguments")
+        if expected:
+            if len(expected) == 1 and not isinstance(expected[0], basestring):
+                raise ValueError("match_form_params() positional arguments "
+                                 "must be strings")
+            super(match_form_params, self).__init__(*expected)
+        else:
+            super(match_form_params, self).__init__(kwargs)
+
     def _values_set(self, cls, req, **kwargs):
-        return frozenset(req.form)
+        return req.form
 
 
 class match_http_method(ExpectedValuePredicate):
--- a/test/unittest_predicates.py	Thu Jun 18 18:26:02 2015 +0200
+++ b/test/unittest_predicates.py	Wed Jul 01 21:00:13 2015 +0200
@@ -27,7 +27,7 @@
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.predicates import (is_instance, adaptable, match_kwargs, match_user_groups,
                                 multi_lines_rset, score_entity, is_in_state,
-                                rql_condition, relation_possible)
+                                rql_condition, relation_possible, match_form_params)
 from cubicweb.selectors import on_transition # XXX on_transition is deprecated
 from cubicweb.view import EntityAdapter
 from cubicweb.web import action
@@ -391,6 +391,102 @@
                 rset = req.execute('Any X WHERE X is IN(CWGroup, CWUser)')
                 self.assertTrue(selector(None, req, rset=rset))
 
+
+class MatchFormParamsTC(CubicWebTC):
+    """tests for match_form_params predicate"""
+
+    def test_keyonly_match(self):
+        """test standard usage: ``match_form_params('param1', 'param2')``
+
+        ``param1`` and ``param2`` must be specified in request's form.
+        """
+        web_request = self.admin_access.web_request
+        vid_selector = match_form_params('vid')
+        vid_subvid_selector = match_form_params('vid', 'subvid')
+        # no parameter => KO,KO
+        with web_request() as req:
+            self.assertEqual(vid_selector(None, req), 0)
+            self.assertEqual(vid_subvid_selector(None, req), 0)
+        # one expected parameter found => OK,KO
+        with web_request(vid='foo') as req:
+            self.assertEqual(vid_selector(None, req), 1)
+            self.assertEqual(vid_subvid_selector(None, req), 0)
+        # all expected parameters found => OK,OK
+        with web_request(vid='foo', subvid='bar') as req:
+            self.assertEqual(vid_selector(None, req), 1)
+            self.assertEqual(vid_subvid_selector(None, req), 2)
+
+    def test_keyvalue_match_one_parameter(self):
+        """test dict usage: ``match_form_params(param1=value1)``
+
+        ``param1`` must be specified in the request's form and its value
+        must be ``value1``.
+        """
+        web_request = self.admin_access.web_request
+        # test both positional and named parameters
+        vid_selector = match_form_params(vid='foo')
+        # no parameter => should fail
+        with web_request() as req:
+            self.assertEqual(vid_selector(None, req), 0)
+        # expected parameter found with expected value => OK
+        with web_request(vid='foo', subvid='bar') as req:
+            self.assertEqual(vid_selector(None, req), 1)
+        # expected parameter found but value is incorrect => KO
+        with web_request(vid='bar') as req:
+            self.assertEqual(vid_selector(None, req), 0)
+
+    def test_keyvalue_match_two_parameters(self):
+        """test dict usage: ``match_form_params(param1=value1, param2=value2)``
+
+        ``param1`` and ``param2`` must be specified in the request's form and
+        their respective value must be ``value1`` and ``value2``.
+        """
+        web_request = self.admin_access.web_request
+        vid_subvid_selector = match_form_params(vid='list', subvid='tsearch')
+        # missing one expected parameter => KO
+        with web_request(vid='list') as req:
+            self.assertEqual(vid_subvid_selector(None, req), 0)
+        # expected parameters found but values are incorrect => KO
+        with web_request(vid='list', subvid='foo') as req:
+            self.assertEqual(vid_subvid_selector(None, req), 0)
+        # expected parameters found and values are correct => OK
+        with web_request(vid='list', subvid='tsearch') as req:
+            self.assertEqual(vid_subvid_selector(None, req), 2)
+
+    def test_keyvalue_multiple_match(self):
+        """test dict usage with multiple values
+
+        i.e. as in ``match_form_params(param1=('value1', 'value2'))``
+
+        ``param1`` must be specified in the request's form and its value
+        must be either ``value1`` or ``value2``.
+        """
+        web_request = self.admin_access.web_request
+        vid_subvid_selector = match_form_params(vid='list', subvid=('tsearch', 'listitem'))
+        # expected parameters found and values correct => OK
+        with web_request(vid='list', subvid='tsearch') as req:
+            self.assertEqual(vid_subvid_selector(None, req), 2)
+        with web_request(vid='list', subvid='listitem') as req:
+            self.assertEqual(vid_subvid_selector(None, req), 2)
+        # expected parameters found but values are incorrect => OK
+        with web_request(vid='list', subvid='foo') as req:
+            self.assertEqual(vid_subvid_selector(None, req), 0)
+
+    def test_invalid_calls(self):
+        """checks invalid calls raise a ValueError"""
+        # mixing named and positional arguments should fail
+        with self.assertRaises(ValueError) as cm:
+            match_form_params('list', x='1', y='2')
+        self.assertEqual(str(cm.exception),
+                         "match_form_params() can't be called with both "
+                         "positional and named arguments")
+        # using a dict as first and unique argument should fail
+        with self.assertRaises(ValueError) as cm:
+            match_form_params({'x': 1})
+        self.assertEqual(str(cm.exception),
+                         "match_form_params() positional arguments must be strings")
+
+
 if __name__ == '__main__':
     unittest_main()