Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

- New API: ``pyramid.config.Configurator.add_forbidden_view``. This is a

  wrapper for ``pyramid.Config.configurator.add_view`` which does the right
  thing about permissions.  It should be preferred over calling ``add_view``
  directly with ``context=HTTPForbidden`` as was previously recommended.

- New API: ``pyramid.view.forbidden_view_config``.  This is a decorator
  constructor like ``pyramid.view.view_config`` that calls
  ``pyramid.config.Configurator.add_forbidden_view`` when scanned.  It should
  be preferred over using ``pyramid.view.view_config`` with
  ``context=HTTPForbidden`` as was previously recommended.

- Updated the "Creating a Not Forbidden View" section of the "Hooks" chapter,
  replacing explanations of registering a view using ``add_view`` or
  ``view_config`` with ones using ``add_forbidden_view`` or
  ``forbidden_view_config``.

- Updated all tutorials to use ``pyramid.view.forbidden_view_config`` rather
  than ``pyramid.view.view_config`` with an HTTPForbidden context.
  • Loading branch information...
commit a7fe30f0eabd6c6fd3bcc910faa41720a75056de 1 parent 2d04589
@mcdonc mcdonc authored
View
45 CHANGES.txt
@@ -15,8 +15,9 @@ Features
- New API: ``pyramid.config.Configurator.add_notfound_view``. This is a
wrapper for ``pyramid.Config.configurator.add_view`` which provides easy
- append_slash support. It should be preferred over calling ``add_view``
- directly with ``context=HTTPNotFound`` as was previously recommended.
+ append_slash support and does the right thing about permissions. It should
+ be preferred over calling ``add_view`` directly with
+ ``context=HTTPNotFound`` as was previously recommended.
- New API: ``pyramid.view.notfound_view_config``. This is a decorator
constructor like ``pyramid.view.view_config`` that calls
@@ -24,6 +25,17 @@ Features
be preferred over using ``pyramid.view.view_config`` with
``context=HTTPNotFound`` as was previously recommended.
+- New API: ``pyramid.config.Configurator.add_forbidden_view``. This is a
+ wrapper for ``pyramid.Config.configurator.add_view`` which does the right
+ thing about permissions. It should be preferred over calling ``add_view``
+ directly with ``context=HTTPForbidden`` as was previously recommended.
+
+- New API: ``pyramid.view.forbidden_view_config``. This is a decorator
+ constructor like ``pyramid.view.view_config`` that calls
+ ``pyramid.config.Configurator.add_forbidden_view`` when scanned. It should
+ be preferred over using ``pyramid.view.view_config`` with
+ ``context=HTTPForbidden`` as was previously recommended.
+
Backwards Incompatibilities
---------------------------
@@ -40,14 +52,16 @@ Backwards Incompatibilities
- The ``pyramid.registry.noop_introspector`` API object has been removed.
- The older deprecated ``set_notfound_view`` Configurator method is now an
- alias for the new ``add_notfound_view`` Configurator method. This has the
- following impact: the ``context`` sent to views with a ``(context,
- request)`` call signature registered via the deprecated
- ``add_notfound_view``/``set_notfound_view`` will now be the HTTPNotFound
- exception object instead of the actual resource context found. Use
+ alias for the new ``add_notfound_view`` Configurator method. Likewise, the
+ older deprecated ``set_forbidden_view`` is now an alias for the new
+ ``add_forbidden_view``. This has the following impact: the ``context`` sent
+ to views with a ``(context, request)`` call signature registered via the
+ ``set_notfound_view`` or ``set_forbidden_view`` will now be an exception
+ object instead of the actual resource context found. Use
``request.context`` to get the actual resource context. It's also
recommended to disuse ``set_notfound_view`` in favor of
- ``add_notfound_view``, despite the aliasing.
+ ``add_notfound_view``, and disuse ``set_forbidden_view`` in favor of
+ ``add_forbidden_view`` despite the aliasing.
Deprecations
------------
@@ -59,8 +73,9 @@ Deprecations
``pyramid.view.notfound_view_config(append_slash=True)`` to get the same
behavior.
-- The ``set_forbidden_view`` method of the Configurator was removed from the
- documentation. It has been deprecated since Pyramid 1.1.
+- The ``set_forbidden_view`` and ``set_notfound_view`` methods of the
+ Configurator were removed from the documentation. They have been
+ deprecated since Pyramid 1.1.
Bug Fixes
---------
@@ -76,16 +91,24 @@ Bug Fixes
Documentation
-------------
-- Updated the "Registering a Not Found View" section of the "Hooks" chapter,
+- Updated the "Creating a Not Found View" section of the "Hooks" chapter,
replacing explanations of registering a view using ``add_view`` or
``view_config`` with ones using ``add_notfound_view`` or
``notfound_view_config``.
+- Updated the "Creating a Not Forbidden View" section of the "Hooks" chapter,
+ replacing explanations of registering a view using ``add_view`` or
+ ``view_config`` with ones using ``add_forbidden_view`` or
+ ``forbidden_view_config``.
+
- Updated the "Redirecting to Slash-Appended Routes" section of the "URL
Dispatch" chapter, replacing explanations of registering a view using
``add_view`` or ``view_config`` with ones using ``add_notfound_view`` or
``notfound_view_config``
+- Updated all tutorials to use ``pyramid.view.forbidden_view_config`` rather
+ than ``pyramid.view.view_config`` with an HTTPForbidden context.
+
1.3a8 (2012-02-19)
==================
View
2  TODO.txt
@@ -4,8 +4,6 @@ Pyramid TODOs
Nice-to-Have
------------
-- Add forbidden_view_config?
-
- Add docs about upgrading between Pyramid versions (e.g. how to see
deprecation warnings).
View
1  docs/api/config.rst
@@ -25,6 +25,7 @@
.. automethod:: add_static_view(name, path, cache_max_age=3600, permission=NO_PERMISSION_REQUIRED)
.. automethod:: add_view
.. automethod:: add_notfound_view
+ .. automethod:: add_forbidden_view
:methodcategory:`Adding an Event Subscriber`
View
3  docs/api/view.rst
@@ -22,6 +22,9 @@
.. autoclass:: notfound_view_config
:members:
+ .. autoclass:: forbidden_view_config
+ :members:
+
.. autoclass:: static
:members:
:inherited-members:
View
29 docs/narr/hooks.rst
@@ -145,23 +145,40 @@ the view which generates it can be overridden as necessary.
The :term:`forbidden view` callable is a view callable like any other. The
:term:`view configuration` which causes it to be a "forbidden" view consists
-only of naming the :exc:`pyramid.httpexceptions.HTTPForbidden` class as the
-``context`` of the view configuration.
+of using the meth:`pyramid.config.Configurator.add_forbidden_view` API or the
+:class:`pyramid.view.forbidden_view_config` decorator.
-You can replace the forbidden view by using the
-:meth:`pyramid.config.Configurator.add_view` method to register an "exception
-view":
+For example, you can add a forbidden view by using the
+:meth:`pyramid.config.Configurator.add_forbidden_view` method to register a
+forbidden view:
.. code-block:: python
:linenos:
from helloworld.views import forbidden_view
from pyramid.httpexceptions import HTTPForbidden
- config.add_view(forbidden_view, context=HTTPForbidden)
+ config.add_forbidden_view(forbidden_view)
Replace ``helloworld.views.forbidden_view`` with a reference to the Python
:term:`view callable` you want to use to represent the Forbidden view.
+If instead you prefer to use decorators and a :term:`scan`, you can use the
+:class:`pyramid.view.forbidden_view_config` decorator to mark a view callable
+as a forbidden view:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import forbidden_view_config
+
+ forbidden_view_config()
+ def forbidden(request):
+ return Response('forbidden')
+
+ def main(globals, **settings):
+ config = Configurator()
+ config.scan()
+
Like any other view, the forbidden view must accept at least a ``request``
parameter, or both ``context`` and ``request``. The ``context`` (available
as ``request.context`` if you're using the request-only view argument
View
28 docs/tutorials/wiki/authorization.rst
@@ -132,14 +132,14 @@ We'll add these views to the existing ``views.py`` file we have in our
project. Here's what the ``login`` view callable will look like:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 83-111
+ :lines: 86-113
:linenos:
:language: python
Here's what the ``logout`` view callable will look like:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 113-117
+ :lines: 115-119
:linenos:
:language: python
@@ -149,18 +149,18 @@ different :term:`view configuration` for the ``login`` view callable.
The first view configuration decorator configures the ``login`` view callable
so it will be invoked when someone visits ``/login`` (when the context is a
-Wiki and the view name is ``login``). The second decorator (with context of
-``pyramid.httpexceptions.HTTPForbidden``) specifies a :term:`forbidden view`.
-This configures our login view to be presented to the user when
-:app:`Pyramid` detects that a view invocation can not be authorized. Because
-we've configured a forbidden view, the ``login`` view callable will be
-invoked whenever one of our users tries to execute a view callable that they
-are not allowed to invoke as determined by the :term:`authorization policy`
-in use. In our application, for example, this means that if a user has not
-logged in, and he tries to add or edit a Wiki page, he will be shown the
-login form. Before being allowed to continue on to the add or edit form, he
-will have to provide credentials that give him permission to add or edit via
-this login form.
+Wiki and the view name is ``login``). The second decorator, named
+``forbidden_view_config`` specifies a :term:`forbidden view`. This
+configures our login view to be presented to the user when :app:`Pyramid`
+detects that a view invocation can not be authorized. Because we've
+configured a forbidden view, the ``login`` view callable will be invoked
+whenever one of our users tries to execute a view callable that they are not
+allowed to invoke as determined by the :term:`authorization policy` in use.
+In our application, for example, this means that if a user has not logged in,
+and he tries to add or edit a Wiki page, he will be shown the login form.
+Before being allowed to continue on to the add or edit form, he will have to
+provide credentials that give him permission to add or edit via this login
+form.
Note that we're relying on some additional imports within the bodies of these
views (e.g. ``remember`` and ``forget``). We'll see a rendering of the
View
8 docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -3,7 +3,10 @@
from pyramid.httpexceptions import HTTPFound
-from pyramid.view import view_config
+from pyramid.view import (
+ view_config,
+ forbidden_view_config,
+ )
from pyramid.security import (
authenticated_userid,
@@ -82,8 +85,7 @@ def edit_page(context, request):
@view_config(context='.models.Wiki', name='login',
renderer='templates/login.pt')
-@view_config(context='pyramid.httpexceptions.HTTPForbidden',
- renderer='templates/login.pt')
+@forbidden_view_config(renderer='templates/login.pt')
def login(request):
login_url = request.resource_url(request.context, 'login')
referrer = request.url
View
8 docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -3,7 +3,10 @@
from pyramid.httpexceptions import HTTPFound
-from pyramid.view import view_config
+from pyramid.view import (
+ view_config,
+ forbidden_view_config,
+ )
from pyramid.security import (
authenticated_userid,
@@ -82,8 +85,7 @@ def edit_page(context, request):
@view_config(context='.models.Wiki', name='login',
renderer='templates/login.pt')
-@view_config(context='pyramid.httpexceptions.HTTPForbidden',
- renderer='templates/login.pt')
+@forbidden_view_config(renderer='templates/login.pt')
def login(request):
login_url = request.resource_url(request.context, 'login')
referrer = request.url
View
26 docs/tutorials/wiki2/authorization.rst
@@ -159,33 +159,35 @@ logged in user and redirect back to the front page.
The ``login`` view callable will look something like this:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 87-113
+ :lines: 89-115
:linenos:
:language: python
The ``logout`` view callable will look something like this:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 115-119
+ :lines: 117-121
:linenos:
:language: python
-The ``login`` view callable is decorated with two ``@view_config``
-decorators, one which associates it with the ``login`` route, the other which
-associates it with the ``HTTPForbidden`` context. The one which associates
-it with the ``login`` route makes it visible when we visit ``/login``. The
-one which associates it with the ``HTTPForbidden`` context makes it the
-:term:`forbidden view`. The forbidden view is displayed whenever Pyramid or
-your application raises an HTTPForbidden exception. In this case, we'll be
-relying on the forbidden view to show the login form whenver someone attempts
-to execute an action which they're not yet authorized to perform.
+The ``login`` view callable is decorated with two decorators, a
+``@view_config`` decorators, which associates it with the ``login`` route,
+the other a ``@forbidden_view_config`` decorator which turns it in to an
+:term:`exception view` when Pyramid raises a
+:class:`pyramid.httpexceptions.HTTPForbidden` exception. The one which
+associates it with the ``login`` route makes it visible when we visit
+``/login``. The other one makes it a :term:`forbidden view`. The forbidden
+view is displayed whenever Pyramid or your application raises an
+HTTPForbidden exception. In this case, we'll be relying on the forbidden
+view to show the login form whenver someone attempts to execute an action
+which they're not yet authorized to perform.
The ``logout`` view callable is decorated with a ``@view_config`` decorator
which associates it with the ``logout`` route. This makes it visible when we
visit ``/login``.
We'll need to import some stuff to service the needs of these two functions:
-the ``HTTPForbidden`` exception, a number of values from the
+the ``pyramid.view.forbidden_view_config`` class, a number of values from the
``pyramid.security`` module, and a value from our newly added
``tutorial.security`` package.
View
8 docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -4,10 +4,12 @@
from pyramid.httpexceptions import (
HTTPFound,
HTTPNotFound,
- HTTPForbidden,
)
-from pyramid.view import view_config
+from pyramid.view import (
+ view_config,
+ forbidden_view_config,
+ )
from pyramid.security import (
remember,
@@ -85,7 +87,7 @@ def edit_page(request):
)
@view_config(route_name='login', renderer='templates/login.pt')
-@view_config(context=HTTPForbidden, renderer='templates/login.pt')
+@forbidden_view_config(renderer='templates/login.pt')
def login(request):
login_url = request.route_url('login')
referrer = request.url
View
8 docs/tutorials/wiki2/src/tests/tutorial/views.py
@@ -4,10 +4,12 @@
from pyramid.httpexceptions import (
HTTPFound,
HTTPNotFound,
- HTTPForbidden,
)
-from pyramid.view import view_config
+from pyramid.view import (
+ view_config,
+ forbidden_view_config,
+ )
from pyramid.security import (
remember,
@@ -88,7 +90,7 @@ def edit_page(request):
)
@view_config(route_name='login', renderer='templates/login.pt')
-@view_config(context=HTTPForbidden, renderer='templates/login.pt')
+@forbidden_view_config(renderer='templates/login.pt')
def login(request):
login_url = request.route_url('login')
referrer = request.url
View
46 docs/whatsnew-1.3.rst
@@ -211,8 +211,10 @@ added, as well, but the configurator method should be preferred as it
provides conflict detection and consistency in the lifetime of the
properties.
-Not Found View Helpers
-~~~~~~~~~~~~~~~~~~~~~~
+Not Found and Forbidden View Helpers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Not Found helpers:
- New API: :meth:`pyramid.config.Configurator.add_notfound_view`. This is a
wrapper for :meth:`pyramid.Config.configurator.add_view` which provides
@@ -227,6 +229,20 @@ Not Found View Helpers
should be preferred over using ``pyramid.view.view_config`` with
``context=HTTPNotFound`` as was previously recommended.
+Forbidden helpers:
+
+- New API: :meth:`pyramid.config.Configurator.add_forbidden_view`. This is a
+ wrapper for :meth:`pyramid.Config.configurator.add_view` which does the
+ right thing about permissions. It should be preferred over calling
+ ``add_view`` directly with ``context=HTTPForbidden`` as was previously
+ recommended.
+
+- New API: :class:`pyramid.view.forbidden_view_config`. This is a decorator
+ constructor like :class:`pyramid.view.view_config` that calls
+ :meth:`pyramid.config.Configurator.add_forbidden_view` when scanned. It
+ should be preferred over using ``pyramid.view.view_config`` with
+ ``context=HTTPForbidden`` as was previously recommended.
+
Minor Feature Additions
-----------------------
@@ -431,14 +447,16 @@ Backwards Incompatibilities
Pyramid.
- The older deprecated ``set_notfound_view`` Configurator method is now an
- alias for the new :meth:`pyramid.config.Configurator.add_notfound_view`
- method. This has the following impact: the ``context`` sent to views with
- a ``(context, request)`` call signature registered via the deprecated
- ``add_notfound_view`` / ``set_notfound_view`` will now be the HTTPNotFound
- exception object instead of the actual resource context found. Use
+ alias for the new ``add_notfound_view`` Configurator method. Likewise, the
+ older deprecated ``set_forbidden_view`` is now an alias for the new
+ ``add_forbidden_view`` Configurator method. This has the following impact:
+ the ``context`` sent to views with a ``(context, request)`` call signature
+ registered via the ``set_notfound_view`` or ``set_forbidden_view`` will now
+ be an exception object instead of the actual resource context found. Use
``request.context`` to get the actual resource context. It's also
recommended to disuse ``set_notfound_view`` in favor of
- ``add_notfound_view``, despite the aliasing.
+ ``add_notfound_view``, and disuse ``set_forbidden_view`` in favor of
+ ``add_forbidden_view`` despite the aliasing.
Deprecations
------------
@@ -450,8 +468,9 @@ Deprecations
``pyramid.view.notfound_view_config(append_slash=True)`` to get the same
behavior.
-- The ``set_forbidden_view`` method of the Configurator was removed from the
- documentation. It has been deprecated since Pyramid 1.1.
+- The ``set_forbidden_view`` and ``set_notfound_view`` methods of the
+ Configurator were removed from the documentation. They have been
+ deprecated since Pyramid 1.1.
Documentation Enhancements
--------------------------
@@ -485,6 +504,10 @@ Documentation Enhancements
Rationale: it provides the correct info for the Python 2.5 version of GAE
only, and this version of Pyramid does not support Python 2.5.
+- Updated the :ref:`changing_the_forbidden_view` section, replacing
+ explanations of registering a view using ``add_view`` or ``view_config``
+ with ones using ``add_forbidden_view`` or ``forbidden_view_config``.
+
- Updated the :ref:`changing_the_notfound_view` section, replacing
explanations of registering a view using ``add_view`` or ``view_config``
with ones using ``add_notfound_view`` or ``notfound_view_config``.
@@ -493,6 +516,9 @@ Documentation Enhancements
explanations of registering a view using ``add_view`` or ``view_config``
with ones using ``add_notfound_view`` or ``notfound_view_config``
+- Updated all tutorials to use ``pyramid.view.forbidden_view_config`` rather
+ than ``pyramid.view.view_config`` with an HTTPForbidden context.
+
Dependency Changes
------------------
View
94 pyramid/config/views.py
@@ -1329,42 +1329,59 @@ def _derive_view(self, view, permission=None, predicates=(),
return deriver(view)
@action_method
- def set_forbidden_view(self, view=None, attr=None, renderer=None,
- wrapper=None):
- """ Add a default forbidden view to the current configuration
- state.
+ def add_forbidden_view(
+ self, view=None, attr=None, renderer=None, wrapper=None,
+ route_name=None, request_type=None, request_method=None,
+ request_param=None, containment=None, xhr=None, accept=None,
+ header=None, path_info=None, custom_predicates=(), decorator=None,
+ mapper=None, match_param=None):
+ """ Add a forbidden view to the current configuration state. The
+ view will be called when Pyramid or application code raises a
+ :exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of
+ circumstances implied by the predicates provided are matched. The
+ simplest example is:
- .. warning::
+ .. code-block:: python
- This method has been deprecated in :app:`Pyramid` 1.0. *Do not use
- it for new development; it should only be used to support older code
- bases which depend upon it.* See :ref:`changing_the_forbidden_view`
- to see how a forbidden view should be registered in new projects.
-
- The ``view`` argument should be a :term:`view callable` or a
- :term:`dotted Python name` which refers to a view callable.
-
- The ``attr`` argument should be the attribute of the view
- callable used to retrieve the response (see the ``add_view``
- method's ``attr`` argument for a description).
-
- The ``renderer`` argument should be the name of (or path to) a
- :term:`renderer` used to generate a response for this view
- (see the
- :meth:`pyramid.config.Configurator.add_view`
- method's ``renderer`` argument for information about how a
- configurator relates to a renderer).
-
- The ``wrapper`` argument should be the name of another view
- which will wrap this view when rendered (see the ``add_view``
- method's ``wrapper`` argument for a description)."""
- view = self._derive_view(view, attr=attr, renderer=renderer)
- def bwcompat_view(context, request):
- context = getattr(request, 'context', None)
- return view(context, request)
- return self.add_view(bwcompat_view, context=HTTPForbidden,
- wrapper=wrapper, renderer=renderer)
+ def forbidden(request):
+ return Response('Forbidden', status='403 Forbidden')
+
+ config.add_forbidden_view(forbidden)
+
+ All arguments have the same meaning as
+ :meth:`pyramid.config.Configurator.add_view` and each predicate
+ argument restricts the set of circumstances under which this notfound
+ view will be invoked.
+
+ .. note::
+ This method is new as of Pyramid 1.3.
+ """
+ settings = dict(
+ view=view,
+ context=HTTPForbidden,
+ wrapper=wrapper,
+ request_type=request_type,
+ request_method=request_method,
+ request_param=request_param,
+ containment=containment,
+ xhr=xhr,
+ accept=accept,
+ header=header,
+ path_info=path_info,
+ custom_predicates=custom_predicates,
+ decorator=decorator,
+ mapper=mapper,
+ match_param=match_param,
+ route_name=route_name,
+ permission=NO_PERMISSION_REQUIRED,
+ attr=attr,
+ renderer=renderer,
+ )
+ return self.add_view(**settings)
+
+ set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias
+
@action_method
def add_notfound_view(
self, view=None, attr=None, renderer=None, wrapper=None,
@@ -1373,13 +1390,16 @@ def add_notfound_view(
header=None, path_info=None, custom_predicates=(), decorator=None,
mapper=None, match_param=None, append_slash=False):
""" Add a default notfound view to the current configuration state.
- The view will be called when a view cannot otherwise be found for the
- set of circumstances implied by the predicates provided. The
- simplest example is:
+ The view will be called when Pyramid or application code raises an
+ :exc:`pyramid.httpexceptions.HTTPForbidden` exception (e.g. when a
+ view cannot be found for the request). The simplest example is:
.. code-block:: python
- config.add_notfound_view(someview)
+ def notfound(request):
+ return Response('Not Found', status='404 Not Found')
+
+ config.add_notfound_view(notfound)
All arguments except ``append_slash`` have the same meaning as
:meth:`pyramid.config.Configurator.add_view` and each predicate
View
31 pyramid/tests/pkgs/forbiddenview/__init__.py
@@ -0,0 +1,31 @@
+from pyramid.view import forbidden_view_config, view_config
+from pyramid.response import Response
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+
+@forbidden_view_config(route_name='foo')
+def foo_forbidden(request): # pragma: no cover
+ return Response('foo_forbidden')
+
+@forbidden_view_config()
+def forbidden(request):
+ return Response('generic_forbidden')
+
+@view_config(route_name='foo')
+def foo(request): # pragma: no cover
+ return Response('OK foo')
+
+@view_config(route_name='bar')
+def bar(request): # pragma: no cover
+ return Response('OK bar')
+
+def includeme(config):
+ authn_policy = AuthTktAuthenticationPolicy('seekri1')
+ authz_policy = ACLAuthorizationPolicy()
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+ config.set_default_permission('a')
+ config.add_route('foo', '/foo')
+ config.add_route('bar', '/bar')
+ config.scan('pyramid.tests.pkgs.forbiddenview')
+
View
24 pyramid/tests/test_config/test_views.py
@@ -1649,14 +1649,14 @@ def test_add_static_view_absolute(self):
self.assertEqual(info.added,
[(config, 'static', static_path, {})])
- def test_set_forbidden_view(self):
+ def test_add_forbidden_view(self):
from pyramid.renderers import null_renderer
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
from pyramid.httpexceptions import HTTPForbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: 'OK'
- config.set_forbidden_view(view, renderer=null_renderer)
+ config.add_forbidden_view(view, renderer=null_renderer)
request = self._makeRequest(config)
view = self._getViewCallable(config,
ctx_iface=implementedBy(HTTPForbidden),
@@ -1664,24 +1664,6 @@ def test_set_forbidden_view(self):
result = view(None, request)
self.assertEqual(result, 'OK')
- def test_set_forbidden_view_request_has_context(self):
- from pyramid.renderers import null_renderer
- from zope.interface import implementedBy
- from pyramid.interfaces import IRequest
- from pyramid.httpexceptions import HTTPForbidden
- config = self._makeOne(autocommit=True)
- view = lambda *arg: arg
- config.set_forbidden_view(view, renderer=null_renderer)
- request = self._makeRequest(config)
- request.context = 'abc'
- view = self._getViewCallable(config,
- ctx_iface=implementedBy(HTTPForbidden),
- request_iface=IRequest)
- result = view(None, request)
- self.assertEqual(result, ('abc', request))
-
-
-
def test_add_notfound_view(self):
from pyramid.renderers import null_renderer
from zope.interface import implementedBy
@@ -1743,7 +1725,7 @@ def test_add_forbidden_view_with_renderer(self):
from pyramid.httpexceptions import HTTPForbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
- config.set_forbidden_view(
+ config.add_forbidden_view(
view,
renderer='pyramid.tests.test_config:files/minimal.pt')
config.begin()
View
9 pyramid/tests/test_integration.py
@@ -373,6 +373,15 @@ def test_it(self):
self.assertTrue(b'OK foo2' in res.body)
res = self.testapp.get('/baz', status=200)
self.assertTrue(b'baz_notfound' in res.body)
+
+class TestForbiddenView(IntegrationBase, unittest.TestCase):
+ package = 'pyramid.tests.pkgs.forbiddenview'
+
+ def test_it(self):
+ res = self.testapp.get('/foo', status=200)
+ self.assertTrue(b'foo_forbidden' in res.body)
+ res = self.testapp.get('/bar', status=200)
+ self.assertTrue(b'generic_forbidden' in res.body)
class TestViewPermissionBug(IntegrationBase, unittest.TestCase):
# view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html
View
44 pyramid/tests/test_view.py
@@ -94,6 +94,48 @@ class view(object): pass
self.assertEqual(settings[0]['attr'], 'view')
self.assertEqual(settings[0]['_info'], 'codeinfo')
+class Test_forbidden_view_config(BaseTest, unittest.TestCase):
+ def _makeOne(self, **kw):
+ from pyramid.view import forbidden_view_config
+ return forbidden_view_config(**kw)
+
+ def test_ctor(self):
+ inst = self._makeOne(attr='attr', path_info='path_info')
+ self.assertEqual(inst.__dict__,
+ {'attr':'attr', 'path_info':'path_info'})
+
+ def test_it_function(self):
+ def view(request): pass
+ decorator = self._makeOne(attr='attr', renderer='renderer')
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ wrapped = decorator(view)
+ self.assertTrue(wrapped is view)
+ config = call_venusian(venusian)
+ settings = config.settings
+ self.assertEqual(
+ settings,
+ [{'attr': 'attr', 'venusian': venusian,
+ 'renderer': 'renderer', '_info': 'codeinfo', 'view': None}]
+ )
+
+ def test_it_class(self):
+ decorator = self._makeOne()
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ decorator.venusian.info.scope = 'class'
+ class view(object): pass
+ wrapped = decorator(view)
+ self.assertTrue(wrapped is view)
+ config = call_venusian(venusian)
+ settings = config.settings
+ self.assertEqual(len(settings), 1)
+ self.assertEqual(len(settings[0]), 4)
+ self.assertEqual(settings[0]['venusian'], venusian)
+ self.assertEqual(settings[0]['view'], None) # comes from call_venusian
+ self.assertEqual(settings[0]['attr'], 'view')
+ self.assertEqual(settings[0]['_info'], 'codeinfo')
+
class RenderViewToResponseTests(BaseTest, unittest.TestCase):
def _callFUT(self, *arg, **kw):
from pyramid.view import render_view_to_response
@@ -716,7 +758,7 @@ def __init__(self):
def add_view(self, **kw):
self.settings.append(kw)
- add_notfound_view = add_view
+ add_notfound_view = add_forbidden_view = add_view
def with_package(self, pkg):
self.pkg = pkg
View
65 pyramid/view.py
@@ -391,6 +391,71 @@ def callback(context, name, ob):
settings['_info'] = info.codeinfo # fbo "action_method"
return wrapped
+class forbidden_view_config(object):
+ """
+
+ An analogue of :class:`pyramid.view.view_config` which registers a
+ :term:`forbidden view`.
+
+ The forbidden_view_config constructor accepts most of the same arguments
+ as the constructor of :class:`pyramid.view.view_config`. It can be used
+ in the same places, and behaves in largely the same way, except it always
+ registers a forbidden exception view instead of a "normal" view.
+
+ Example:
+
+ .. code-block:: python
+
+ from pyramid.view import forbidden_view_config
+ from pyramid.response import Response
+
+ forbidden_view_config()
+ def notfound(request):
+ return Response('You are not allowed', status='401 Unauthorized')
+
+ All have the same meaning as :meth:`pyramid.view.view_config` and each
+ predicate argument restricts the set of circumstances under which this
+ notfound view will be invoked.
+
+ See :ref:`changing_the_forbidden_view` for detailed usage information.
+
+ .. note::
+
+ This class is new as of Pyramid 1.3.
+ """
+
+ venusian = venusian
+
+ def __init__(self, request_type=default, request_method=default,
+ route_name=default, request_param=default, attr=default,
+ renderer=default, containment=default, wrapper=default,
+ xhr=default, accept=default, header=default,
+ path_info=default, custom_predicates=default,
+ decorator=default, mapper=default, match_param=default):
+ L = locals()
+ for k, v in L.items():
+ if k not in ('self', 'L') and v is not default:
+ self.__dict__[k] = v
+
+ def __call__(self, wrapped):
+ settings = self.__dict__.copy()
+
+ def callback(context, name, ob):
+ config = context.config.with_package(info.module)
+ config.add_forbidden_view(view=ob, **settings)
+
+ info = self.venusian.attach(wrapped, callback, category='pyramid')
+
+ if info.scope == 'class':
+ # if the decorator was attached to a method in a class, or
+ # otherwise executed at class scope, we need to set an
+ # 'attr' into the settings if one isn't already in there
+ if settings.get('attr') is None:
+ settings['attr'] = wrapped.__name__
+
+ settings['_info'] = info.codeinfo # fbo "action_method"
+ return wrapped
+
def is_response(ob):
""" Return ``True`` if ``ob`` implements the interface implied by
:ref:`the_response`. ``False`` if not.
Please sign in to comment.
Something went wrong with that request. Please try again.