diff options
| -rw-r--r-- | CHANGES.txt | 42 | ||||
| -rw-r--r-- | docs/narr/install.rst | 2 | ||||
| -rw-r--r-- | docs/tutorials/wiki2/installation.rst | 7 | ||||
| -rw-r--r-- | pyramid/router.py | 21 | ||||
| -rw-r--r-- | pyramid/tests/fixtures/static/héhé.html | 1 | ||||
| -rw-r--r-- | pyramid/tests/fixtures/static/héhé/index.html | 1 | ||||
| -rw-r--r-- | pyramid/tests/test_integration.py | 71 | ||||
| -rw-r--r-- | pyramid/tests/test_router.py | 98 |
8 files changed, 221 insertions, 22 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 53a09078a..d087bf43b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,48 @@ Features ``pyramid.config.Configurator.add_static_view``. This allows externally-hosted static URLs to be generated based on the current protocol. +Bug Fixes +--------- + +- View lookup will now search for valid views based on the inheritance + hierarchy of the context. It tries to find views based on the most + specific context first, and upon predicate failure, will move up the + inheritance chain to test views found by the super-type of the context. + In the past, only the most specific type containing views would be checked + and if no matching view could be found then a PredicateMismatch would be + raised. Now predicate mismatches don't hide valid views registered on + super-types. Here's an example that now works:: + + .. code-block:: python + + class IResource(Interface): + ... + + @view_config(context=IResource) + def get(context, request): + ... + + @view_config(context=IResource, request_method='POST') + def post(context, request): + ... + + @view_config(context=IResource, request_method='DELETE') + def delete(context, request): + ... + + @implementor(IResource) + class MyResource: + ... + + @view_config(context=MyResource, request_method='POST') + def override_post(context, request): + ... + + Previously the override_post view registration would hide the get + and delete views in the context of MyResource -- leading to a + predicate mismatch error when trying to use GET or DELETE + methods. Now the views are found and no predicate mismatch is + raised. 1.4 (2012-12-18) ================ diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 04a060ac3..9bc62dc62 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -269,7 +269,7 @@ you can then create a virtual environment. To do so, invoke the following: .. code-block:: text - $ export $VENV=~/env + $ export VENV=~/env $ virtualenv --no-site-packages $VENV New python executable in /home/foo/env/bin/python Installing setuptools.............done. diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 255a60ec2..64e069e6f 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -66,6 +66,13 @@ On Windows: startup problems, try putting both the virtualenv and the project into directories that do not contain spaces in their paths. +Pcreate is a script that comes with Pyramid that helps by creating and organizing files +needed as part of a Pyramid project. By passing in `alchemy` was are asking the script to +create the files needed to use SQLAlchemy. By passing in our app name `tutorial` it goes through and +places that application name in all the different files required. For example, the ``initialize_tutorial_db`` +that is in the ``pyramidtut/bin`` directory that we use later in this tutorial was created by `pcreate` + + .. _installing_project_in_dev_mode: diff --git a/pyramid/router.py b/pyramid/router.py index 9b6138ea9..63c12a1af 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,4 +1,5 @@ from zope.interface import ( + Interface, implementer, providedBy, ) @@ -24,6 +25,7 @@ from pyramid.events import ( NewResponse, ) +from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request from pyramid.threadlocal import manager @@ -158,8 +160,23 @@ class Router(object): msg = request.path_info raise HTTPNotFound(msg) else: - response = view_callable(context, request) - + try: + response = view_callable(context, request) + except PredicateMismatch: + # look for other views that meet the predicate + # criteria + for iface in context_iface.flattened(): + view_callable = adapters.lookup( + (IViewClassifier, request.request_iface, iface), + IView, name=view_name, default=None) + if view_callable is not None: + try: + response = view_callable(context, request) + break + except PredicateMismatch: + pass + else: + raise return response def invoke_subrequest(self, request, use_tweens=False): diff --git a/pyramid/tests/fixtures/static/héhé.html b/pyramid/tests/fixtures/static/héhé.html deleted file mode 100644 index fe5e9af50..000000000 --- a/pyramid/tests/fixtures/static/héhé.html +++ /dev/null @@ -1 +0,0 @@ -<html>hehe file</html> diff --git a/pyramid/tests/fixtures/static/héhé/index.html b/pyramid/tests/fixtures/static/héhé/index.html deleted file mode 100644 index 67623d866..000000000 --- a/pyramid/tests/fixtures/static/héhé/index.html +++ /dev/null @@ -1 +0,0 @@ -<html>hehe</html> diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index bf3960b2d..c8418c61d 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -3,7 +3,6 @@ import datetime import locale import os -import platform import unittest from pyramid.wsgi import wsgiapp @@ -82,27 +81,40 @@ class TestStaticAppBase(IntegrationBase): res = self.testapp.get('/static/.hiddenfile', status=200) _assertBody(res.body, os.path.join(here, 'fixtures/static/.hiddenfile')) - if defaultlocale is not None and platform.system() == 'Linux': + if defaultlocale is not None: # These tests are expected to fail on LANG=C systems due to decode # errors and on non-Linux systems due to git highchar handling # vagaries def test_highchars_in_pathelement(self): - url = url_quote('/static/héhé/index.html') - res = self.testapp.get(url, status=200) - _assertBody( - res.body, - os.path.join(here, - text_('fixtures/static/héhé/index.html', 'utf-8')) - ) + path = os.path.join( + here, + text_('fixtures/static/héhé/index.html', 'utf-8')) + pathdir = os.path.dirname(path) + body = b'<html>hehe</html>\n' + try: + os.makedirs(pathdir) + with open(path, 'wb') as fp: + fp.write(body) + url = url_quote('/static/héhé/index.html') + res = self.testapp.get(url, status=200) + self.assertEqual(res.body, body) + finally: + os.unlink(path) + os.rmdir(pathdir) def test_highchars_in_filename(self): - url = url_quote('/static/héhé.html') - res = self.testapp.get(url, status=200) - _assertBody( - res.body, - os.path.join(here, - text_('fixtures/static/héhé.html', 'utf-8')) - ) + path = os.path.join( + here, + text_('fixtures/static/héhé.html', 'utf-8')) + body = b'<html>hehe file</html>\n' + with open(path, 'wb') as fp: + fp.write(body) + try: + url = url_quote('/static/héhé.html') + res = self.testapp.get(url, status=200) + self.assertEqual(res.body, body) + finally: + os.unlink(path) def test_not_modified(self): self.testapp.extra_environ = { @@ -634,6 +646,32 @@ class RendererScanAppTest(IntegrationBase, unittest.TestCase): res = testapp.get('/two', status=200) self.assertTrue(b'Two!' in res.body) +class AcceptContentTypeTest(unittest.TestCase): + def setUp(self): + def hello_view(request): + return {'message': 'Hello!'} + from pyramid.config import Configurator + config = Configurator() + config.add_route('hello', '/hello') + config.add_view(hello_view, route_name='hello', accept='text/plain', renderer='string') + config.add_view(hello_view, route_name='hello', accept='application/json', renderer='json') + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + + def test_ordering(self): + res = self.testapp.get('/hello', headers={'Accept': 'application/json; q=1.0, text/plain; q=0.9'}, status=200) + self.assertEqual(res.content_type, 'application/json') + res = self.testapp.get('/hello', headers={'Accept': 'text/plain; q=0.9, application/json; q=1.0'}, status=200) + self.assertEqual(res.content_type, 'application/json') + + def test_wildcards(self): + res = self.testapp.get('/hello', headers={'Accept': 'application/*'}, status=200) + self.assertEqual(res.content_type, 'application/json') + res = self.testapp.get('/hello', headers={'Accept': 'text/*'}, status=200) + self.assertEqual(res.content_type, 'text/plain') + + class DummyContext(object): pass @@ -663,4 +701,3 @@ def _assertBody(body, filename): data = data.replace(b'\r', b'') data = data.replace(b'\n', b'') assert(body == data) - diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 65152ca05..432959147 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -1164,6 +1164,104 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() self.assertRaises(RuntimeError, router, environ, start_response) + def test_call_view_raises_predicate_mismatch(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest + view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view, '', IViewClassifier, IRequest, None) + environ = self._makeEnviron() + router = self._makeOne() + start_response = DummyStartResponse() + self.assertRaises(PredicateMismatch, router, environ, start_response) + + def test_call_view_predicate_mismatch_doesnt_hide_views(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest, IResponse + from pyramid.response import Response + from zope.interface import Interface, implementer + class IContext(Interface): + pass + @implementer(IContext) + class DummyContext: + pass + context = DummyContext() + self._registerTraverserFactory(context) + view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view, '', IViewClassifier, IRequest, + DummyContext) + good_view = DummyView('abc') + self._registerView(self.config.derive_view(good_view), + '', IViewClassifier, IRequest, IContext) + router = self._makeOne() + def make_response(s): + return Response(s) + router.registry.registerAdapter(make_response, (str,), IResponse) + environ = self._makeEnviron() + start_response = DummyStartResponse() + app_iter = router(environ, start_response) + self.assertEqual(app_iter, [b'abc']) + + def test_call_view_multiple_predicate_mismatches_dont_hide_views(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest, IResponse + from pyramid.response import Response + from zope.interface import Interface, implementer + class IBaseContext(Interface): + pass + class IContext(IBaseContext): + pass + @implementer(IContext) + class DummyContext: + pass + context = DummyContext() + self._registerTraverserFactory(context) + view1 = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view1, '', IViewClassifier, IRequest, + DummyContext) + view2 = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view2, '', IViewClassifier, IRequest, + IContext) + good_view = DummyView('abc') + self._registerView(self.config.derive_view(good_view), + '', IViewClassifier, IRequest, IBaseContext) + router = self._makeOne() + def make_response(s): + return Response(s) + router.registry.registerAdapter(make_response, (str,), IResponse) + environ = self._makeEnviron() + start_response = DummyStartResponse() + app_iter = router(environ, start_response) + self.assertEqual(app_iter, [b'abc']) + + def test_call_view_predicate_mismatch_doesnt_find_unrelated_views(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest + from zope.interface import Interface, implementer + class IContext(Interface): + pass + class IOtherContext(Interface): + pass + @implementer(IContext) + class DummyContext: + pass + context = DummyContext() + self._registerTraverserFactory(context) + view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view, '', IViewClassifier, IRequest, + DummyContext) + please_dont_call_me_view = DummyView('abc') + self._registerView(self.config.derive_view(please_dont_call_me_view), + '', IViewClassifier, IRequest, IOtherContext) + router = self._makeOne() + environ = self._makeEnviron() + router = self._makeOne() + start_response = DummyStartResponse() + self.assertRaises(PredicateMismatch, router, environ, start_response) + class DummyPredicate(object): def __call__(self, info, request): return True |
