Attempting to make decorators anticipate every usage is the wrong approach. In Django, the sole interface to a view is as a callable that accepts an `HttpRequest` and turns it into an `HttpResponse` or exception. Django the framework doesn't actually need to support class-based views--because CBVs are just another way to build view callables.
View callables are an elegant abstraction that keeps the core of Django, and the decorators it provides, simple. Trying to anticipate all the ways of building view callables is not only complex, it is impossible. Why shouldn't I be able to use the built-in decorators with my own implementations of class-based views? If the built-in decorators' implementations are simple, I will be able to provide an adapter to use them with my CBV system. There are many useful decorators built-in to Django and in the wild, and my adapter will work with all of them, because they treat views as simple callables. `method_decorator` is the right idea, but it is painful to use. It requires a tedious empty `dispatch` method, just to have something to decorate. Using decorators to modify views makes sense, because decoration is the only way to modify the functionality of a callable. Classes, however, have much more powerful ways of adding optional functionality: multiple inheritance. # `DecoratorMixin` `DecoratorMixin` is a class factory that converts any view decorator into a class-based view mixin. It works as you would expect with inheritance--MRO provides the same ordering that function decorators do, and multiple inheritance allows the mixins to be composed naturally. Inheriting from a decorated view decorates the subclass as you would expect. It averts the need for the annoying empty `dispatch` method in each class that wants to use a decorator. Its implementation is simple: https://gist.github.com/gavinwahl/5694349. This is a complete implementation, and is all that's necessary to use view decorators with Django's CBVs. I use this code in all my projects with great success. [Here is an example](https://github.com/fusionbox/django-authtools/blob/master/authtools/views.py#L94) of its use in a reimplementation of Django's auth views as CBVs. Using it is easy and intuitive: LoginRequiredMixin = DecoratorMixin(login_required) class LoginRequiredAndCsrfMixin(DecoratorMixin(login_required), DecoratorMixin(csrf_protect)): """ Inheriting from this class is the same as decorating your view thus:: @login_required @csrf_protect def someview(request): pass """ pass class SomeView(LoginRequiredMixin, View): # ... I propose `DecoratorMixin` be added to Django. It retains compatibility with all existing decorators, elegantly adapts functional decorators to good object-oriented practices, and has a simple implementation. 'Universal' decorators don't accommodate existing third-party code, contain complex magic, and annul one of the most elegant principles of Django: views are simply callables. On Thursday, September 15, 2011 2:44:39 PM UTC-6, Jacob Kaplan-Moss wrote: > > Hi folks -- > > I'd like to convert all the view decorators built into Django to be > "universal" -- so they'll work to decorate *any* view, whether a > function, method, or class. I believe I've figured out a technique for > this, but I'd like some feedback on the approach before I dive in too > deep. > > Right now view decorators (e.g. @login_required) only work on > functions. If you want to use a decorator on a method then you need to > "convert" the decorator using method_decorator(original_decorator). > You can't use view decorators on class-based views at all. This means > making a class-based view require login requires this awesomeness:: > > class MyView(View): > @method_decorator(login_required) > def dispatch(self, *args, **kwargs): > return super(MyView, self.dispatch(*args, **kwargs) > > This makes me sad. It's really counter-intuitive and relies on a > recognizing that functions and methods are different to even know to > look for method_decorator. > > #14512 proposes a adding another view-decorator-factory for decorating > class-based views, which would turn the above into:: > > @class_view_decorator(login_required) > class MyView(View): > ... > > This makes me less sad, but still sad. Factory functions. Ugh. > > I want @login_required to work for anything:: > > @login_required > class MyView(View): > ... > > class Something(object): > @login_required > def my_view(self, request): > ... > > @login_required > def my_view(request): > ... > > > Now, back in the day we somewhat had this: decorators tried to work > with both functions and methods. The technique turned out not to work > (see #12804) and was removed in [12399]. See > > http://groups.google.com/group/django-developers/browse_thread/thread/3024b14a47f5404d > for the discussion. > > I believe, however, I've figured out a different technique to make > this work: don't try to detect bound versus unbound methods, but > instead look for the HttpRequest object. It'll either be args[0] if > the view's a function, or args[1] if the view's a method. This > technique won't work for any old decorator, but it *will* work (I > think) for any decorator *designed to be applied only to views*. > > I've written a proof-of-concept patch to @login_required (well, > @user_passes_test, actually): > > https://gist.github.com/1220375 > > The test suite passes with this, with one exception: > > https://code.djangoproject.com/browser/django/trunk/tests/regressiontests/decorators/tests.py#L87 > . > I maintain that this test is broken and should be using RequestFactory > instead. > > Can I get some thoughts on this technique and some feedback on whether > it's OK to apply to every decorator built into Django? > > Jacob > > -- You received this message because you are subscribed to the Google Groups "Django developers" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscr...@googlegroups.com. To post to this group, send email to django-developers@googlegroups.com. Visit this group at http://groups.google.com/group/django-developers?hl=en. For more options, visit https://groups.google.com/groups/opt_out.