首页 > 代码库 > 3、视图和模板

3、视图和模板

1、在我们的 poll 应用程序中,将有以下四个视图:

  • Poll “index” 页 – 显示最新发布的民意调查。
  • Poll “detail” 页 – 显示一项民意调查的具体问题,不显示该项的投票结果但可以进行投票的 form 。
  • Poll “results” 页 – 显示一项给定的民意调查的投票结果。
  • 投票功能 – 为一项给定的民意调查处理投票选项。

在 Django 中,网页及其他内容是由视图来展现的。而每个视图就是一个简单的 Python 函数(或方法, 对于基于类的视图情况下)。Django 会通过检查所请求的 URL (确切地说是域名之后的那部分 URL)来匹配一个视图。

让我们编写第一个视图。打开文件 polls/views.py 并在其中输入以下 Python 代码

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You‘re at the poll index.")

在 Django 中这可能是最简单的视图了。为了调用这个视图,我们需要将它映射到一个 URL – 为此我们需要配置一个URLconf ,创建项目的时候就已经创建了polls/url.py,

from django.conf.urls import patterns, url

from polls import views

urlpatterns = patterns(‘‘,
    url(r‘^index$‘, views.index, name=‘index‘)
)

  下一步是将 polls.urls 模块指向 root URLconf 。在 mysite/urls.py 中插入一个 include() 方法,最后的样子如下所示

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

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

  现在你在 URLconf 中配置了 index 视图。通过浏览器访问 http://localhost:8000/polls/index ,如同你在 index 视图中定义的一样,你将看到 “Hello, world. You’re at the poll index.” 文字。

2、编写更多视图

现在让我们添加一些视图到 polls/views.py 中去。这些视图与之前的略有不同,因为 它们有一个参数::

def detail(request, poll_id):
    return HttpResponse("You‘re looking at poll %s." % poll_id)

def results(request, poll_id):
    return HttpResponse("You‘re looking at the results of poll %s." % poll_id)

def vote(request, poll_id):
    return HttpResponse("You‘re voting on poll %s." % poll_id)

  将新视图按如下所示的 url() 方法添加到 polls.urls 模块中去:

urlpatterns = patterns(‘‘,
    # ex: /polls/
    url(r‘^index$‘, views.index, name=‘index‘),
    # ex: /polls/5/
    url(r‘^(?P<poll_id>\d+)/$‘, views.detail, name=‘detail‘),
    # ex: /polls/5/results/
    url(r‘^(?P<poll_id>\d+)/results/$‘, views.results, name=‘results‘),
    # ex: /polls/5/vote/
    url(r‘^(?P<poll_id>\d+)/vote/$‘, views.vote, name=‘vote‘),
)

  其中poll_id作为视图中(view.py)中函数传入的参数名匹配,如果这里是poll_id改为poll_id1那么view.py中的参数名称也要对应的修改。

当有人访问你的网站页面如 “ /polls/34/ ” 时,Django 会加载 mysite.urls 模块,这是因为 ROOT_URLCONF 设置指向它。接着在该模块中寻找名为urlpatterns 的变量并依次匹配其中的正则表达式。 include() 可让我们便利地引用其他 URLconfs 。请注意 include() 中的正则表达式没有 $ (字符串结尾的匹配符 match character) 而尾部是一个反斜杠。当 Django 解析 include() 时,它截取匹配的 URL 那部分而把剩余的字符串交由 加载进来的 URLconf 作进一步处理。

以下是当用户访问 “/polls/34/” 路径时系统中将发生的事:

    • Django 将寻找 ‘^polls/‘ 的匹配
    • 接着,Django 截取匹配文本 ("polls/") 后剩余的文本 – "34/" – 传递到 ‘polls.urls’ URLconf 中作进一步处理, 再将匹配 r‘^(?P<poll_id>\d+)/$‘ 的结果作为参数传给 detail() 视图

poll_id=‘34‘ 这部分就是来自 (?P<poll_id>\d+) 匹配的结果。 使用括号包围一个 正则表达式所“捕获”的文本可作为一个参数传给视图函数;?P<poll_id> 将会定义名称用于标识匹配的内容; 而 \d+ 是一个用于匹配数字序列(即一个数字)的正则表达式。

3、在视图中添加些实际的功能

我们来使用 Django 自己的数据库 API 吧, 在 教程 第1部分 中提过。修改下 index() 视图, 让它显示系统中最新发布的 5 个调查问题,以逗号分割并按发布日期排序:

from django.http import HttpResponse

from polls.models import Poll

def index(request):
    latest_poll_list = Poll.objects.order_by(‘-pub_date‘)[:5]
    output = ‘, ‘.join([p.question for p in latest_poll_list])
    return HttpResponse(output)

Django 的 TEMPLATE_LOADERS 配置中包含一个知道如何从各种来源导入模板的可调用的方法列表。 其中有一个默认值是 django.template.loaders.app_directories.Loader ,Django 就会在每个 INSTALLED_APPS 的 “templates” 子目录下查找模板 - 这就是 Django 知道怎么找到 polls 模板的原因。

 现在我们在polls目录下面创建templates 目录,并在templates目录下创建一个 index.html 文件。换句话说,你的模板应该是 polls/templates/index.html 。

将以下代码添加到该模板中index.html:

{% if latest_poll_list %}
    <ul>
    {% for poll in latest_poll_list %}
        <li><a href="http://www.mamicode.com/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

  现在让我们在 index 视图中使用这个模板:

from django.http import HttpResponse
from django.template import Context, loader

from polls.models import Poll

def index(request):
    latest_poll_list = Poll.objects.order_by(‘-pub_date‘)[:5]
    template = loader.get_template(‘index.html‘)
    context = Context({
        ‘latest_poll_list‘: latest_poll_list,
    })
    return HttpResponse(template.render(context))

 在你的浏览器中加载 “/polls/” 页,你应该看到一个列表,包含了之前教程中创建的 “What’s up” 调查。而链接指向 poll 的详细页面。 (后面是自己添加了一个新的事件)

技术分享

4、快捷方式: render()

这是一个非常常见的习惯用语,用于加载模板,填充上下文并返回一个含有模板渲染结果的 HttpResponse 对象。 Django 提供了一种快捷方式。这里重写完整的 index() 视图

from django.shortcuts import render

from polls.models import Poll

def index(request):
    latest_poll_list = Poll.objects.all().order_by(‘-pub_date‘)[:5]
    context = {‘latest_poll_list‘: latest_poll_list}
    return render(request, ‘index.html‘, context)

请注意,一旦我们在所有视图中都这样做了,我们就不再需要导入 loader , Context 和 HttpResponse ( 如果你仍然保留了 detail,resutls, 和vote 方法,你还是需要保留 HttpResponse ) 。

render() 函数中第一个参数是 request 对象,第二个参数是一个模板名称,第三个是一个字典类型的可选参数。 它将返回一个包含有给定模板根据给定的上下文渲染结果的 HttpResponse 对象。

5、抛出 404 异常

现在让我们解决 poll 的详细视图 – 该页显示一个给定 poll 的详细问题。 视图代码如下所示::

from django.http import Http404
# ...
def detail(request, poll_id):
    try:
        poll = Poll.objects.get(pk=poll_id)
    except Poll.DoesNotExist:
        raise Http404
    return render(request, ‘detail.html‘, {‘poll‘: poll})

 技术分享

在这有个新概念:如果请求的 poll 的 ID 不存在,该视图将抛出 Http404 异常。

我们稍后讨论如何设置 polls/detail.html 模板,若是你想快速运行上面的例子, 在模板文件detail.html中添加如下代码:

{{ poll }}

6、快捷方式: get_object_or_404()

这很常见,当你使用 get() 获取对象时 对象却不存在时就会抛出 Http404 异常。对此 Django 提供了一个快捷操作。如下所示重写 detail() 视图:

from django.shortcuts import render, get_object_or_404
# ...
def detail(request, poll_id):
    poll = get_object_or_404(Poll, pk=poll_id)
    return render(request, ‘polls/detail.html‘, {‘poll‘: poll})

  还有个 get_list_or_404() 函数,与 get_object_or_404() 一样 – 不过执行的是 filter() 而不是 get() 。若返回的是空列表将抛出 Http404 异常。

技术分享

7、编写一个 404 ( 页面未找到 ) 视图

当你在视图中抛出 Http404 时,Django 将载入一个特定的视图来处理 404 错误。Django 会根据你的 root URLconf ( 仅在你的 root URLconf 中;在其他任何地方设置 handler404 都无效 )中设置的 handler404 变量来查找该视图

一些有关 404 视图需要注意的事项 :

    • 如果 DEBUG 设为 True ( 在你的 settings 模块里 ) 那么你的 404 视图将永远不会被使用 ( 因此 404.html 模板也将永远不会被渲染 ) 因为将要显示的是跟踪信息。
    • 当 Django 在 URLconf 中不能找到能匹配的正则表达式时 404 视图也将被调用。编写一个 500 ( 服务器错误 ) 视图。

 按照要求去修改:

技术分享

自己动手去写404.html页面

技术分享

 访问一个不存在的polls的时候页面显示如下:

技术分享

8、使用模板系统

 回到我们 poll 应用的 detail() 视图中,指定 poll 变量后,polls/detail.html 模板可能看起来这样 :

<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

模板系统使用了“变量.属性”的语法访问变量的属性值。 例如 {{ poll.question }} ,访问页面后的结果显示如下:

技术分享

9、移除模板中硬编码的 URLS

记得吗? 在 templates/index.html 模板中,我们链接到 poll 的链接是硬编码成这样子的:

<li><a href="http://www.mamicode.com/polls/{{ poll.id }}/">{{ poll.question }}</a></li>

问题出在硬编码,紧耦合使得在大量的模板中修改 URLs 成为富有挑战性的项目。 不过,既然你在 polls.urls 模块中的 url() 函数中定义了 命名参数,那么就可以在 url 配置中使用 {% url %} 模板标记来移除特定的 URL 路径依赖:下面有截图提供理解。

<li><a href="http://www.mamicode.com/{% url ‘detail‘ poll.id %}">{{ poll.question }}</a></li>

其原理就是在 polls.urls 模块中寻找指定的 URL 定义。 你知道命名为 ‘detail’ 的 URL 就如下所示那样定义的一样::

# ‘name‘ 的值由 {% url %} 模板标记来引用
url(r‘^(?P<poll_id>\d+)/$‘, views.detail, name=‘detail‘),

如果你想将 polls 的 detail 视图的 URL 改成其他样子,或许像 polls/specifics/12/ 这样子,那就不需要在模板(或者模板集)中修改而只要在 polls/urls.py 修改就行了:

# 新增 ‘specifics‘
url(r‘^specifics/(?P<poll_id>\d+)/$‘, views.detail, name=‘detail‘),

  之前我们的url.py中的name值有以下一些选项:

技术分享

下面的detail如果换成不是上面三者中的一个,则会报错。

技术分享

10、URL 名称的命名空间

本教程中的项目只有一个应用:polls 。在实际的 Django 项目中,可能有 5、10、20 或者 更多的应用。Django 是如何区分它们的 URL 名称的呢?比如说,polls 应用有一个 detail 视图,而可能会在同一个项目中是一个博客应用的视图。Django 是如何知道 使用 {% url %} 模板标记创建应用的 url 时选择正确呢?

答案是在你的 root URLconf 配置中添加命名空间。在 mysite/urls.py 文件 (项目的 urls.py,不是应用的) 中,修改为包含命名空间的定义:

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

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

现在将你的 templates/index.html 模板中原来的 detail 视图:

<li><a href="http://www.mamicode.com/{% url ‘detail‘ poll.id %}">{{ poll.question }}</a></li>  

修改为包含命名空间的 detail 视图:

<li><a href="http://www.mamicode.com/{% url ‘polls:detail‘ poll.id %}">{{ poll.question }}</a></li>
技术分享

3、视图和模板