Django高级视图和URL配置

本文纯手工搬运,为自己学习记录使用.

调整模式中的特例

整个框架关注的是存在一个名为 urlpatterns 的模块级别的变量。如上例,这个变量可以动态生成。 这里我们要特别说明一下,patterns()返回的对象是可相加的.
说到动态构建 urlpatterns ,你可能想利用这一技术,在 Django 的调试模式下修改 URLconf 的行为。 为了做到这一点,只要在运行时检查 DEBUG 配置项的值即可,如:

from django.conf import settings
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^$', views.homepage),
    (r'^(\d{4})/([a‐z]{3})/$', views.archive_month),
    )
if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^debuginfo/$', views.debug),
        )

在这个例子中,URL链接 /debuginfo/ 只在你的 DEBUG 配置项设为 True 时才有效。

使用命名组

在 Python 正则表达式中,命名的正则表达式组的语法是 (?P<name>pattern) ,这里 name 是组的名字,而pattern 是匹配的某个模式。
下面是一个使用无名组的 URLconf 的例子:

from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^articles/(\d{4})/$', views.year_archive),
    (r'^articles/(\d{4})/(\d{2})/$', views.month_archive),
    )

下面是相同的 URLconf,使用命名组进行了重写:

from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^articles/(?P<year>\d{4})/$', views.year_archive),
    (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
    )

这段代码和前面的功能完全一样,只有一个细微的差别: 取的值是以关键字参数的方式而不是以位置参数的方式传递给视图函数的。
注意 两种方式2选1,建议不要一起使用

传递额外的参数到视图函数中

有时你会发现你写的视图函数是十分类似的,只有一点点的不同。 比如说,你有两个视图,它们的内容是一致的,除了它们所用的模板不太一样:

# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^foo/$', views.foo_view),
    (r'^bar/$', views.bar_view),
    )
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foo_view(request):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response('template1.html', {'m_list': m_list})
    def bar_view(request):
        m_list = MyModel.objects.filter(is_new=True)
        return render_to_response('template2.html', {'m_list': m_list})

我们在这代码里面做了重复的工作,不够简练。 起初你可能会想,通过对两个URL都使用同样的视图,在URL中使用括号捕捉请求,然后在视图中检查并决定使用哪个模板来去除代码的冗余,就像这样:

# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^(foo)/$', views.foobar_view),
    (r'^(bar)/$', views.foobar_view),
    )
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foobar_view(request, url):
    m_list = MyModel.objects.filter(is_new=True)
    if url == 'foo':
        template_name = 'template1.html'
    elif url == 'bar':
        template_name = 'template2.html'
    return render_to_response(template_name, {'m_list': m_list})

这种解决方案的问题还是老缺点,就是把你的URL耦合进你的代码里面了。 如果你打算把 /foo/ 改成 /fooey/的话,那么你就得记住要去改变视图里面的代码。
对一个可选URL配置参数的优雅解决方法: URLconf里面的每一个模式都可以包含第三个数据: 一个关键字参数的字典:
有了这个概念以后,我们就可以把我们现在的例子改写成这样:

# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^foo/$', views.foobar_view, {'template_name': 'template1.html'}),
    (r'^bar/$', views.foobar_view, {'template_name': 'template2.html'}),
    )
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foobar_view(request, template_name):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response(template_name, {'m_list': m_list})

如你所见,这个例子中,URLconf指定了 template_name 。 而视图函数会把它当成另一个参数。
这种使用额外的URLconf参数的技术以最小的代价给你提供了向视图函数传递额外信息的一个好方法。 正因如此,这技术已被很多Django的捆绑应用使用,其中以我们将在第11章讨论的通用视图系统最为明显。
下面的几节里面有一些关于你可以怎样把额外URLconf参数技术应用到你自己的工程的建议。

创建一个通用视图

比如这个视图显示一系列的 Event 对象,那个视图显示一系列的 BlogEntry 对象,并意识到它们都是一个用来显示一系列对象的视图的特例,而对象的类型其实就是一个变量。
以这段代码作为例子:

# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^events/$', views.event_list),
    (r'^blog/entries/$', views.entry_list),
    )
# views.py
from django.shortcuts import render_to_response
from mysite.models import Event, BlogEntry
def event_list(request):
    obj_list = Event.objects.all()
    return render_to_response('mysite/event_list.html', {'event_list': obj_list})
def entry_list(request):
    obj_list = BlogEntry.objects.all()
    return render_to_response('mysite/blogentry_list.html', {'entry_list': obj_list})

这两个视图做的事情实质上是一样的: 显示一系列的对象。 让我们把它们显示的对象的类型抽象出来:

# urls.py
from django.conf.urls.defaults import *
from mysite import models, views
urlpatterns = patterns('',
    (r'^events/$', views.object_list, {'model': models.Event}),
    (r'^blog/entries/$', views.object_list, {'model': models.BlogEntry}),
    )
# views.py
from django.shortcuts import render_to_response
def object_list(request, model):
    obj_list = model.objects.all()
    template_name = 'mysite/%s_list.html' % model.__name__.lower()
    return render_to_response(template_name, {'object_list': obj_list})

就这样小小的改动,我们突然发现我们有了一个可复用的,模型无关的视图! 从现在开始,当我们需要一个视图来显示一系列的对象时,我们可以简简单单的重用这一个 object_list 视图,而无须另外写视图代码了。

了解捕捉值和额外参数之间的优先级

当冲突出现的时候,额外URLconf参数优先于捕捉值。 也就是说,如果URLconf捕捉到的一个命名组变量和一个额外URLconf参数包含的变量同名时,额外URLconf参数的值会被使用。
例如,下面这个URLconf:

from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^mydata/(?P<id>\d+)/$', views.my_view, {'id': 3}),
    )

这里,正则表达式和额外字典都包含了一个 id 。硬编码的(额外字典的) id 将优先使用。 就是说任何请求(比如, /mydata/2/ 或者 /mydata/432432/ )都会作 id 设置为 3 对待,不管URL里面能捕捉到什么样的值。
聪明的读者会发现在这种情况下,在正则表达式里面写上捕捉是浪费时间的,因为 id 的值总是会被字典中的值覆盖。 没错,我们说这个的目的只是为了让你不要犯这样的错误。

使用缺省视图参数

可以给一个视图指定默认的参数。 这样,当没有给这个参数赋值的时候将会使用默认的值。
例子:

# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
    (r'^blog/$', views.page),
    (r'^blog/page(?P<num>\d+)/$', views.page),
    )
# views.py
def page(request, num='1'):
# Output the appropriate page of blog entries, according to num.
# ...

在这里,两个URL表达式都指向了同一个视图 views.page ,但是第一个表达式没有传递任何参数。 如果匹配到了第一个样式, page() 函数将会对参数 num 使用默认值 "1" ,如果第二个表达式匹配成功, page() 函数将使用正则表达式传递过来的num的值。
(注:我们已经注意到设置默认参数值是字符串 ‘1’ ,不是整数1 。为了保持一致,因为捕捉给num 的值总是字符串。

特殊情况下的视图

有时你有一个模式来处理在你的URLconf中的一系列URL,但是有时候需要特别处理其中的某个URL。 在这种情况下,要使用将URLconf中把特殊情况放在首位的线性处理方式 。
比方说,你可以考虑通过下面这个URLpattern所描述的方式来向Django的管理站点添加一个目标页面

urlpatterns = patterns('',
    # ...
    ('^([^/]+)/([^/]+)/add/$', views.add_stage),
    # ...
)

这将匹配像 /myblog/entries/add/ 和 /auth/groups/add/ 这样的URL 。然而,对于用户对象的添加页面(/auth/user/add/ )是个特殊情况,因为它不会显示所有的表单域,它显示两个密码域等等
因为它把URL逻辑放在了视图中。 优雅的解决方法是,我们要利用URLconf从顶向下的解析顺序这个特点:

urlpatterns = patterns('',
# ...
    ('^auth/user/add/$', views.user_add_stage),
    ('^([^/]+)/([^/]+)/add/$', views.add_stage),
# ...
)

在这种情况下,象 /auth/user/add/ 的请求将会被 user_add_stage 视图处理。 尽管URL也匹配第二种模式,它会先匹配上面的模式。 (这是短路逻辑。)

从URL中捕获文本

每个被捕获的参数将被作为纯Python字符串来发送,而不管正则表达式中的格式。 举个例子,在这行URLConf中:

url(r'^articles/(?P<year>\d{4})/$', views.year_archive),

尽管 \d{4} 将只匹配整数的字符串,但是参数 year 是作为字符串传至 views.year_archive() 的,而不是整型。
注意 捕获的参数都是字符串格式的,在视图函数中要对捕获的参数进行处理时要记得这个参数是个字符串

视图函数的高级概念

说到关于请求方法的分支,让我们来看一下可以用什么好的方法来实现它。 考虑这个 URLconf/view 设计:

# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
# ...
    (r'^somepage/$', views.some_page),
# ...
)
# views.py
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
def some_page(request):
    if request.method == 'POST':
        do_something_for_post()
        return HttpResponseRedirect('/someurl/')
    elif request.method == 'GET':
        do_something_for_get()
        return render_to_response('page.html')
    else:
        raise Http404()

在这个示例中,some_page() 视图函数对POSTGET 这两种请求方法的处理完全不同。 它们唯一的共同点是共享一个URL地址: /somepage/.正如大家所看到的,在同一个视图函数中对POSTGET 进行处理是一种很初级也很粗糙的做法。 一个比较好的设计习惯应该是,用两个分开的视图函数——一个处理POST 请求,另一个处理GET 请求,然后在相应的地方分别进行调用。
我们可以像这样做:先写一个视图函数然后由它来具体分派其它的视图,在之前或之后可以执行一些我们自定的程序逻辑。 下边的示例展示了这个技术是如何帮我们改进前边那个简单的some_page() 视图的:

# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
# ...
    (r'^somepage/$', views.method_splitter, {'GET': views.some_page_get, 'POST': views.some_page_p
# ...
)
# views.py
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
def method_splitter(request, GET=None, POST=None):
    if request.method == 'GET' and GET is not None:
        return GET(request)
    elif request.method == 'POST' and POST is not None:
        return POST(request)
        raise Http404


def some_page_get(request):
    assert request.method == 'GET'
    do_something_for_get()
    return render_to_response('page.html')


def some_page_post(request):
    assert request.method == 'POST'
    do_something_for_post()
    return HttpResponseRedirect('/someurl/')

在urls.py文件先一步将'GET','POST'函数放到第三个参数中 (即 **kwarg) 中,在url绑定的method_splitter视图函数也接收到绑定函数的参数,视图函数先对请求类型进行判断,然后在进行处理.
现在我们就拥有了一个不错的,可以通用的视图函数了,里边封装着由request.method 的返回值来分派不同的视图的程序。关于method_splitter() 就不说什么了,当然,我们可以把它们重用到其它项目中。

改进方法(如果要传一些参数)

然而,当我们做到这一步时,我们仍然可以改进method_splitter 。从代码我们可以看到,它假设GetPOST 视图除了request 之外不需要任何其他的参数。那么,假如我们想要使用method_splitter 与那种会从URL里捕捉字符,或者会接收一些可选参数的视图一起工作时该怎么办呢?
为了实现这个,我们可以使用Python中一个优雅的特性 带星号的可变参数 我们先展示这些例子,接着再进行解释

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

这里,我们重构method_splitter(),去掉了GET和POST两个关键字参数,改而支持使用args和和kwargs(注意号) 这是一个Python特性,允许函数接受动态的、可变数量的、参数名只在运行时可知的参数。 如果你在函数定义时,只在参数前面加一个号,所有传递给函数的参数将会保存为一个元组. 如果你在函数定义时,在参数前面加两个号,所有传递给函数的关键字参数,将会保存为一个字典
例如,下面这个函数:

def foo(*args, **kwargs):
    print "Positional arguments are:"
    print args
    print "Keyword arguments are:"
    print kwargs
>>> foo(1, 2, name='Adrian', framework='Django')
Positional arguments are:
(1, 2)
Keyword arguments are:
{'framework': 'Django', 'name': 'Adrian'}

回过头来看,你能发现我们用 method_splitter() 和 *args 接受 **kwargs 函数参数并把它们传递到正确的视图。 any 但是在我们这样做之前,我们要调用两次获得参数 kwargs.pop()GETPOST ,如果它们合法的话。 (我们通过指定pop的缺省值为None,来避免由于一个或者多个关键字缺失带来的KeyError)

包装视图函数 (装饰器)

假设你发现自己在各个不同视图里重复了大量代码,就像这个例子:

def my_view1(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/accounts/login/')
# ...
    return render_to_response('template1.html')
def my_view2(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/accounts/login/')
# ...
    return render_to_response('template2.html')
def my_view3(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/accounts/login/')
# ...
    return render_to_response('template3.html')

这里,每一个视图开始都检查 request.user 是否是已经认证的,是的话,当前用户已经成功登陆站点否则就重定向 /accounts/login/ (注意,虽然我们还没有讲到request.user,但是14章将要讲到它.就如你所想像的,request.user描述当前用户是登陆的还是匿名)
如果我们能够丛每个视图里移除那些 重复代,并且只在需要认证的时候指明它们,那就完美了。 我们能够通过使用一个视图包装达到目的。 花点时间来看看这个:

def requires_login(view):
    def new_view(request, *args, **kwargs):
        if not request.user.is_authenticated():
            return HttpResponseRedirect('/accounts/login/')
        return view(request, *args, **kwargs)
    return new_view

现在,我们可以从views中去掉if not request.user.is_authenticated()验证.我们可以在URLconf中很容易的用requires_login来包装实现.

包含其他URLconf

如果你试图让你的代码用在多个基于Django的站点上,你应该考虑将你的URLconf以包含的方式来处理。
在任何时候,你的URLconf都可以包含其他URLconf模块。 对于根目录是基于一系列URL的站点来说,这是必要的。 例如下面的,URLconf包含了其他URLConf:

from django.conf.urls.defaults import *
urlpatterns = patterns('',
    (r'^weblog/', include('mysite.blog.urls')),
    (r'^photos/', include('mysite.photos.urls')),
    (r'^about/$', 'mysite.views.about'),
)

这里有个很重要的地方: 例子中的指向 include() 的正则表达式并 不 包含一个 $ (字符串结尾匹配符),但是包含了一个斜杆。 每当Django遇到 include() 时,它将截断匹配的URL,并把剩余的字符串发往包含的URLconf作进一步处理。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 已经同步到gitbook,想阅读的请转到gitbook: Django 1.10 中文文档 URL dispatc...
    leyu阅读 14,186评论 0 16
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 基于类的视图 Django中的视图是一个可调用对象,它接收一个请求然后返回一个响应。这个可调用对象不仅仅限于函数,...
    兰山小亭阅读 4,562评论 1 13
  • 11-1-14陈晓君 交个朋友难吗?如果他很可爱,很有趣,很漂亮,那么也许很容易。可是如果他们和你长得完全不一样...
    chen晓君阅读 1,712评论 0 1
  • 每个人的人生都有两条路,一条用心走,叫做梦想;一条用脚走,叫做现实。如果心走的太慢,现实就会苍白;而如果脚走的太慢...
    VV_Vivian阅读 377评论 0 3