第三节:视图(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)
注意我们所有的视图都做到这一点的话,我们不在需要导入loader和HttpResponse。
这个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'), ...
本教程的项目仅仅有一个应用,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>