[part 4] 第一个 Django 应用

Part 4

继续上节,本章会对代码量进一步精简,并展示如何处理表单

表单

  • 表单循环输出单选按钮项,用 post 形式提交
  • 框架模板标记 {% csrf_token %} 用于防止跨站点请求伪造
  • 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>

投票

  • 下面代码实现了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):
        # 重示被提交的数据
        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'] 若未提交,则抛出 KeyError异常,向表单页渲染一个示错信息
  • reverse() 避免在视图功能中对 URL 进行硬编码, 其返回一个字符串,其作用类似于 url 生成器
  • 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>

vote() 视图代码段有一个小问题,selected_choice 对象先取后存数据库,若有两个用户在同一时间操作,可能产生数据竞争 解决一问题避免竞态条件,框架提供了 F() 函数

简化

  • detail() 与 results() 模板有一些相似,可抽象通用视图
  • 代码重构
    1. 修改 URLconf
    2. 删除一些不必要的视图
    3. 基于通用视图引新视图

      强化Django视图概念,与其他框架的控制器方法,动作之类的概念更相近

通用视图类

  • 修改 URLconf, 文件路径 polls/urls.py
  • 需要注意的是将 url 配置中的 <question_id> 变成了 <pk>.
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'),
]
  • 与此同时将视图函数转为视图类,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
  • DetailView 对应的模板默认格式 <app name>/<model name>_detail.html
    • template_name 则是显性指定模板名,会覆盖默认值
    • ListView 也遵从相同规则,只不过默认名后缀是_list.html
  • context_object_name 指定模板上下文对象变量名
    •  model = Question 隐式生成模板上下文对象变量 question

源码

第一个Django应用程序

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!