I'm diving into Django's generic views, figuring out how they return a simple HttpResponse object, like a simple view function would.
I have written a simple project for testing, and I added some logging commands to the basic View classed defined in the file django/views/generic/base.py, so that I can track what is happening under the hood.
I have some questions that came up during my research.
I've been trying to keep this post short, however, for the a complete understanding I felt it essential to include the code snippets and the logs.
I will be really thankful to anyone who takes the time to give some helpful comment, possibly answering some of my questions.
urls.py
from django.conf.urls import patterns, url
from views import WelcomeView
urlpatterns = patterns('',
url(r'^welcome/(?P<name>\w+)/$', WelcomeView.as_view()),
)
views.py
from django.http import HttpResponse
from django.views.generic import View
class WelcomeView(View):
def get(self, request, name):
return HttpResponse('What is up, {0}?'.format(name))
django/views/generic/base.py
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', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
#####logging
logging.error('*** View.__init__ is started with kwargs: {0} ***'.format(
repr(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 kwargs.iteritems():
setattr(self, key, value)
#####logging
logging.error('*** View.__init__ reached its end. No return value. ***')
@classonlymethod
def as_view(cls, **initkwargs):
#####logging
logging.error('*** View.as_view is started with initkwargs: {0} ***'.format(
repr(initkwargs)))
"""
Main entry point for a request-response process.
"""
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(u"You tried to pass in the %s method name as a "
u"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError(u"%s() received an invalid keyword %r" % (
cls.__name__, key))
def view(request, *args, **kwargs):
#####logging
logging.error('*** View.as_view.view is called with args: {0};\
and kwargs: {1} ***'.format(
repr(args),
repr(kwargs)))
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
#####logging
logging.error('*** View.as_view.view reached its end.\
Now calls dispatch() and returns the return value.')
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=())
#####logging
logging.error('*** View.as_view reached its end. Now returns view. ***')
return view
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.
#####logging
logging.error('*** View.dispatch called, with args: {0};\
and kwargs: {1} ***'.format(
repr(args),
repr(kwargs)))
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
self.request = request
self.args = args
self.kwargs = kwargs
#####logging
logging.error('*** View.dispatch reached its end.\
Now calls handler and returns the return value. ***')
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
logger.warning('Method Not Allowed (%s): %s', request.method, request.path,
extra={
'status_code': 405,
'request': self.request
}
)
return http.HttpResponseNotAllowed(allowed_methods)
Logs from some test requests
Django version 1.4.5, using settings 'try1.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
ERROR:root:*** View.as_view is started with initkwargs: {} ***
ERROR:root:*** View.as_view reached its end. Now returns view. ***
ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:43:19] "GET /welcome/Dude/ HTTP/1.1" 200 17
ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Dude'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:43:32] "GET /welcome/Dude/ HTTP/1.1" 200 17
[24/Feb/2013 12:44:43] "GET /welcome/ HTTP/1.1" 404 1939
ERROR:root:*** View.as_view.view is called with args: (); and kwargs: {'name': u'Bro'} ***
ERROR:root:*** View.__init__ is started with kwargs: {} ***
ERROR:root:*** View.__init__ reached its end. No return value. ***
ERROR:root:*** View.as_view.view reached its end. Now calls dispatch() and returns the return value.
ERROR:root:*** View.dispatch called, with args: (); and kwargs: {'name': u'Bro'} ***
ERROR:root:*** View.dispatch reached its end. Now calls handler and returns the return value. ***
[24/Feb/2013 12:44:59] "GET /welcome/Bro/ HTTP/1.1" 200 16
After all, my questions
1.
According to the logs, as_view is called before View.init.
Does it mean that it calls a View method even before creating a View instance?
2.
Why isn't as_view() called after it is executed for the first call?
I'm not yet an expert at Python's imports, compilation and memory use,
but I have this feeling that they play some role here.
3.
In the definition of view(), what does the following snippet do?
self = cls(**initkwargs)
According to the logs, it triggers View.init.
Is it that it creates a new View instance with initkwargs and assigns it to the instance in use (self)?
If so, why is it needed?
4.
How can we make use of initkwargs (arguments of as_view)?