= Class-Based Views = The proposal to add a class-based framework for describing views (#6735) has been around for a while. It's been proposed for inclusion in Django 1.0, 1.1, and 1.2. This is a summary of the state of debate, with the intention of landing a patch for the 1.3 release. == The brief == At present, generic views are defined as methods. If you want to customize them, you have to pass in arguments to the view at time of use in urls.py. This is cumbersome: * urls.py rapidly becomes large an unwieldly * complex behaviors that need to be wrapped in callables depend on the underlying views handling callable arguments (which isn't implemented universally) * There's no such thing as a simple extension -- you can't easily say "use that view, but change one argument"; you have to reproduce the full argument list. * .. and much more Moving to a class-based structure means that the complexities of defining and customizing generic views can be handled by subclassing. urls.py returns to being a simple process of pointing at a class. Extension logic can be arbitrarily complex, abstracted behind methods on the generic class. It also means we can provide an entry point for HTTP method-based dispatch -- e.g., a GET() method to handle GET requests, POST() to handle posts, etc. == The problem == However, the devil is in the detail. There are several issues that any class-based view solution needs to address: * '''Deployment''': How do you deploy an instance of the view * '''URLResolver interaction''': Does the approach require any special handling in URLResolver to identify and/or instantiate the view? * '''Subclassing''': How easy is it to subclass and override a specific method on a view class? * '''Thread safety''': Does each request get a clean instance of self to work with? If so, how is this achieved? * '''Ease of testing''': Does testing the view require any special handling (such as a specialized method to instantiate the view) * '''Ease of decoration''': Does the proposed technique pose any impediments to easily decorating views? === View instantiation and thread safety === The [http://groups.google.com/group/django-developers/browse_thread/thread/e23d9a5a58fe5891 most recent django-developers discussion on the topic] discussed many of the available options on instantiating the views and protecting them from threading issues. Here is a summary of the various approaches that have been proposed. ==== Store state on request, not view ==== Pass around the request object via method arguments, store arbitrary state on the request (perhaps in a blessed dictionary for that, or just in attributes). Document that state should be stored on the request, storing it on the view instance is unsafe, just like is already the case with ModelAdmin subclasses. Example usage and view would be the same as shown below in "__call__ and copy()". Arguments for: * Avoids messy and non-idiomatic hacks. * Avoids copying or creating new view instance on every request. Arguments against: * Leaves some room for people to shoot themselves in the foot. ==== !__call!__() and copy() ==== [http://github.com/bfirsh/django-class-based-views Implementation] Example usage: {{{ url(r'^detail/author/(?P\d+)/$', views.AuthorDetail(), name="author_detail"), }}} Example class: {{{ class AuthorView(View): def GET(self, request, *args, **kwargs) return self.render_to_response('author_list.html', {'authors': Author.objects.all()}) }}} This approach proposes that an class instance be placed in urls.py; the instance has a !__call!__() method, and when that method is invoked, it takes a shallow copy of the instance defined in urls.py, and returns the result of invoking the request method (e.g., GET()). This achieves thread safety by ensuring that every request is given a clean instance on the view class to work on. No special handling is required in !UrlResolver -- the class instance is a callable, so it appears just like an existing view function. Arguments against: * The "copy on !__call!__()" approach is a little messy. Normal Python idiom wouldn't lead users to expect that !__call!__() would cause a copy to be created. However, this is entirely hidden by the implementation. ==== !__new!__() ==== [http://github.com/fitzgen/django-class-based-views Implementation] Example usage: {{{ url(r'^detail/author/(?P\d+)/$', views.AuthorDetail, name="author_detail"), }}} Example class: {{{ class AuthorView(View): def GET(self, request, *args, **kwargs) return self.render_to_response('author_list.html', {'authors': Author.objects.all()}) }}} This approach is much the same as the !__copy!__() on !__call!__() approach, except that !__new!__() is used to create the new instance. Arguments against: * You can't use arguments to __init__() to instantiate a class view * x = !AuthorView() returns x as a HTTPResponse, not an !AuthorView instance. This violates expectations of normal class usage. ==== HTTPResponse subclassing ==== [http://github.com/roalddevries/class_based_views/ Implementation] This approach exploits the fact that the aim of a view is to produce a HttpResponse instance; so it shortcuts the process, and makes a 'view' the constructor for the !HttpResponse. Arguments against: * Binds the Response object the concept of a view. A view isn't 'is-a' response, in the OO-sense. * Makes it difficult or impossible to subclass !HttpResponse and use that subclass * Makes it difficult to use !HttpResponse-returning methods; you can't just call out to a subview/utility method that returns a !HttpResponse instance, because the view needs to return 'self'. ==== !UrlResolver view instantiation ==== Rather than try to make the view look like a callable, this approach modifies !UrlResolver so that it can identify when a class-based view is in use, and instantiate an instance when one is detected. Arguments against: * Requires special cases in !UrlResolver which almost inevitably involve isinstance(!ViewClass) or some analog. ==== Recommendation ==== Based on these discussions, plus in-person discussions at !DjangoCon.eu, !__copy!__() on !__call!__() appears to be a slight front runner, with !__new!__() as a close runner up. === Class Hierarchy === There are several ways to lay out the tree of classes that will make up both the base views and the new generic views. The current recommended approach is to use mixins to achieve certain parts of functionality (e.g. "rendering a form", "accepting form arguments", etc.), then combine these into generic views. While mixins are a relatively unused part of Python, and multiple inheritance can cause some odd bugs, the idea is to have the fact mixins are used as more of an implementation detail rather than part of the public API. === Method-based dispatch === This involves having the base view automatically call self.GET for GET requests, self.POST for POST requests, and so forth. This has the advantage of saving boilerplate for several types of view, but the disadvantage of getting in the way if you do want to share a lot of code between GET and POST methods. The recommended solution is that the very base view have only a dispatch() method (or a similar name) that gets called per-request, and then a subclass that also ships with Django that does method-based dispatch, which would probably be used by a lot of the class-based generic views. === Storing request, args, kwargs on self === One more controversial move is, if we have one instance per request (which is looking more likely), whether the request object and any positional/keyword arguments from the URL should be only passed around in the actual function calls, or stored on self. Advantages: - Allows much less fragile code (functions are far less subclassable if it's been decided in advance they will never see the request object) - Cleaner method signatures Disadvantages: - Won't work if there's not one instance per request The current recommendation is that, if one instance per request is chosen, that these are stored on the instance of the view (using the names `request`, `args` and `kwargs`), and that the `dispatch()` and `GET()`, `POST()`, etc. methods still take these as parameters, but all other methods (like `render()`, or `get_form()`) don't. === Methods for everything === Some attempts contain methods for everything - one to get the context instance, one to get the context data, one to get the whole context object, and so on. In real-life usage, this turns out to be both very verbose to override as well as being mostly unused (if I want to override how things are rendered, it's a lot easier to just provide a new `render()` function than to override five different other methods, and the logic flow can be changed as well). For this reason, the current recommendation is to break things down into moderately-sized chunks, but not too small - 5 lines or more. Things like template names/patterns should probably be provided as overrideable attributes on the class, however (just not which context instance one should use). === Justification === There are quite a few sets of class-based views out there already; they include: - Andrew Godwin's baseviews (https://bitbucket.org/andrewgodwin/baseviews/), which was developed alongside a project being built, and has some information about why certain patterns were chosen in the README. - bkondle's django-baseviews (http://github.com/bkonkle/django-baseviews) == How to help == Class-based views are being developed as a [http://github.com/bfirsh/django-class-based-views separate application on Github]. There are a few things yet to be done: * A simple readme for getting started * Testing it in real applications * More test coverage * Support for ModelForms that mimics the current generic views * Full documentation Fork the Github project if you want to help out.