CHANGES.txt | ●●●●● patch | view | raw | blame | history | |
docs/narr/urldispatch.rst | ●●●●● patch | view | raw | blame | history | |
pyramid/config/routes.py | ●●●●● patch | view | raw | blame | history | |
pyramid/tests/test_url.py | ●●●●● patch | view | raw | blame | history |
CHANGES.txt
@@ -142,6 +142,10 @@ - The ``alchemy`` scaffold tests now provide better coverage. See https://github.com/Pylons/pyramid/pull/1029 - The ``pyramid.config.Configurator.add_route`` method now supports being called with an external URL as pattern. See https://github.com/Pylons/pyramid/issues/611 for more information. Bug Fixes --------- docs/narr/urldispatch.rst
@@ -105,6 +105,7 @@ .. _route_pattern_syntax: Route Pattern Syntax ~~~~~~~~~~~~~~~~~~~~ @@ -126,6 +127,10 @@ .. code-block:: text /{foo}/bar/baz If a pattern is a valid URL it won't be ever matched against an incoming request. Instead it can be useful for generating external URLs. See :ref:`External routes <external_route_narr>` for details. A pattern segment (an individual item between ``/`` characters in the pattern) may either be a literal string (e.g. ``foo``) *or* it may be a @@ -754,9 +759,39 @@ exception to this rule is use of the ``pregenerator`` argument, which is not ignored when ``static`` is ``True``. :ref:`External routes <external_route_narr>` are implicitly static. .. versionadded:: 1.1 the ``static`` argument to :meth:`~pyramid.config.Configurator.add_route` .. _external_route_narr: External Routes --------------- .. versionadded:: 1.5 Route patterns that are valid URLs, are treated as external routes. Like :ref:`static routes <static_route_narr>` they are useful for URL generation purposes only and are never considered for matching at request time. .. code-block:: python :linenos: >>> config = Configurator() >>> config.add_route('youtube', 'https://youtube.com/watch/{video_id}') ... >>> request.route_url('youtube', video_id='oHg5SJYRHA0') >>> "https://youtube.com/watch/oHg5SJYRHA0" Most pattern replacements and calls to :meth:`pyramid.request.Request.route_url` will work as expected. However, calls to :meth:`pyramid.request.Request.route_path` against external patterns will raise an exception, and passing ``_app_url`` to :meth:`~pyramid.request.Request.route_url` to generate a URL against a route that has an external pattern will also raise an exception. .. index:: single: redirecting to slash-appended routes pyramid/config/routes.py
@@ -1,5 +1,6 @@ import warnings from pyramid.compat import urlparse from pyramid.interfaces import ( IRequest, IRouteRequest, @@ -384,7 +385,39 @@ if pattern is None: raise ConfigurationError('"pattern" argument may not be None') if self.route_prefix: # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') parsed = urlparse.urlparse(pattern) if parsed.hostname: pattern = parsed.path original_pregenerator = pregenerator def external_url_pregenerator(request, elements, kw): if '_app_url' in kw: raise ValueError( 'You cannot generate a path to an external route ' 'pattern via request.route_path nor pass an _app_url ' 'to request.route_url when generating a URL for an ' 'external route pattern (pattern was "%s") ' % (pattern,) ) if '_scheme' in kw: scheme = kw['_scheme'] elif parsed.scheme: scheme = parsed.scheme else: scheme = request.scheme kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) if original_pregenerator: elements, kw = original_pregenerator( request, elements, kw) return elements, kw pregenerator = external_url_pregenerator static = True elif self.route_prefix: pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') mapper = self.get_routes_mapper() pyramid/tests/test_url.py
@@ -1027,6 +1027,77 @@ self.assertEqual(request.elements, ('abc',)) self.assertEqual(request.kw, {'_anchor':'abc'}) class Test_external_static_url_integration(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _makeRequest(self): from pyramid.request import Request return Request.blank('/') def test_generate_external_url(self): self.config.add_route('acme', 'https://acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'https://acme.org/path/bar') def test_generate_external_url_without_scheme(self): self.config.add_route('acme', '//acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'http://acme.org/path/bar') def test_generate_external_url_with_explicit_scheme(self): self.config.add_route('acme', '//acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar', _scheme='https'), 'https://acme.org/path/bar') def test_generate_external_url_with_explicit_app_url(self): self.config.add_route('acme', 'http://acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertRaises(ValueError, request.route_url, 'acme', foo='bar', _app_url='http://fakeme.com') def test_generate_external_url_route_path(self): self.config.add_route('acme', 'https://acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertRaises(ValueError, request.route_path, 'acme', foo='bar') def test_generate_external_url_with_pregenerator(self): def pregenerator(request, elements, kw): kw['_query'] = {'q': 'foo'} return elements, kw self.config.add_route('acme', 'https://acme.org/path/{foo}', pregenerator=pregenerator) request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'https://acme.org/path/bar?q=foo') def test_external_url_with_route_prefix(self): def includeme(config): config.add_route('acme', '//acme.org/{foo}') self.config.include(includeme, route_prefix='some_prefix') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'http://acme.org/bar') class DummyContext(object): def __init__(self, next=None): self.next = next