6

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?

tereško
  • 58,060
  • 25
  • 98
  • 150
palmsey
  • 5,812
  • 3
  • 37
  • 41

3 Answers3

11

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
  • 12,775
  • 4
  • 37
  • 46
  • 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 Jun 19 '09 at 19:56
  • 3
    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 Jun 20 '09 at 15:16
9

Updated answer in 2016: Modern Django has everything necessary built-in and available through the class-based views. In the most raw form, the canonical approach is subclasssing django.views.generic.View and implementing class methods that are named after the HTTP verbs:

class MyView(View):
    def get(self, request, *args, **kwargs):
        # ...

    def post(self, request, *args, **kwargs):
        # ...

Internally, this works in a way very similar to my ancient code below (which was written before Django had class-based views). There is a View.dispatch method that basically looks up what to call, or return 405 if it can't find anything: getattr(self, request.method.lower(), self.http_method_not_allowed).

Of course, if you do form processing, template rendering or common CRUD stuff, be sure to check out the available View subclasses.


Legacy answer from 2009 below. The code still works in 2016, but isn't a DRY solution, so don't use it. In 2011 Django got class-based views and nowadays they're the standard way how things should be done. I'm keeping this here solely for historical purposes. Old answer text follows:

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
  • 11,159
  • 7
  • 59
  • 104
  • 1
    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 Jun 18 '09 at 17:04
  • 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 Jun 18 '09 at 18:06
  • 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 Jun 18 '09 at 18:20
  • 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 Jun 18 '09 at 18:33
  • Hmm, all the formatting in my previous comment was killed, sorry! Hope you can still get the point. – palmsey Jun 18 '09 at 18:34
  • 1
    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 Jun 18 '09 at 18:38
5

You can use View Decorators

From the docs:

from django.views.decorators.http import require_http_methods

@require_http_methods(["GET", "POST"])
def my_view(request):
    # I can assume now that only GET or POST requests make it this far
    # ...
    pass
santiagobasulto
  • 11,320
  • 11
  • 64
  • 88