Django项目实战——用户投票系统(四)

Django项目实战——用户投票系统(四)

前情概要

在上一部分,我们开始了网页的初设,但其实有一个忽略

image-20220404104851442

因此完整HTML代码我将在本部分附上,以完善真正的保姆级教程

在本部分,我们开始了投票界面的编辑,同时,在该界面的编写中,我也从中感悟出了不少坑,希望可以通过我的笔下,让大家避坑。

正文开始

官方文档链接附上

编写你的第一个 Django 应用,第 4 部分 | Django 文档 | Django (djangoproject.com)

编写一个简单的表单

  1. 首先在上一个部分的html文档中,官方文档只给出了核心代码,因此我们需要给他补齐,

一个模板:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My test page</title>
  </head>
  <body>
    <p>This is my page</p>
  </body>
</html>

现在更新一下第三部分中编写的模板—— (“polls/detail.html”) ,让它包含一个 HTML <form> 元素:

polls/templates/polls/detail.html

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    {% 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 %}
</fieldset>
<input type="submit" value="Vote">
</form>

说明:

  • 该的作用是在 Question 的每个 Choice 前添加一个单选按钮。 每个单选按钮的 value 属性是对应的各个 Choice 的 ID。每个单选按钮的 name"choice" 。这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个 POST 数据 choice=# ,其中# 为选择的 Choice 的 ID。这是 HTML 表单的基本概念

  • 将表单的 action 设置为 {% url 'polls:vote' question.id %},并设置 method="post"

    使用 method="post" (而不是 method="get" )是非常重要的,

    因为提交这个表单的行为将改变服务器端的数据。当你创建一个改变服务器端数据的表单时,使用 method="post"。这不是 Django 的特定技巧;这是优秀的网站开发技巧

  • forloop.counter 指示 for 标签已经循环多少次。

  • 由于创建一个 POST 表单(它具有修改数据的作用),所以需要小心跨站点请求伪造。

    因为 Django 自带了一个非常有用的防御系统。 简而言之,所有针对内部 URL 的 POST 表单都应该使用 {% csrf_token %} 模板标签

  1. 下面创建一个真正的vote()模板

将下面的代码添加到 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 是一个类字典对象,让你可以通过关键字的名字获取提交的数据。 这个例子中, request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID。 request.POST 的值永远是字符串

  • 注意,Django 还以同样的方式提供 request.GET 用于访问 GET 数据 —— 但我们在代码中显式地使用 request.POST ,以保证数据只能通过 POST 调用改动

  • 如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息

  • 在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponseHttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL(请继续看下去,我们将会解释如何构造这个例子中的 URL)。

  • 在这个例子中,我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。 在本例中, reverse() 调用将返回一个这样的字符串:

    '/polls/3/results/'
    

    其中 3question.id 的值。重定向的 URL 将调用 'results' 视图来显示最终的页面

  1. 当在该页面投票后,

    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})
    
  2. 创建一个 polls/results.html 模板:

    polls/templates/polls/results.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>My test page</title>
      </head>
      <body>
        <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>
      </body>
    </html>
    
    

终于,现在再次打开该网址

My test page

然后为 Question 投票。你应该看到一个投票结果页面,并且在你每次投票之后都会更新。 如果你提交时没有选择任何 Choice,你应该看到错误信息

简化代码——使用通用视图

detail()results() 视图都很精简 —— 并且,像上面提到的那样,存在冗余问题。用来显示一个投票列表的 index() 视图和它们类似。

这些视图反映基本的网络开发中的一个常见情况:根据 URL 中的参数从数据库中获取数据、载入模板文件然后返回渲染后的模板。 由于这种情况特别常见,Django 提供一种快捷方式,叫做 “通用视图” 系统。

通用视图将常见的模式抽象化,可以使你在编写应用时甚至不需要编写Python代码。

现在将投票应用转换成使用通用视图系统,可以简化许多代码

步骤:

  • 转换URLconf
  • 删除一些旧的、不再需要的视图
  • 基于 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'),
]

注意,第二个和第三个匹配准则中,路径字符串中匹配模式的名称已经由 <question_id> 改为 <pk>

改良视图

将删除旧的 index, detail, 和 results 视图,并用 Django 的通用视图代替。打开 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.

解释:

  • 我们在这里使用两个通用视图: [ListView]和 [DetailView]。这两个视图分别抽象“显示一个对象列表”和“显示一个特定类型对象的详细信息页面”这两种概念。
  • 每个通用视图需要知道它将作用于哪个模型。 这由 model 属性提供
  • [DetailView] 期望从 URL 中捕获名为 "pk" 的主键值,所以我们为通用视图把 question_id 改成 pk
  • 更多关于通用视图的详细信息,请查看 通用视图的文档

未完待续

在第五部分,我们将对前面的成功继续精进,并且对投票应用进行测试。敬请期待

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值