开始Django之旅-part4_使用表格简化你的代码

开始Django之旅-part4_使用表格简化你的代码

前一篇:开始Django之旅-part3_Django和view
后一篇:开始Django之旅-part5_自动测试你的代码

前言

这篇文章来告诉你,如何使用表格来简化代码。

写一个表格

我们更新一些上节课讲过的(‘polls/detail.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>

快速的了解一下:

  • 上面的模板为每个问题的选项设置了一个单选按钮,它们的id和choice.id关联,名字都是choice。这就说明,当有人选中某个单选按钮然后提交表格,它就会发送POST数据choice=#(#表示被选中按钮的id)。这是基本的HTML表格概念。
  • 我们设置表格的action为 {% url ‘polls:vote’ question.id %},method为post。使用post方法很重要,因为提交表格会改变服务器端的数据。无论什么时候你创建了一个修改服务器端数据的表格,都要使用method=post。这在web开发中是比较常见的操作。
  • forloop.counter表明for标签进行了多少次循环。
  • 因为我们建立了一个post表格,它可以修改数据,我们可能会担心其他站点的伪造的请求。幸运的是,你不需要太担心,因为django结合了一个保护系统。就是说,所有针对内部的URL表单都应该使用 {% csrf_token %}标签。

现在,我们开始创建一个处理提交过来的数据并实施确定操作的view。记住,上一篇我们已经创建了polls的URL配置文件,它包括了:

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

我们需要实现vote()方法:

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是一个类型字典的对象,可以让你访问通过关键字name访问提交过来的数据。在这个例子中,request.POST[‘choice’]返回了被选择的选项的id,把它当做了一个字符串。request.POST的值总是字符串。注意:Django也提供了request.GET方法以同样的方式访问GET数据。
  • request.POST[‘choice’]如果提供的post数据中choice不存在就会引发KeyError,然后重新展示question表格和错误信息。
  • 在增加选择次数之后,代码返回HttpResponseRedirect对象而不是HttpResponse,HttpResponseRedirect只携带了一个参数:一个用户将要被重定向的地址。上面的python注释指出:当成功的处理POST数据之后都应该返回一个HttpResponseRedirect对象.
  • 我们在HttpResponseRedirect构造方法中使用了reverse()方法,它有助于避免在view中使用固定的URL。它给出了我们想要把控制权交给某个view的名字,和指向该view的URL的变量部分。在这个例子中,reverse()会范围一个字符串:
'/polls/3/results/'

3是question.id的值,该重定向的URL将会调用‘results’view,展示在最终页面上。

在上一篇中讲到,request是一个HttpRequest对象,关于更多,请看:request and response documentation.

在有人给问题投票后,vote() view就会重定向到question的结果页面,看下面的view:

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模板:

<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/问题下面投票,你投票点击后,应该会看到一个更新的结果。如果你没有选择选项就投票就会看到一个错误页面。

注意:view中的vote()方法还有一点小问题。它首先从数据库中获取selected_choiceduixiang ,然后计算votes的新值。如果两个用户在同一时间投票,可能会出错:加入数据库中存储的是42,对于两个个同时投票的用户都会得到43的结果,但是实际上应该是44。
这被称为紊乱,如果你有兴趣,你可以阅读使用F()来避免紊乱

通用的view:代码更少

上一篇说提到的detail()和刚刚提到的results()都非常简短,并且冗余。index()也是类似的。

这些views体现了web开发的一个共同的情况:通过URL根据参数从数据库中获取数据,加载一个模板,然后返回已渲染的模板。因为这台平常了,所以Django提供了一个更简洁的称为‘通用view’系统。

通用的view抽象了一般的模式,你写app甚至不需要写python代码。

让我们的投票app开始使用通用view系统,所以我们要删除自己的代码,执行下面的步骤:

  1. 修改URLconf
  2. 删除旧的,不需要的views
  3. 引进基于Django通用的view。

一般来讲,当我们写Django app时,你会评估是否通用views适合你的问题,你应该一开始就使用它们,而不是半路使用它们重构你的代码。

Amend URLconf

首先,打开polls/urls.py URL配置文件,修改如下:

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.

Amend views

下一步,我们将要移除index,detail,results view,然后使用Django通用views代替。修改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.

这里我们使用了两个通用view:ListView和DetailView。对应的,它们两个view抽象了‘显示对象列表’和‘显示某一个对象的详情’的概念。

  • 每个通用view需要去知道它操作的模型是什么,所以使用了model参数。
  • DetailView期待重URL捕获到一个主键值(pk),所以我们使用pk替换了question_id。

默认情况下,DetailView使用一个叫做<app name>/<model name>_detail.html,在我们的例子中,它会使用"polls/question_detail.html"这个模板。template_name 参数用于告诉Django使用一个具体的名字代替默认的模板名称。我们也可以具体或results的模板名称。

同样地,ListView通用view使用了一个默认的模板叫做<app name>/<model name>_list.html,我们使用template_name告诉ListView使用我们存在的“polls/index.html”模板。

运行服务器,查看一下效果吧。

前一篇:开始Django之旅-part3_Django和view
后一篇:开始Django之旅-part5_自动测试你的代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值