Added support for passing multiple decorators to add_view. #627

Closed
wants to merge 2 commits into
from
@@ -807,14 +807,36 @@ def add_view(self, view=None, name="", for_=None, permission=None,
decorator
- A :term:`dotted Python name` to function (or the function itself)
+ A :term:`dotted Python name` to function (or the function itself,
+ or a list or tuple of the aforementioned)
which will be used to decorate the registered :term:`view
- callable`. The decorator function will be called with the view
+ callable`. The decorator function(s) will be called with the view
callable as a single argument. The view callable it is passed will
- accept ``(context, request)``. The decorator must return a
+ accept ``(context, request)``. The decorator(s) must return a
replacement view callable which also accepts ``(context,
request)``.
+ If decorator is a tuple or list of callables, the callables will be
+ combined and used in the order provided as a decorator.
+ For example::
+
+ @view_config(..., decorator=[decorator1, decorator2])
+ def myview(request):
+ ....
+
+ Is similar to doing::
+
+ @view_config(...)
+ @decorator2
+ @decorator1
+ def myview(request):
+ ...
+
+ Except with the existing benefits of ``decorator=`` (having a common
+ decorator syntax for all view calling conventions and not having to
+ think about preserving function attributes such as ``__name__`` and
+ ``__module__`` within decorator logic).
+
mapper
A Python object or :term:`dotted Python name` which refers to a
@@ -979,7 +1001,18 @@ def add_view(self, view=None, name="", for_=None, permission=None,
for_ = self.maybe_dotted(for_)
containment = self.maybe_dotted(containment)
mapper = self.maybe_dotted(mapper)
- decorator = self.maybe_dotted(decorator)
+
+ def combine(*decorators):
+ def decorated(view_callable):
+ for decorator in decorators:
+ view_callable = decorator(view_callable)
+ return view_callable
+ return decorated
+
+ if isinstance(decorator, (tuple, list)):
+ decorator = combine(*map(self.maybe_dotted, decorator))
+ else:
+ decorator = self.maybe_dotted(decorator)
if not view:
if renderer:
@@ -245,7 +245,7 @@ def out(msg):
def makedirs(dir, verbosity, pad):
parent = os.path.dirname(os.path.abspath(dir))
if not os.path.exists(parent):
- makedirs(parent, verbosity, pad)
+ makedirs(parent, verbosity, pad) # pragma: no cover
os.mkdir(dir)
def substitute_filename(fn, vars):
@@ -184,6 +184,28 @@ def inner(context, request):
result = wrapper(None, None)
self.assertEqual(result, 'OK')
+ def test_add_view_with_decorator_tuple(self):
+ from pyramid.renderers import null_renderer
+ def view(request):
+ """ ABC """
+ return 'OK'
+ def view_wrapper1(fn):
+ def inner(context, request):
+ return 'wrapped1' + fn(context, request)
+ return inner
+ def view_wrapper2(fn):
+ def inner(context, request):
+ return 'wrapped2' + fn(context, request)
+ return inner
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, decorator=(view_wrapper1, view_wrapper2),
+ renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ self.assertFalse(wrapper is view)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ result = wrapper(None, None)
+ self.assertEqual(result, 'wrapped2wrapped1OK')
+
def test_add_view_with_http_cache(self):
import datetime
from pyramid.response import Response
@@ -371,6 +371,10 @@ def test_create_nondefaults(self):
self.assertEqual(decorator.mapper, 'mapper')
self.assertEqual(decorator.decorator, 'decorator')
self.assertEqual(decorator.match_param, 'match_param')
+
+ def test_create_decorator_tuple(self):
+ decorator = self._makeOne(decorator=('decorator1', 'decorator2'))
+ self.assertEqual(decorator.decorator, ('decorator1', 'decorator2'))
def test_call_function(self):
decorator = self._makeOne()