这一章,我们将重点关注公共接口的--视图的创建。
概览
视图是你的Django应用里的一种页面类型,用来指向一个特定的函数,并且有指定的模板格式。例如,在一个blog应用里。你可能会有下面的视图:
- 博客主页 - 显示最新的条目
- “详细”页面条目 - 固定链接页面的一个条目
- 以年为归档的页面 - 显示给出年份的所有月份条目
- 以月份归档的页面 - 显示给出月份的所有天数条目
- 以天数归档的页面 - 显示给出的天数当天的所有条目
- 评论动作 - 用于处理给出条目的评论
在我们的投票应用里,我们会有下面4个视图:
- Question的索引页 - 显示最新的几个问题
- Question的详细页面 - 显示这个问题的详细内容
- Question的结果页面 - 显示特定问题的结果
- 投票动作 - 处理特定Question特定Choice的投票动作
在Django里,web页面和其他内容通过视图来传递。每个视图由一个简单的Python函数来表示(或者方法,在以类为基础的视图中)。Django会检查请求的URL(准确的说,是域名后的URL部分)来选择指定的视图.
现在在网络上的时候,你可能会遇到这样的链接“ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”
,但是你很快会知道,在Django中会有更优雅的URL模式来处理这样的URL。
一个URL模式只是一个URL的一般形式,例如:
/newsarchive/<year>/<month>/
为了根据一个URL获取一个视图,Django用了所谓的“URLconf”,URLconf将一个URL模式映射到一个视图。
写更多视图
我们来添加几个视图到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)
通过后面的path()调用将这些新的视图插入到polls.urls模块里:
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
看一下你的浏览器,在“/polls/34/”链接里,会运行detail()方法并且显示你在URL里提供的ID。也尝试一下“/polls/34/results/”和“/polls/34/vote/”,这些会显示占位符结果和投票页面。
当用户从你的网站请求一个页面的时候,说:“/polls/”,Django会加载mysite.urlsPython模块,因为它是由ROOT_URLCONF设置指向的。它会查找一个叫做urlpatterns的变量,然后按顺序遍历所有的模式。找到匹配'polls/'
的模式以后,它会截断文本"polls/"
,然后把剩下的文本"34/"
发送给polls.urls
这这个URLconf进行进一步处理。这里它会匹配'<int:question_id>/',会生成像下面这样一个detail()视图的调用:
detail(request=<HttpRequest object>, question_id=34)
question_id=34部分来自于<int:question_id>,使用尖括号捕获部分URL,并将其作为关键字参数发送给视图函数,这个字符串的:question_id>部分定义了将用于标识匹配模式的名称,而<int:部分则是要给转换器,用于确定哪些模式应匹配这部分URL路径。
不需要添加URL中多余的东西,例如.html,除非你希望这样做。例如像下面的例子:
path('polls/latest.html', views.index),
但是不要这样做,这样很愚蠢。
写出执行实际工作的视图
每一个视图负责做一到两个事情:返回一个HttpResponse对象,包含请求的页面的内容。或者抛出一个异常,例如Http404。其余的由你决定。
你的视图可以从数据库中读取记录,或者不可以。你可以使用一个模板系统,例如Django的—或者一个三方的Python模板系统,也可以不用。它可以生产一个PDF文件,输出XML,创建一个ZIP文件,或者你想要的任何东西,可以使用任何你想用的Python库。
所有的Django都想要返回一个HttResponse或者一个异常。
因为这样非常方便,让我们使用Django自带的数据库API,我们会在第二篇教程中讲到这个。下面是一个新的index()视图,它会显示系统里最近的5个投票问题,通过逗号分隔。根据发布日期排序:
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。因为上面讲到的app_directories模板加载器是这样工作的,你可以简单的通过polls/index.html就可以引用你刚才创建的模板。
模板名称空间
现在我们也许可以直接将模板放在polls/templates目录下(而不是在这个目录下新建一个polls子目录),但是它可能是一个坏主意。Django会选择名称匹配的第一个模板,如果在另外一个应用里有一个名称相同的模板,Django是没有能力去分辨这两个应用中模板之间有啥区别。我们需要将Django指引到正确的位置,最简单的方法就是通过名称空间来命名模板。这种方法就是将所有模板都放在以应用名称命名的子目录里去。
把下面的代码放到模板里去
{% 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模板,并将它传递给上下文管理器,上下文管理器是一个字典,用来将模板里的变量映射到Python对象。
在你的浏览器里输入地址"http://localhost:8000/polls"
,然后打开页面,你就会看到一个括起来的列表,包含上一节中我们创建的问题"What's Up"。链接会指向问题的详细信息页面。
快捷方式:render()
加载模板是一个非常普遍的习惯,通过填充上下文管理器,并返回一个带有渲染后的模板作为结果的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
(如果你仍然保留detail、results和vote的根方法,那么你可能会像保留HttpResponse
)
render()
方法接收请求对象作为它的第一个参数,一个模板名作为它的第二个参数和一个字段作为它的第三个可选参数。返回一个由给出的上下文管理器渲染后的模板的HttpResponse
对象。
抛出一个404错误
现在让我们来解决问题详细信息页面视图——用来显示给定投票的问题内容的页面。下面是视图:
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})
新的想法是:如果请求的问题ID不存在,则视图抛出一个404异常。
我们稍后会讨论你能放什么东西到polls/detail.html
模板里。如果你想快速看到上面的示例的效果,可以在polls/templates/polls/details.html
文件里使用下面的内容:
{{ question }}
可以让你很快就能见到效果。
快捷方式:get_object_or_404()
使用get()
方法,以及对象不存在时抛出Http404()
是一种非常普遍的习惯。Django提供一种快捷的方式。下面是重写后的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})
get_object_or_404()
方法带一个Django模型作为他的第一个参数,一个任意数量的关键字参数。它会把关键字参数传递给模型管理器的get()
方法。如果对象不曾存在时,会抛出Http404
异常。
思想
为什么我们使用帮助函数get_object_or_404()
,而不是在更高级别自动捕获ObjectDoesNotExist
异常,或者让模型的API抛出Http404
来代替ObjectDoesNotExist
呢?
因为这会将模型层耦合到视图层,Django最重要的目标就是保持松耦合,所有在django.shortcuts
模块里引入了一些控制耦合的模块。
也有一个get_list_or_404()
方法,和get_object_or_404()
工作方式相同——除了使用filter()
方法替换get()
方法以外。如果列表是空的话,会抛出Http404
异常。
使用模板系统
回到我们投票应用的detail()
视图,给出上下文管理器变量question
,下面是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
中进行字典查找。如果这种情况下查找失败,它会尝试属性查找。如果属性查找也失败了,Django会尝试列表索引查找。
在{% for %}循环中会出现方法调用:
qustion.choice_set.all
被解释为Python代码question.choice_set.all()
,会返回一个Choice
对象的迭代器,适用于{% for %}标签。
移除模板中硬编码的URL
记住,当我们在polls/index.html
模板中写一个问题的链接时,链接通常都会像下面这样硬编码:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
这种硬编码、紧密耦合的方法存在的问题是:在一个有大量模板的项目中,修改URL是非常困难的。然而,即使你在polls.urls
模块里的path()
方法参数里定义了名称参数,你也可以使用{% url %}
模板标签来移除你的url配置里定义的特定的URL路径。
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
这种方式是通过在polls.urls
模块里查找指定的URL定义来正常工作。你可以看到下面名称是detail
的URL详细定义:
...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...
如果你想将应用的polls的details视图修改成其他样式,例如看起来像polls/specifics/12/来替代上面的模板中看到的样子,你可以在polls/urls.py里修改:
...
# 添加 'specifics' 以后
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
名称空间式的URL名称
这篇教程的项目只有一个应用,就是polls
,在实际的Django项目中,可能会有5个、10个、20个或者更多的应用。那么Django是怎么在这些应用中分辨不同的URL呢?例如,polls
应用有一个details
视图,然而在同一个项目里可能会有一个应叫做blog。怎么让Django在使用{% url %}
标签的时候知道为哪个应用创建视图?
正确答案是添加名称空间到你的URLconf,在polls/urls.py
文件里,直接添加一个app_name
来设置应用的名称空间:
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/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>
当你对视图非常熟悉的时候,就可以进入第4节,开始学习更多关于处理和生成视图的内容了。
网友评论