Django中URL视图函数的一些高级概念介绍
说到关于请求方法的分支,让我们来看一下可以用什么好的方法来实现它。考虑这个URLconf/view设计:
#urls.py fromdjango.conf.urls.defaultsimport* frommysiteimportviews urlpatterns=patterns('', #... (r'^somepage/$',views.some_page), #... ) #views.py fromdjango.httpimportHttp404,HttpResponseRedirect fromdjango.shortcutsimportrender_to_response defsome_page(request): ifrequest.method=='POST': do_something_for_post() returnHttpResponseRedirect('/someurl/') elifrequest.method=='GET': do_something_for_get() returnrender_to_response('page.html') else: raiseHttp404()
在这个示例中,``some_page()``视图函数对``POST``和``GET``这两种请求方法的处理完全不同。它们唯一的共同点是共享一个URL地址:``/somepage/.``正如大家所看到的,在同一个视图函数中对``POST``和``GET``进行处理是一种很初级也很粗糙的做法。一个比较好的设计习惯应该是,用两个分开的视图函数——一个处理``POST``请求,另一个处理``GET``请求,然后在相应的地方分别进行调用。
我们可以像这样做:先写一个视图函数然后由它来具体分派其它的视图,在之前或之后可以执行一些我们自定的程序逻辑。下边的示例展示了这个技术是如何帮我们改进前边那个简单的``some_page()``视图的:
#views.py fromdjango.httpimportHttp404,HttpResponseRedirect fromdjango.shortcutsimportrender_to_response defmethod_splitter(request,GET=None,POST=None): ifrequest.method=='GET'andGETisnotNone: returnGET(request) elifrequest.method=='POST'andPOSTisnotNone: returnPOST(request) raiseHttp404 defsome_page_get(request): assertrequest.method=='GET' do_something_for_get() returnrender_to_response('page.html') defsome_page_post(request): assertrequest.method=='POST' do_something_for_post() returnHttpResponseRedirect('/someurl/') #urls.py fromdjango.conf.urls.defaultsimport* frommysiteimportviews urlpatterns=patterns('', #... (r'^somepage/$',views.method_splitter,{'GET':views.some_page_get,'POST':views.some_page_post}), #... )
让我们从头看一下代码是如何工作的:
我们写了一个新的视图,``method_splitter()``,它根据``request.method``返回的值来调用相应的视图。可以看到它带有两个关键参数,``GET``和``POST``,也许应该是*视图函数*。如果``request.method``返回``GET``,那它就会自动调用``GET``视图。如果``request.method``返回的是``POST``,那它调用的就是``POST``视图。如果``request.method``返回的是其它值(如:``HEAD``),或者是没有把``GET``或``POST``提交给此函数,那它就会抛出一个``Http404``错误。
在URLconf中,我们把``/somepage/``指到``method_splitter()``函数,并把视图函数额外需要用到的``GET``和``POST``参数传递给它。
最终,我们把``some_page()``视图分解到两个视图函数中``some_page_get()``和``some_page_post()``。这比把所有逻辑都挤到一个单一视图的做法要优雅得多。
注意,在技术上这些视图函数就不用再去检查``request.method``了,因为``method_splitter()``已经替它们做了。(比如,``some_page_post()``被调用的时候,我们可以确信``request.method``返回的值是``post``。)当然,这样做不止更安全也能更好的将代码文档化,这里我们做了一个假定,就是``request.method``能象我们所期望的那样工作。
现在我们就拥有了一个不错的,可以通用的视图函数了,里边封装着由``request.method``的返回值来分派不同的视图的程序。关于``method_splitter()``就不说什么了,当然,我们可以把它们重用到其它项目中。
然而,当我们做到这一步时,我们仍然可以改进``method_splitter``。从代码我们可以看到,它假设``Get``和``POST``视图除了``request``之外不需要任何其他的参数。那么,假如我们想要使用``method_splitter``与那种会从URL里捕捉字符,或者会接收一些可选参数的视图一起工作时该怎么办呢?
为了实现这个,我们可以使用Python中一个优雅的特性带星号的可变参数我们先展示这些例子,接着再进行解释
defmethod_splitter(request,*args,**kwargs): get_view=kwargs.pop('GET',None) post_view=kwargs.pop('POST',None) ifrequest.method=='GET'andget_viewisnotNone: returnget_view(request,*args,**kwargs) elifrequest.method=='POST'andpost_viewisnotNone: returnpost_view(request,*args,**kwargs) raiseHttp404
这里,我们重构method_splitter(),去掉了GET和POST两个关键字参数,改而支持使用*args和和**kwargs(注意*号)这是一个Python特性,允许函数接受动态的、可变数量的、参数名只在运行时可知的参数。如果你在函数定义时,只在参数前面加一个*号,所有传递给函数的参数将会保存为一个元组.如果你在函数定义时,在参数前面加两个*号,所有传递给函数的关键字参数,将会保存为一个字典
例如,对于这个函数
deffoo(*args,**kwargs): print"Positionalargumentsare:" printargs print"Keywordargumentsare:" printkwargs
看一下它是怎么工作的
>>>foo(1,2,3) Positionalargumentsare: (1,2,3) Keywordargumentsare: {} >>>foo(1,2,name='Adrian',framework='Django') Positionalargumentsare: (1,2) Keywordargumentsare: {'framework':'Django','name':'Adrian'}
回过头来看,你能发现我们用method_splitter()和*args接受**kwargs函数参数并把它们传递到正确的视图。any但是在我们这样做之前,我们要调用两次获得参数kwargs.pop()GETPOST,如果它们合法的话。(我们通过指定pop的缺省值为None,来避免由于一个或者多个关键字缺失带来的KeyError)