diff options
| author | Chris McDonough <chrism@plope.com> | 2012-10-13 19:02:44 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-10-13 19:02:44 -0400 |
| commit | a187ea92eda80aebedf8d2d3c41fbbeda11a9471 (patch) | |
| tree | ad3a762fccf0d48b5300ace29b52c09298409767 | |
| parent | b33a6a79fe614bd50a9a6993d8538ac0e8469bc1 (diff) | |
| parent | dddddedc546506f6736054bd562e90a0b23def68 (diff) | |
| download | pyramid-a187ea92eda80aebedf8d2d3c41fbbeda11a9471.tar.gz pyramid-a187ea92eda80aebedf8d2d3c41fbbeda11a9471.tar.bz2 pyramid-a187ea92eda80aebedf8d2d3c41fbbeda11a9471.zip | |
Merge branch '1.4-branch'
| -rw-r--r-- | CHANGES.txt | 10 | ||||
| -rw-r--r-- | docs/narr/viewconfig.rst | 11 | ||||
| -rw-r--r-- | docs/whatsnew-1.4.rst | 10 | ||||
| -rw-r--r-- | pyramid/config/predicates.py | 49 | ||||
| -rw-r--r-- | pyramid/config/views.py | 9 | ||||
| -rw-r--r-- | pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl | 16 | ||||
| -rw-r--r-- | pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl | 16 | ||||
| -rw-r--r-- | pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt | 16 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_predicates.py | 86 |
9 files changed, 159 insertions, 64 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index df4ada7e9..95375e5ba 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,9 +1,19 @@ Next release ============ +Features +-------- + +- Allow multiple values to be specified to the ``request_param`` view/route + predicate as a sequence. Previously only a single string value was allowed. + See https://github.com/Pylons/pyramid/pull/705 + Bug Fixes --------- +- The match_param predicate's text method was fixed to sort its values. + Part of https://github.com/Pylons/pyramid/pull/705 + - 1.4a ``pyramid.scripting.prepare`` behaved differently than 1.3 series function of same name. In particular, if passed a request, it would not set the ``registry`` attribute of the request like 1.3 did. A symptom diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index f65435cc6..3c7897969 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -290,12 +290,13 @@ configured view. of the ``REQUEST_METHOD`` of the :term:`WSGI` environment. ``request_param`` - This value can be any string. A view declaration with this argument - ensures that the view will only be called when the :term:`request` has a - key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` - variable) that has a name which matches the supplied value. + This value can be any string or a sequence of strings. A view declaration + with this argument ensures that the view will only be called when the + :term:`request` has a key in the ``request.params`` dictionary (an HTTP + ``GET`` or ``POST`` variable) that has a name which matches the a + supplied value. - If the value supplied has a ``=`` sign in it, + If any value supplied has a ``=`` sign in it, e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist in the ``request.params`` dictionary, *and* the value must match the right hand side of the expression (``123``) for the view to "match" the current diff --git a/docs/whatsnew-1.4.rst b/docs/whatsnew-1.4.rst index 4e64d8162..6aa390e64 100644 --- a/docs/whatsnew-1.4.rst +++ b/docs/whatsnew-1.4.rst @@ -63,7 +63,7 @@ Partial Mako and Chameleon Template Renderings of rendering the entire template. An example asset spec: ``package:path/to/template#macroname.pt``. This will render the macro defined as ``macroname`` within the ``template.pt`` template instead of the - entire templae. + entire template. Subrequest Support ~~~~~~~~~~~~~~~~~~ @@ -78,7 +78,7 @@ Minor Feature Additions ----------------------- - :meth:`pyramid.config.Configurator.add_directive` now accepts arbitrary - callables like partials or objects implementing ``__call__`` which dont + callables like partials or objects implementing ``__call__`` which don't have ``__name__`` and ``__doc__`` attributes. See https://github.com/Pylons/pyramid/issues/621 and https://github.com/Pylons/pyramid/pull/647. @@ -112,7 +112,7 @@ Minor Feature Additions - An :meth:`pyramid.config.Configurator.add_permission` directive method was added to the Configurator. This directive registers a free-standing permission introspectable into the Pyramid introspection system. - Frameworks built atop Pyramid can thus use the the ``permissions`` + Frameworks built atop Pyramid can thus use the ``permissions`` introspectable category data to build a comprehensive list of permissions supported by a running system. Before this method was added, permissions were already registered in this introspectable category as a side effect of @@ -172,7 +172,7 @@ Backwards Incompatibilities ``bfg.routes.matchdict`` to the request's WSGI environment dictionary. These values were docs-deprecated in ``repoze.bfg`` 1.0 (effectively seven minor releases ago). If your code depended on these values, use - request.matched_route and request.matchdict instead. + ``request.matched_route`` and ``request.matchdict`` instead. - It is no longer possible to pass an environ dictionary directly to ``pyramid.traversal.ResourceTreeTraverser.__call__`` (aka @@ -223,7 +223,7 @@ Backwards Incompatibilities * ``registerEventListener``, use :meth:`pyramid.config.Configurator.testing_add_subscriber` instead. - * ``registerTemplateRenderer`` (aka `registerDummyRenderer``), use + * ``registerTemplateRenderer`` (aka ``registerDummyRenderer``), use :meth:`pyramid.config.Configurator.testing_add_template` instead. * ``registerView``, use :meth:`pyramid.config.Configurator.add_view` instead. diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py index 77b55d9b3..100c9454e 100644 --- a/pyramid/config/predicates.py +++ b/pyramid/config/predicates.py @@ -1,7 +1,5 @@ import re -from pyramid.compat import is_nonstr_iter - from pyramid.exceptions import ConfigurationError from pyramid.traversal import ( @@ -64,43 +62,48 @@ class PathInfoPredicate(object): class RequestParamPredicate(object): def __init__(self, val, config): - name = val - v = None - if '=' in name: - name, v = name.split('=', 1) - name, v = name.strip(), v.strip() - if v is None: - self._text = 'request_param %s' % (name,) - else: - self._text = 'request_param %s = %s' % (name, v) - self.name = name - self.val = v + val = as_sorted_tuple(val) + reqs = [] + for p in val: + k = p + v = None + if '=' in p: + k, v = p.split('=', 1) + k, v = k.strip(), v.strip() + reqs.append((k, v)) + self.val = val + self.reqs = reqs def text(self): - return self._text + return 'request_param %s' % ','.join( + ['%s=%s' % (x,y) if y else x for x, y in self.reqs] + ) phash = text def __call__(self, context, request): - if self.val is None: - return self.name in request.params - return request.params.get(self.name) == self.val - + for k, v in self.reqs: + actual = request.params.get(k) + if actual is None: + return False + if v is not None and actual != v: + return False + return True class HeaderPredicate(object): def __init__(self, val, config): name = val v = None if ':' in name: - name, v = name.split(':', 1) + name, val_str = name.split(':', 1) try: - v = re.compile(v) + v = re.compile(val_str) except re.error as why: raise ConfigurationError(why.args[0]) if v is None: self._text = 'header %s' % (name,) else: - self._text = 'header %s = %s' % (name, v) + self._text = 'header %s=%s' % (name, val_str) self.name = name self.val = v @@ -156,9 +159,7 @@ class RequestTypePredicate(object): class MatchParamPredicate(object): def __init__(self, val, config): - if not is_nonstr_iter(val): - val = (val,) - val = sorted(val) + val = as_sorted_tuple(val) self.val = val reqs = [ p.split('=', 1) for p in val ] self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ] diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 9ace96c1d..15263ad04 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -904,11 +904,12 @@ class ViewsConfiguratorMixin(object): request_param - This value can be any string. A view declaration with this - argument ensures that the view will only be called when the - :term:`request` has a key in the ``request.params`` + This value can be any string or any sequence of strings. A view + declaration with this argument ensures that the view will only be + called when the :term:`request` has a key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` variable) that has a - name which matches the supplied value. If the value + name which matches the supplied value (if the value is a string) + or values (if the value is a tuple). If any value supplied has a ``=`` sign in it, e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist in the ``request.params`` dictionary, *and* diff --git a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl index ac0140789..99606fe0e 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl @@ -32,7 +32,7 @@ <div class="bottom"> <div id="left" class="align-right"> <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/search.html"> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html"> <input type="text" id="q" name="q" value="" /> <input type="submit" id="x" value="Go" /> </form> @@ -44,22 +44,22 @@ <a href="http://pylonsproject.org">Pylons Website</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#reference-material">API Documentation</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#tutorials">Tutorials</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#detailed-change-history">Change History</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#sample-applications">Sample Applications</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#support-and-development">Support and Development</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a> </li> <li> <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> @@ -70,7 +70,7 @@ </div> </div> <div id="footer"> - <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + <div class="footer">© Copyright 2008-2012, Agendaless Consulting.</div> </div> </body> </html> diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl index 743eab026..4a71dd992 100644 --- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl @@ -32,7 +32,7 @@ <div class="bottom"> <div id="left" class="align-right"> <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/search.html"> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html"> <input type="text" id="q" name="q" value="" /> <input type="submit" id="x" value="Go" /> </form> @@ -44,22 +44,22 @@ <a href="http://pylonsproject.org">Pylons Website</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#reference-material">API Documentation</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#tutorials">Tutorials</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#detailed-change-history">Change History</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#sample-applications">Sample Applications</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#support-and-development">Support and Development</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a> </li> <li> <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> @@ -70,7 +70,7 @@ </div> </div> <div id="footer"> - <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + <div class="footer">© Copyright 2008-2012, Agendaless Consulting.</div> </div> </body> </html> diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt index d64f18fca..5391509fe 100644 --- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt +++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt @@ -32,7 +32,7 @@ <div class="bottom"> <div id="left" class="align-right"> <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/search.html"> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html"> <input type="text" id="q" name="q" value="" /> <input type="submit" id="x" value="Go" /> </form> @@ -44,22 +44,22 @@ <a href="http://pylonsproject.org">Pylons Website</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#reference-material">API Documentation</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#tutorials">Tutorials</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#detailed-change-history">Change History</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#sample-applications">Sample Applications</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#support-and-development">Support and Development</a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a> </li> <li> <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> @@ -70,7 +70,7 @@ </div> </div> <div id="footer"> - <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + <div class="footer">© Copyright 2008-2012, Agendaless Consulting.</div> </div> </body> </html> diff --git a/pyramid/tests/test_config/test_predicates.py b/pyramid/tests/test_config/test_predicates.py index 005b1b27a..2f0ef4132 100644 --- a/pyramid/tests/test_config/test_predicates.py +++ b/pyramid/tests/test_config/test_predicates.py @@ -117,6 +117,20 @@ class TestRequestParamPredicate(unittest.TestCase): result = inst(None, request) self.assertTrue(result) + def test___call___true_multi(self): + inst = self._makeOne(('abc', 'def =2 ')) + request = Dummy() + request.params = {'abc':'1', 'def': '2'} + result = inst(None, request) + self.assertTrue(result) + + def test___call___false_multi(self): + inst = self._makeOne(('abc=3', 'def =2 ')) + request = Dummy() + request.params = {'abc':'3', 'def': '1'} + result = inst(None, request) + self.assertFalse(result) + def test___call___false(self): inst = self._makeOne('abc') request = Dummy() @@ -130,7 +144,11 @@ class TestRequestParamPredicate(unittest.TestCase): def test_text_withval(self): inst = self._makeOne('abc= 1') - self.assertEqual(inst.text(), 'request_param abc = 1') + self.assertEqual(inst.text(), 'request_param abc=1') + + def test_text_multi(self): + inst = self._makeOne(('abc= 1', 'def')) + self.assertEqual(inst.text(), 'request_param abc=1,def') def test_phash_exists(self): inst = self._makeOne('abc') @@ -138,7 +156,7 @@ class TestRequestParamPredicate(unittest.TestCase): def test_phash_withval(self): inst = self._makeOne('abc= 1') - self.assertEqual(inst.phash(), "request_param abc = 1") + self.assertEqual(inst.phash(), "request_param abc=1") class TestMatchParamPredicate(unittest.TestCase): def _makeOne(self, val): @@ -299,6 +317,70 @@ class Test_CheckCSRFTokenPredicate(unittest.TestCase): result = inst(None, request) self.assertEqual(result, True) +class TestHeaderPredicate(unittest.TestCase): + def _makeOne(self, val): + from pyramid.config.predicates import HeaderPredicate + return HeaderPredicate(val, None) + + def test___call___true_exists(self): + inst = self._makeOne('abc') + request = Dummy() + request.headers = {'abc':1} + result = inst(None, request) + self.assertTrue(result) + + def test___call___true_withval(self): + inst = self._makeOne('abc:1') + request = Dummy() + request.headers = {'abc':'1'} + result = inst(None, request) + self.assertTrue(result) + + def test___call___true_withregex(self): + inst = self._makeOne(r'abc:\d+') + request = Dummy() + request.headers = {'abc':'1'} + result = inst(None, request) + self.assertTrue(result) + + def test___call___false_withregex(self): + inst = self._makeOne(r'abc:\d+') + request = Dummy() + request.headers = {'abc':'a'} + result = inst(None, request) + self.assertFalse(result) + + def test___call___false(self): + inst = self._makeOne('abc') + request = Dummy() + request.headers = {} + result = inst(None, request) + self.assertFalse(result) + + def test_text_exists(self): + inst = self._makeOne('abc') + self.assertEqual(inst.text(), 'header abc') + + def test_text_withval(self): + inst = self._makeOne('abc:1') + self.assertEqual(inst.text(), 'header abc=1') + + def test_text_withregex(self): + inst = self._makeOne(r'abc:\d+') + self.assertEqual(inst.text(), r'header abc=\d+') + + def test_phash_exists(self): + inst = self._makeOne('abc') + self.assertEqual(inst.phash(), 'header abc') + + def test_phash_withval(self): + inst = self._makeOne('abc:1') + self.assertEqual(inst.phash(), "header abc=1") + + def test_phash_withregex(self): + inst = self._makeOne(r'abc:\d+') + self.assertEqual(inst.phash(), r'header abc=\d+') + class predicate(object): def __repr__(self): return 'predicate' |
