Django-1-7-3-Tutorial-Part-3-视图和模板
Tim Chen(motion$) Lv5
  • 本教程是接着上一节教程继续的。我们继续讨论网页投票(Web-poll)应用程序,然后关注与创建公共接口–“视图”。

哲学

  • 一个视图是在你的Django应用中一种有特定应用功能和有一个模板的网页的“类型”。譬如,在一个博客应用中,你可能需要以下视图:

    • 博客首页–显示最新的博客
    • “具体”页面的入口–单独入口的固定链接
    • 把博客按照年排序的架构页面–给定年份,按月列出所有的文章
    • 把博客按照月排序的架构页面–给定月份,按日列出所有的文章
    • 把博客按照日排序的架构页面–给定日期,列出所有的文章
    • 评论操作–给指定的文章下评论
  • 在我们的投票应用中(poll),我们已经有了以下四个页面:

    • 问题“首页”–显示最新的问题
    • 问题“具体页”–显示一个问题文本,不能显示结果,但是提供投票表格
    • 问题“结果页”–为指定的问题显示投票结果
    • 投票操作–在给定的问题中投给定的选项
  • 在Django中,网页和其他的内容页是通过视图展示的。每一个视图是通过一个简单的Python功能展示的(或者方法,如果在基于类的视图下)。Django通过测试请求的URL而选择视图(更准确的说,是跟在域名后面的URL部分)。

  • 现在你在浏览网页的时候可能看过这样的网页链接:“ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”。但是更值得我们高兴的是,Django允许我们使用比以上更优雅的链接模式。

  • 一个URL模式是一个简单的URL通常格式–譬如:

  • 要从一个URL得到一个视图,Django利用了我们熟知的’URLconfs’。一个URLconf把一个URL模式(通常被描述为正则表达式)映射到一个视图。

  • 本节教程提供了URLconfs的基本用法,然后你可以参考django.core.urlresolvers得到更多信息。

写你的第一个视图

  • 让我们开始写第一个视图吧。打开文件polls/views.py然后写入以下python代码:

  • polls/views.py

    1
    2
    3
    4
    from django.http import HttpResponse

    def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")
  • 这可能是Django中最简单的视图了。要调用这个视图,我们需要把它映射到一个URL–然后我们就需要一个URLconf。

  • 在polls目录下创建一个URLconf,创建一个urls.py文件。你的app目录应该看起来是这样:

  • polls/urls.py文件中加入以下代码:

  • polls/urls.py

    1
    2
    3
    4
    5
    6
    from django.conf.urls import patterns, url
    from polls import views

    urlpatterns = patterns('',
    url(r'^$', views.index, name='index'),
    )
  • 下一步就是在polls.urls模块中指定根URLconf。在mysite/urls.py中插入一个**include()**,像这样:

  • mysite/urls.py

    1
    2
    3
    4
    5
    6
    7
    from django.conf.urls import patterns, include, url
    from django.contrib import admin

    urlpatterns = patterns('',
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', include(admin.site.urls)),
    )

    和你所见的不匹配吗?
    如果你在urlpatterns定义之前就看见了**admin.autodiscover()**,你可能是在用一个和本教程不匹配的版本,你应该更改Django的版本。

  • 你现在可以把**首页(index)视图转换为URLconf。在你的浏览器中输入http://localhost:8000/polls/,按下Enter键,你应该可以看到文本“Hello, world. You’re at the polls index.”,它是在首页(index)**视图所定义的。

  • url()函数有四个参数,其中两个是必须的:regex(正则表达式)view(视图),两个是可选的:kwargs和**name(名字)**。现在,让我们来了解一下这些参数具体是什么来的。

url()参数:regex

  • 术语regex通常是**regular expression(正则表达式)**的缩写,它是一个字符串匹配的语法规则,在这里,它是url模式的匹配。Django从第一个正则表达式开始,然后把它们造一个列表,把请求的URL和每一个正则表达式单元进行比较,然后找到匹配的那一个。
  • 请注意,这些正则表达式不会搜寻GET和POST的参数或者域名。譬如,在请求URLhttp://www.example.com/myapp/**中,URLconf会寻找**myapp/**。而在请求URL**http://www.example.com/myapp/?page=3中,URLconf也会寻找**myapp/**。
  • 如果你不了解正则表达式,请看Wikipedia’s entry或者re模块的文档。另外,O’Reilly的书–Jeffrey Friedl著的《Mastering Regular Expressions》非常不错。实际上,你不需要成为正则表达式方面的专家,如果你仅仅需要知道一些简单的模式匹配的话。事实上,复杂的正则表达式有比较弱的检查能力,所以,你不太可能依赖正则表达式。
  • 最后,性能笔记:这些正则表达式在URLconf模块加载时会被首次编译。它们非常快(只要查找的不是太复杂的正则表达式)。

url()参数:view

  • 当Django匹配到了正则表达式时,Django调用指定的视图函数,第一个参数是一个HttpRequest对象,其他的参数是根据需要从正则表达式中捕捉的任意值。如果正则表达式是用简单的捕捉,值就会当成位置参数来传递;如果正则表达式是用命名捕捉,值就会被当成关键字参数来传递。我们稍后会给出例子进行说明。

url()参数:kwargs

  • 任意的关键字参数都可以传递到目标视图中的字典。在这个教程中我们不会讨论这个特性。

url()参数:name

  • 对你的URL命名可以让你清楚地从Django中的任何地方中引用,特别是在模板中。这个有力的特性让你对项目中的url模式做全局的修改,当你仅仅创建了单个文件时。

写更多的视图

  • 现在让我们在polls/views.py文件中添加更多的视图。这些视图可能有小小的不同,因为它们有一个参数。

  • polls/views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

    def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

    def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)
  • 把这些新的视图通过添加以下的url()调用写进polls.urls模块:

  • polls/urls.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from django.conf.urls import patterns, url

    from polls import views

    urlpatterns = patterns('',
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>\d+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>\d+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
    )
  • 在浏览器中打开**”/polls/34/“浏览一下。它运行detail()方法并且根据你的URL提供的ID显示你的东西。试一下“/polls/34/results/”/polls/34/vote/**–这些会显示占位符结果和投票页面。

  • 当某人从你的网站请求网页时–譬如,**/polls/34/,Django将会加载mysite.urls模块,因为它是被ROOT_URLCONF设置所指向的。它找到名叫urlpatterns的变量然后遍历正则表达式。include()函数只是引用了其他的URLconfs。注意到关于include()方法的正则表达式没有$(字符串匹配的结束符)而只有末尾斜杠。无论何时Django遇上了include()**方法,它会发送剩余的字符串到包含的URLconf给下一步做准备,而砍掉其他的部分。

  • **include()背后的思想是使得URLs即插即用,用起来非常简单。由于polls在它们自己的URLconf(polls/urls.py)中,它们可以被放在/polls/或者/fun_polls/或者/content/polls/**或者其它的根路径后面,应用还是可以运行的。

  • 如果用户去到系统中的**/polls/34/**,它会发生以下事情:

    • Django会对**^polls/**进行匹配
    • 然后,Django会去掉匹配的文本(“polls/”)并且把剩下的文本34/发送到polls.urlsURLconf作进一步的操作,**34/是匹配,然后对detail()**视图进行调用:
  • **question_id=’34’是来自。用圆括号把匹配到的模式文本括起来当成一个参数传到视图函数;定义了将要确定匹配模式的名字;而\d+**是一个匹配一串数字的正则表达式。

  • 因为URL模式是一个正则表达式,它们没有任何限制。没必要添加URL烂尾,譬如**.html**,除非你想要加,然后你可以这样做:

  • 但是不要这样做,它看起来傻傻的。

写一些起作用的视图

  • 每一个视图都应该做一到两样事情:返回一个包含请求页的内容的HttpResponse对象,或者产生一个像Http404这样的异常。剩下的就由你决定。
  • 你的视图能够从数据库中读取记录,或者不读。它能够使用一个像Django这样的模板系统,或者第三方模板系统,又或者不用。它能够生成一个PDF文件,输出XML,飞速创建一个ZIP文件,或者任何你想要的文件,使用任何你想要的Python库。
  • Django想做的就是一个HttpResponse,或者一个异常。
  • 由于方便,我们使用Django默认的数据库API,就是我们在教程1当中介绍的。下面是我们在**index()**视图中插入新的东西,它显示了系统中最新的5个投票问题,根据发布日期通过逗号隔开:
  • polls/views.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    from django.http import HttpResponse

    from polls.models import Question

    def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ','.join([p.question_text for p in latest_question_list])

    # leave the rest of views (detail, results, vote) unchanged
  • 但还有一个问题:页面的设计是在视图硬性写定了。如果你像更改页面的设计,你需要重新编辑Python 代码。所以让我们使用Django的模板系统把设计和Python代码通过创建一个视图可调用的模板来分开。
  • 首先,在你的polls目录下创建一个名为templates的文件。Django会在那里进行搜寻的。
  • Django的TEMPLATE_LOADERS设置包含一系列知道如何从不同资源中导入模板的可调用方法。其中默认的方法是django.template.loaders.app_directories.Loader,它在每一个INSTALLED_APPS中寻找一个“模板”子目录。这就是为什么Django在没有修改TEMPLATE_DIRS的情况下知道怎么找到poll模板的原因。

组织模板
我们可以把所有的模板放在一起,在一个大的模板目录中,而且它可以工作的很顺利。但是,这个模板是属于polls应用的,所以不像上一个教程中我们介绍的管理员模板那样,我们把它放在程序的模板目录而不是项目的模板目录。我们会讨论更多关于这个更多的可重用的app教程,并对其进行解释。

  • 在你刚刚创建的template模板目录中,创建另外一个叫做polls的目录,然后再里面创建一个index.html文件。换句话说,你的模板应该是在polls/templates/polls/index.html目录中。正是由于上面我们讨论的关于app_directories模板加载器是如何工作的,你可以像polls/index.html一样通过Django简单的找到模板文件。

模板命名空间
现在我们可能不需要把我们的模板放进polls/templates目录(而不是在polls的子目录下新建一个),但这确实是一个坏主意。Django会选择它第一个匹配到的模板名字,如果你在不同的目录下有相同的名字的应用,Django就不会对它们进行分辨。我们应该要为Django正确指定哪一个模板,最简单的方法来保证这样的事情就是通过给它们创建命名空间。也就是说,把那些模板放进另一个为本应用命名的目录。

  • 在模板中写进以下代码:
  • polls/templates/polls/index.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
    <li><a href="/polls.{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
    {% else %}
    <p>No polls are available.</p>
    {% endif %}
  • 现在让我们更新polls/views.py中的视图来使用模板:
  • polls/views.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from django.http import HttpResponse
    from django.template import RequestContext, loader

    from polls.models import Question

    def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = RequestionContext(request, {
    'latest_question_list': latest_question_list,
    })
    return HttpResponse(template.render(context))
  • 上面的代码加载了polls/index.html模板,然后传递了一个context。context是一个匹配模板变量名到Python对象的字典。
  • 在你的浏览器里点击**”/polls/“加载页面,你会看到包含Tutorial_1中的“What’s up”**问题的无序列表。这个链接指向问题详细内容页。

一个快捷键:render()

  • 这是一种常用的语法:加载一个模板,传递一个context,然后返回一个对显示模板结果的HttpResponse对象。Django提供一个快捷键。下面是完整的**index()**视图,重写:
  • polls/views.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    from django.shortcuts import render

    from polls.models import Question

    def index(request):
    lastest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'lastest_question_list': lastest_question_list}
    return render(request, 'polls/index.html', context)

  • 既然我们搞好了所有的视图,我们不需要去导入loader,RequestContextHttpResponse(如果你需要detailresultsvote的根方法,你需要保留HttpResponse对象)。
  • render()方法的第一个参数是Request对象,第二个参数是一个模板名,然后第三参数是可选的,它是一个字典类型。方法返回的是一个带根据给定的context而呈现的给定的模板的HttpResponse对象。

报告404错误

  • 现在,让我们浏览一下问题详细视图-根据给定的poll显示问题文本的页面。这就是视图:
  • polls/views.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from django.http import Http404
    from djang.shortcuts import render

    from polls.models import Question
    # ...
    def detail(request, question_id):
    try:
    question = Question.objects.get(pk = question_id)
    except Question.DoesNotExist:
    raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

  • 新的概念:如果问题的请求ID不存在,那么视图就会产生要给404异常。
  • 我们会在稍后讨论你能够在polls/detail.html模板中放入什么,但是如果你想快速地运行上面的例子,文件应该包含如下:
  • polls/templates/polls/detail.html
    1
    {{ question }}
  • 会让你现在开始。

快捷键:get_object_or_404()

  • 这是一种常用的语法:如果对象不存在,利用get()方法报告一个Http404异常。Django提供一个快捷键。下面是detial视图,重写:
  • polls/views.py
    1
    2
    3
    4
    5
    6
    7
    from django.shortcuts import get_object_or_404, render

    from polls.models import Question
    # ...
    def detail(request, question_id):
    question = get_object_or_404(Question, pk = question_id)
    return render(request, 'polls/detail.html', {'question':question})
  • get_object_or_404()函数的第一个参数是要给Django模型,然后有数个关键字参数,它们是被传递到模型管理的get()函数。如果对象不存在,它会报告Http404异常。

Philosophy
为什么我们在更高的水平上使用帮助函数get_object_or_404()而不是自动获取ObjectDoesNotExist异常,或者通过模型API报告Http404异常而不是ObjectDoesNotExist异常。
因为这样会把模型层结合视图层。Django的一个最重要的设计目标是保持低耦合。在Django.shortcuts模型中引入了一些强耦合。

  • 还有一个get_list_or_404()函数,它就像get_object_or_404()函数一样工作。用filter()而不是get()方法。它会报告Http404异常,如果列表是空的话。

使用模板系统

  • 回到我们的poll应用的detail()视图。给定context变量question,这就是polls/detail.html模板的代码:
 评论