天天看點

民意調查Django實作(四)

版權聲明:您好,轉載請留下本人部落格的位址,謝謝 https://blog.csdn.net/hongbochen1223/article/details/50329533

我們接着上一小節的末尾開始學習,在上一個小節中,我們主要是了解了Django中的模闆,即templates的使用。在這個小節中,我們主要關注于簡單的表單處理并且裁剪我們的代碼。

編寫一個簡單的表單

我們來更新一下我們的detail模闆(“polls/detail.html”),添加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>
           

簡單描述一下:

- 上面的模闆為每一個question展示了一組單選按鈕。每一個按鈕的值(value)是相關問題的選項ID。每一個按鈕的名稱是”choice”。也就是意味着,當使用者選擇其中一個選項框并且送出表單的時候,将會發送post資料

choice=#

,其中#就是對應的選擇的選項的ID。這個是HTML表單的基本概念。

- 我們設定表單的action為

{% url 'polls:vote' question.id %}

,并且我們設定

method="post"

。使用post方式(和使用get方式對應)是非常重要的,因為送出表單的行為将會改變服務端的資料。無論你什麼時候建立能夠修改服務端資料的表單的時候,一定要使用

methon="post"

。這個技巧不是Django專有的。這個僅僅就是好的網頁開發技巧。

- forloop.counter标示for标簽在這個循環中出現的次數。

- 既然我們建立了一個POST表單(該表單可能會影響修改資料),我們就需要去擔心通過站點請求的僞裝。幸運的是,我們不必太過擔心,因為Django提供了一個簡單易用的系統來阻止他。簡單來說,目标是内部URLs的所有的POST表單都應該使用

{% crsf_token %}

模闆标簽。

現在,我們來建立處理送出資料的視圖。在上一個小節中,我們建立了包含下面這一行的URLconf。

polls/urls.py

url(r'^(?P<question_id>\d+)/vote/$',views.vote,name='vote'),           

我們也創造了一個vote()函數的臨時實作。現在我們來真正使用起來。向polls/views.py中添加下面的代碼:

# -*- coding:utf-8 -*-
from django.http import HttpResponse,HttpResponseRedirect
from django.shortcuts import get_object_or_404,render
from django.core.urlresolvers import reverse

from polls.models import Question

....

def vote(request,question_id):
    p = get_object_or_404(Question,pk=question_id)

    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExit):
        return render(request,'polls/detail.html',{
            'question':p,
            'errot_message':"You did not select a choice."
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 每次成功處理POST資料之後一定要傳回HttpResponseRedirect。
        # 這個可以避免二次送出
        return HttpResponseRedirect(reverse('polls/results',args=(p.id)))
           

該代碼包含下面幾個事情:

  • request.POST是一個類似于字典的對象,能夠使我們通過鍵名稱去通路送出的資料。在這個例子中,

    request.POST['choice']

    傳回的是選中的選項的ID,這裡是字元串。

    request.POST

    值一直是字元串。

    注意,Django也提供了

    request.GET

    去通路GET資料的方式,但是對于

    request.POST

    來說隻有在代碼中才能擷取到相應的資料,這個大家可以去了解get方式請求和post方式請求的差別。
  • 如果在POST資料中沒有提供choice選項,那麼

    request.POST['choice']

    将會抛出KeyError異常。上面的代碼檢查KeyError異常,如果沒有提供choice的話,重新顯示帶有錯誤資訊的question表單。
  • 在增加了選項數量之後,代碼傳回一個HttpResponseRedirect而不是一個正常的HttpResponse。HttpResponseRedirect使用一個單一的參數:也就是使用者将要重定向的url位址。
  • 在這個例子的HttpResponseRedirect結構中我們使用reverse()函數。這個函數幫助我們避免發生視圖函數中寫死一個URL位址。他被給予我們想要傳遞到的視圖名稱和指向視圖的URL正則中的變量部分。在這個例子中,使用我們在上一小節中設定的URLconf,最終reverse()調用将會傳回類似于下面的字元串:

    '/polls/3/results'

    ,其中的3是

    p.id

    的值。這個重定向URL然後調用’results’視圖去展示最終的頁面。

在使用者對相應問題投票之後,vote()視圖将會重定向到這個問題的results頁面。現在我們來重寫這個頁面:

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})
           

下面我們來添加results模闆:

建立一個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/中去為相應的問題去投票。每次投票後你都能看到相應的結果更新。如果你沒有選擇選項而點選送出的話,你将會看到錯誤資訊。

下面來看一下我的運作結果:

使用通用視圖:減少代碼更好

detail()和results()視圖是非常簡單的 - 就像是上面提到的,太備援了。展示投票清單的index()視圖也是一樣的。

這些視圖代表着基本Web開發的通用例子:通過URL傳來的參數從資料庫擷取資料,加載模闆并且傳回一個渲染模闆。因為這些都是雷同的,是以Django提供了一個快捷方式,成為”通用視圖”系統。

通用試圖系統抽象除了通用正則,這使得我們可能不需要編寫Python代碼也能構件一個應用。

下面讓我們的應用使用通用視圖,是以我們可以删除一大些代碼了。我們僅僅需要幾步就能做出轉換。

  1. 轉換URLconf
  2. 删除一些舊的,不需要的視圖
  3. 基于Django的通用視圖引入新的視圖

修改URLconf

首先,打開polls/urls.py并且像下面這樣修改:

from django.conf.urls import patterns,url

from polls import views

urlpatterns = patterns('',
    # ex: /polls/
    url(r'^$', views.IndexView.as_view(),name='index'),
    # ex: /polls/5/
    url(r'^(?P<pk>\d+)/$',views.DetailView.as_view(),name='detail'),
    # ex: /polls/5/results
    url(r'^(?P<pk>\d+)/results/$',views.ResultsView.as_view(),name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>\d+)/vote/$',views.vote,name='vote'),
)
           

注意,正則比對中間的

<question_id>

改成了

<pk>

修改視圖

下一步,我們将要移除我們舊的index,detail和results視圖,然後使用Django的通用視圖。打開polls/view.py檔案,并且像下面這樣修改:

# -*- coding:utf-8 -*-
from django.http import HttpResponse,HttpResponseRedirect
from django.shortcuts import get_object_or_404,render
from django.core.urlresolvers import reverse
from django.views import generic

from polls.models import 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):
    # 和原來一樣
           

我們使用了兩個通用視圖:ListView和DetailView。這兩個視圖抽象除了“顯示對象清單”和“現象特定對象詳細資訊”的概念。

  • 每一個通用視圖都需要知道他們操作的模型是什麼。這個通過

    model

    屬性提供
  • DetailView

    通用視圖需要從URL中擷取稱為”pk”的主鍵,這就是為什麼剛剛在urls.py中修改

    question_id

    pk

    的原因了。

預設情況下,DetailView通用視圖使用稱為

<app name>/<model name>_detail.html

作為模闆。在我們的例子中,他會使用模闆”polls/question_detail.html”。屬性

template_name

被用來告訴Django使用能夠哪一個模闆代替預設模闆。我們也為results指定了template_name。

相似的,ListView通用視圖使用預設的模闆,稱為

<app name>/<model name>_list.html

,我們同樣為他指定了我們自己的模闆。

在之前的小節中,模闆已經被提供了context,也就是上下文,該上下文包含question和latest_question_list變量。對于DetailView,question已經被自動提供了。我們使用Django的模型(Question),是以Django能夠決定為上下文變量使用一個近似的名稱。然而,對于ListView,自動生成的上下文變量是question_list。為了重寫,我們可以使用

context_object_name

屬性,指定我們想要使用的

latest_question_list

。這樣的話,通過Django通用視圖提供的屬性,我們就可以指定我們想使用的變量了。

下面我們來運作一下,看一下效果:

運作效果和上面的。