Django快速入门 03
https://docs.djangoproject.com/zh-hans/3.0/intro/tutorial03/
编写你的第一个 Django 应用,第 3 部分
在我们的投票应用中,我们需要下列几个视图:
问题索引页——展示最近的几个投票问题。
问题详情页——展示某个投票的问题和不带结果的选项列表。
问题结果页——展示某个投票的结果。
投票处理器——用于响应用户为某个问题的特定选项投票的操作。
Django使用了 URLconf(路由配置器)来将视图和URL连接起来,将 URL 模式映射到视图
编写更多视图
在之前我们编写了 view.py 中的 index 视图,并使之返回文字。下面我们按照上面的几个视图来进行添加
view.py
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("hello !")
def detail(request, question_id):
return HttpResponse("question %s. " % question_id)
def results(request, question_id):
return HttpResponse("results of %s. " % question_id)
def vote(request, question_id):
return HttpResponse("voting of %s. " % question_id)
其中视图函数传入了 question_id 参数,以便在网页中返回 问题序号
接着在 polls/urls.py 添加
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('<int:q_id>/', views.detail, name='detail'),
path('<int:q_id>/results/', views.results, name='results'),
path('<int:q_id>/vote/', views.vote, name='vote'),
]
因为在外层的 urls.py 中已经include了 polls 所以不必重复添加(见第一部分)
当然,如果访问http://127.0.0.1:8000/polls/20/我们在 urls.py 中写的代码运行出来是错误的,报错 detail() got an unexpected keyword argument 'q_id'
我们来看一下URL与view之间的工作机制:
URL与view
我们访问了 polls/20/ 因为 settings.py 文件中设置了
ROOT_URLCONF = 'my_try.urls'
所以先将 polls/20/ 交给 my_try.urls.py ,其中 polls 部分得到匹配,剪切到,剩下的 20/ 转送到 polls.urls.py
在这里剩余文本匹配了(20符合int) '<int:q_id>/'
用尖括号捕获了20,便以 q_id = 20 形式调用 views.detail() ,但是 detail 并没有 q_id 参数,所以报错
所以我们看到,URL的参数名(尖括号) 应与 view函数的参数名保持一致
所以进行改进,如下:
from django.urls import path
from . import views
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'),
]
写一个真正有用的视图
每个视图必须要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse 对象,或者抛出一个异常,比如 Http404
我们在 index()
函数里插入了一些新内容,让它能展示数据库里以发布日期排序的最近 5 个投票问题
from django.http import HttpResponse
from .models import Question
# Create your views here.
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)
这里注意 Question.objects 才能调用表格中的内容
同时 q.question_text 即question_text属性显示其文字内容
这里有个问题:页面的设计写死在视图函数的代码里的。
如果你想改变页面的样子,你需要编辑 Python 代码。
所以让我们使用 Django 的模板系统,
只要创建一个视图,就可以将页面的设计从代码中分离出来。
首先,在你的 polls 目录里创建一个 templates 目录。
Django 将会在这个目录里查找模板文件。
在 templates 中建立 polls 文件夹,在其中放置模板
编辑 polls/templates/polls/index.html
{% if latest_questions %}
<ul>
{% for question in latest_questions %}
<li>
<a href="/polls/{{ question.pk }}/">{{ question.question_text }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available. </p>
{% endif %}
更改 views.py
from django.http import HttpResponse
from .models import Question
from django.template import loader
# Create your views here.
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_questions': latest_question_list,
}
return HttpResponse(template.render(context))
上述代码,当我们调用 index() 时,通过 loader.get_template 调用了 'polls/index.html' 这个HTML文件,并通过html.render 带入上下文(context)。上下文是一个字典,键为HTML中的变量名,值为当前可调用的变量
在HTML文件中,当存在question时,遍历每一个question,并设置超链接,指向 polls/{{该问题id}}
一个快捷函数:render()
「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。
于是 Django 提供了一个快捷函数,我们用它来重写 index() 视图:
from django.shortcuts import render
from django.http import HttpResponse
from .models import Question
from django.template import loader
# Create your views here.
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {
'latest_questions': latest_question_list,
}
return render(request, 'polls/index.html', context)
render()函数的第一个参数是请求对象,第二个参数是模板名,第三个可选参数是字典。它返回使用给定上下文呈现的给定模板的HttpResponse对象。
抛出 404 错误
我们来处理投票详情视图——它会显示指定投票的问题标题
detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
views.py
from django.shortcuts import render
from django.http import HttpResponse, Http404
from .models import Question
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except:
raise Http404("not exist! ")
return render(request, 'polls/detail.html', {'question': question})
向模板传递了上下文 question
在HTML中,显示question 的question_text的标题,并且在下面显示 choice的 choice_text
其中的 question.choice_set.all 的 choice_set 是一种Django方法 ,返回相关的objects,具体官方文档可参考如下链接
https://docs.djangoproject.com/en/dev/topics/db/queries/#related-objects
在 view 中,我们返回该 ID 下的question,如果无法返回,我们返回 404。同样使用 render
一个快捷函数:get_object_or_404()
尝试用 get() 函数获取一个对象,如果不存在就抛出 Http404 错误也是一个普遍的流程。
Django 也提供了一个快捷函数
view.py 更改 detail 函数
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, Http404
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() 而不是自己捕获 ObjectDoesNotExist 异常呢?
还有,为什么模型 API 不直接抛出 ObjectDoesNotExist 而是抛出 Http404 呢?
因为这样做会增加模型层和视图层的耦合性。
指导 Django 设计的最重要的思想之一就是要保证松散耦合。
一些受控的耦合将会被包含在 django.shortcuts 模块中。
去除模板中的硬编码 URL
还记得吗,我们在 polls/index.html
里编写投票链接时,链接是硬编码的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
问题在于,硬编码和强耦合的链接,对于一个包含很多应用的项目来说,修改起来是十分困难的。然而,因为你在 polls.urls
的 url()
函数中通过 name 参数为 URL 定义了名字,你可以使用 {% url %}
标签代替它:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
我们之前在 urls 中为URL定义了名字
所以该 HTML编码会在 URL中寻找 'detail' 的一项,并将 id 赋给 question_id
这样,我们想更改URL时,只需要在 urls.py 文件里更改即可
为 URL 名称添加命名空间
教程项目只有一个应用,polls 。
在一个真实的 Django 项目中,可能会有五个,十个,二十个,甚至更多应用。
Django 如何分辨重名的 URL 呢?
举个例子,polls 应用有 detail 视图,可能另一个博客应用也有同名的视图。
Django 如何知道 {% url %} 标签到底对应哪一个应用的 URL 呢?
答案是:在根 URLconf 中添加命名空间。
在 polls/urls.py 文件中稍作修改,加上 app_name 设置命名空间:
在 urls.py 修改为:
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'),
]
在 index.html 中修改为:
<a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a>
指向了详细的 polls 的 detail