Writing your first Django app,part3
概貌
现在让我们聚焦views,一个view是你Django应用的一种网页,它能生成若干特定的功能可以有一个特定的模版。例如,一个blog应用,你可能需要以下的views:
- Blog主页 - 显示最新的几篇文章
- 进入页面’细节‘ - 每篇文章有一个永久性的页面
- 以年为单位的‘达成’页面 - 给定年份后显示所有月份的文章
- 以日为单位的‘达成’页面 - 给定日子后显示该日的所有文章
- 评论功能 - 给定文章设立的处理评论功能
在我们的poll应用里,我们需要以下四个views:
- Question的’index’索引页面 - 显示最近的若干questions
- Question的’detail’细节页面 - 显示一个question的文本,不显示结果但有一个投票的表单
- Question的’results’结果页面 - 显示特定question的结果
- 投票操作 - 处理特定question特定choice的投票
在Django,web页面和其他内容都是通过views发布。每个view都表现为一个简单的python函数(在一些以类为基础的views里,表现为类方法)。Django收到URL的请求后,选择一个view(注意是域名之后的URL,也就是相对URL)。
为了把URL导向view,Django使用被称为’URLconfs’的功能,一个URLconf映射很多URL patterns到views。
这个教程只涉及到极处的URLconfs,更多信息在django.urls
写更多的views
现在我们再写几个views,这几个和之前有些轻微的差别,它们都接受一个参数:
polls/views.py:
def detail(request, question_id):
return HttpResponse("You're looking at question {}.".format(question_id))
def results(request,question_id):
response = "You're loking at the results of question {}."
return HttpResponse(rensponse.format(question_id))
def vote(request,question_id):
return HttpResponse("You're voting on question {}. ".format(question_id))
然后把这些新的views加入polls.urls:
polls/urls.py:
from django.conf.urls import url
from . import views
urlpatterns = [
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# ex: /polls/5/results/
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
url(r'^(?P<question_id>[0-9]+)/vote/$, views.vote, name='vote'),
]
打开浏览器,看看’/polls/34/’,已经运行了detail()方法并且显示了你提供的ID,同样的也可以试试’/polls/34/results/’,’polls/34/vote/’。
当某人对你的网站的莫以页面发出请求-说:‘/polls/34/’,Django将会加载mysite.urls**python模块因为设置中这是ROOT_URLCONF。它发现了变量名叫**urlpatterns并且通过了正则表达式匹配,然后找到了匹配至‘^polls/’的’polls.urls’的URLconf,之后找到了匹配r’^(?P(0-9)+)/$’的view函数detail(),返回一个请求就像这样:
detail(request=<HttpRequest object>, question_id='34')
question_id=’34’部分来自(?P[0-9]+)。在pattern里使用括号捕获文本匹配,并将它当作一个参数传递给view函数;?P定义里名字并将之用来识别匹配的pattern。
写一些真正的views
每个view都负责做一两件事:返回包含请求页面内容的HttpResponse对象,或者引发一个exception,就像Http404。
你可以阅读数据库的记录,或者不。你可以使用Django自带的模版系统 - 或者第三方的Python模版系统 - 或者不。你可以生成PDF文件,输出XML,创建ZIP文件,你可以使用任何Python库做你想做的任何事。
所有的Django最终都是一个HttpResponse,或者一个exception。
因为方便的原因,我们使用Django的数据库API。我们插入一个新的index() view,它可以根据pub_date显示最新的5个poll question,通过逗号分隔。
polls/view.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的模版系统来使设计分离。
第一步,在你的polls文件夹里,创建一个名为templates的文件夹,Django会在那里找到模版。
你的项目TEMPLATES设置描述了Django是怎么载入和渲染模版的。默认情况下,DjangoTemplates的APP_DIRS设置为True.出于惯例,DjangoTemplates会在每一个INSTALLED_APP里搜寻‘templates’子目录。
在你刚创建的templates文件夹里,创建另一个polls文件夹,然后在里面创建一个文件叫做index.html。换句话说,你的模版应该在polls/templates/polls/index.html。
模版命名空间
你可以自己把文件放在polls/templates,但是这样做并不好。因为如果你有一个模版和其它应用的模版重名,Django没法分辨它们。我们需要向Django指出哪个是对的,那么使用把应用名称放在它的模版文件夹里做子目录名称是一个好办法。
在模版里写入以下代码:
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 %}
然后更新index view,让它使用模版
polls/views.py:
from django.http import HttpResponse
from django.template import loader
from .model 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是一个类字典,可以把python对象映射成变量名传给模版。
一个捷径:render()
常用的语法就是读取一个模版,传入一个上下文,并且返回一个包含渲染过的模版的HttpResponse对象。Django提供了一个更简洁的方法:
polls/views.py:
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)
现在我们不需要在import loader 和 HttpResponse。render()函数接受request对象作为它的第一个参数,一个模版名字作为它的第二个参数,第三个可选参数是字典形式。它返回包含经过给定context渲染的模版的HttpResponse对象。
–
引发 404error
现在,我们来处理detail view - 这个页面显示question文本:
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 })
这里有个新概念:当请求的question_id不存在,则会引发Http404错误。
一个捷径:get_object_or_404()
使用get(),raise Http404()是一个很常用的用法,Django仍然提供了更简洁的方法来代替:
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(reqiest, 'polls/detail.html', {'question':question}
这个get_object_or_404()函数接受一个Django model作为它的第一个参数,然后接受任意数量的关键词参数,把这些关键词参数通过get()方法传递给model,如果object不存在就引发Http404。
哲学
为什么要用get_object_or_404()
因为为了耦合model层和view层。Django最初的设计目标之一就是维持松散的耦合。一些受控制的耦合介绍在django.shortcuts模块
还有一个get_list_or_404()函数,它和get_object_or_404()类似 – 除了用filter()代替get(),如果列表为空就会引发Http404错误。
–
使用模版系统
回到我们应用的detail() view。传递了一个context变量question,polls/detail.html模版应该看起来像这样:
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>
模版系统使用点查询语法来访问变量属性,在这个例子里 {{ question.question_text }},试图进行一次属性查询,如果失败,则会试着进行一次列表索引式查询。更多关于templates请看template guide.
–
移去模版里的硬编码
我们在polls/index.html模版里写入链接时,链接部分是硬编码,如下:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
问题在于,使用硬编码,之后如果要更改URLs会很麻烦。然而,当你在polls.url模块的url定义了name参数后,你可以使用{% url %}模版标签去代替依赖于编写URL路径的方法。
<li><a href=“{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
–
URL的命名空间
这个教学的项目仅仅只有一个app,在真实的Django项目里,可有有5,10,20或则更多。Django怎么区分它们的URL名字呢?举个例子,polls应用有一个detail视图,但是也许同一个项目的其它app也有detail视图,那么Django在使用{% url %}标签时要怎么区分它们呢?
答案是为你的URLconf增加命名空间,在polls/urls.py文件里,增加一个变量,叫做app_name,它的值设为命名空间,惯例是设置成应用名称。
polls/urls.py:
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
#...
]
然后更改你的polls/index.html模版:
polls/templates/polls/index.html:
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>