Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第四部分(Page 9)

编写你的第一个 Django app,第四部分(Page 9)转载请注明链接地址

该教程上接前面的第三部分。我们会继续开发 web-poll 应用,并专注于简单的表单处理和简化代码。

写一个简单的表单(form)

让我们更新一下我们上个教程编写的的 poll 的 detai 模板(“polls/detail.html”),模板会包含一个 HTML <form> 元素:

<!--polls/templates/polls/detail.html-->
<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% 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 %}
<input type="submit" value="Vote" />
</form>

简要说明:

  • 上面的模板为每个 question 的 choice 显示了一个单选按钮。每个单选按钮的值对应 quesiton 里 choice 的 ID。单选按钮的名字都是 “choice”。这表示,当有人选择其中一个按钮并提交表单,它会发送 POST 数据 choice=#, “#”表示选中的 choice 的 ID。这是 HTML 中 forms 的基本概念。
  • 我们设置 forms 的 action(应该是行为,但不太确定) 为 {% url 'polls:vote' question.id %} ,并设置 method="post"。使用method="post" (与之相反的是method="get")非常重要,因为这个 form 的提交动作会修改服务端的数据。无论何时,你创建的 form 在修改服务端数据时,都要使用 method="post"。这个技巧并不限于 Django;它是一个很好的 web 开发习惯。
  • forloop.counter 表示 for 标签已经循环了多少次。
  • 我们已经创建了一个POST form(它可以修改用来修改数据),我们需要注意伪装的跨站点请求。幸亏,你不需要太过担心,因为django有一个用来防御它的易于使用的系统。简单的说,所有的POST form 有针对性的在内部URLs中使用 {% csrf_token %}(这里少一个链接)模板标签

现在,我们创建一个处理提交的数据的 django 视图,并用它搞一些事情。记住,在前面第三部分的教程中,我们为polls应用创建了一个 URLconf,它包含下面一行:

# polls/urls.py
path('<int:question_id>/vote/', views.vote, name='vote'),

我们还创建了一个 vote() 函数的虚拟实现。现在我们创建一个真正的。在polls/views.py中添加如下内容:

# polls/views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
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,)))

这里的代码有少量教程中还没有涉及的内容。

'/polls/3/results/'

3 是 question.id 的值,这个重定向的URL之后会调用 results 视图来显示最终的页面。

正如前一节教程中提到的,request 是一个 HttpRequest(这里少一个链接) 对象。更多关于 HttpRequest(这里少一个链接)的内容,请查看 request and response documentation(这里少一个链接)
当有人对一个问题投票后,vote() 视图重定向到问题的结果页面,我们来写一个这个视图:

# polls/views.py
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})

它几乎和前一节中的 detail()视图一样。仅有模板名字不一样。稍后我们会修复这个冗余(说是代码重复准确一些)问题。
现在,创建一个 polls/results.html 模板:

<!--polls/templates/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/”并给这个问题投票。你应该可以看到每次投票结果更新后的页面。如果你提交的form中没有选中的choice,你会看到一个错误消息。

注意
代码中我们的vote()视图有一个小小的问题,它首先从数据库中得到一个selected_choice 对象,然后计算新的投票结果,并将结果保存到数据库。如果你的站点有两个用户尝试在同一个时间点投票,这可能会导致错误: 相同的值,比如被取回来的投票数是42。然后两个用户都会43这个新值并被保存,而不是预期的值44.
这个叫做竞态条件(race condition:指设备或系统出现不恰当的执行时序,而得到不正确的结果)。如果你感兴趣。你可以阅读 使用F()避免静态条件(这里少一个链接),学习如何解决这个问题。

使用通用视图(Generic views):代码还是少点好

detail() 和 results()视图都很简单 —— 并且,像上面提到的一样,冗余(代码重复)。和index()视图类似,显示投票题目的一个列表。
这些视图反应了web开发中一个常见情况:根据URL中传递的参数从数据库获取数据,记在模板并返回渲染后的模板。由于这个太常见,Django提供了一个快捷的,叫做“通用视图”的系统。
通用视图抽象常见的模式, 可以让你编写app时甚至不需要写python代码。

让我们讲poll app转换成通用视图,这样我们可以删除许多代码。我们仅需要几步来完成转换。我们会:

  1. 调整URLconf
  2. 删除一些旧的,没用的视图
  3. 引入基于Django通用视图的新视图

详情请阅读。

为什么重构代码? (code-shuffle:我理解的是代码重构)
一般,当我们写Django app,你需要评估通用视图是否适合解决你的问题,你可以从一开始就使用它,不是半途重构你的代码。但到现在为止,本教程有意专注使用“困难的方式”编写视图,把重点放在核心概念上。
使用计算器之前,你应该对数学有一个基本的了解。

改进 URLconf

首先,打开 polls/urls.py ,像下面一样修改 URLconf。

# polls/urls.py
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>

改进视图

下一步,我们会移除旧的 indexdetailresults 视图,并用Django的通用视图替换它们。 这需要打开 polls/views.py 文件将它改成类似下面的样子:

# polls/views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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(这里少一个链接)DetailView(这里少一个链接)。这两个视图分别抽象了 “显示一个对象的列表” 和 “显示一个特定类型对象的详情页” 的概念。

  • 每个通用视图需要知道她将作用于那个模型。这由使用的模型的属性提供。
  • DetailView(这里少一个链接) 通用视图期望从URL捕捉到的名为“pk”的主键值,所以我们将question_id 改成 pk 来使通用视图可以找到它。

默认情况下,DetailView(这里少一个链接)通用视图使用一个名为 <app name>/<model name>_detail.html的模板。在我们的例子中,我们使用 "polls/question_detail.html"模板。template_name属性用于告诉Django使用特定的模板名去替换自动生成的默认模板名,我们还需要为 result 列表视图指定 template_name —— 这是为确保result视图和detail视图在渲染时呈现不同的外观,虽然它们后面是同一个 DetailView(这里少一个链接)视图。

类似的,ListVIew(这里少一个链接)通用视图使用了名为 <app name>/<model name>_detail.html 的模板。我们使用 template_name 告诉 ListVIew(这里少一个链接) 去使用我们已经有的 "polls/index.html"模板。
在教程前面的部分,已经提供了一个包含 questionlatest_question_list 的 context 变量的模板。对DetailView来说, question变量已经被自动提供 —— 从我们使用模型(Question)开始,Django可以为context变量取一个合适的名字。然而,对于 ListView,自动生成的context变量是 question_list。重写我们提供的 context_object_name 属性,用我们想使用的 latest_question_list 替代它。作为一种替代方法,你可以更改你的模板来匹配新的默认的context变量 —— 然而直接告诉Django去使用你想用的变量会简单很多。

运行服务器,使用新的基于通用视图的投票app。
更多关于通用视图的内容,请参阅 [通用视图文档](这里少一个链接)()。

当你熟悉了表单和通用视图,可以继续向下学习 测试我们的投票app的内容。

原文地址:https://www.cnblogs.com/resn/p/8353559.html