GET and POST handling in Django views
It's not uncommon for a Django view to look something like this:
def view(request, object_id):
ob = get_object_or_404(Foo, object_id)
if (request.POST):
# do some processing of the POST
raise new HttpResponseRedirect('/')
# do standard view processing
return render_to_response('template')
With more complex view logic, this can get pretty messy.
How about if you could write something like this:
class FooView(BaseView):
@get
def getFoo(self, request, object_id):
return object_detail(request, queryset=Foo.objects,
object_id=object_id)
@post
def editFoo(self, request, object_id):
# do POST processing
return HttpResponseRedirect('/')
This shows how you can still group logically-related operations into a single class, but keeping the individual activities separate.
Note how you simply indicate which method should be used for each HTTP method using the appropriate decorator.
The first version of a module to let you do that is below. It works pretty well as it is, but needs some more work to make it work properly with other Django standard decorators like require_POST, permission_required, and so on. Put this in a file called httpmethod.py on your PYTHONPATH:
_class_view_registry = {}
class BaseView(object):
def __new__(cls, *args, **kwargs):
instance = super(BaseView, cls).__new__(cls)
# Try to get the view registry for this class from the global view
# registry. If it's not there, create one - just a dict - then iterate
# through the class's members looking for objects which have been
# annotated with an _httpmethod_name attribute. The value of this
# attribute is the HTTP method name that we want to associate this
# method with.
#
# If the registry is there, then we've processed this class before
# and so all the mappings should be there.
view_registry = _class_view_registry.get(cls, None)
if view_registry is None:
_class_view_registry[cls] = view_registry = {}
for name in dir(instance):
obj = getattr(instance, name)
httpmethod_name = getattr(obj, '_httpmethod_name', None)
if httpmethod_name is not None:
view_registry[httpmethod_name] = obj
return instance
def __call__(self, request, *args, **kwargs):
methodname = request.method.strip().upper()
method = _class_view_registry.get(self.__class__).get(methodname)
return method(request, *args, **kwargs)
def httpmethod(name, func):
if not name:
raise ValueError, 'name must be set'
func._httpmethod_name = name
return func
# Decorators for HTTP methods as defined in:
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
def post(func):
return httpmethod('POST', func)
def get(func):
return httpmethod('GET', func)
def head(func):
return httpmethod('HEAD', func)
def delete(func):
return httpmethod('DELETE', func)
def options(func):
return httpmethod('OPTIONS', func)
def put(func):
return httpmethod('PUT', func)
def trace(func):
return httpmethod('TRACE', func)
def connect(func):
return httpmethod('CONNECT', func)
To use this, your URLConf lines should look similar to this:
from project.app.views import FooView
urlpatterns = patterns('',
(r'^foo/(\d+)/$', FooView()),
)
I'll update the code to be compatible with Django's decorators as I need to - when that's done, I'll also stick it on djangosnippets.org. I've included decorators for all the HTTP methods.
Until then, enjoy - and as ever, feedback is welcome!
I've disabled comments for now due to spam problems - I'll turn them back on when I've fixed it!