You are here: Home Blog GET and POST handling in Django views

GET and POST handling in Django views

by Dan Fairs last modified Dec 16, 2007 11:57 PM
Django view functions often feature an initial stanza near the start in order to handle POST data differently. You often end up with quite a lot of logic in one method. Here I describe a cleaner way, using a decorated view class to replace the single function.

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!

Filed under:
Ryan
Ryan says:
Mar 01, 2011 08:25 PM
Thanks for the help! Your method is quite a bit cleaner than the more common one.

On a side note, you really should think about messing with the CSS for your code snippets so the highlighting underneath the white text doesn't make the white text nearly invisible.
Dan Fairs
Dan Fairs says:
Mar 24, 2011 05:37 PM
Yeah, I know. I'd spent enough time skinning the site that I just needed to get it out the door :)
Add comment

You can add a comment by filling out the form below. Plain text formatting.