summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-09-17 19:58:01 +0000
committerChris McDonough <chrism@agendaless.com>2009-09-17 19:58:01 +0000
commit750ce41f217cd7b638ad5b69fcb9df1b49841b58 (patch)
treea0a8edbc60694d3ae16b70570b0184a8ef28ee65
parent19473e78e61ad084f07a0f7820a75b6c64d93dcd (diff)
downloadpyramid-750ce41f217cd7b638ad5b69fcb9df1b49841b58.tar.gz
pyramid-750ce41f217cd7b638ad5b69fcb9df1b49841b58.tar.bz2
pyramid-750ce41f217cd7b638ad5b69fcb9df1b49841b58.zip
- Add a ``repoze.bfg.url.static_url`` API which is capable of
generating URLs to static resources defined by the ``<static>`` ZCML directive. See the "Views" narrative chapter's section titled "Generating Static Resource URLs" for more information.
-rw-r--r--CHANGES.txt5
-rw-r--r--docs/api/url.rst2
-rw-r--r--docs/narr/views.rst69
-rw-r--r--repoze/bfg/static.py7
-rw-r--r--repoze/bfg/tests/test_static.py7
-rw-r--r--repoze/bfg/tests/test_url.py65
-rw-r--r--repoze/bfg/tests/test_urldispatch.py9
-rw-r--r--repoze/bfg/tests/test_zcml.py11
-rw-r--r--repoze/bfg/url.py56
-rw-r--r--repoze/bfg/urldispatch.py3
-rw-r--r--repoze/bfg/zcml.py8
11 files changed, 226 insertions, 16 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index eeada7e49..bd1a0a96e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,11 @@
Next release
============
+- Add a ``repoze.bfg.url.static_url`` API which is capable of
+ generating URLs to static resources defined by the ``<static>`` ZCML
+ directive. See the "Views" narrative chapter's section titled
+ "Generating Static Resource URLs" for more information.
+
- Add a ``string`` renderer. This renderer converts a non-Response
return value of any view callble into a string. It is documented in
the "Views" narrative chapter.
diff --git a/docs/api/url.rst b/docs/api/url.rst
index 36d3c5b65..578a4a2fc 100644
--- a/docs/api/url.rst
+++ b/docs/api/url.rst
@@ -9,5 +9,7 @@
.. autofunction:: route_url
+ .. autofunction:: static_url
+
.. autofunction:: urlencode
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index b64fd2a4b..4bb2dade7 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -1093,6 +1093,75 @@ directive's ``path`` is ``/path/to/static``,
subdirectories recursively, and any subdirectories may hold files;
these will be resolved by the static view as you would expect.
+.. note:: The ``<static>`` ZCML directive is new in :mod:`repoze.bfg`
+ 1.1.
+
+Generating Static Resource URLs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When a ``<static>`` directive is used to register a static resource
+directory, a special helper API named ``repoze.bfg.static_url`` can be
+used to generate the appropriate URL for a package resource that lives
+in one of the directories named by the ``<static>`` directive's
+``path`` attribute.
+
+For example, let's assume you create a set of ``static`` declarations
+in ZCML like so:
+
+.. code-block:: xml
+ :linenos:
+
+ <static
+ name="static1"
+ path="resources/1"
+ />
+
+ <static
+ name="static2"
+ path="resources/2"
+ />
+
+These declarations create URL-accessible directories which have URLs
+which begin, respectively, with ``/static1`` and ``/static2``. The
+resources in the ``resources/1`` directory are consulted when a user
+visits a URL which begins with ``/static1``, and the resources in the
+``resources/2`` directory are consulted when a user visits a URL which
+begins with ``/static2``.
+
+You needn't generate the URLs to static resources "by hand" in such a
+configuration. Instead, use the ``repoze.bfg.url.static_url`` API to
+generate them for you. For example, let's imagine that the following
+code lives in a module that shares the same directory as the above
+ZCML file:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.url import static_url
+ from repoze.bfg.chameleon_zpt import render_template_to_response
+
+ def my_view(context, request):
+ css_url = static_url('resources/1/foo.css', request)
+ js_url = static_url('resources/2/foo.js', request)
+ return render_template_to_response('templates/my_template.pt',
+ css_url = css_url,
+ js_url = js_url)
+
+If the request "application URL" of the running system is
+``http://example.com``, the ``css_url`` generated above would be:
+``http://example.com/static1/foo.css``. The ``js_url`` generated
+above would be ``'http://example.com/static2/foo.js``.
+
+One benefit of using the ``static_url`` function rather than
+constructing static URLs "by hand" is that if you need to change the
+``name`` of a static URL declaration in ZCML, the generated URLs will
+continue to resolve properly after the rename.
+
+See :ref:`url_module` for detailed information about inputs and
+outputs of the ``static_url`` function.
+
+.. note:: The ``static_url`` API is new in :mod:`repoze.bfg` 1.1.
+
Serving Static Resources Using a View
-------------------------------------
diff --git a/repoze/bfg/static.py b/repoze/bfg/static.py
index 01ce98a30..ef893875f 100644
--- a/repoze/bfg/static.py
+++ b/repoze/bfg/static.py
@@ -76,3 +76,10 @@ class PackageURLParser(StaticURLParser):
return '<%s %s:%s at %s>' % (self.__class__.__name__, self.package_name,
self.root_resource, id(self))
+class StaticRootFactory:
+ def __init__(self, spec):
+ self.spec = spec
+
+ def __call__(self, environ):
+ return self
+
diff --git a/repoze/bfg/tests/test_static.py b/repoze/bfg/tests/test_static.py
index 7c7b5627c..355afac2a 100644
--- a/repoze/bfg/tests/test_static.py
+++ b/repoze/bfg/tests/test_static.py
@@ -148,6 +148,13 @@ class TestPackageURLParser(unittest.TestCase):
self.failUnless('404 Not Found' in body)
self.assertEqual(sr.status, '404 Not Found')
+class TestStaticRootFactory(unittest.TestCase):
+ def test_it(self):
+ from repoze.bfg.static import StaticRootFactory
+ factory = StaticRootFactory('abc')
+ self.assertEqual(factory.spec, 'abc')
+ self.assertEqual(factory({}), factory)
+
class DummyStartResponse:
def __call__(self, status, headerlist, exc_info=None):
self.status = status
diff --git a/repoze/bfg/tests/test_url.py b/repoze/bfg/tests/test_url.py
index 5f0fde872..5833b8880 100644
--- a/repoze/bfg/tests/test_url.py
+++ b/repoze/bfg/tests/test_url.py
@@ -202,7 +202,62 @@ class TestRouteUrl(unittest.TestCase):
mapper.raise_exc = KeyError
request = DummyRequest()
self.assertRaises(KeyError, self._callFUT, 'flub', request, a=1)
+
+class TestStaticUrl(unittest.TestCase):
+ def setUp(self):
+ cleanUp()
+
+ def tearDown(self):
+ cleanUp()
+ def _callFUT(self, *arg, **kw):
+ from repoze.bfg.url import static_url
+ return static_url(*arg, **kw)
+
+ def test_notfound(self):
+ from repoze.bfg.interfaces import IRoutesMapper
+ from zope.component import getSiteManager
+ mapper = DummyRoutesMapper(result='/1/2/3')
+ sm = getSiteManager()
+ sm.registerUtility(mapper, IRoutesMapper)
+ request = DummyRequest()
+ self.assertRaises(ValueError, self._callFUT, 'static/foo.css', request)
+
+ def test_abspath(self):
+ from repoze.bfg.interfaces import IRoutesMapper
+ from zope.component import getSiteManager
+ mapper = DummyRoutesMapper(result='/1/2/3')
+ sm = getSiteManager()
+ sm.registerUtility(mapper, IRoutesMapper)
+ request = DummyRequest()
+ self.assertRaises(ValueError, self._callFUT, '/static/foo.css', request)
+
+ def test_found_rel(self):
+ from repoze.bfg.interfaces import IRoutesMapper
+ from repoze.bfg.static import StaticRootFactory
+ from zope.component import getSiteManager
+ factory = StaticRootFactory('repoze.bfg.tests:fixtures')
+ routes = [DummyRoute('name', factory=factory)]
+ mapper = DummyRoutesMapper(result='/1/2/3', routes = routes)
+ sm = getSiteManager()
+ sm.registerUtility(mapper, IRoutesMapper)
+ request = DummyRequest()
+ url = self._callFUT('fixtures/minimal.pt', request)
+ self.assertEqual(url, 'http://example.com:5432/1/2/3')
+
+ def test_found_abs(self):
+ from repoze.bfg.interfaces import IRoutesMapper
+ from repoze.bfg.static import StaticRootFactory
+ from zope.component import getSiteManager
+ factory = StaticRootFactory('repoze.bfg.tests:fixtures')
+ routes = [DummyRoute('name', factory=factory)]
+ mapper = DummyRoutesMapper(result='/1/2/3', routes = routes)
+ sm = getSiteManager()
+ sm.registerUtility(mapper, IRoutesMapper)
+ request = DummyRequest()
+ url = self._callFUT('repoze.bfg.tests:fixtures/minimal.pt', request)
+ self.assertEqual(url, 'http://example.com:5432/1/2/3')
+
class DummyContext(object):
def __init__(self, next=None):
self.next = next
@@ -216,11 +271,19 @@ class DummyRequest:
class DummyRoutesMapper:
raise_exc = None
- def __init__(self, result='/1/2/3', raise_exc=False):
+ def __init__(self, result='/1/2/3', raise_exc=False, routes=()):
self.result = result
+ self.routes = routes
+
+ def get_routes(self):
+ return self.routes
def generate(self, *route_args, **newargs):
if self.raise_exc:
raise self.raise_exc
return self.result
+class DummyRoute:
+ def __init__(self, name, factory=None):
+ self.name = name
+ self.factory = factory
diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py
index 1de3d6f3b..faa375dbd 100644
--- a/repoze/bfg/tests/test_urldispatch.py
+++ b/repoze/bfg/tests/test_urldispatch.py
@@ -120,6 +120,15 @@ class RoutesRootFactoryTests(unittest.TestCase):
mapper.connect('whatever', 'archives/:action/:article')
self.assertEqual(mapper.has_routes(), True)
+ def test_get_routes(self):
+ from repoze.bfg.urldispatch import Route
+ mapper = self._makeOne(None)
+ self.assertEqual(mapper.get_routes(), [])
+ mapper.connect('whatever', 'archives/:action/:article')
+ routes = mapper.get_routes()
+ self.assertEqual(len(routes), 1)
+ self.assertEqual(routes[0].__class__, Route)
+
def test_generate(self):
mapper = self._makeOne(None)
def generator(kw):
diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py
index 7af9ab193..6161f6567 100644
--- a/repoze/bfg/tests/test_zcml.py
+++ b/repoze/bfg/tests/test_zcml.py
@@ -1848,7 +1848,7 @@ class TestStaticDirective(unittest.TestCase):
from zope.interface import implementedBy
from zope.component import getSiteManager
from repoze.bfg.zcml import connect_route
- from repoze.bfg.zcml import StaticRootFactory
+ from repoze.bfg.static import StaticRootFactory
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IRouteRequest
import os
@@ -1887,7 +1887,7 @@ class TestStaticDirective(unittest.TestCase):
from zope.component import getSiteManager
from zope.interface import implementedBy
from repoze.bfg.zcml import connect_route
- from repoze.bfg.zcml import StaticRootFactory
+ from repoze.bfg.static import StaticRootFactory
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IRouteRequest
context = DummyContext()
@@ -1922,7 +1922,7 @@ class TestStaticDirective(unittest.TestCase):
from zope.component import getSiteManager
from zope.interface import implementedBy
from repoze.bfg.zcml import connect_route
- from repoze.bfg.zcml import StaticRootFactory
+ from repoze.bfg.static import StaticRootFactory
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IRouteRequest
import repoze.bfg.tests
@@ -2226,11 +2226,6 @@ class TestAll(unittest.TestCase):
self.assertEqual(all([False, False]), False)
self.assertEqual(all([False, True]), False)
-class TestStaticRootFactory(unittest.TestCase):
- def test_it(self):
- from repoze.bfg.zcml import StaticRootFactory
- StaticRootFactory({}) # it just needs construction
-
class DummyModule:
__path__ = "foo"
__name__ = "dummy"
diff --git a/repoze/bfg/url.py b/repoze/bfg/url.py
index dcaea67ff..f21334191 100644
--- a/repoze/bfg/url.py
+++ b/repoze/bfg/url.py
@@ -1,5 +1,6 @@
""" Utility functions for dealing with URLs in repoze.bfg """
+import os
import urllib
from zope.component import queryMultiAdapter
@@ -7,6 +8,8 @@ from zope.component import getUtility
from repoze.bfg.interfaces import IContextURL
from repoze.bfg.interfaces import IRoutesMapper
+from repoze.bfg.path import caller_package
+from repoze.bfg.static import StaticRootFactory
from repoze.bfg.traversal import TraversalContextURL
from repoze.bfg.traversal import quote_path_segment
@@ -29,9 +32,9 @@ def route_url(route_name, request, *elements, **kw):
route_url('foobar', request, foo='1') => <KeyError exception>
route_url('foobar', request, foo='1', bar='2') => <KeyError exception>
route_url('foobar', request, foo='1', bar='2',
- 'traverse=('a','b') => http://e.com/1/2/a/b
+ 'traverse=('a','b')) => http://e.com/1/2/a/b
route_url('foobar', request, foo='1', bar='2',
- 'traverse=('/a/b') => http://e.com/1/2/a/b
+ 'traverse=('/a/b')) => http://e.com/1/2/a/b
Values replacing ``:segment`` arguments can be passed as strings
or Unicode objects. They will be encoded to UTF-8 and URL-quoted
@@ -197,6 +200,55 @@ def model_url(model, request, *elements, **kw):
return model_url + suffix + qs + anchor
+def static_url(path, request, **kw):
+ """
+ Generates a fully qualified URL for a static resource. The
+ resource must live within a location defined via the ``<static>``
+ ZCML directive.
+
+ The ``path`` argument points at a file or directory on disk which
+ a URL should be generated for. The ``path`` may be either a
+ relative path (e.g. ``static/foo.css``) or a :term:`resource
+ specification` (e.g. ``mypackage:static/foo.css``). A ``path``
+ may not be an absolute filesystem path (a ValueError will be
+ raised if this function is supplied with an absolute path).
+
+ The ``request`` argument should be a WebOb request.
+
+ The purpose of the ``**kw`` argument is the same as the purpose of
+ the ``route_url`` ``*kw`` argument. See the documentation for
+ that function to understand the arguments which you can provide to
+ it. However, typically, you don't need to pass anything as
+ ``*kw`` when generating a static resource URL.
+
+ This function raises a ValueError if a ``<static>`` ZCML
+ definition cannot be found which matches the path specification.
+
+ .. note:: This feature is new in :mod:`repoze.bfg` 1.1.
+ """
+ mapper = getUtility(IRoutesMapper)
+ routes = mapper.get_routes()
+ if os.path.isabs(path):
+ raise ValueError('Absolute paths cannot be used to generate static '
+ 'urls (use a package-relative path or a resource '
+ 'specification).')
+ if not ':' in path:
+ # if it's not a package:relative/name and it's not an
+ # /absolute/path it's a relative/path; this means its relative
+ # to the package in which the caller's module is defined.
+ package = caller_package(level=2)
+ path = '%s:%s' % (package.__name__, path)
+
+ for route in routes:
+ factory = route.factory
+ if factory.__class__ is StaticRootFactory:
+ if path.startswith(factory.spec):
+ subpath = path[len(factory.spec):]
+ kw['subpath'] = subpath
+ return route_url(route.name, request, **kw)
+
+ raise ValueError('No static URL definition matching %s' % path)
+
def urlencode(query, doseq=False):
"""
A wrapper around Python's stdlib `urllib.urlencode function
diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py
index 747faace7..dfb7c9979 100644
--- a/repoze/bfg/urldispatch.py
+++ b/repoze/bfg/urldispatch.py
@@ -29,6 +29,9 @@ class RoutesRootFactory(object):
def has_routes(self):
return bool(self.routelist)
+ def get_routes(self):
+ return self.routelist
+
def connect(self, path, name, factory=None):
route = Route(path, name, factory)
self.routelist.append(route)
diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py
index fd8e086a4..8ad0c3ec9 100644
--- a/repoze/bfg/zcml.py
+++ b/repoze/bfg/zcml.py
@@ -51,6 +51,8 @@ from repoze.bfg.security import Unauthorized
from repoze.bfg.settings import get_settings
+from repoze.bfg.static import StaticRootFactory
+
from repoze.bfg.traversal import find_interface
from repoze.bfg.view import static as static_view
@@ -611,10 +613,6 @@ class IStaticDirective(Interface):
required=False,
default=None)
-class StaticRootFactory:
- def __init__(self, environ):
- pass
-
def static(_context, name, path, cache_max_age=3600):
""" Handle ``static`` ZCML directives
"""
@@ -626,7 +624,7 @@ def static(_context, name, path, cache_max_age=3600):
view = static_view(path, cache_max_age=cache_max_age)
route(_context, name, "%s*subpath" % name, view=view,
- view_for=StaticRootFactory, factory=StaticRootFactory)
+ view_for=StaticRootFactory, factory=StaticRootFactory(path))
class IViewDirective(Interface):
for_ = GlobalObject(