summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt20
-rw-r--r--docs/api/view.rst3
-rw-r--r--docs/narr/viewconfig.rst181
-rw-r--r--docs/whatsnew-1.3.rst64
-rw-r--r--pyramid/config/views.py2
-rw-r--r--pyramid/tests/test_view.py20
-rw-r--r--pyramid/view.py8
7 files changed, 296 insertions, 2 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 9cdaac5be..c979c4dc1 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,23 @@
+Next release
+============
+
+Features
+--------
+
+- New API: ``pyramid.view.view_defaults``. If you use a class as a view, you
+ can use the new ``view_defaults`` class decorator on the class to provide
+ defaults to the view configuration information used by every
+ ``@view_config`` decorator that decorates a method of that class. It also
+ works against view configurations involving a class made imperatively.
+
+Documentation
+-------------
+
+- Added documentation to "View Configuration" narrative documentation chapter
+ about ``view_defaults`` class decorator.
+
+- Added API docs for ``view_defaults`` class decorator.
+
1.3a1 (2011-12-09)
==================
diff --git a/docs/api/view.rst b/docs/api/view.rst
index 4dddea25f..9f59ddae7 100644
--- a/docs/api/view.rst
+++ b/docs/api/view.rst
@@ -16,6 +16,9 @@
.. autoclass:: view_config
:members:
+ .. autoclass:: view_defaults
+ :members:
+
.. autoclass:: static
:members:
:inherited-members:
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index af5d7f242..03000629c 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -621,6 +621,7 @@ against the ``amethod`` method could be spelled equivalently as the below:
def amethod(self):
return Response('hello')
+
.. index::
single: add_view
@@ -658,6 +659,186 @@ configurations, you don't need to issue a :term:`scan` in order for the view
configuration to take effect.
.. index::
+ single: view_defaults class decorator
+
+.. _view_defaults:
+
+``@view_defaults`` Class Decorator
+----------------------------------
+
+.. note::
+
+ This feature is new in Pyramid 1.3.
+
+If you use a class as a view, you can use the
+:class:`pyramid.view.view_defaults` class decorator on the class to provide
+defaults to the view configuration information used by every ``@view_config``
+decorator that decorates a method of that class.
+
+For instance, if you've got a class that has methods that represent "REST
+actions", all which are mapped to the same route, but different request
+methods, instead of this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+ from pyramid.response import Response
+
+ class RESTView(object):
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='rest', request_method='GET')
+ def get(self):
+ return Response('get')
+
+ @view_config(route_name='rest', request_method='POST')
+ def post(self):
+ return Response('post')
+
+ @view_config(route_name='rest', request_method='DELETE')
+ def delete(self):
+ return Response('delete')
+
+You can do this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_defaults
+ from pyramid.view import view_config
+ from pyramid.response import Response
+
+ @view_defaults(route_name='rest')
+ class RESTView(object):
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(request_method='GET')
+ def get(self):
+ return Response('get')
+
+ @view_config(request_method='POST')
+ def post(self):
+ return Response('post')
+
+ @view_config(request_method='DELETE')
+ def delete(self):
+ return Response('delete')
+
+In the above example, we were able to take the ``route_name='rest'`` argument
+out of the call to each individual ``@view_config`` statement, because we
+used a ``@view_defaults`` class decorator to provide the argument as a
+default to each view method it possessed.
+
+Arguments passed to ``@view_config`` will override any default passed to
+``@view_defaults``.
+
+The ``view_defaults`` class decorator can also provide defaults to the
+:meth:`pyramid.config.Configurator.add_view` directive when a decorated class
+is passed to that directive as its ``view`` argument. For example, instead
+of this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+ from pyramid.config import Configurator
+
+ class RESTView(object):
+ def __init__(self, request):
+ self.request = request
+
+ def get(self):
+ return Response('get')
+
+ def post(self):
+ return Response('post')
+
+ def delete(self):
+ return Response('delete')
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('rest', '/rest')
+ config.add_view(
+ RESTView, route_name='rest', attr='get', request_method='GET')
+ config.add_view(
+ RESTView, route_name='rest', attr='post', request_method='POST')
+ config.add_view(
+ RESTView, route_name='rest', attr='delete', request_method='DELETE')
+
+To reduce the amount of repetion in the ``config.add_view`` statements, we
+can move the ``route_name='rest'`` argument to a ``@view_default`` class
+decorator on the RESTView class:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+ from pyramid.response import Response
+ from pyramid.config import Configurator
+
+ @view_defaults(route_name='rest')
+ class RESTView(object):
+ def __init__(self, request):
+ self.request = request
+
+ def get(self):
+ return Response('get')
+
+ def post(self):
+ return Response('post')
+
+ def delete(self):
+ return Response('delete')
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('rest', '/rest')
+ config.add_view(RESTView, attr='get', request_method='GET')
+ config.add_view(RESTView, attr='post', request_method='POST')
+ config.add_view(RESTView, attr='delete', request_method='DELETE')
+
+:class:`pyramid.view.view_defaults` accepts the same set of arguments that
+:class:`pyramid.view.view_config` does, and they have the same meaning. Each
+argument passed to ``view_defaults`` provides a default for the view
+configurations of methods of the class it's decorating.
+
+Normal Python inheritance rules apply to defaults added via
+``view_defaults``. For example:
+
+.. code-block:: python
+ :linenos:
+
+ @view_defaults(route_name='rest')
+ class Foo(object):
+ pass
+
+ class Bar(Foo):
+ pass
+
+The ``Bar`` class above will inherit its view defaults from the arguments
+passed to the ``view_defaults`` decorator of the ``Foo`` class. To prevent
+this from happening, use a ``view_defaults`` decorator without any arguments
+on the subclass:
+
+.. code-block:: python
+ :linenos:
+
+ @view_defaults(route_name='rest')
+ class Foo(object):
+ pass
+
+ @view_defaults()
+ class Bar(Foo):
+ pass
+
+The ``view_defaults`` decorator only works as a class decorator; using it
+against a function or a method will produce nonsensical results.
+
+.. index::
single: view security
pair: security; view
diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst
index f51c7977a..608db74cd 100644
--- a/docs/whatsnew-1.3.rst
+++ b/docs/whatsnew-1.3.rst
@@ -126,6 +126,70 @@ New APIs were added to support introspection
:attr:`pyramid.config.Configurator.introspectable`,
:attr:`pyramid.registry.Registry.introspector`.
+``@view_defaults`` Decorator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you use a class as a view, you can use the new
+:class:`pyramid.view.view_defaults` class decorator on the class to provide
+defaults to the view configuration information used by every ``@view_config``
+decorator that decorates a method of that class.
+
+For instance, if you've got a class that has methods that represent "REST
+actions", all which are mapped to the same route, but different request
+methods, instead of this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+ from pyramid.response import Response
+
+ class RESTView(object):
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='rest', request_method='GET')
+ def get(self):
+ return Response('get')
+
+ @view_config(route_name='rest', request_method='POST')
+ def post(self):
+ return Response('post')
+
+ @view_config(route_name='rest', request_method='DELETE')
+ def delete(self):
+ return Response('delete')
+
+You can do this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_defaults
+ from pyramid.view import view_config
+ from pyramid.response import Response
+
+ @view_defaults(route_name='rest')
+ class RESTView(object):
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(request_method='GET')
+ def get(self):
+ return Response('get')
+
+ @view_config(request_method='POST')
+ def post(self):
+ return Response('post')
+
+ @view_config(request_method='DELETE')
+ def delete(self):
+ return Response('delete')
+
+This also works for imperative view configurations that involve a class.
+
+See :ref:`view_defaults` for more information.
+
Minor Feature Additions
-----------------------
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index fd736682a..5efe1f2bb 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -562,7 +562,7 @@ def viewdefaults(wrapped):
if inspect.isclass(view):
defaults = getattr(view, '__view_defaults__', {}).copy()
defaults.update(kw)
- defaults['_backframes'] = 3
+ defaults['_backframes'] = 3 # for action_method
return wrapped(*arg, **defaults)
return wraps(wrapped)(wrapper)
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index 2fa9ced74..0d00e65c6 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -578,7 +578,7 @@ class Test_view_defaults(unittest.TestCase):
self.assertEqual(Foo.__view_defaults__['route_name'],'abc')
self.assertEqual(Foo.__view_defaults__['renderer'],'def')
- def test_it_single_inheritance_non_overridden(self):
+ def test_it_inheritance_not_overridden(self):
from pyramid.view import view_defaults
@view_defaults(route_name='abc', renderer='def')
class Foo(object): pass
@@ -586,6 +586,24 @@ class Test_view_defaults(unittest.TestCase):
self.assertEqual(Bar.__view_defaults__['route_name'],'abc')
self.assertEqual(Bar.__view_defaults__['renderer'],'def')
+ def test_it_inheritance_overriden(self):
+ from pyramid.view import view_defaults
+ @view_defaults(route_name='abc', renderer='def')
+ class Foo(object): pass
+ @view_defaults(route_name='ghi')
+ class Bar(Foo): pass
+ self.assertEqual(Bar.__view_defaults__['route_name'],'ghi')
+ self.assertEqual(Bar.__view_defaults__['renderer'], None)
+
+ def test_it_inheritance_overriden_empty(self):
+ from pyramid.view import view_defaults
+ @view_defaults(route_name='abc', renderer='def')
+ class Foo(object): pass
+ @view_defaults()
+ class Bar(Foo): pass
+ self.assertEqual(Bar.__view_defaults__['route_name'], None)
+ self.assertEqual(Bar.__view_defaults__['renderer'], None)
+
class ExceptionResponse(Exception):
status = '404 Not Found'
app_iter = ['Not Found']
diff --git a/pyramid/view.py b/pyramid/view.py
index 098488f85..eae56a661 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -224,6 +224,14 @@ class view_config(object):
bfg_view = view_config # bw compat (forever)
class view_defaults(view_config):
+ """ A class :term:`decorator` which, when applied to a class, will
+ provide defaults for all view configurations that use the class. This
+ decorator accepts all the arguments accepted by
+ :class:`pyramid.config.view_config`, and each has the same meaning.
+
+ See :ref:`view_defaults` for more information.
+ """
+
def __call__(self, wrapped):
wrapped.__view_defaults__ = self.__dict__.copy()
return wrapped