Django官方教程翻译

Part4 表单处理和通用视图

2017-12-20  本文已影响0人  shark_tear

这篇教程会从第三节结束的地方继续,我们继续开始处理web投票应用,并且将精力放在简单表单处理,并且分割我们的代码。

写一个简单的表单

我们来升级一下上一节里的poll detail模板("polls/detail.html"),模板包含一个HTML<form>元素:

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

简要说明:

现在,我们来创建一个Django视图来处理提交的数据。记住,在上一节中,我们为polls应用创建了下面的URLconf:

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

我们还创建了vote()方法的一个虚假实现。现在我们来创建一个有真正功能的版本,把下面的代码添加到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,)))

代码里包含一些这篇教程里我们还没有涉及到的内容:

像上一节里提到的,request是一个HttpRequest对象,想知道这个对象的更多信息,可以查看Django官方的帮助文档。

当某人在给某个question投了一票,vote()视图就会重定向到这个question的结果页面。我们来写这个视图:

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>

现在,在你的浏览器里输入地址"http://localhost:8000/polls/1/",然后给这个问题投票。你会看到一个结果页面,你投票一次,结果就会更新一次。如果你在提交页面的时候没有选择任何选项,会给出错误信息。

注意
我们的vote()视图的代码还有一点小问题。它首先从数据中取到selected_choice对象,然后计算votes的新值,再然后将它存回数据库。如果你的网站上有两个用户同时为同一个问题投票。就可能会出现问题:例如投票之前的值是42,会作为vote()视图获取到的值,两个用户同时投票时都是计算得到新的值43然后保存到数据库,而我们想要的值却是44.
这种情况被称为竞赛情况,如果你对这个感兴趣,你可以查看官方文档《使用F()来避免竞赛情况》来解决这个问题。

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

detail()results()视图都非常简单,像上面提到的那样,很多内容也是多余的。index()视图用来显示polls列表,也和它们功能类似。
这些视图显示了Web开发过程中的一个典型例子:根据URL里传递的参数到数据库中取数据,加载模板并且返回渲染后的模板。因为这些都是功能都是一样的,所以Django提供了更简单的方式,叫做通用视图系统。
通用视图抽象常用的模式,甚至不需要编写Python代码来编写应用。
我们来将poll应用转换到通用视图视图,所以我们可以删除很大一部分代码,我们需要执行下面的几步来执行我们转换:

  1. 转换URLconf
  2. 删除旧的不需要的视图
  3. 介绍新的基于Django通用视图系统的视图

阅读下面的详细信息

为什么要代码重构?

通常情况下,写一个Django应用的时候,你会去评估使用通用视图,对于你遇到的问题来说是不是一个很好的解决方式。并且从最开始就使用这种方式,而不是在中途开始重构你的代码。但是这个教程中故意把重点放在视图上,直到现在才开始关注核心思想。
就像在你开始使用一个计算器的时候你必须知道一些基础的数学知识一样。

修改URLconf

首先,打开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'),
]

注意第二个和第三个模式里path字符串中的匹配模式名称,将<question_id>修改为<pk>

修改视图

下一步,我们来移除旧的indexdetailresults视图,使用Django通用视图来代替。要这样做的话,打开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.

我们在这里使用了连个通用视图:ListViewDetailView。这两个视图的思想分别抽象自“显示一个对象列表”和“为一个特定类型的对象显示一个详细页面”。

默认情况下,DetailView通用视图使用一个叫做<app name>/<modelname>_detail.html的模板,在我们的例子里,我们使用模板"polls/question_detail.html"template_name属性用来告诉Django使用一个特定的模板名称,而不是一个自动生成的默认模板名称。我们也为results列表视图指定templat_name。这保证了results视图和detail视图在渲染的时候会有一个不同的样式,即使是它们在后台使用了相同的DetailView

相似的,ListView通用视图使用一个叫做 <app name>/<model name>_list.html的模板,我们使用template_name告诉ListView来使用现有的"polls/index.html"模板。

在这个教程之前的部分中,提供了一个模板和一个包含questionlatest_question_list变量的上下文管理器。对于DetailViewquestion变量是自动提供的—从我们开始使用一个Django模型(Question)开始,Django能够为上下文管理器变量确定一个不同的值。然后,对于ListViews来说,自动生成的上下文管理器变量是question_list。为了覆盖这个变量,我们提供context_object_name属性,指定我们想要使用latest_question_list。作为一种替代方法,你可以改变你的模板来匹配新的默认上下文管理器变量。但是直接告诉Django你想要使用的变量更简单。

运行开发服务器,并且根据通用视图使用新的poll应用程序。

对于更多的通用视图信息,可以查看Django官方文档里通用视图的内容。

当你对表单和通用视图的内容都理解了以后,就可以继续学习第5节,学习如何测试我们的投票应用。

上一篇下一篇

猜你喜欢

热点阅读