summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/narr/introduction.rst18
-rw-r--r--pyramid/config/views.py49
-rw-r--r--pyramid/tests/pkgs/static_routeprefix/__init__.py7
-rw-r--r--pyramid/tests/test_config/test_views.py41
-rw-r--r--pyramid/tests/test_integration.py19
5 files changed, 93 insertions, 41 deletions
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index ed0f322b6..823c1ea13 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -377,7 +377,7 @@ Example: :ref:`events_chapter` and :ref:`event_types`.
Built-in internationalization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Pyramid ships with internalization-related features in its core:
+Pyramid ships with internationalization-related features in its core:
localization, pluralization, and creating message catalogs from source files
and templates. Pyramid allows for a plurality of message catalog via the use
of translation domains: you can create a system that has its own translations
@@ -456,9 +456,9 @@ No singletons
Pyramid is written in such a way that it requires your application to have
exactly zero "singleton" data structures. Or, put another way, Pyramid
doesn't require you to construct any "mutable globals". Or put even a
-different way, an import of a Pyramid application needn't have any "import-
-time side effects". This is esoteric-sounding, but if you've ever tried to
-cope with parameterizing a Django "settings.py" file for multiple
+different way, an import of a Pyramid application needn't have any
+"import-time side effects". This is esoteric-sounding, but if you've ever
+tried to cope with parameterizing a Django "settings.py" file for multiple
installations of the same application, or if you've ever needed to
monkey-patch some framework fixture so that it behaves properly for your use
case, or if you've ever wanted to deploy your system using an asynchronous
@@ -503,7 +503,7 @@ data if you're not extremely careful. Some data will have been written to
the database that probably should not have. Having a centralized commit
point saves you from needing to think about this; it's great for lazy people
who also care about data integrity. Either the request completes
-successfully, and all chages are committed, or it does not, and all changes
+successfully, and all changes are committed, or it does not, and all changes
are aborted.
Also, Pyramid's transaction management system allows you to synchronize
@@ -534,7 +534,7 @@ Configuration extensibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unlike other systems, Pyramid provides a structured "include" mechanism (see
-:meth:`~pyramid.config.Configurator.include`) that allows you compose
+:meth:`~pyramid.config.Configurator.include`) that allows you to compose
applications from multiple Python packages. All the configuration statements
that can be performed in your "main" Pyramid application can also be
performed by included packages including the addition of views, routes,
@@ -592,8 +592,8 @@ to be arbitrarily extensible: it's a lot easier to add a node to a tree than
it is to shoehorn a route into an ordered list of other routes, or to create
another entire instance of an application to service a department and glue
code to allow disparate apps to share data. It's a great fit for sites that
-naturally lend themselves to changing departmental hierarchies, such as CMS
-systems and document management systems. Traversal also lends itself well to
+naturally lend themselves to changing departmental hierarchies, such as
+content management systems and document management systems. Traversal also lends itself well to
systems that require very granular security ("Bob can edit *this* document"
as opposed to "Bob can edit documents").
@@ -618,7 +618,7 @@ A lot is made of the aesthetics of what *kinds* of objects you're allowed to
return from view callables in various frameworks. In a previous section in
this document we showed you that, if you use a :term:`renderer`, you can
usually return a dictionary from a view callable instead of a full-on
-:term:`Response` object. But some frameworks allow you return strings or
+:term:`Response` object. But some frameworks allow you to return strings or
tuples from view callables. When frameworks allow for this, code looks
slightly prettier, because fewer imports need to be done, and there is less
code. For example, compare this:
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 1612d9a65..b179e39b3 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -34,7 +34,6 @@ from pyramid.httpexceptions import HTTPForbidden
from pyramid.httpexceptions import HTTPNotFound
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.static import static_view
-from pyramid.url import route_url
from pyramid.view import render_view_to_response
from pyramid.config.util import DEFAULT_PHASH
@@ -1412,14 +1411,15 @@ class StaticURLInfo(object):
return reg
def generate(self, path, request, **kw):
- for (name, spec, is_url) in self._get_registrations(request.registry):
+ registry = request.registry
+ for (url, spec, route_name) in self._get_registrations(registry):
if path.startswith(spec):
subpath = path[len(spec):]
- if is_url:
- return urljoin(name, subpath)
- else:
+ if url is None:
kw['subpath'] = subpath
- return request.route_url(name, **kw)
+ return request.route_url(route_name, **kw)
+ else:
+ return urljoin(url, subpath)
raise ValueError('No static URL definition matching %s' % path)
@@ -1442,19 +1442,14 @@ class StaticURLInfo(object):
# make sure it ends with a slash
name = name + '/'
- registrations = self._get_registrations(config.registry)
-
- names = [ t[0] for t in registrations ]
-
- if name in names:
- idx = names.index(name)
- registrations.pop(idx)
-
if urlparse(name)[0]:
# it's a URL
- registrations.append((name, spec, True))
+ # url, spec, route_name
+ url = name
+ route_name = None
else:
# it's a view name
+ url = None
cache_max_age = extra.pop('cache_max_age', None)
# create a view
view = static_view(spec, cache_max_age=cache_max_age,
@@ -1487,14 +1482,32 @@ class StaticURLInfo(object):
# register a route using the computed view, permission, and
# pattern, plus any extras passed to us via add_static_view
pattern = "%s*subpath" % name # name already ends with slash
- config.add_route(name, pattern, **extra)
+ if config.route_prefix:
+ route_name = '__%s/%s' % (config.route_prefix, name)
+ else:
+ route_name = '__%s' % name
+ config.add_route(route_name, pattern, **extra)
config.add_view(
- route_name=name,
+ route_name=route_name,
view=view,
permission=permission,
context=context,
renderer=renderer,
attr=attr
)
- registrations.append((name, spec, False))
+
+ def register():
+ registrations = self._get_registrations(config.registry)
+
+ names = [ t[0] for t in registrations ]
+
+ if name in names:
+ idx = names.index(name)
+ registrations.pop(idx)
+
+ # url, spec, route_name
+ registrations.append((url, spec, route_name))
+
+ config.action(None, callable=register)
+
diff --git a/pyramid/tests/pkgs/static_routeprefix/__init__.py b/pyramid/tests/pkgs/static_routeprefix/__init__.py
new file mode 100644
index 000000000..9b539380a
--- /dev/null
+++ b/pyramid/tests/pkgs/static_routeprefix/__init__.py
@@ -0,0 +1,7 @@
+def includeme(config):
+ config.add_static_view('/static', 'pyramid.tests:fixtures')
+ config.include(includeme2, route_prefix='/prefix')
+
+def includeme2(config):
+ config.add_static_view('/static', 'pyramid.tests:fixtures/static')
+
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 3876f1e6c..5fcad3eec 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -1419,8 +1419,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
from pyramid.interfaces import IViewClassifier
config = self._makeOne(autocommit=True)
config.add_static_view('static', 'files', renderer=null_renderer)
- request_type = self._getRouteRequestIface(config, 'static/')
- self._assertRoute(config, 'static/', 'static/*subpath')
+ request_type = self._getRouteRequestIface(config, '__static/')
+ self._assertRoute(config, '__static/', 'static/*subpath')
wrapped = config.registry.adapters.lookup(
(IViewClassifier, request_type, Interface), IView, name='')
from pyramid.request import Request
@@ -3294,8 +3294,8 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_registration_miss(self):
inst = self._makeOne()
- registrations = [('name', 'spec', False),
- ('http://example.com/foo/', 'package:path/',True)]
+ registrations = [(None, 'spec', 'route_name'),
+ ('http://example.com/foo/', 'package:path/', None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/abc', request)
@@ -3303,7 +3303,7 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_slash_in_name1(self):
inst = self._makeOne()
- registrations = [('http://example.com/foo/', 'package:path/',True)]
+ registrations = [('http://example.com/foo/', 'package:path/', None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/abc', request)
@@ -3311,7 +3311,7 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_slash_in_name2(self):
inst = self._makeOne()
- registrations = [('http://example.com/foo/', 'package:path/',True)]
+ registrations = [('http://example.com/foo/', 'package:path/', None)]
inst._get_registrations = lambda *x: registrations
request = self._makeRequest()
result = inst.generate('package:path/', request)
@@ -3319,10 +3319,10 @@ class TestStaticURLInfo(unittest.TestCase):
def test_generate_route_url(self):
inst = self._makeOne()
- registrations = [('viewname/', 'package:path/', False)]
+ registrations = [(None, 'package:path/', '__viewname/')]
inst._get_registrations = lambda *x: registrations
def route_url(n, **kw):
- self.assertEqual(n, 'viewname/')
+ self.assertEqual(n, '__viewname/')
self.assertEqual(kw, {'subpath':'abc', 'a':1})
return 'url'
request = self._makeRequest()
@@ -3333,23 +3333,23 @@ class TestStaticURLInfo(unittest.TestCase):
def test_add_already_exists(self):
inst = self._makeOne()
config = self._makeConfig(
- [('http://example.com/', 'package:path/', True)])
+ [('http://example.com/', 'package:path/', None)])
inst.add(config, 'http://example.com', 'anotherpackage:path')
- expected = [('http://example.com/', 'anotherpackage:path/', True)]
+ expected = [('http://example.com/', 'anotherpackage:path/', None)]
self._assertRegistrations(config, expected)
def test_add_url_withendslash(self):
inst = self._makeOne()
config = self._makeConfig()
inst.add(config, 'http://example.com/', 'anotherpackage:path')
- expected = [('http://example.com/', 'anotherpackage:path/', True)]
+ expected = [('http://example.com/', 'anotherpackage:path/', None)]
self._assertRegistrations(config, expected)
def test_add_url_noendslash(self):
inst = self._makeOne()
config = self._makeConfig()
inst.add(config, 'http://example.com', 'anotherpackage:path')
- expected = [('http://example.com/', 'anotherpackage:path/', True)]
+ expected = [('http://example.com/', 'anotherpackage:path/', None)]
self._assertRegistrations(config, expected)
def test_add_viewname(self):
@@ -3358,12 +3358,21 @@ class TestStaticURLInfo(unittest.TestCase):
config = self._makeConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1)
- expected = [('view/', 'anotherpackage:path/', False)]
+ expected = [(None, 'anotherpackage:path/', '__view/')]
self._assertRegistrations(config, expected)
- self.assertEqual(config.route_args, ('view/', 'view/*subpath'))
+ self.assertEqual(config.route_args, ('__view/', 'view/*subpath'))
self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED)
self.assertEqual(config.view_kw['view'].__class__, static_view)
+ def test_add_viewname_with_route_prefix(self):
+ config = self._makeConfig()
+ config.route_prefix = '/abc'
+ inst = self._makeOne()
+ inst.add(config, 'view', 'anotherpackage:path',)
+ expected = [(None, 'anotherpackage:path/', '__/abc/view/')]
+ self._assertRegistrations(config, expected)
+ self.assertEqual(config.route_args, ('__/abc/view/', 'view/*subpath'))
+
def test_add_viewname_with_permission(self):
config = self._makeConfig()
inst = self._makeOne()
@@ -3475,6 +3484,7 @@ class DummySecurityPolicy:
return self.permitted
class DummyConfig:
+ route_prefix = ''
def add_route(self, *args, **kw):
self.route_args = args
self.route_kw = kw
@@ -3483,6 +3493,9 @@ class DummyConfig:
self.view_args = args
self.view_kw = kw
+ def action(self, discriminator, callable):
+ callable()
+
from zope.interface import implements
from pyramid.interfaces import IMultiView
class DummyMultiView:
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 1cbebd4c1..1fa1cbbcf 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -197,6 +197,25 @@ class TestStaticAppNoSubpath(unittest.TestCase):
self.assertEqual(result.status, '200 OK')
self._assertBody(result.body, os.path.join(here, 'fixtures/minimal.pt'))
+class TestStaticAppWithRoutePrefix(IntegrationBase, unittest.TestCase):
+ package = 'pyramid.tests.pkgs.static_routeprefix'
+ def _assertBody(self, body, filename):
+ self.assertEqual(
+ body.replace('\r', ''),
+ open(filename, 'r').read()
+ )
+
+ def test_includelevel1(self):
+ res = self.testapp.get('/static/minimal.pt', status=200)
+ self._assertBody(res.body,
+ os.path.join(here, 'fixtures/minimal.pt'))
+
+ def test_includelevel2(self):
+ res = self.testapp.get('/prefix/static/index.html', status=200)
+ self._assertBody(res.body,
+ os.path.join(here, 'fixtures/static/index.html'))
+
+
class TestFixtureApp(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.fixtureapp'
def test_another(self):