Django的HTTP动词装饰器?

6
在ASP.NET MVC中,可以使用AcceptVerbs属性将视图函数与HTTP动词相关联:
public ActionResult Create()
{
    // do get stuff
} 

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

Django之书建议使用以下代码:

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}),
    # ...
)

这对我来说看起来有点丑陋 - 有没有类似ASP.NET MVC的装饰器可以将HTTP动词与视图关联起来呢,或者其他可接受的做法?

3个回答

11

这确实是正确的方法。请注意,其中还有一个装饰器生成器,它可以让您为任何组合的方法创建装饰器。 (我不知道为什么你因为给出明显且正确的答案而被投票否决...) - James Bennett
3
我认为这不是正确的答案,因为require_http_methods()是一个过滤器,而不是调度程序。一个函数不能使用@require_http_methods("GET"),另一个函数不能使用@require_http_methods("POST")(即使它们拥有相同的名称!),并让Django根据方法动词选择适当的函数来调用。 - drdaeman

9

2016年更新的答案: 现代Django已经内置了一切必要的内容,并通过基于类的视图提供。最基本的形式是子类化django.views.generic.View并实现根据HTTP动词命名的类方法:

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

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

在内部,这个功能的工作方式非常类似于下面我古老的代码(在Django有基于类的视图之前编写)。有一个View.dispatch方法,它基本上查找要调用的内容,如果找不到则返回405:getattr(self,request.method.lower(),self.http_method_not_allowed)
当然,如果您进行表单处理、模板渲染或通用CRUD操作,请确保查看可用的View子类。

以下是2009年的旧答案。 该代码仍然可以在2016年运行,但不是DRY解决方案,因此不要使用它。 2011年Django推出了基于类的视图,现在它们是应该完成任务的标准方式。 我只为历史目的保留这个。

在一个特定的视图中,我需要为不同的HTTP方法编写不同的代码(这是我的微型WebDAV实现),我正在做如下操作:

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()),

新增/编辑:好的,我想了一下并实现了装饰器方法。它其实没有我最初想象的那么糟糕。

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'),

这篇文章是一个社区维基,所以如果您喜欢这个想法,请随意改进!而且修订历史记录中还包含我在撰写本文之前尝试过的一些不同方法...


1
请注意,在大多数表单处理工作中,您可能实际上并不想将GET和POST方法分开,因为在未成功提交POST时,您可能会返回包含错误表单的页面。 - drdaeman
干得好 - 当我看到你的答案时,我正在编写类似的解决方案。不过你已经成功地得到了一个更优雅的结果 ;) - Guðmundur H
谢谢。实际上,还有很多需要改进的地方——当前代码甚至不尝试保留属性(如__doc__或__name__),而且真的容易出错(例如,根本没有签名检查)。我正在考虑使用装饰器模块(http://pypi.python.org/pypi/decorator),但我太懒了;) - drdaeman
谢谢! 我是新手Django,所以你能详细解释一下为什么处理表单时不会分离GET和POST吗?我想POST方法只能做类似以下的事情: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))这样不会保留表单状态,错误消息等吗? - palmsey
哎呀,我之前的评论里所有的格式都被破坏了,很抱歉!希望你仍然能理解我的意思。 - palmsey
1
因为你需要在POST和GET视图中都写return render_to_response(...)部分。而且你可能会重复使用装饰器,比如@login_required。重复代码并不是一个好习惯。我认为,在这种情况下将视图分开并没有太大的好处。 - drdaeman

5
你可以使用查看装饰器
来自文档:
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接