视图和模板

1 概况

我们的投票应用需要下列几个视图:

  1. 问题索引页:展示最近几个投票的问题。
  2. 问题详情页:展示某个投票的问题和不带结果的选项列表。
  3. 问题结果页:展示某个投票的结果。
  4. 投票处理器:响应用户投票的操作。

2 编写更多视图

与官方教程的顺序不同,我们先填写 urls.py 。

# polls/urls.py
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'),
]

用户访问的地址与 urlpatterns 里的条目成功匹配后,地址中的关键值将被捕获并赋给变量 question_id ,作为传入下列视图函数的参数。

# 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)

3 写一个真正有用的视图

每个视图必须要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse 对象,或者抛出一个异常比如 Http404 。至于你还想干些什么,随便。

# 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)

页面设计应当与视图代码分离。比如将问题索引页的模板文件放在 polls/templates/polls/index.html虽然模板文件可以直接放在 polls/templates 文件夹中,但是这样不好。因为 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
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))

此时访问 http://localhost:8000/polls/ ,将会看到一个无序列表。

新视图函数做了三件事:载入模板、填充上下文、返回由它生成的 HttpResponse 对象。这一固定流程也可以通过快捷函数 render() 完成。

# 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)

4 抛出 404 错误

将问题详情页的模板文件放在 polls/templates/polls/detail.html ,相关内容修改如下。

{{ 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})

如果 ID 所对应的问题不存在,抛出 Http404 异常。同样它也有快捷函数 get_object_or_404()get_list_or_404()get_object_or_404() 类似。

# 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)

5 使用模板系统

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>

6 去除模板中的硬编码 URL

polls/urls.py 中的 URL name 终于派上用场,结合标签 {% url %} 可以完美替代硬编码 URL 。 注意参数。

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

当你想修改 URL 时,不必修改模板,只要修改 polls/urls.py 就行。

7 为 URL 名称添加命名空间

from django.urls import path
from . import views

app_name = 'polls' # NEW!
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'),
]

上文的 {% url %} 也得指定命名空间。

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>