编写你的第一个 Django 应用程序,第4部分

发布时间 2023-04-19 11:47:09作者: 辉哥XH

本教程从教程 3 停止的地方开始。我们是 继续民意调查应用程序,并将专注于表单处理和 减少我们的代码。

一、编写最小表单

让我们更新上一个教程的投票详细信息模板(“polls/detail.html”) ,以便模板包含一个 HTML  <form>元素:

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>
  • 由于我们正在创建一个 POST 表单(这可能会产生修改的效果 数据),我们需要担心跨站点请求伪造。 值得庆幸的是,你不必太担心,因为 Django 带有一个 有用的系统来防止它。简而言之,所有 POST 表单 定位到内部网址应使用 {% csrf_token %} 模板标记。

现在,让我们创建一个 Django 视图来处理提交的数据并 随之而来的东西。请记住,在教程 3 中,我们 为包含以下行的投票应用程序创建了一个 URLconf:

path("<int:question_id>/vote/", views.vote, name="vote"),

我们之前创建了vote()函数的虚拟实现。让我们 创建一个真实版本。将以下内容添加到:polls/views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from .models import Choice, Question


# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST["choice"])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(
            request,
            "polls/detail.html",
            {
                "question": question,
                "error_message": "You didn't select a choice.",
            },
        )
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))

此代码包含一些我们在本教程中尚未介绍的内容:

  • 请求。POST是一个类似字典的 允许您按键名访问提交数据的对象。在本例中,返回所选选项的 ID,作为 字符串。请求。开机自检值为 始终为字符串。request.POST['choice']

    请注意,Django 也提供了请求。 GET 以相同的方式访问 GET 数据 – 但我们明确使用请求。在我们的代码中 POST,以确保数据仅 通过开机自检呼叫进行更改。

  • request.POST['choice']如果开机自检数据中未提供,将引发密钥错误。上面的代码检查 KeyError 并重新显示错误的问题表单 消息(如果未给出)。choicechoice

  • 递增选择计数后,代码将返回 HttpResponseRedirect,而不是正常的 HttpResponseHttpResponseRedirect采用单个参数: 用户将被重定向到的 URL(请参阅以下点,了解如何 在这种情况下,我们构造 URL)。

    正如上面的 Python 注释所指出的,在成功处理 开机自检数据。这个技巧不是特定于 Django 的;这是很好的网络开发 一般做法。

  • 在这个例子中,我们在 HttpResponseRedirect 构造函数中使用了 reverse() 函数。 此函数有助于避免在视图函数中对 URL 进行硬编码。 它被赋予了我们要向其传递控制权的视图的名称和 指向该视图的 URL 模式的变量部分。在此 案例,使用我们在教程 3 中设置的 URLconf, 这个 reverse() 调用将返回一个字符串,例如 

    "/polls/3/results/"

对问题进行投票后,vote() 视图将重定向到结果 问题的页面。让我们改写下这个视图:

from django.shortcuts import get_object_or_404, render


def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/results.html", {"question": question})

这与教程 3 中的 detail() 视图几乎完全相同。唯一的区别是模板名称。我们将解决此问题 以后冗余。

现在,创建一个模板:polls/results.html

<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

现在,转到/polls/1/ 浏览器中并在问题中投票。您应该会看到一个 每次投票时都会更新的结果页面。如果您提交表格 如果没有选择选项,您应该会看到错误消息。

 

二、使用通用视图:代码越少越好

detail()(来自教程 3)和 results() 视图非常短 - 如上所述,冗余。显示投票列表的 index() 视图与此类似。

这些视图代表了基本 Web 开发的常见情况:从以下位置获取数据 数据库根据 URL 中传递的参数,加载模板和 返回呈现的模板。

因为这太常见了,Django 提供了一个 快捷方式,称为“通用视图”系统。

通用视图将常见模式抽象到您甚至不需要的程度 编写 Python 代码来编写应用程序。

让我们将我们的投票应用程序转换为使用通用视图系统,以便我们可以删除 一堆我们自己的代码。我们必须采取几个步骤才能进行转换。 我们会的:

  1. 转换网址。
  2. 删除一些旧的、不需要的视图。
  3. 基于 Django 的通用视图引入新视图。

请继续阅读以了解详细信息。

三、修改网址

首先,打开polls/urls.py文件 , URLconf 并像这样更改它:

from django.urls import path

from . import views

app_name = "polls"
urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path("<int:pk>/", views.DetailView.as_view(), name="detail"),
    path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

请注意,第二个和 第三种模式已从 <question_id>更改为 <pk>。

四、修改视图

接下来,我们将删除旧的 index、detail 和 results 视图,并使用 Django 的通用视图。为此,请打开 polls/views.py 文件并按如下所示进行更改:

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = "polls/index.html"
    context_object_name = "latest_question_list"

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by("-pub_date")[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = "polls/detail.html"


class ResultsView(generic.DetailView):
    model = Question
    template_name = "polls/results.html"


def vote(request, question_id):
    ...  # same as above, no changes needed.

我们在这里使用两个通用视图: ListView and DetailView视图。分别是那些 两个视图抽象化了“显示对象列表”和 “显示特定类型对象的详细信息页面。”

  • 每个通用视图都需要知道它将执行什么模型 后。这是使用model属性提供的。
  • DetailView通用视图 期望调用从 URL 称为pk值,因此我们把question_id更改为pk 的通用视图。

在本教程的前面部分中,提供了模板 具有包含 和 上下文变量的上下文。

对于变量提供 自动 – 因为我们使用的是 Django 模型 (), Django 能够为上下文变量确定适当的名称。

但是,对于 ListView,自动生成的上下文变量为 。为了覆盖它,我们提供了属性,指定我们要改用。

作为替代方法,您可以更改模板以匹配 新的默认上下文变量 - 但告诉 Django 要容易得多 使用所需的变量。

 

运行服务器,并使用基于通用视图的新网络投票应用。

有关通用视图的完整详细信息,请参阅通用视图文档

当您熟悉表单和通用视图后,请阅读本文的第 5 部分 教程,了解如何测试我们的民意调查应用程序。

  ---------------------------------------end--------------------------