编写你的第一个 Django 应用程序,第 3 部分 — Django 文档

来自菜鸟教程
Django/docs/2.2.x/intro/tutorial03
跳转至:导航、​搜索

编写你的第一个 Django 应用,第 3 部分

本教程从 教程 2 停止的地方开始。 我们将继续使用网络投票应用程序,并将专注于创建公共界面——“视图”。

概况

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

  • 博客主页 - 显示最新的几个条目。
  • 条目“详细信息”页面 – 单个条目的永久链接页面。
  • 基于年份的存档页面 - 显示给定年份中包含条目的所有月份。
  • 基于月的存档页面 - 显示给定月份中包含条目的所有日期。
  • 基于日期的存档页面 - 显示给定日期的所有条目。
  • 评论操作 - 处理对给定条目发布评论。

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

  • 问题“索引”页面——显示最近的几个问题。
  • 问题“详细信息”页面 - 显示问题文本,没有结果,但有投票表格。
  • 问题“结果”页面 – 显示特定问题的结果。
  • 投票操作 - 处理对特定问题中特定选项的投票。

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

现在,在您上网时,您可能遇到过诸如“ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”之类的美女。 你会很高兴知道 Django 允许我们提供比这更优雅的 URL 模式

URL 模式只是 URL 的一般形式 - 例如:/newsarchive/<year>/<month>/

为了从 URL 到视图,Django 使用了所谓的“URLconfs”。 URLconf 将 URL 模式映射到视图。

本教程提供了 URLconfs 的基本使用说明,更多信息可以参考 URL dispatcher


编写更多视图

现在让我们为 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)

通过添加以下 path() 调用,将这些新视图连接到 polls.urls 模块中:

polls/urls.py

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() 方法并显示您在 URL 中提供的任何 ID。 也试试“/polls/34/results/”和“/polls/34/vote/”——它们会显示占位符结果和投票页面。

当有人从你的网站请求一个页面时——比如“/polls/34/”,Django 将加载 mysite.urls Python 模块,因为它由 :setting:`ROOT_URLCONF` 设置指向. 它找到名为 urlpatterns 的变量并按顺序遍历模式。 在 'polls/' 处找到匹配后,它去除匹配文本 ("polls/") 并将剩余文本 - "34/" - 发送到“polls.urls”URLconf 以进行进一步处理. 在那里它匹配 '<int:question_id>/',导致调用 detail() 视图,如下所示:

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

question_id=34 部分来自 <int:question_id>。 使用尖括号“捕获”部分 URL 并将其作为关键字参数发送给视图函数。 字符串的 :question_id> 部分定义将用于标识匹配模式的名称,而 <int: 部分是一个转换器,用于确定哪些模式应该与 URL 路径的这一部分匹配。

无需添加诸如 .html 之类的 URL cruft – 除非您愿意,在这种情况下,您可以执行以下操作:

path('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 会在那里寻找模板。

您项目的 :setting:`TEMPLATES` 设置描述了 Django 将如何加载和呈现模板。 默认设置文件配置了一个DjangoTemplates后端的 :设置:`APP_DIRS ` 选项设置为True . 按照惯例,DjangoTemplates 在每个 :setting:`INSTALLED_APPS` 中查找“模板”子目录。

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

模板命名空间

现在我们 可能 能够将我们的模板直接放在 polls/templates 中(而不是创建另一个 polls 子目录),但这实际上是一个坏主意。 Django 将选择它找到的第一个名称匹配的模板,如果您在 different 应用程序中有一个同名的模板,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 %}

现在让我们更新 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 中的“最新消息”问题。 该链接指向问题的详细信息页面。

快捷方式: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(如果您仍然需要保留 HttpResponse具有 detailresultsvote 的存根方法)。

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 而不是 [ X215X]ObjectDoesNotExist?

因为这会将模型层耦合到视图层。 Django 的首要设计目标之一是保持松散耦合。 django.shortcuts 模块中引入了一些受控耦合。


还有一个 get_list_or_404() 函数,它和 get_object_or_404() 一样工作——除了使用 filter() 而不是 get() . 如果列表为空,它会引发 Http404


使用模板系统

回到我们的投票应用程序的 detail() 视图。 给定上下文变量 questionpolls/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 进行字典查找。 如果失败,它会尝试进行属性查找 - 在这种情况下有效。 如果属性查找失败,它会尝试列表索引查找。

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

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


去除模板中的硬编码 URL

请记住,当我们在 polls/index.html 模板中编写问题链接时,该链接部分硬编码如下:

<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 模块中指定的 URL 定义。 您可以确切地看到“详细信息”的 URL 名称在下面定义的位置:

...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...

如果您想将投票详细信息视图的 URL 更改为其他内容,也许更改为 polls/specifics/12/ 之类的内容,而不是在模板(或模板)中进行更改,您可以在 polls/urls.py 中进行更改:

...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...

为 URL 名称添加命名空间

教程项目只有一个应用程序,polls。 在真正的 Django 项目中,可能有五个、十个、二十个或更多的应用程序。 Django 如何区分它们之间的 URL 名称? 例如,polls 应用程序具有 detail 视图。 当使用 {% url %} 模板标签时,如何让 Django 知道为 url 创建哪个应用程序视图?

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

polls/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'),
]

现在改变你的 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 部分 以了解简单的表单处理和通用视图。