diff --git a/CHANGES b/CHANGES index 6727e98ffb..fa8c00bf33 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,8 @@ Relase date to be decided, codename to be chosen. - Refactored test client internally. The ``APPLICATION_ROOT`` configuration variable as well as ``SERVER_NAME`` are now properly used by the test client as defaults. +- Added :attr:`flask.views.View.decorators` to support simpler decorating of + pluggable (class based) views. Version 0.7.3 ------------- diff --git a/flask/views.py b/flask/views.py index 9a18557022..2fe3462247 100644 --- a/flask/views.py +++ b/flask/views.py @@ -30,10 +30,38 @@ def dispatch_request(self, name): return 'Hello %s!' % name app.add_url_rule('/hello/', view_func=MyView.as_view('myview')) + + When you want to decorate a pluggable view you will have to either do that + when the view function is created (by wrapping the return value of + :meth:`as_view`) or you can use the :attr:`decorators` attribute:: + + class SecretView(View): + methods = ['GET'] + decorators = [superuser_required] + + def dispatch_request(self): + ... + + The decorators stored in the decorators list are applied one after another + when the view function is created. Note that you can *not* use the class + based decorators since those would decorate the view class and not the + generated view function! """ + #: A for which methods this pluggable view can handle. methods = None + #: The canonical way to decorate class based views is to decorate the + #: return value of as_view(). However since this moves parts of the + #: logic from the class declaration to the place where it's hooked + #: into the routing system. + #: + #: You can place one or more decorators in this list and whenever the + #: view function is created the result is automatically decorated. + #: + #: .. versionadded:: 0.8 + decorators = [] + def dispatch_request(self): """Subclasses have to override this method to implement the actual view functionc ode. This method is called with all @@ -54,6 +82,13 @@ def as_view(cls, name, *class_args, **class_kwargs): def view(*args, **kwargs): self = view.view_class(*class_args, **class_kwargs) return self.dispatch_request(*args, **kwargs) + + if cls.decorators: + view.__name__ = name + view.__module__ = cls.__module__ + for decorator in cls.decorators: + view = decorator(view) + # we attach the view class to the view function for two reasons: # first of all it allows us to easily figure out what class based # view this thing came from, secondly it's also used for instanciating diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 1d9daf8352..dcc3e1a022 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -2258,6 +2258,27 @@ def delete(self): meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) + def test_view_decorators(self): + app = flask.Flask(__name__) + + def add_x_parachute(f): + def new_function(*args, **kwargs): + resp = flask.make_response(f(*args, **kwargs)) + resp.headers['X-Parachute'] = 'awesome' + return resp + return new_function + + class Index(flask.views.View): + decorators = [add_x_parachute] + def dispatch_request(self): + return 'Awesome' + + app.add_url_rule('/', view_func=Index.as_view('index')) + c = app.test_client() + rv = c.get('/') + self.assertEqual(rv.headers['X-Parachute'], 'awesome') + self.assertEqual(rv.data, 'Awesome') + class DeprecationsTestCase(FlaskTestCase):