编写你的第一个 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的内容。

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

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

    编写你的第一个 Django app,第一部分(Page 6)转载请注明链接地址 Django 2.0.1 官方文档翻译: Django 2.0.1.dev20171223092829 documen ...

  2. Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第五部分(Page 10)

    编写你的第一个 Django app,第五部分(Page 10)转载请注明链接地址 我们继续建设我们的 Web-poll 应用,本节我们会为它创建一些自动测试. 介绍自动测试 什么是自动测试 测试是简 ...

  3. Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第二部分(Page 7)

    编写你的第一个 Django app,第二部分(Page 7)转载请注明链接地址 本教程上接前面的教程.我们会配置数据,创建你的第一个 model,并对Django 自动生成的 admin 站点进行快 ...

  4. Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第七部分(Page 12)

    编写你的第一个 Django app,第七部分(Page 12)转载请注明链接地址 本节教程承接第六部分(page 11)的教程.我们继续开发 web-poll应用,并专注于自定义django的自动生 ...

  5. Django 2.0.1 官方文档翻译:编写你的第一个 Django app,第六部分(Page 11)

    编写你的第一个 Django app,第六部分(Page 11)转载请注明链接地址 本教程上接前面第五部分的教程.我们构建了一个经过测试的 web-poll应用,现在我们会添加一个样式表和一张图片. ...

  6. Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第三部分(Page 8)

    编写你的第一个 Django app,第三部分(Page 8)转载请注明链接地址 本页教程接前面的第二部分.我们继续开发 web-poll app,我们会专注于创建公共接口上 -- "视图& ...

  7. Django 2.0.1 官方文档翻译:编写你的第一个djang补丁(page 15)

    编写你的第一个djang补丁(page 15) 介绍 有兴趣为社区做一些贡献?可能你发现了django中的一个你想修复的bug,或者你你想添加一个小小的功能. 回馈django就是解决你遇到的问题的最 ...

  8. Django 2.0.1 官方文档翻译: 高级教程:如何编写可重用的app (page 13)

    高级教程:如何编写可重用的app (page 13) 本节教程上接第七部分(Page 12).我们会把我们的 web-poll应用转换成一个独立的python包,你可以在新的项目中重用或者把它分享给其 ...

  9. Django 2.0.1 官方文档翻译: 如何安装 django (Page 17)

    如何安装 django(Page 17) 这一部分可以让你将 Django 运行起来. 安装 Python 作为 python 的一个 web 框架,Django 依赖 Python.Python 的 ...

随机推荐

  1. C1WPF制作OLAP Cube浏览工具

    经过前期一段时间对WPF的学习了解,相信大家对WPF有了一定的了解.今天我们一起来了解使用Component One(简称C1)的WPF控件制作CUBE浏览工具.其实这个OLAP控件官方已经有了很详细 ...

  2. 全选练习-原生版和jQuery

    今天来做一些练习,做全选练习 原生版的实现: <!DOCTYPE html> <html> <head> <meta charset="UTF-8& ...

  3. 关于SVM数学细节逻辑的个人理解(一) :得到最大间隔分类器的基本形式

    网上,书上有很多的关于SVM的资料,但是我觉得一些细节的地方并没有讲的太清楚,下面是我对SVM的整个数学原理的推导过程,其中逻辑的推导力求每一步都是有理有据.现在整理出来和大家讨论分享. 因为目前我的 ...

  4. Ubuntu下面 PHPSTORM2017.2破解方法

    Ubuntu下面 PHPSTORM2017.2破解方法 下载破解文件 在 http://idea.lanyus.com/上面新下载一个破解文件. 破解步骤 将JetbrainsCrack-2.6.3_ ...

  5. SQLSERVER 升级版本的方法

    1. 以SQLSERVER2014为例说明 SQLSERVER升级版本的方法, 也适用于evaluation 版本超过180天之后的处理. 2. 打开所有的应用 看到有一个 sqlserver2008 ...

  6. Nginx负载均衡配置与负载策略

    原理 负载均衡的目的是为了解决单个节点压力过大,造成Web服务响应过慢,严重的情况下导致服务瘫痪,无法正常提供服务. 应用场景 春节期间在12306网站上买过火车票的朋友应该深有体会,有时查询一张火车 ...

  7. input accept 属性

    *.3gpp audio/3gpp, video/3gpp 3GPP Audio/Video *.ac3 audio/ac3 AC3 Audio *.asf allpication/vnd.ms-as ...

  8. Handler,Looper,HandlerThread浅析

    Handler想必在大家写Android代码过程中已经运用得炉火纯青,特别是在做阻塞操作线程到UI线程的更新上.Handler用得恰当,能防止很多多线程异常. 而Looper大家也肯定有接触过,只不过 ...

  9. eclipse中添加配置文件夹config

    1. 在项目上右键->Build path->Configure Build Path->Source下的Add Folder,如图 2. 在弹出框中,Create New Fold ...

  10. 【刷题】BZOJ 3531 [Sdoi2014]旅行

    Description S国有N个城市,编号从1到N.城市间用N-1条双向道路连接,满足 从一个城市出发可以到达其它所有城市.每个城市信仰不同的宗教,如飞天面条神教.隐形独角兽教.绝地教都是常见的信仰 ...