四、视图和模板
1、使用视图
在我们的投票应用程序中,需要有四个视图,分别是:
- 问题“索引”(index)页面 - 显示最新的几个问题。
- 问题“详细”(detail)页面 - 显示一个问题文本,没有结果,但有一个表格投票。
- 问题“结果”(results)页面 - 显示特定问题的结果。
- 投票行动(vote) - 处理针对特定问题的特定选择的投票。
在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)
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)
接着修改URLConf,对polls/urls.py的代码进行修改:
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/1/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/1/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/1/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
在polls/views.py中,我们直接生成一个字符串,作为http回复,返回给客户端。这一过程中使用了 django.http.HttpResponse()。
在这样的一种回复生成过程中,我们实际上将数据和视图格式混合了到上面的字符串中。看似方便,却为我们的管理带来困难。想像一个成熟的网站,其显示格式会有许多重复的地方。如果可以把数据和视图格式分离,就可以重复使用同一视图格式了。
Django中自带的模板系统,可以将视图格式分离出来,作为模板使用。这样,不但视图可以容易修改,程序也会显得美观大方。
2、使用模板
创建一个独立的index.html文件作为模板,路径为polls/templates/polls/index.html,文件夹templates和polls自己创建,内容如下:
{% 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,增加一个新的对象,用于向模板提交数据:
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))
3、使用shortcut:render()
Django提供了一个快捷方式来让我们加载模板,这里就需要使用render来替换之前使用的HttpResponse,这也是我们习惯性的使用方法。
重写polls/views.py中的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)
可以看到,使用render就不需要使用loader和HttpResponse,代码简洁了许多。render()函数将请求对象作为第一个参数,将模板名称作为第二个参数,并将字典作为其可选的第三个参数,这就是我们的数据。
4、404错误的处理
在从数据库获取数据时,存在数据不存在的情况,这时就需要对异常进行处理。在Python中我们的处理方式是使用类似try…except…的异常处理机制,在polls/views.py中,我们重写detail()方法,代码如下:
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})
但实质上我们并不这样做,Django也给我们提供了一个快捷方式,重写polls/views.py中的detail()函数,代码如下:
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})
detail.html里代码如下:
{{ question }}
get_object_or_404()函数将Django模型作为第一个参数,并将任意数量的关键字参数传递给模型管理器的get()函数。如果对象不存在,则引发Http404。
同样,也有一个get_list_or_404()函数,用于filter()而不是get()。如果列表为空,则引发Http404。
5、循环和选择
在polls/templates/polls/index.html中,可以看到,循环语句在{% for %}调用,通过{% endfor %}结束循环,选择语句在{% if %}调用,在{% endif %}结束。修改polls/templates/polls/detail.html里的代码,如下:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
PS.一般的变量之类的用 {{ }}(变量);功能类的,比如循环,条件判断是用 {% %}(标签)。
6、使用灵活的URL
在polls/templates/polls/index.html中,我们将链接硬编码为:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
这样,URLConfig里面的URL改变了,这里链接也要改变。Django通过在模板使用URL name去URLConfig查找指定的URL定义,而不是直接“写死网址”。在polls/urls.py中可以看到:
path('<int:question_id>/', views.detail, name='detail'),
name可以用于在templates,models,views……中得到对应的网址,相当于“给网址取了个名字”,只要这个名字不变,网址变了也能通过名字获取到。在模板中通过{% url %}调用,改写polls/index.html,代码如下:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
7、使用extends和include
网站模板的设计,一般的,我们做网站有一些通用的部分,比如导航、底部、访问统计代码等。可以写一个base.html来包含这些通用文件(include),然后用继承的方式来实现模板的复用。
先建立一个基础模板polls/templates/polls/base.html,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
{% include 'polls/nav.html' %}
{% block content %}
<div>这里是默认内容,所有继承自这个模板的,如果不覆盖就显示这里的默认内容。</div>
{% endblock %}
{% include 'polls/bottom.html' %}
</body>
</html>
polls/templates/polls/nav.html代码如下:
<p>这是导航栏</p>
polls/templates/polls/bottom.html代码如下:
<p>这是底部</p>
如果需要,写足够多的block以便继承的模板可以重写该部分,include是包含其它文件的内容,就是把一些网页共用的部分拿出来,重复利用,改动的时候也方便一些,还可以把广告代码放在一个单独的html中,改动也方便一些,在用到的地方include进去。其它的页面继承自base.html就好了,继承后的模板也可以在block块中include其它的模板文件。
比如我们的首页home.html,继承(extends)原来的base.html,可以简单这样写,重写部分代码(默认值的那一部分不用改),这样,我们可以使用base.html的主体,只替换掉特定的部分。polls/templates/polls/home.html代码如下:
{% extends "polls/base.html" %}
{% block title %}欢迎光临首页{% endblock %}
{% block content %}
{% include 'polls/ad.html' %}
这里是首页,欢迎光临
{% endblock %}
polls/templates/polls/ad.html代码如下:
<p>这是广告</p>
最后,自己试着定义视图函数和配置URL显示,代码如下:
#polls/views.py
def home(request):
return render(request, 'polls/home.html')
#polls/urls.py
path('home/', views.home, name = 'home')
PS.模板一般放在app下的templates中,Django会自动去这个文件夹中找。但假如我们每个app的templates中都有一个index.html,当我们在views.py中使用的时候,直接写一个render(request, ‘index.html’),Django能不能找到当前app的templates文件夹中的index.html文件夹呢?(答案是不一定能,有可能找错)。
Django 模板查找机制:Django查找模板的过程是在每个app的templates文件夹中找(而不只是当前 app中的代码只在当前的app的templates 文件夹中找)。各个app的templates形成一个文件夹列表,Django 遍历这个列表,一个个文件夹进行查找,当在某一个文件夹找到的时候就停止,所有的都遍历完了还找不到指定的模板的时候就是Template Not Found(过程类似于Python找包)。这样设计有利当然也有弊,有利是的地方是一个app可以用另一个app的模板文件,弊是有可能会找错了。所以我们使用的时候在 templates 中建立一个app同名的文件夹,这样就好了。
这就需要把每个app中的templates文件夹中再建一个app的名称,仅和该app相关的模板放在app/templates/app/目录下面。
参考资料