第一个Django应用[part3]
django中web页面和其它内容都是通过视图(view)来传递的。每一个视图都是一个python函数(或方法),django通过检查url的请选择视图。
一个URL的一般形式是:
/newsarchive/<year>/<month>/
从url到视图的转换,django使用了‘URLconfs’。它是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)
然后在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()方法 。具体过程是这样的:
当请求该页面时,django会加载mysite.urls模块,因为它被指向ROOT_URLCONF设置。它会查找并按序遍历urlpatterns这个变量。在找到‘polls/’后,从字符串中去除掉匹配到的“polls”字符串,并将剩下的字符串“34/”发送到‘polls.urls’中进行下一步处理。在‘polls.urls’中匹配到’<int:question_id>/’,结果调用detail()视图,就如同:
detail(request=<HttpRequest object>, question_id=34)
question_id=34这部分来自<int:question_id>。使用了尖括号包含了URL的一部分,并把它做为关键字参数发送到视图。question_id定义了会被匹配到部分的名字,int是个转换器,它判断什么模式应该匹配这部分URL,冒号(:)分开转换器和模式名。
让视图有一些实际功能
每个视图都会付责做一两件事:要么返回一个HttpResponse对象,其中包含了请求页请求的内容,或者返回一个“例外”如Http404。
重写polls/views.py中的index()方法:
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会查找应用下面的”templates“子目录。
在刚建立的templates目录里创建一个名为polls的目录,并且在里面建立一个名为index.html的文件。也就是说,模版路径应该是polls/templates/polls/index.html,这时可以在项目中用polls/index.html的方式调用这个模版。
PS:这样可以防止同名模版的问题,因为polls指明了路径,因为所有的模版共享一个名字空间,如果直接将index.html放在templates中的话,在其它应用也有index.html模版时,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 %}
现在更新一下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对象。
PS:模版中可以解析出context的内容,在模版中使用字典的内容使用{{}}来引用,context的key。在上面的例子中使用{{latest_question_list}}相当于python代码的context[‘latest_question_list’]。
快捷方式:render()
它是个非常常用的加载模版的方法,填充内容并返回一个携带相应模版结果的HttpResponse对象。使用render()重写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头文件了。(如果还想使用静态方法的话,那就要继续使用loader和HttpResponse)。
render()函数的第一个参数是request对象,第二个参数是模版名,第三个参数是可选的字典,用来传递python变量。
抛出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})
在question_id不存在时,抛出404错误
快捷方式:get_object_or_404()
这是个常用于get()和抛出404错误的快捷方式。用此方法改写一个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()函数。
使用模版系统
简单写一下detail()视图的模版:
<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对象上进行字典查找。如果失败了,它会尝试属性查找——在上面的例字中使用了属性查找,如果属性查找失败了,它将尝试列表索引查找。(PS:这意味着,如果你要访问一个对象的属性,可以使用点号来访问它。如果想要访问一个对象的字典键,也可以使用点号和字典键名来访问它。如果要访问一个对象的列表元素,可以使用点号和列表引来访问它。)
方法调用发生在{% for %}循环中:question.choice_set.all被解释为python代码question.choice_set.all(),它返回一个可迭代的Choice对象并且可以用于{% for %}标签。
移除模版中的硬链接
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
模版中出现上面的代码就是硬链接。在项目中如果想改变URL,上面的硬链接会产生很大的工作量。但是因为我们在polls.urls模块中path()函数使用name参数,会改变这一切。可以通过使用{% url %}模版标签来做到这点,更新后的代码如下:
<li><a href="{% url 'detail' question.id %}">{{question.question_text}}</a></li>
这时polls.urls中path()的第一个参数可以改成任何想要改变的模式,比如像我改成了以k开头的链接,模版中的链接也会跟着改变,而不用因为修改了url中的模式,而改变模版了。
path("k/<int:question_id>/", views.detail,name='detail'),
URL名字空间
给url命名的方法可以让模版调用更方便,但是如果有多个应用都有detail怎么办?那就需要在URLconf中配置名字空间——app_name:
在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"),
]
模版中做如下改变:
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>