This is my first pass at writing a set of generic class-based views for Django.
It's not perfect - it was written in a few days in a remote part of the Scottish Borders -
but it's a start, and has been changed and modified as we used it to build a real project.
There are several design decisions that could really go either way, and I have no doubt
there's good arguments for either side. Here are my decisions, and their justifications.
- A set of small, mixed-in and inherited base classes rather than one big monolithic
class. Ben Firshman's original code that I based this off had one massive class with
every possible eventuality covered (different return formats, templates, etc.)
I decided to go for a set of inheriting classes and mixins that provide these features;
it feels more Pythonic to me, and also means you don't have extra methods kicking
around that you don't need.
- Views have to be instantiated in the urlconf. This allows you to pass in keyword
arguments to override things like template names, if you want. There's a check
in the constructor which makes sure a sensible error is raised if you forget to
instantiate them. Whenever a class is called to render to a response, it
uses copy() on itself to ensure you can store things on self while being threadsafe.
- Method dispatch is in the basemost class, as part of __call__. This seems like
something that nearly every class-based view will want to be distinguishing on,
and if not, doing POST = GET in the class body is still quite nice.
- request, args, and kwargs are stored on self. This was a tough decision, and the
package started with request, args and kwargs being passed around in all the
function calls, but this either clutters the method signatures or restricts the
flexibility of subclasses, depending on how many functions you insist on passing
this into. In the end, having them on the object itself is both threadsafe (since
it's copied on call) and provides for much nicer calls around the class (like
self.get_object() rather than self.get_object(request, *args, **kwargs) everywhere)
- The default functions - like render() - don't call other subfunctions to get context,
etc. Most things - like form_valid, or just GET on most classes - do call other methods
for important parts, but are also simple enough that you can override them completely
and add a few more lines. We found having tens of small functions for everything made
subclasses' logic much more unreadable than just writing a new form_valid or a new GET.