Skip to main content

编写您的第一个Django应用程序,第3部分

本教程从 教程2 离开时开始。我们将继续使用Web-poll应用程序,并将专注于创建公共界面 - “视图”。

概述

视图是Django应用程序中通常用于特定功能并具有特定模板的Web页面的“类型”。例如,在博客应用程序中,您可能有以下视图:

  • 博客主页 - 显示最近的几个条目。

  • 条目“详细信息”页面 - 单个条目的永久链接页面。

  • 基于年份的归档页面 - 显示给定年份中所有条目的月份。

  • 基于月份的归档页面 - 显示指定月份中所有条目的天数。

  • 基于日的归档页面 - 显示给定日期的所有条目。

  • 注释操作 - 处理将注释发布到给定条目。

在我们的投票应用程序中,我们将有以下四个视图:

  • 问题“索引”页 - 显示最近的几个问题。

  • 问题“详细信息”页面 - 显示问题文本,没有结果,但带有要投票的表格。

  • 问题“结果”页 - 显示特定问题的结果。

  • 投票行动 - 处理特定问题中的特定选择的投票。

在Django中,网页和其他内容是通过视图传递的。每个视图由一个简单的Python函数(或基于类的视图的方法)表示。 Django将通过检查请求的URL(即域名后的URL部分)来选择一个视图。

现在在你的时间在网上,你可能会遇到像“ME2/Sites/dirmod.asp?sid =&type = gen&mod = Core + Pages&gid = A6CD4967199A42D9B65B1B”这样的美女。你会很高兴知道,Django允许我们更优雅的 网址格式

网址格式只是网址的一般形式,例如:/newsarchive/<year>/<month>/

要从URL到视图,Django使用所谓的“URLconfs”。 URLconf将URL模式(描述为正则表达式)映射到视图。

本教程提供了使用URLconfs的基本指导,您可以参考 django.urls 了解更多信息。

写更多的意见

现在让我们向 polls/views.py 添加几个视图。这些视图略有不同,因为它们接受一个参数:

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)

通过添加以下 url() 调用将这些新视图连接到 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() 方法并显示您在URL中提供的任何ID。尝试“/polls/34/results/”和“/polls/34/vote/” - 这些将显示占位符结果和投票页面。

当有人向你的网站请求一个页面 - “/polls/34/”时,Django将加载 mysite.urls Python模块,因为它是由 ROOT_URLCONF 设置指向的。它找到名为 urlpatterns 的变量,并按顺序遍历正则表达式。在 '^polls/' 找到匹配之后,它剥离匹配的文本("polls/"),并将剩余的文本("34/")发送到’polls.urls’URLconf以供进一步处理。在那里它匹配 r'^(?P<question_id>[0-9]+)/$',导致对 detail() 视图的调用像这样:

detail(request=<HttpRequest object>, question_id='34')

question_id='34' 部分来自 (?P<question_id>[0-9]+)。使用模式周围的括号“捕获”该模式匹配的文本,并将其作为参数发送到视图函数; ?P<question_id> 定义将用于标识匹配模式的名称;并且 [0-9]+ 是匹配数字序列(即,数字)的正则表达式。

因为URL模式是正则表达式,所以对它们可以做什么没有任何限制。并且没有必要添加URL cruft如 .html - 除非你想,在这种情况下你可以做这样的事情:

url(r'^polls/latest\.html$', views.index),

但是,不要这样做。这是愚蠢的。

编写实际做某事的视图

每个视图负责执行以下两项操作之一:返回包含所请求页面的内容的 HttpResponse 对象,或提出异常(如 Http404)。其余由你决定。

您的视图可以从数据库读取记录,也可以不读取。它可以使用模板系统,如Django的 - 或第三方Python模板系统 - 或不。它可以生成PDF文件,输出XML,即时创建ZIP文件,任何你想要的,使用任何你想要的Python库。

所有Django想要的是 HttpResponse。或者异常。

因为它很方便,让我们使用Django自己的数据库API,我们在 教程2 中介绍。这里有一个新的 index() 视图,根据发布日期显示系统中的最新5个投票问题,以逗号分隔:

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)

# Leave the rest of the views (detail, results, vote) unchanged

这里有一个问题,虽然:页面的设计是硬编码在视图中。如果你想改变页面的外观,你必须编辑这个Python代码。因此,让我们使用Django的模板系统通过创建视图可以使用的模板将设计与Python分离。

首先,在您的 polls 目录中创建一个名为 templates 的目录。 Django将在那里寻找模板。

项目的 TEMPLATES 设置描述了Django如何加载和渲染模板。默认设置文件配置其 APP_DIRS 选项设置为 TrueDjangoTemplates 后端。按照惯例,DjangoTemplates 在每个 INSTALLED_APPS 中寻找一个“templates”子目录。

在刚刚创建的 templates 目录中,创建另一个名为 polls 的目录,并在其中创建一个名为 index.html 的文件。换句话说,您的模板应该在 polls/templates/polls/index.html。由于 app_directories 模板加载器如上所述工作,您可以在Django中简单地将此模板称为 polls/index.html

模板命名空间

现在我们 might 能够放弃我们的模板直接在 polls/templates (而不是创建另一个 polls 子目录),但它实际上是一个坏主意。 Django将选择它找到的名字匹配的第一个模板,如果你在 different 应用程序中有一个相同名称的模板,Django将无法区分它们。我们需要能够指向Django在正确的一个,和最简单的方法来确保这是由 namespacing 他们。也就是说,将这些模板放在为应用程序本身命名的 another 目录中。

将以下代码放在该模板中:

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 视图以使用模板:

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

该代码加载称为 polls/index.html 的模板并传递它一个上下文。上下文是将模板变量名映射到Python对象的字典。

通过将浏览器指向“/polls/”来加载页面,您应该会看到包含 教程2 的“What’s up”问题的项目符号列表。链接指向问题的详细信息页面。

快捷方式:render()

加载模板,填充上下文并返回带有渲染模板结果的 HttpResponse 对象是非常常见的习语。 Django提供了一个快捷方式。这里是完整的 index() 视图,重写:

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)

注意,一旦我们在所有这些视图中完成这一操作,我们不再需要导入 loaderHttpResponse (如果您仍然具有 detailresultsvote 的存根方法,您将需要保留 HttpResponse)。

render() 函数接受请求对象作为其第一个参数,模板名称作为其第二个参数,字典作为其可选的第三个参数。它返回使用给定上下文呈现的给定模板的 HttpResponse 对象。

提出404错误

现在,让我们处理问题详细信息视图 - 显示给定投票的问题文本的页面。这里是视图:

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 异常。

我们将讨论你稍后可以在 polls/detail.html 模板中加入什么,但是如果你想快速得到上面的例子,一个文件只包含:

polls/templates/polls/detail.html
{{ question }}

会让你现在开始。

快捷方式:get_object_or_404()

这是一个非常常见的成语,使用 get() 和提高 Http404 如果对象不存在。 Django提供了一个快捷方式。这里是 detail() 视图,重写:

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(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 模板可能是什么样子:

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 }} 的示例中,首先Django对对象 question 执行字典查找。如果没有,它尝试一个属性查找 - 在这种情况下工作。如果属性查找失败,它将尝试列表索引查找。

方法调用发生在 {% for %} 循环中:question.choice_set.all 被解释为Python代码 question.choice_set.all(),它返回一个可迭代的 Choice 对象,并适用于 {% for %} 标签。

有关模板的更多信息,请参阅 模板指南

删除模板中的硬编码网址

记住,当我们在 polls/index.html 模板中写一个问题的链接时,链接被部分硬编码,如下所示:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

这种硬编码,紧密耦合的方法的问题是,变更具有大量模板的项目上的URL变得具有挑战性。但是,由于您在 polls.urls 模块中的 url() 函数中定义了name参数,因此可以使用 {% url %} 模板标记来删除对URL配置中定义的特定URL路径的依赖:

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

它的工作方式是查找 polls.urls 模块中指定的URL定义。您可以在下面查看“详细信息”的网址名称的确切位置:

...
# the 'name' value as called by the {% url %} template tag
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

如果要将投票明细视图的URL更改为其他格式,可能是像 polls/specifics/12/,而不是在模板(或模板)中,它将更改它在 polls/urls.py:

...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

命名空间URL名称

教程项目只有一个应用程序,polls。在真正的Django项目中,可能有5个,10个,20个或更多的应用程序。 Django如何区分它们之间的URL名称?例如,polls 应用程序具有 detail 视图,因此可能是同一项目上用于博客的应用程序。如何做到这一点,使Django知道使用 {% url %} 模板标签时为URL创建哪个应用程序视图?

答案是在URLconf中添加命名空间。在 polls/urls.py 文件中,继续并添加一个 app_name 以设置应用程序命名空间:

polls/urls.py
from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

现在更改您的 polls/index.html 模板:

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

指向命名空间的详细视图:

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

当您习惯于编写视图时,请阅读 本教程的第4部分 以了解简单的表单处理和通用视图。