How Django Works (5) Class Based View
Why Class-based Views
As is discussed in series 4, with URL resolution, Django maps an URL to a given view function based on regular expression. So theoretically, function views would work just fine, because we have all the information we need to process a request, as is shown in the example below.
from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
So Why Bother With Class-based Views
I think there are two major reasons:
1) to organize code related to specific HTTP methods (GET, POST, etc), so they can be addressed by separate methods instead of conditional branching in a big function;
2) to use mix-in, inheritance, and built-in views to reduce the boilerplate code.
How Class-based Views Work
Everything starts with base class View (site-packages\django\views\generic\base.py), which provide basic dispatching mechanism based on the HTTP verbs.urlpatterns = patterns('', url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),)
If a function view is used (last url), function name is passed to url as the second argument, if class-based views are used, a function as_view is called and its return value servers as the second argument to url.
as_view, defined in class View, returns a function that serves as the view function. Under the hood, the view has to be a function, Classed-based view is only another layer of indirection.
as_view in View Class
as_view is defined as a class only method -- the technique was described in the prior post. It returns a pre-defined view that serves as a view function.The arguments to as_view must be key value argument pairs, which will be recorded in the closure object for function view.
The view function, serving as a wrapper, also inherits some of the attributes from the two wrapped objects cls and clas.dispatch (update_wrapper). This is done for introspection purpose.
The view function initialize an object of cls and call dispatch on it to process requests.
To summarize, as_view dynamically configures and creates a view function with two configurable variables:1) cls and 2) initkwargs.
class View(object): """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. """ http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, **kwargs): """ Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things. """ # Go through keyword arguments, and either save their values to our # instance, or raise an error. for key, value in six.iteritems(kwargs): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view
dispatch in View Class
The default dispatch function defined in View class check to see if the current object, referred to by self, has the corresponding handler for the given HTTP verb. If so, the handler is called, otherwise, a pre-defined http_method_not_allowed is used to display an error message.
def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowedreturn handler(request, *args, **kwargs)