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