summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt42
-rw-r--r--docs/narr/install.rst2
-rw-r--r--docs/tutorials/wiki2/installation.rst7
-rw-r--r--pyramid/router.py21
-rw-r--r--pyramid/tests/fixtures/static/héhé.html1
-rw-r--r--pyramid/tests/fixtures/static/héhé/index.html1
-rw-r--r--pyramid/tests/test_integration.py71
-rw-r--r--pyramid/tests/test_router.py98
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