diff options
| -rw-r--r-- | CHANGES.txt | 20 | ||||
| -rw-r--r-- | docs/api/view.rst | 3 | ||||
| -rw-r--r-- | docs/narr/viewconfig.rst | 181 | ||||
| -rw-r--r-- | docs/whatsnew-1.3.rst | 64 | ||||
| -rw-r--r-- | pyramid/config/views.py | 2 | ||||
| -rw-r--r-- | pyramid/tests/test_view.py | 20 | ||||
| -rw-r--r-- | pyramid/view.py | 8 |
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 |
