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_allowed 
return handler(request, *args, **kwargs)

Popular posts from this blog

LeetCode 68 Text Justification (C++, Python)

How Django Works (4) URL Resolution

Python Class Method and Class Only Method