| 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. |