(四)django——表单和类视图

一、表单form

为了接收用户的投票选择,我们需要在前端页面显示一个投票界面。让我们重写先前的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>

说明:

  • 上面的模板显示一系列单选按钮,按钮的值是选项的ID,按钮的名字是字符串"choice"。这意味着,当你选择了其中某个按钮,并提交表单,一个包含数据choice=#的POST请求将被发送到指定的url,#是被选择的选项的ID。这就是HTML表单的基本概念。
  • 如果form标签的action属性和method属性,action表示你要发送的目的url,method表示提交数据的方式,一般分POST和GET。
  • forloop.counter是DJango模板系统专门提供的一个变量,用来表示你当前循环的次数,一般用来给循环项目添加有序数标。
  • 由于我们发送了一个POST请求,就必须考虑一个跨站请求伪造的安全问题,简称CSRF(具体含义请百度)。Django为你提供了一个简单的方法来避免这个困扰,那就是在form表单内添加一条{% csrf_token %}标签,标签名不可更改,固定格式,位置任意,只要是在form表单内。这个方法对form表单的提交方式方便好使,但如果是用ajax的方式提交数据,那么就不能用这个方法了。

现在,创建一个Django视图来处理提交的数据。前面我们已经写了一个“占坑”的vote视图的url(polls/urls.py)

polls/urls.py
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),

修改 vote() 函数。 把“占坑”的vote视图函数(polls/views.py)坑填起来,将下面的代码添加到 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):
        # 发生choice未找到异常时,重新返回表单页面,并给出提示信息
        return render(request, 'polls/detail.html', {
        'question': question,
        'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 成功处理数据后,自动跳转到结果页面,防止用户连续多次提交。
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
  • request.POST是一个类似字典的对象,允许你通过键名访问提交的数据。代码中request.POST[’choice’]返回被选择选项Choice的ID,并且值的类型永远是string字符串!同样的,也可以用类似的手段获取GET请求发送过来的数据。
  • 如果在POST数据中没有提供choice键值,request.POST[’choice’]有可能触发一个KeyError异常,上面的 try ... except 就是用来检查 KeyError,如果没有给出choice将重新显示Question表单和错误信息;PS:通常我们会给个默认值,防止这种异常的产生,例如request.POST[’choice’,None],一个None解决所有问题。
  • 在Choice选择计数器加一后,返回的是一个HttpResponseRedirect而不是先前常用的HttpResponseHttpResponseRedirect只接收一个参数:重定向的URL。建议:当你成功处理POST数据后,应当保持一个良好的习惯,始终返回一个HttpResponseRedirect
  • 在上面HttpResponseRedirect的构造器中使用了一个reverse()函数。它可以避免在视图函数中硬编码URL。它首先需要一个我们在URLconf中指定的name,然后是传递的数据。例如'/polls/3/results/',其中的3是某个question.id的值。重定向后将进入polls:results对应的视图,并将question.id传递给它。其实就是把活扔给另外一个路由对应的视图去干。

当有人对某个问题Question投票后,vote()视图将请求重定向到了问卷Question的结果显示页面。下面我们来写这个处理结果页面的视图

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/templates/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/了,投票吧。你会看到一个结果页面,每投一次,它的内容就更新一次。如果你提交的时候没有选择Choice,则会得到一个错误提示(You didn't select a choice.)。
在这里插入图片描述
这是“what’s up”问卷选项页面:
在这里插入图片描述
这是选择结果页面:
在这里插入图片描述
这是没有选择选项时,提示错误信息的页面:
在这里插入图片描述

二、 使用类视图:减少重复代码

上面的 detail 、index 和 results 视图的代码非常相似,有点冗余,这是一个程序猿不能忍受的。 他们都具有类似的业务逻辑,实现类似的功能:通过从URL传递过来的参数去数据库查询数据,加载一个模板, 利用刚才的数据渲染模板,返回这个模板。由于这个过程是如此的常见,Django又很善解人意的帮你想办法偷懒了, 它提供了一种快捷方式,名为 generic views 系统。

Generic views会将常见的模式抽象化,可以使你在编写app时甚至不需要编写Python代码。

下面将原来投票应用的代码转换成使用通用视图的方式,这样可以删除许多冗余的代码。仅仅需要做以下三步来完成转换:

  • 修改URLconf;
  • 删除一些旧的无用的视图;
  • 采用基于类视图的新视图。
    2.1、改进URLconf

polls/urls.py

from django.conf.urls import url
from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

注意:在上面的的第2,3条目中将原来的<question_id>修改成了<pk>.
2.2.修改视图
接下来,打开polls/views.py文件,删掉index、detail和results视图,替换成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

# ListView显示对象列表
class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """返回最近发布的5个问卷."""
        return Question.objects.order_by('-pub_date')[:5]

#DetailView显示特定类型对象的详细信息页面
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):
    ... # 这个视图未改变!!!

在这里,我们使用了两种类视图ListView和DetailView(它们是作为父类被继承的)。这两者分别代表“显示一个对象的列表”和“显示特定类型对象的详细页面”的抽象概念。

  • 每一种类视图都需要知道它要作用在哪个模型上,这通过model属性提供。
  • DetailView类视图需要从url捕获到的称为"pk"的主键值,因此才需要把 polls/urls.py中2和3条目的<question_id>修改成了<pk>
    默认情况下,DetailView类视图使用一个称作<app name>/<model name>_detail.html的模板。在本例中,实际使用的是polls/detail.htmltemplate_name属性就是用来指定这个模板名的,用于代替自动生成的默认模板名。同样的,在resutls列表视图中,指定template_name'polls/results.html',这样就确保了虽然resulst视图和detail视图同样继承了DetailView类,使用了同样的model:Qeustion,但它们依然会显示不同的页面。(模板不同!)

类似的,ListView类视图使用一个默认模板称为<app name>/<model name>_list.html。我们也使用template_name这个变量来告诉ListView使用我们已经存在的 "polls/index.html"模板,而不是使用它自己默认的那个。

在教程的前面部分,我们给模板提供了一个包含questionlatest_question_list的上下文变量。而对于DetailView,question变量会被自动提供,因为我们使用了Django的模型(Question),Django会智能的选择合适的上下文变量。然而,对于ListView,自动生成的上下文变量是question_list。为了覆盖它,我们提供了context_object_name属性,指定说我们希望使用latest_question_list而不是question_list

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值