Merge branch 'master' of github.com:Pylons/pyramid
| | |
| | | Features |
| | | -------- |
| | | |
| | | - A new http exception subclass named ``pyramid.httpexceptions.HTTPSuccessful`` |
| | | was added. You can use this class as the ``context`` of an exception |
| | | view to catch all 200-series "exceptions" (e.g. "raise HTTPOk"). This |
| | | also allows you to catch *only* the ``HTTPOk`` exception itself; previously |
| | | this was impossible because a number of other exceptions |
| | | (such as ``HTTPNoContent``) inherited from ``HTTPOk``, but now they do not. |
| | | |
| | | - You can now generate "hybrid" urldispatch/traversal URLs more easily |
| | | by using the new ``route_name``, ``route_kw`` and ``route_remainder_name`` |
| | | arguments to ``request.resource_url`` and ``request.resource_path``. See |
| | | the new section of the "Combining Traversal and URL Dispatch" documentation |
| | | chapter entitled "Hybrid URL Generation". |
| | | |
| | | - It is now possible to escape double braces in Pyramid scaffolds (unescaped, |
| | | these represent replacement values). You can use ``\{\{a\}\}`` to |
| | | represent a "bare" ``{{a}}``. See |
| | |
| | | |
| | | - 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. |
| | | https://github.com/Pylons/pyramid/issues/611 and the documentation section |
| | | in the "URL Dispatch" chapter entitled "External Routes" for more information. |
| | | |
| | | Bug Fixes |
| | | --------- |
| | | |
| | | - It was not possible to use ``pyramid.httpexceptions.HTTPException`` as |
| | | the ``context`` of an exception view as very general catchall for |
| | | http-related exceptions when you wanted that exception view to override the |
| | | default exception view. See https://github.com/Pylons/pyramid/issues/985 |
| | | |
| | | - When the ``pyramid.reload_templates`` setting was true, and a Chameleon |
| | | template was reloaded, and the renderer specification named a macro |
| | |
| | | are now "reified" properties that look up a locale name and localizer |
| | | respectively using the machinery described in the "Internationalization" |
| | | chapter of the documentation. |
| | | |
| | | - If you send an ``X-Vhm-Root`` header with a value that ends with a slash (or |
| | | any number of slashes), the trailing slash(es) will be removed before a URL |
| | | is generated when you use use ``request.resource_url`` or |
| | | ``request.resource_path``. Previously the virtual root path would not have |
| | | trailing slashes stripped, which would influence URL generation. |
| | | |
| | | - The ``pyramid.interfaces.IResourceURL`` interface has now grown two new |
| | | attributes: ``virtual_path_tuple`` and ``physical_path_tuple``. These should |
| | | be the tuple form of the resource's path (physical and virtual). |
| | | |
| | | 1.4 (2012-12-18) |
| | | ================ |
| | |
| | | finished callbacks are executed. This is in support of the |
| | | ``request.invoke_subrequest`` feature. |
| | | |
| | | - The 200-series exception responses named ``HTTPCreated``, ``HTTPAccepted``, |
| | | ``HTTPNonAuthoritativeInformation``, ``HTTPNoContent``, ``HTTPResetContent``, |
| | | and ``HTTPPartialContent`` in ``pyramid.httpexceptions`` no longer inherit |
| | | from ``HTTPOk``. Instead they inherit from a new base class named |
| | | ``HTTPSuccessful``. This will have no effect on you unless you've registered |
| | | an exception view for ``HTTPOk`` and expect that exception view to |
| | | catch all the aforementioned exceptions. |
| | | |
| | | Documentation |
| | | ------------- |
| | | |
| | |
| | | no object contained by the root object with the key ``bazbuz``. A |
| | | different request URI, such as ``/abc/foo/bar``, would invoke the |
| | | default ``myproject.views.abc`` view. |
| | | |
| | | .. index:: |
| | | pair: hybrid urls; generating |
| | | |
| | | .. _generating_hybrid_urls: |
| | | |
| | | Generating Hybrid URLs |
| | | ---------------------- |
| | | |
| | | .. versionadded:: 1.5 |
| | | |
| | | The :meth:`pyramid.request.Request.resource_url` method and the |
| | | :meth:`pyramid.request.Request.resource_path` method both accept optional |
| | | keyword arguments that make it easier to generate route-prefixed URLs that |
| | | contain paths to traversal resources:``route_name``, ``route_kw``, and |
| | | ``route_remainder_name``. |
| | | |
| | | Any route that has a pattern that contains a ``*remainder`` pattern (any |
| | | stararg remainder pattern, such as ``*traverse`` or ``*subpath`` or ``*fred``) |
| | | can be used as the target name for ``request.resource_url(..., route_name=)`` |
| | | and ``request.resource_path(..., route_name=)``. |
| | | |
| | | For example, let's imagine you have a route defined in your Pyramid application |
| | | like so: |
| | | |
| | | .. code-block:: python |
| | | |
| | | config.add_route('mysection', '/mysection*traverse') |
| | | |
| | | If you'd like to generate the URL ``http://example.com/mysection/a/``, you can |
| | | use the following incantation, assuming that the variable ``a`` below points to |
| | | a resource that is a child of the root with a ``__name__`` of ``a``: |
| | | |
| | | .. code-block:: python |
| | | |
| | | request.resource_url(a, route_name='mysection') |
| | | |
| | | You can generate only the path portion ``/mysection/a/`` assuming the same: |
| | | |
| | | .. code-block:: python |
| | | |
| | | request.resource_path(a, route_name='mysection') |
| | | |
| | | The path is virtual host aware, so if the ``X-Vhm-Root`` environ variable is |
| | | present in the request, and it's set to ``/a``, the above call to |
| | | ``request.resource_url`` would generate ``http://example.com/mysection/`` |
| | | and the above call to ``request.resource_path`` would generate ``/mysection/``. |
| | | See :ref:`virtual_root_support` for more information. |
| | | |
| | | If the route you're trying to use needs simple dynamic part values to be filled |
| | | in to succesfully generate the URL, you can pass these as the ``route_kw`` |
| | | argument to ``resource_url`` and ``resource_path``. For example, assuming that |
| | | the route definition is like so: |
| | | |
| | | .. code-block:: python |
| | | |
| | | config.add_route('mysection', '/{id}/mysection*traverse') |
| | | |
| | | You can pass ``route_kw`` in to fill in ``{id}`` above: |
| | | |
| | | .. code-block:: python |
| | | |
| | | request.resource_url(a, route_name='mysection', route_kw={'id':'1'}) |
| | | |
| | | If you pass ``route_kw`` but do not pass ``route_name``, ``route_kw`` will |
| | | be ignored. |
| | | |
| | | By default this feature works by calling ``route_url`` under the hood, |
| | | and passing the value of the resource path to that function as ``traverse``. |
| | | If your route has a different ``*stararg`` remainder name (such as |
| | | ``*subpath``), you can tell ``resource_url`` or ``resource_path`` to use that |
| | | instead of ``traverse`` by passing ``route_remainder_name``. For example, |
| | | if you have the following route: |
| | | |
| | | .. code-block:: python |
| | | |
| | | config.add_route('mysection', '/mysection*subpath') |
| | | |
| | | You can fill in the ``*subpath`` value using ``resource_url`` by doing: |
| | | |
| | | .. code-block:: python |
| | | |
| | | request.resource_path(a, route_name='mysection', |
| | | route_remainder_name='subpath') |
| | | |
| | | If you pass ``route_remainder_name`` but do not pass ``route_name``, |
| | | ``route_remainder_name`` will be ignored. |
| | | |
| | | If you try to use ``resource_path`` or ``resource_url`` when the ``route_name`` |
| | | argument points at a route that does not have a remainder stararg, an error |
| | | will not be raised, but the generated URL will not contain any remainder |
| | | information either. |
| | | |
| | | All other values that are normally passable to ``resource_path`` and |
| | | ``resource_url`` (such as ``query``, ``anchor``, ``host``, ``port``, and |
| | | positional elements) work as you might expect in this configuration. |
| | | |
| | | Note that this feature is incompatible with the ``__resource_url__`` feature |
| | | (see :ref:`overriding_resource_url_generation`) implemented on resource |
| | | objects. Any ``__resource_url__`` supplied by your resource will be ignored |
| | | when you pass ``route_name``. |
| | | |
| | |
| | | |
| | | Exception |
| | | HTTPException |
| | | HTTPOk |
| | | HTTPSuccessful |
| | | * 200 - HTTPOk |
| | | * 201 - HTTPCreated |
| | | * 202 - HTTPAccepted |
| | |
| | | value = text_type(value) |
| | | return value |
| | | |
| | | class HTTPException(Exception): # bw compat |
| | | """ Base class for all :term:`exception response` objects.""" |
| | | |
| | | @implementer(IExceptionResponse) |
| | | class WSGIHTTPException(Response, HTTPException): |
| | | class HTTPException(Response, Exception): |
| | | |
| | | ## You should set in subclasses: |
| | | # code = 200 |
| | |
| | | 'html_comment':html_comment, |
| | | } |
| | | body_tmpl = self.body_template_obj |
| | | if WSGIHTTPException.body_template_obj is not body_tmpl: |
| | | if HTTPException.body_template_obj is not body_tmpl: |
| | | # Custom template; add headers to args |
| | | for k, v in environ.items(): |
| | | if (not k.startswith('wsgi.')) and ('.' in k): |
| | |
| | | self.prepare(environ) |
| | | return Response.__call__(self, environ, start_response) |
| | | |
| | | class HTTPError(WSGIHTTPException): |
| | | WSGIHTTPException = HTTPException # b/c post 1.5 |
| | | |
| | | class HTTPError(HTTPException): |
| | | """ |
| | | base class for exceptions with status codes in the 400s and 500s |
| | | |
| | |
| | | and that any work in progress should not be committed. |
| | | """ |
| | | |
| | | class HTTPRedirection(WSGIHTTPException): |
| | | class HTTPRedirection(HTTPException): |
| | | """ |
| | | base class for exceptions with status codes in the 300s (redirections) |
| | | |
| | |
| | | condition. |
| | | """ |
| | | |
| | | class HTTPOk(WSGIHTTPException): |
| | | class HTTPSuccessful(HTTPException): |
| | | """ |
| | | Base class for exceptions with status codes in the 200s (successful |
| | | responses) |
| | | """ |
| | | |
| | | ############################################################ |
| | | ## 2xx success |
| | | ############################################################ |
| | | |
| | | class HTTPOk(HTTPSuccessful): |
| | | """ |
| | | subclass of :class:`~HTTPSuccessful` |
| | | |
| | | Indicates that the request has succeeded. |
| | | |
| | | code: 200, title: OK |
| | | """ |
| | | code = 200 |
| | | title = 'OK' |
| | | |
| | | ############################################################ |
| | | ## 2xx success |
| | | ############################################################ |
| | | |
| | | class HTTPCreated(HTTPOk): |
| | | class HTTPCreated(HTTPSuccessful): |
| | | """ |
| | | subclass of :class:`~HTTPOk` |
| | | subclass of :class:`~HTTPSuccessful` |
| | | |
| | | This indicates that request has been fulfilled and resulted in a new |
| | | resource being created. |
| | |
| | | code = 201 |
| | | title = 'Created' |
| | | |
| | | class HTTPAccepted(HTTPOk): |
| | | class HTTPAccepted(HTTPSuccessful): |
| | | """ |
| | | subclass of :class:`~HTTPOk` |
| | | subclass of :class:`~HTTPSuccessful` |
| | | |
| | | This indicates that the request has been accepted for processing, but the |
| | | processing has not been completed. |
| | |
| | | title = 'Accepted' |
| | | explanation = 'The request is accepted for processing.' |
| | | |
| | | class HTTPNonAuthoritativeInformation(HTTPOk): |
| | | class HTTPNonAuthoritativeInformation(HTTPSuccessful): |
| | | """ |
| | | subclass of :class:`~HTTPOk` |
| | | subclass of :class:`~HTTPSuccessful` |
| | | |
| | | This indicates that the returned metainformation in the entity-header is |
| | | not the definitive set as available from the origin server, but is |
| | |
| | | code = 203 |
| | | title = 'Non-Authoritative Information' |
| | | |
| | | class HTTPNoContent(HTTPOk): |
| | | class HTTPNoContent(HTTPSuccessful): |
| | | """ |
| | | subclass of :class:`~HTTPOk` |
| | | subclass of :class:`~HTTPSuccessful` |
| | | |
| | | This indicates that the server has fulfilled the request but does |
| | | not need to return an entity-body, and might want to return updated |
| | |
| | | title = 'No Content' |
| | | empty_body = True |
| | | |
| | | class HTTPResetContent(HTTPOk): |
| | | class HTTPResetContent(HTTPSuccessful): |
| | | """ |
| | | subclass of :class:`~HTTPOk` |
| | | subclass of :class:`~HTTPSuccessful` |
| | | |
| | | This indicates that the server has fulfilled the request and |
| | | the user agent SHOULD reset the document view which caused the |
| | |
| | | title = 'Reset Content' |
| | | empty_body = True |
| | | |
| | | class HTTPPartialContent(HTTPOk): |
| | | class HTTPPartialContent(HTTPSuccessful): |
| | | """ |
| | | subclass of :class:`~HTTPOk` |
| | | subclass of :class:`~HTTPSuccessful` |
| | | |
| | | This indicates that the server has fulfilled the partial GET |
| | | request for the resource. |
| | |
| | | pregenerator = Attribute('This attribute should either be ``None`` or ' |
| | | 'a callable object implementing the ' |
| | | '``IRoutePregenerator`` interface') |
| | | |
| | | def match(path): |
| | | """ |
| | | If the ``path`` passed to this function can be matched by the |
| | |
| | | matched. Static routes will not be considered for matching. """ |
| | | |
| | | class IResourceURL(Interface): |
| | | virtual_path = Attribute('The virtual url path of the resource.') |
| | | physical_path = Attribute('The physical url path of the resource.') |
| | | virtual_path = Attribute( |
| | | 'The virtual url path of the resource as a string.' |
| | | ) |
| | | physical_path = Attribute( |
| | | 'The physical url path of the resource as a string.' |
| | | ) |
| | | virtual_path_tuple = Attribute( |
| | | 'The virtual url path of the resource as a tuple. (New in 1.5)' |
| | | ) |
| | | physical_path_tuple = Attribute( |
| | | 'The physical url path of the resource as a tuple. (New in 1.5)' |
| | | ) |
| | | |
| | | class IContextURL(IResourceURL): |
| | | """ An adapter which deals with URLs related to a context. |
| | |
| | | from pyramid.httpexceptions import HTTPException |
| | | |
| | | def includeme(config): |
| | | config.add_route('route_raise_exception', 'route_raise_exception') |
| | | config.add_route('route_raise_httpexception', 'route_raise_httpexception') |
| | | config.add_route('route_raise_exception2', 'route_raise_exception2', |
| | | factory='.models.route_factory') |
| | | config.add_route('route_raise_exception3', 'route_raise_exception3', |
| | |
| | | route_name='route_raise_exception4') |
| | | config.add_view('.views.whoa', context='.models.AnException', |
| | | route_name='route_raise_exception4') |
| | | config.add_view('.views.raise_httpexception', |
| | | route_name='route_raise_httpexception') |
| | | config.add_view('.views.catch_httpexception', context=HTTPException) |
| | | |
| | | |
| | |
| | | from webob import Response |
| | | from .models import AnException |
| | | from pyramid.httpexceptions import HTTPBadRequest |
| | | |
| | | def no(request): |
| | | return Response('no') |
| | |
| | | |
| | | def raise_exception(request): |
| | | raise AnException() |
| | | |
| | | def raise_httpexception(request): |
| | | raise HTTPBadRequest |
| | | |
| | | def catch_httpexception(request): |
| | | return Response('caught') |
| | |
| | | duo = DummyUnicodeObject() |
| | | self.assertEqual(self._callFUT(duo), text_('42')) |
| | | |
| | | class TestWSGIHTTPException(unittest.TestCase): |
| | | class TestHTTPException(unittest.TestCase): |
| | | def _getTargetClass(self): |
| | | from pyramid.httpexceptions import WSGIHTTPException |
| | | return WSGIHTTPException |
| | | from pyramid.httpexceptions import HTTPException |
| | | return HTTPException |
| | | |
| | | def _getTargetSubclass(self, code='200', title='OK', |
| | | explanation='explanation', empty_body=False): |
| | |
| | | res = self.testapp.get('/route_raise_exception4', status=200) |
| | | self.assertTrue(b'whoa' in res.body) |
| | | |
| | | def test_raise_httpexception(self): |
| | | res = self.testapp.get('/route_raise_httpexception', status=200) |
| | | self.assertTrue(b'caught' in res.body) |
| | | |
| | | class TestConflictApp(unittest.TestCase): |
| | | package = 'pyramid.tests.pkgs.conflictapp' |
| | | def _makeConfig(self): |
| | |
| | | context_url = self._makeOne(two, request) |
| | | self.assertEqual(context_url.physical_path, '/one/two/') |
| | | self.assertEqual(context_url.virtual_path, '/two/') |
| | | |
| | | self.assertEqual(context_url.physical_path_tuple, ('', 'one', 'two','')) |
| | | self.assertEqual(context_url.virtual_path_tuple, ('', 'two', '')) |
| | | |
| | | def test_IResourceURL_attributes_vroot_ends_with_slash(self): |
| | | from pyramid.interfaces import VH_ROOT_KEY |
| | | root = DummyContext() |
| | | root.__parent__ = None |
| | | root.__name__ = None |
| | | one = DummyContext() |
| | | one.__parent__ = root |
| | | one.__name__ = 'one' |
| | | two = DummyContext() |
| | | two.__parent__ = one |
| | | two.__name__ = 'two' |
| | | environ = {VH_ROOT_KEY:'/one/'} |
| | | request = DummyRequest(environ) |
| | | context_url = self._makeOne(two, request) |
| | | self.assertEqual(context_url.physical_path, '/one/two/') |
| | | self.assertEqual(context_url.virtual_path, '/two/') |
| | | self.assertEqual(context_url.physical_path_tuple, ('', 'one', 'two','')) |
| | | self.assertEqual(context_url.virtual_path_tuple, ('', 'two', '')) |
| | | |
| | | def test_IResourceURL_attributes_no_vroot(self): |
| | | root = DummyContext() |
| | | root.__parent__ = None |
| | |
| | | context_url = self._makeOne(two, request) |
| | | self.assertEqual(context_url.physical_path, '/one/two/') |
| | | self.assertEqual(context_url.virtual_path, '/one/two/') |
| | | |
| | | self.assertEqual(context_url.physical_path_tuple, ('', 'one', 'two','')) |
| | | self.assertEqual(context_url.virtual_path_tuple, ('', 'one', 'two', '')) |
| | | |
| | | class TestVirtualRoot(unittest.TestCase): |
| | | def setUp(self): |
| | | cleanUp() |
| | |
| | | from pyramid.interfaces import IResourceURL |
| | | from zope.interface import Interface |
| | | class DummyResourceURL(object): |
| | | def __init__(self, context, request): |
| | | self.physical_path = '/context/' |
| | | self.virtual_path = '/context/' |
| | | physical_path = '/context/' |
| | | virtual_path = '/context/' |
| | | def __init__(self, context, request): pass |
| | | reg.registerAdapter(DummyResourceURL, (Interface, Interface), |
| | | IResourceURL) |
| | | return DummyResourceURL |
| | | |
| | | def test_resource_url_root_default(self): |
| | | request = self._makeOne() |
| | |
| | | root.__resource_url__ = resource_url |
| | | result = request.resource_url(root) |
| | | self.assertEqual(result, 'http://example.com/contextabc/') |
| | | |
| | | def test_resource_url_with_route_name_no_remainder_on_adapter(self): |
| | | from pyramid.interfaces import IRoutesMapper |
| | | environ = { |
| | | 'wsgi.url_scheme':'http', |
| | | 'SERVER_PORT':'8080', |
| | | 'SERVER_NAME':'example.com', |
| | | } |
| | | request = self._makeOne(environ) |
| | | adapter = self._registerResourceURL(request.registry) |
| | | # no virtual_path_tuple on adapter |
| | | adapter.virtual_path = '/a/b/c/' |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route) |
| | | request.registry.registerUtility(mapper, IRoutesMapper) |
| | | root = DummyContext() |
| | | result = request.resource_url(root, route_name='foo') |
| | | self.assertEqual(result, 'http://example.com:5432/1/2/3') |
| | | self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) |
| | | |
| | | def test_resource_url_with_route_name_remainder_on_adapter(self): |
| | | from pyramid.interfaces import IRoutesMapper |
| | | environ = { |
| | | 'wsgi.url_scheme':'http', |
| | | 'SERVER_PORT':'8080', |
| | | 'SERVER_NAME':'example.com', |
| | | } |
| | | request = self._makeOne(environ) |
| | | adapter = self._registerResourceURL(request.registry) |
| | | # virtual_path_tuple on adapter |
| | | adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route) |
| | | request.registry.registerUtility(mapper, IRoutesMapper) |
| | | root = DummyContext() |
| | | result = request.resource_url(root, route_name='foo') |
| | | self.assertEqual(result, 'http://example.com:5432/1/2/3') |
| | | self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) |
| | | |
| | | def test_resource_url_with_route_name_and_app_url(self): |
| | | from pyramid.interfaces import IRoutesMapper |
| | | environ = { |
| | | 'wsgi.url_scheme':'http', |
| | | 'SERVER_PORT':'8080', |
| | | 'SERVER_NAME':'example.com', |
| | | } |
| | | request = self._makeOne(environ) |
| | | adapter = self._registerResourceURL(request.registry) |
| | | # virtual_path_tuple on adapter |
| | | adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route) |
| | | request.registry.registerUtility(mapper, IRoutesMapper) |
| | | root = DummyContext() |
| | | result = request.resource_url(root, route_name='foo', app_url='app_url') |
| | | self.assertEqual(result, 'app_url/1/2/3') |
| | | self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) |
| | | |
| | | def test_resource_url_with_route_name_and_scheme_host_port_etc(self): |
| | | from pyramid.interfaces import IRoutesMapper |
| | | environ = { |
| | | 'wsgi.url_scheme':'http', |
| | | 'SERVER_PORT':'8080', |
| | | 'SERVER_NAME':'example.com', |
| | | } |
| | | request = self._makeOne(environ) |
| | | adapter = self._registerResourceURL(request.registry) |
| | | # virtual_path_tuple on adapter |
| | | adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route) |
| | | request.registry.registerUtility(mapper, IRoutesMapper) |
| | | root = DummyContext() |
| | | result = request.resource_url(root, route_name='foo', scheme='scheme', |
| | | host='host', port='port', query={'a':'1'}, |
| | | anchor='anchor') |
| | | self.assertEqual(result, 'scheme://host:port/1/2/3?a=1#anchor') |
| | | self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) |
| | | |
| | | def test_resource_url_with_route_name_and_route_kwargs(self): |
| | | from pyramid.interfaces import IRoutesMapper |
| | | environ = { |
| | | 'wsgi.url_scheme':'http', |
| | | 'SERVER_PORT':'8080', |
| | | 'SERVER_NAME':'example.com', |
| | | } |
| | | request = self._makeOne(environ) |
| | | adapter = self._registerResourceURL(request.registry) |
| | | # virtual_path_tuple on adapter |
| | | adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route) |
| | | request.registry.registerUtility(mapper, IRoutesMapper) |
| | | root = DummyContext() |
| | | result = request.resource_url( |
| | | root, route_name='foo', route_kw={'a':'1', 'b':'2'}) |
| | | self.assertEqual(result, 'http://example.com:5432/1/2/3') |
| | | self.assertEqual( |
| | | route.kw, |
| | | {'traverse': ('', 'a', 'b', 'c', ''), |
| | | 'a':'1', |
| | | 'b':'2'} |
| | | ) |
| | | |
| | | def test_resource_url_with_route_name_and_elements(self): |
| | | from pyramid.interfaces import IRoutesMapper |
| | | environ = { |
| | | 'wsgi.url_scheme':'http', |
| | | 'SERVER_PORT':'8080', |
| | | 'SERVER_NAME':'example.com', |
| | | } |
| | | request = self._makeOne(environ) |
| | | adapter = self._registerResourceURL(request.registry) |
| | | # virtual_path_tuple on adapter |
| | | adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route) |
| | | request.registry.registerUtility(mapper, IRoutesMapper) |
| | | root = DummyContext() |
| | | result = request.resource_url(root, 'e1', 'e2', route_name='foo') |
| | | self.assertEqual(result, 'http://example.com:5432/1/2/3/e1/e2') |
| | | self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) |
| | | |
| | | def test_resource_url_with_route_name_and_remainder_name(self): |
| | | from pyramid.interfaces import IRoutesMapper |
| | | environ = { |
| | | 'wsgi.url_scheme':'http', |
| | | 'SERVER_PORT':'8080', |
| | | 'SERVER_NAME':'example.com', |
| | | } |
| | | request = self._makeOne(environ) |
| | | adapter = self._registerResourceURL(request.registry) |
| | | # virtual_path_tuple on adapter |
| | | adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route) |
| | | request.registry.registerUtility(mapper, IRoutesMapper) |
| | | root = DummyContext() |
| | | result = request.resource_url(root, route_name='foo', |
| | | route_remainder_name='fred') |
| | | self.assertEqual(result, 'http://example.com:5432/1/2/3') |
| | | self.assertEqual(route.kw, {'fred': ('', 'a', 'b', 'c', '')}) |
| | | |
| | | def test_resource_path(self): |
| | | request = self._makeOne() |
| | |
| | | from pyramid.interfaces import IRoutesMapper |
| | | from webob.multidict import GetDict |
| | | request = self._makeOne() |
| | | request.GET = GetDict([('q', '123'), ('b', '2'), ('b', '2'), ('q', '456')], {}) |
| | | request.GET = GetDict( |
| | | [('q', '123'), ('b', '2'), ('b', '2'), ('q', '456')], {}) |
| | | route = DummyRoute('/1/2/3') |
| | | mapper = DummyRoutesMapper(route=route) |
| | | request.matched_route = route |
| | |
| | | vroot_varname = VH_ROOT_KEY |
| | | |
| | | def __init__(self, resource, request): |
| | | physical_path = resource_path(resource) |
| | | if physical_path != '/': |
| | | physical_path_tuple = resource_path_tuple(resource) |
| | | physical_path = _join_path_tuple(physical_path_tuple) |
| | | |
| | | if physical_path_tuple != ('',): |
| | | physical_path_tuple = physical_path_tuple + ('',) |
| | | physical_path = physical_path + '/' |
| | | |
| | | virtual_path = physical_path |
| | | virtual_path_tuple = physical_path_tuple |
| | | |
| | | environ = request.environ |
| | | vroot_path = environ.get(self.vroot_varname) |
| | |
| | | # if the physical path starts with the virtual root path, trim it out |
| | | # of the virtual path |
| | | if vroot_path is not None: |
| | | if physical_path.startswith(vroot_path): |
| | | vroot_path = vroot_path.rstrip('/') |
| | | if vroot_path and physical_path.startswith(vroot_path): |
| | | vroot_path_tuple = tuple(vroot_path.split('/')) |
| | | numels = len(vroot_path_tuple) |
| | | virtual_path_tuple = ('',) + physical_path_tuple[numels:] |
| | | virtual_path = physical_path[len(vroot_path):] |
| | | |
| | | self.virtual_path = virtual_path # IResourceURL attr |
| | | self.physical_path = physical_path # IResourceURL attr |
| | | self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5) |
| | | self.physical_path_tuple = physical_path_tuple # IResourceURL attr (1.5) |
| | | |
| | | # bw compat for IContextURL methods |
| | | self.resource = resource |
| | |
| | | passed for ``scheme``, ``host``, and/or ``port`` will be ignored. |
| | | |
| | | If the ``resource`` passed in has a ``__resource_url__`` method, it |
| | | will be used to generate the URL (scheme, host, port, path) that for |
| | | the base resource which is operated upon by this function. See also |
| | | will be used to generate the URL (scheme, host, port, path) for the |
| | | base resource which is operated upon by this function. See also |
| | | :ref:`overriding_resource_url_generation`. |
| | | |
| | | .. versionadded:: 1.5 |
| | | ``route_name``, ``route_kw``, and ``route_remainder_name`` |
| | | |
| | | If ``route_name`` is passed, this function will delegate its URL |
| | | production to the ``route_url`` function. Calling |
| | | ``resource_url(someresource, 'element1', 'element2', query={'a':1}, |
| | | route_name='blogentry')`` is roughly equivalent to doing:: |
| | | |
| | | remainder_path = request.resource_path(someobject) |
| | | url = request.route_url( |
| | | 'blogentry', |
| | | 'element1', |
| | | 'element2', |
| | | _query={'a':'1'}, |
| | | traverse=traversal_path, |
| | | ) |
| | | |
| | | It is only sensible to pass ``route_name`` if the route being named has |
| | | a ``*remainder`` stararg value such as ``*traverse``. The remainder |
| | | value will be ignored in the output otherwise. |
| | | |
| | | By default, the resource path value will be passed as the name |
| | | ``traverse`` when ``route_url`` is called. You can influence this by |
| | | passing a different ``route_remainder_name`` value if the route has a |
| | | different ``*stararg`` value at its end. For example if the route |
| | | pattern you want to replace has a ``*subpath`` stararg ala |
| | | ``/foo*subpath``:: |
| | | |
| | | request.resource_url( |
| | | resource, |
| | | route_name='myroute', |
| | | route_remainder_name='subpath' |
| | | ) |
| | | |
| | | If ``route_name`` is passed, it is also permissible to pass |
| | | ``route_kw``, which will passed as additional keyword arguments to |
| | | ``route_url``. Saying ``resource_url(someresource, 'element1', |
| | | 'element2', route_name='blogentry', route_kw={'id':'4'}, |
| | | _query={'a':'1'})`` is roughly equivalent to:: |
| | | |
| | | remainder_path = request.resource_path_tuple(someobject) |
| | | kw = {'id':'4', '_query':{'a':'1'}, 'traverse':traversal_path} |
| | | url = request.route_url( |
| | | 'blogentry', |
| | | 'element1', |
| | | 'element2', |
| | | **kw, |
| | | ) |
| | | |
| | | If ``route_kw`` or ``route_remainder_name`` is passed, but |
| | | ``route_name`` is not passed, both ``route_kw`` and |
| | | ``route_remainder_name`` will be ignored. If ``route_name`` |
| | | is passed, the ``__resource_url__`` method of the resource passed is |
| | | ignored unconditionally. This feature is incompatible with |
| | | resources which generate their own URLs. |
| | | |
| | | .. note:: |
| | | |
| | | If the :term:`resource` used is the result of a :term:`traversal`, it |
| | |
| | | resource_url = url_adapter() |
| | | |
| | | else: |
| | | # newer-style IResourceURL adapter (Pyramid 1.3 and after) |
| | | # IResourceURL adapter (Pyramid 1.3 and after) |
| | | app_url = None |
| | | scheme = None |
| | | host = None |
| | | port = None |
| | | |
| | | if 'route_name' in kw: |
| | | newkw = {} |
| | | route_name = kw['route_name'] |
| | | remainder = getattr(url_adapter, 'virtual_path_tuple', None) |
| | | if remainder is None: |
| | | # older user-supplied IResourceURL adapter without 1.5 |
| | | # virtual_path_tuple |
| | | remainder = tuple(url_adapter.virtual_path.split('/')) |
| | | remainder_name = kw.get('route_remainder_name', 'traverse') |
| | | newkw[remainder_name] = remainder |
| | | |
| | | for name in ( |
| | | 'app_url', 'scheme', 'host', 'port', 'query', 'anchor' |
| | | ): |
| | | val = kw.get(name, None) |
| | | if val is not None: |
| | | newkw['_' + name] = val |
| | | |
| | | if 'route_kw' in kw: |
| | | route_kw = kw.get('route_kw') |
| | | if route_kw is not None: |
| | | newkw.update(route_kw) |
| | | |
| | | return self.route_url(route_name, *elements, **newkw) |
| | | |
| | | if 'app_url' in kw: |
| | | app_url = kw['app_url'] |
| | | |
| | |
| | | |
| | | # stolen from bobo and modified |
| | | old_route_re = re.compile(r'(\:[_a-zA-Z]\w*)') |
| | | star_at_end = re.compile(r'\*\w*$') |
| | | star_at_end = re.compile(r'\*(\w*)$') |
| | | |
| | | # The tortuous nature of the regex named ``route_re`` below is due to the |
| | | # fact that we need to support at least one level of "inner" squigglies |