第三节:视图(Views)和模板(Templates)

目录

概览

视图是Django应用的网页的“类型”,一般服务于特定的功能并且有特定的模板,在我们的投票应用中,我们有下面四个视图:

  • Question  "index"页面——展示最新的几个问题。
  • Question  "detail"页面——显示问题内容,没有结果但是有一个投票表单。
  • Question  "results"页面——显示一个特定问题的结果
  • Vote "action"——处理在一个特定问题的进行具体的选择

在Django中,Web页面和其他内容是通过一个视图提供的,每一个视图由一个简单的Python函数表示(或者是类方法),Django将解析被请求的Url去选择一个视图(准确地说,是url域名后面的部分)。

从一个url到一个视图,Django使用所谓的‘Urlconfs’一个Urlconf映射Url模式到一个视图,本教程提供了Urlconfs使用的基本介绍,你也可以参考django.urls获得更多信息。 

编写视图

polls/views.py添加视图:  

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)

polls.urls模块中添加url映射:

from django.conf.urls import url

from . import views

urlpatterns = [
    # 例: /polls/
    url(r'^$', views.index, name='index'),
    # 例: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # 例: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # 例: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

在浏览器打开“/polls/34”它将调用detail()方法并且显示你在url中提供的ID,同样的可以尝试打开“/polls/34/results”和“/polls/34/vote”。

当有人请求你网站的一个页面时,比如“/polls/34/”,Django将加载 mysite.urls Python模块因为它指定在ROOT_URLCONF配置中(mysite/settings.py)。它查找名为urlpatterns的变量并且按顺序遍历正则表达式。然后发现匹配"^polls/",它会替换掉匹配的文本("polls/")并将剩余的文本-"34/"交给"polls.urls"的url配置做进一步处理。它匹配r'^(?P<question_id>[0-9]+)/$',结果调用detail()视图:

detail(request=<HttpRequest object>, question_id='34')

这个question_id='34'部分来自(?P<question_id>[0-9]+)。使用括号通过这个模式在一个“捕获”匹配的文本,并发送它给视图函数作为一个参数。?P<question_id>将被用做匹配模式的标识名称;[0-9]+匹配数字序列的正则表达式。 

因为Url模式是正则表达式,所以格式没有任何限制,比如我们设置一个.html后缀的地址:

url(r'^polls/latest\.html$', views.index),

但是没必要这样做。

编辑视图实际做一些事情

每个视图负责两个事情之一,返回HttpResponse对象包含请求页面的内容,或者抛出异常例如Http404 

你的视图可能从数据库读取记录,它可以使用一个模板系统例如Django的,-或者第三方Python模板系统。它可以产生一个PDF文件,输出XML,创建一个ZIP文件,任何你想要的,都可以使用Python库。 

因为它的方便,让我们使用Django自己的数据库API,在第二部分教程中已经提到过了,在index()视图,我们显示最新的五条投票问题,以逗号分隔,按发布时间排序(polls/views.py):

from django.http import HttpResponse

from .models import Question

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

这儿有个问题,就是:页面的设计你硬编码到视图中的。如果你想要改变页面外观,你就要编辑python代码,让我们来使用Django的模板系统创建一个模板把设计从Python代码中分离出来。

首先在你的polls目录来创建一个名叫templates的文件夹,Django将在这个路径查找模板。

你的项目的TEMPLATES设置描述了Django将怎样加载和渲染模板,默认的配置文件配置了一个DjangoTemplates 后端其APP_DIRS选项设置为True。按照惯例DjangoTemplates 查找每一个INSTALLED_APPS应用的子目录“templates”。

在你刚刚创建的templates 目录下创建另一个名叫polls的文件夹,并且创建一个名叫index.html的文件,也就是说,你的模板应该放在 polls/templates/polls/index.html

注意:现在我们可能把我们的模板文件直接放在polls/templates(而不是创建另外一个polls子目录),但它实际上不是一个好主意。Django将选择第一个名称相匹配的模板,并且如果你已经有一个在不同应用名称相同的模板,Django是无法区分他们的,我们需要一个正确的指向项目模板并确保他们有简单的命名方式,也就是说通过将模板放置到templates的另一个目录中。

在模板中输入以下代码polls/templates/polls/index.html 

{% 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中修改index视图去使用模板: 

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

from .models import Question

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

该代码加载名为polls/index.html的模板并传递context变量,context是一个字典映射在模板的变量名。 

通过在你的浏览器加载页面“/polls/”,然后你应该可以看到Question的一个列表。 

一个快捷方法:render()

它是一个加载模板非常常见的语法,添加一个context并返回呈现模板结果的

HttpResponse对象。Django提供一个快捷方法,现在我们来重写index()视图:

from django.shortcuts import render

from .models import Question

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

注意我们所有的视图都做到这一点的话,我们不在需要导入loaderHttpResponse 

这个render()函数以请求对象作为第一个参数,第二个参数是模板名称和另外一个参数字典类作为可选参数。它返回一个指定上下文指定模板的HttpResponse对象。

抛出404错误

现在,让我们解决问题的详情视图 - 这个页面显示指定投票的问题文本,编辑视图polls/views.py: 

from django.http import Http404
from django.shortcuts import render

from .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})

这儿有个新概念:视图抛出Http404异常如果一个问题的ID不存在的话。 

为了快速的展示一下上面的示例,在模板templates/polls/detail.html中输入简单的代码: 

{{ question }}

一个快捷方法:get_object_or_404()

一个常用的语法去使用并且如果对象不存在抛出Http404异常。修改polls/views.py 

from django.shortcuts import get_object_or_404, render

from .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 

还有一个函数get_list_or_404(),工作原理和get_object_or_404()是一样的,使用filter()而不是get(),如果列表为空的话抛出404异常。

使用模板系统

回到我们投票应用的detail()视图。给一个question的context变量,编辑polls/detail.html模板: 

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

模板系统使用点查找的语法去访问属性变量,例如{{ question.question_text }},

首先Django在一个字典查找question对象,如果属性查找失败,它将尝试列表索引查找。

方法调用发生在{% for %}循环:question.choice_set.all 将被解析为python代码question.choice_set.all(),它返回一个Choice迭代对象并且试用于{% for %}模板标签。

移除在模板中的硬编码网址

请记住,当我们在polls/index.html 模板编写一个链接时,这个链接是这样写的: 

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

这种硬编码,紧密耦合的问题是,当我们的url需要变化时,将非常麻烦需要修改大量的模板。不过,既然你的polls.urls 中url()方法定义的有名称,那么你可以使用{% url %}模板标签:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

这种方式是通过查找在polls.urls模块中定义的url来工作的,你可以准确的看到名为“detail”url定义如下: 

...
# 做为{% url %}模板标签的“name”值
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

如果你想要修改投票的详细视图的url为其他的形式,也许像polls/specifics/12 ,你需要修改它在polls/urls.py 

...
# 增加一个单词 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Url名称的命名空间

本教程的项目仅仅有一个应用,polls。在实际项目中,可能有五个,十个或者更多应用。Django如何区分他们之间的Url名称?例如polls应用有一个detail视图,并且可能同一个项目有一个应用是blog也有一个detail视图。当使用{% url %} 模板标签时Django如何知道url是哪个应用视图的?

答案是在你的url配置中增加命名空间。在polls/urls.py文件中,增加一个app_name 去设置应用的命名空间 

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

现在修改你的polls/index.html 模板,从: 

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

改成: 

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

 

posted @ 2016-09-05 18:41  曹世青  阅读(2209)  评论(0编辑  收藏  举报