|   | 1 | = Class-Based Views =  | 
          
          
            |   | 2 |  | 
          
          
            |   | 3 | 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. | 
          
          
            |   | 4 |  | 
          
          
            |   | 5 | == The brief == | 
          
          
            |   | 6 |  | 
          
          
            |   | 7 | 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: | 
          
          
            |   | 8 |  | 
          
          
            |   | 9 |  * urls.py rapidly becomes large an unwieldly | 
          
          
            |   | 10 |  * complex behaviors that need to be wrapped in callables depend on the underlying views handling callable arguments (which isn't implemented universally) | 
          
          
            |   | 11 |  * 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. | 
          
          
            |   | 12 |  * .. and much more  | 
          
          
            |   | 13 |  | 
          
          
            |   | 14 | 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. | 
          
          
            |   | 15 |  | 
          
          
            |   | 16 | == The problem == | 
          
          
            |   | 17 |  | 
          
          
            |   | 18 | However, the devil is in the detail. There are several issues that any class-based view solution needs to address: | 
          
          
            |   | 19 |  | 
          
          
            |   | 20 |  * '''Deployment''': How do you deploy an instance of the view | 
          
          
            |   | 21 |  * '''URLResolver interaction''': Does the approach require any special handling in URLResolver to identify and/or instantiate the view? | 
          
          
            |   | 22 |  * '''Subclassing''': How easy is it to subclass and override a specific method on a view class? | 
          
          
            |   | 23 |  * '''Thread safety''': Does each request get a clean instance of self to work with? If so, how is this achieved? | 
          
          
            |   | 24 |  * '''Ease of testing''': Does testing the view require any special handling (such as a specialized method to instantiate the view) | 
          
          
            |   | 25 |  * '''Ease of decoration''': Does the proposed technique pose any impediments to easily decorating views? | 
          
          
            |   | 26 |  | 
          
          
            |   | 27 | 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. Here is a summary of the various approaches that have been proposed. | 
          
          
            |   | 28 |  | 
          
          
            |   | 29 | === !__call!__() and copy() === | 
          
          
            |   | 30 |  | 
          
          
            |   | 31 | [http://github.com/bfirsh/django-class-based-views Implementation] | 
          
          
            |   | 32 |  | 
          
          
            |   | 33 | Example usage: | 
          
          
            |   | 34 | {{{ | 
          
          
            |   | 35 |     url(r'^detail/author/(?P<pk>\d+)/$', views.AuthorDetail(), name="author_detail"), | 
          
          
            |   | 36 | }}} | 
          
          
            |   | 37 |  | 
          
          
            |   | 38 | Example class: | 
          
          
            |   | 39 | {{{ | 
          
          
            |   | 40 | class AuthorView(View): | 
          
          
            |   | 41 |     def GET(self, request, *args, **kwargs) | 
          
          
            |   | 42 |         return self.render_to_response('author_list.html', {'authors': Author.objects.all()}) | 
          
          
            |   | 43 | }}} | 
          
          
            |   | 44 |  | 
          
          
            |   | 45 | 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. | 
          
          
            |   | 46 |  | 
          
          
            |   | 47 | No special handling is required in !UrlResolver -- the class instance is a callable, so it appears just like an existing view function. | 
          
          
            |   | 48 |  | 
          
          
            |   | 49 | Arguments against: | 
          
          
            |   | 50 |  * 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. | 
          
          
            |   | 51 |  | 
          
          
            |   | 52 | === !__new!__() ===  | 
          
          
            |   | 53 |  | 
          
          
            |   | 54 | [http://github.com/fitzgen/django-class-based-views Implementation] | 
          
          
            |   | 55 |  | 
          
          
            |   | 56 | Example usage: | 
          
          
            |   | 57 | {{{ | 
          
          
            |   | 58 |     url(r'^detail/author/(?P<pk>\d+)/$', views.AuthorDetail, name="author_detail"), | 
          
          
            |   | 59 | }}} | 
          
          
            |   | 60 |  | 
          
          
            |   | 61 | Example class: | 
          
          
            |   | 62 | {{{ | 
          
          
            |   | 63 | class AuthorView(View): | 
          
          
            |   | 64 |     def GET(self, request, *args, **kwargs) | 
          
          
            |   | 65 |         return self.render_to_response('author_list.html', {'authors': Author.objects.all()}) | 
          
          
            |   | 66 | }}} | 
          
          
            |   | 67 |  | 
          
          
            |   | 68 | This approach is much the same as the !__copy!__() on !__call!__() approach, except that !__new!__() is used to create the new instance. | 
          
          
            |   | 69 |  | 
          
          
            |   | 70 | Arguments against: | 
          
          
            |   | 71 |  * You can't use arguments to __init__() to instantiate a class view | 
          
          
            |   | 72 |  * x = !AuthorView() returns x as a HTTPResponse, not an !AuthorView instance. This violates expectations of normal class usage. | 
          
          
            |   | 73 |  | 
          
          
            |   | 74 | === HTTPResponse subclassing === | 
          
          
            |   | 75 |  | 
          
          
            |   | 76 | [http://github.com/roalddevries/class_based_views/ Implementation] | 
          
          
            |   | 77 |  | 
          
          
            |   | 78 | 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. | 
          
          
            |   | 79 |  | 
          
          
            |   | 80 | Arguments against: | 
          
          
            |   | 81 |  * Binds the Response object the concept of a view. A view isn't 'is-a' response, in the OO-sense. | 
          
          
            |   | 82 |  * Makes it difficult or impossible to subclass !HttpResponse and use that subclass | 
          
          
            |   | 83 |  * 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'. | 
          
          
            |   | 84 |  | 
          
          
            |   | 85 | === !UrlResolver view instantiation === | 
          
          
            |   | 86 |  | 
          
          
            |   | 87 | 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. | 
          
          
            |   | 88 |  | 
          
          
            |   | 89 | Arguments against: | 
          
          
            |   | 90 |  * Requires special cases in !UrlResolver which almost inevitably involve isinstance(!ViewClass) or some analog. | 
          
          
            |   | 91 |  | 
          
          
            |   | 92 | = Recommendation = | 
          
          
            |   | 93 |  | 
          
          
            |   | 94 | 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. |