views:

478

answers:

2

In ASP.NET MVC, you can use the AcceptVerbs attribute to correlate a view function with a verb:

public ActionResult Create()
{
    // do get stuff
} 

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
    // do post stuff
}

The Django Book suggests something like this:

def method_splitter(request, *args, **kwargs):
    get_view = kwargs.pop('GET', None)
    post_view = kwargs.pop('POST', None)
    if request.method == 'GET' and get_view is not None:
        return get_view(request, *args, **kwargs)
    elif request.method == 'POST' and post_view is not None:
        return post_view(request, *args, **kwargs)
    raise Http404

urls.py:

urlpatterns = patterns('',
    # ...
    (r'^somepage/$', views.method_splitter, {'GET': views.some_page_get, 
        'POST': views.some_page_post}),
    # ...
)

That seems a little ugly to me - is there a decorator that can associate an HTTP verb with a view, ASP.NET MVC-style, or another accepted way to do this?

+7  A: 

In one particular view where I need to have separate code for different HTTP methods (this is my tiny WebDAV implementation), I'm doing something like this:

class SomeView(object):
    def method_get(self, request, ...):
        ...

    def __call__(self, request, *args, **kwargs):
        m = getattr(self, 'method_%s' % request.method.lower(), None)
        if m is not None:
            return m(request, user, *args, **kwargs)
        return HttpResponseNotAllowed("405 Method Not Allowed")

# Then url(r'...', SomeView()),

Added/edited: Well, I've thought a bit and actually implemented decorator approach. It is not as bad as I initially thought.

def method_not_allowed_view(request, *args, **kwargs):
    return HttpResponseNotAllowed("405 Method Not Allowed")

def http_method(*methods):
    methods = map(lambda m: m.lower(), methods)
    def __method_wrapper(f):
        this_module = __import__(__name__)
        chain = getattr(this_module, f.__name__, method_not_allowed_view)
        base_view_func = lambda request, *args, **kwargs: \
            f(request, *args, **kwargs) if request.method.lower() in methods \
                                        else chain(request, *args, **kwargs)
        setattr(this_module, f.__name__, base_view_func)
        return base_view_func
    return __method_wrapper

@http_method('get')
def my_view(request):
    return HttpResponse("Thank you for GETting.")

@http_method('post', 'put')
def my_view(request):
    return HttpResponse("Thank you for POSTing or PUTting.")

# url(r'...', 'app.my_view'),

This post is a community wiki, anyway, so feel free to improve if you like the idea! And the revision history also contains some a bit different approaches I tried before writing this...

drdaeman
Note, that in most form-processing work you probably don't actually want to separate GET and POST methods, because you are probably going to return page, containing form with errors on unsuccessful POST.
drdaeman
Nice work – was writing a similar solution when I saw your answer. You've though managed to get to a more elegant result ;)
Guðmundur H
Thanks. Actually, there's a lot to improve — current code does not even try to preserve attributes (like __doc__ or __name__) and not really error-prone (for example, there's no signature checking at all). I was thinking about using decorator module (http://pypi.python.org/pypi/decorator) but I'm too lazy ;)
drdaeman
Thanks! I'm new to Django, so could you elaborate on why you wouldn't separate GET and POST when processing a form? I thought the POST method could just do something like:form = SomeForm(request.POST)if form.is_valid(): # save data return HttpResponseRedirect('/somelist')else: return render_to_response(template_name, {'form': form}, context_instance=RequestContext(request))Would this not preserve the form state, error messages, etc?
palmsey
Hmm, all the formatting in my previous comment was killed, sorry! Hope you can still get the point.
palmsey
Because you would have to write return render_to_response(...) part twice. Once in a POST and once in a GET view. And you'll probably duplicate decorators, like @login_required. Duplicating code isn't a good thing to do. I believe, most of time, there's no real gain from separating views in such cases.
drdaeman
How do frameworks like ASP.NET MVC and Rails manage to separate them successfully without duplication then?
drozzy
+6  A: 

There are standard built-in decorators for requiring particular HTTP method or list of allowed methods.

See the code: http://code.djangoproject.com/browser/django/trunk/django/views/decorators/http.py.

zgoda
This is indeed the way to do it. Note that there's a decorator generator in there as well which lets you create decorators for whatever combinations of methods you like.(and I have no idea why you got downvoted for giving the obvious and correct answer to the question...)
James Bennett
I believe this isn't the correct answer, because require_http_methods() is a filter, not dispatcher. One can't do @require_http_methods("GET") for one function, @require_http_methods("POST") for another (with the same name!), and let Django choose the proper one to call by method verb.
drdaeman