Version 2 (modified by 14 years ago) ( diff ) | ,
---|
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 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.
__call__() and copy() ¶
Example usage:
url(r'^detail/author/(?P<pk>\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__() ¶
Example usage:
url(r'^detail/author/(?P<pk>\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 ¶
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.
How to help ¶
Class-based views are being developed as a 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.