django官方demo翻译简化版 四
本章会专注于讲表单处理及缩减我们的代码量。
编写简单表单
我们向上周的polls/detail.html中加入一个表单,如下:
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>
上述代码的简单关键点解释:
- 上述模板为每个问题选择项增加了一个单选按钮。每个单选按钮的值都被和一个问题选择项关联起来。每个单选框的名称就是一个选择项。这也就意味着当有人选择了一个选择项并且提交了的话,它就会发起一个POST请求并且附带一个参数choice=选项ID。这也就是一个基本的HTML概念。
- 我们给表单的form添加 一个action属性并且赋值为{% url 'polls:vote' question.id %},设置它的方法为POST。使用POST方法很重要,因为这会改变服务端的数据。当你要改变服务器端的数据并且以表单的形式上传的时候,使用POST方法。这种形式并不是django独有的,一般的web开发都会这么做。
- forloop.counter是用来计算for循环执行了多少次。
- 因为我们使用了POST请求这种方式,那么我们需要考虑跨站伪造请求的问题。django自带了一种方法来处理这个问题,通过在html的表单中增加
{% csrf_token %}
现在我们创建一个用于处理提交过来的表单数据的view。记住,我们在第三部分已经编写了一个如下的URLconf:
polls/urls.py path('<int:question_id>/vote/', views.vote, name='vote'),
我们也编写了一个简单的vote函数demo。现在我们开始真正编写这个函数的功能:
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,)))
上述关键点解释:
request.POST是一个字典结构的对象,可以让你通过key的名称来处理提交过来的数据。在这里request.POST['choice']会以字符串的方式返回选择项的ID。request.POST的值的类型永远是string。
注意django也同样提供了request.GET方法来获取数据-但是在这里我们在代码里使用的是request.POST,来确保数据只是通过POST来改变
-request.POST['choice'] 会抛出一个异常假如key不在通过POST方法提交过来的数据里。上述代码会检查KeyError 并且会把error信息反馈回去假如没有找到key。
-在选择项依次递增后,代码会返回一个重定向的http响应而不是直接给一个http响应。
就像在上述py文件里的解释代码说的一样,在你每次成功处理POST请求的数据后,你应该永远使用HttpResponseRedirect。这种方式在所有的web开发里都是这
么做的。
-本次例子里我们在HttpResponseRedirect结构里写了reverse()这个函数。这个函数可以避免我们在编写view时硬编码一个URL。它需要我们提供一个view名称的入参及一个变量。这种写法可以换成另外一种我们前面有提到的写法:
'/polls/3/results/'
上面的3就是question.id。这个重定向的URL将会调用results的view来展示最终的结果。
就像在前一篇里提到的一样,request是一个HttpRequest对象。如想了解更多,查看request and response documentation部分。
当有人给问题投票后,vote()这个view将会指向这个问题的投票结果页面。现在我们开始编写这个view:
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})
上述view基本上和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>
现在重启应用,做一些投票操作看下结果。
使用通用view:减少冗余代码
detail() 以及 results()这2个view的代码很短并且像前面说的一样有点冗余。同样的index()也是。
上述例子都是讲的web开发过程中的一个基本例子:根据url里的传参从数据库获取对应的数据,加载对应的模板并返回对应的添加了数据库数据的模板。因为它们太普通了,django提供了一个更快捷的方式generic views来处理这个问题。
Generic views这个概念在于我们不需要编写python代码来编写一个app。
要实现我们说的Generic views,我们需要做如下事情:
- 转换我们的URLconf
- 删掉一些我们不需要的view
- 引入一些基于django的generic views
修改URLconf
将polls/urls.py修改为如下:
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'), ]
注意上述第二三个path的<question_id>已经修改成了<pk>.
修改views
现在我们要使用Django的generic views来替换掉以前的index, detail及results视图。polls/views.py这个文件会被修改成如下:
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.
在这里我们使用了2个generic views :ListView 和 DetailView。这2个分别代表了展示一列的对象及展示某个对象的具体内容这2个概念。
-每个通用的view都需要知道它本身是依附于哪个model。本次demo中是使用model的属性
-DetailView 这个通用view需要一个来自于URL的传参pk,所以我们改变question_id为pk来做适配这个通用view。
默认情况下,DetailView这个通用view将会使用一个叫做<app name>/<model name>_detail.html的模板。在我们的例子里它会使用"polls/question_detail.html"。这个属性template_name是用来告诉django使用自定义的模板而不是默认的模板。我们也给results这个view定义了一个自己的template_name-这是为了确保在他们继承了相同的DetailView情况下保持不同的呈现效果。
类似的,ListView 也使用一个默认的 <app name>/<model name>_list.html模板。我们使用template_name来告诉django我们使用已存在的"polls/index.html"。
在前面的篇幅中,我们的模板系统提供了question 及latest_question_list。对于DetailView来说,question会被自动提供,因为我们使用了Django的model (Question).django是可以自己决定环境变量的值的。然而,对ListView来说,它的环境变量值是question_list。为了重写这部分,我们提供了一个属性值context_object_name。这样你可以很方便的设置自己需要的环境变量。
然后最后运行一下最新的polls。
浙公网安备 33010602011771号