1. 程式人生 > >Django學習4:form, generic views

Django學習4:form, generic views

Django學習4

 在經過前三節的學習後,基本瞭解了資料庫的連線,views的使用,但是還需要了解如何傳回資料並處理,這裡第四節學習的內容就是如何去用form來獲取資料。

step1:熟悉form

 將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>

 這其中需要解釋的是forloop.counter是表示當前迴圈進行了幾次。而{% csrf_token %}是一個Django 提供的template標記用來防止跨站偽造請求。  這之後由於表單提交給了vote頁面,所以去重寫vote的view來達到能夠登記提交的查詢的目的。  題外話,這裡可以探討一下id和name的作用,可以參考 stackoverflow the difference between id and name。  在vote這個view中就要實現:接收資訊,查詢資訊,更新資訊,顯示結果頁面,程式碼如下:

from django.shortcuts import render, get_object_or_404
# Create your views here.
from django.http import HttpResponse, HttpResponseRedirect
from .models import Question
from django.template import loader
from django.http import Http404
from django.urls import reverse


def vote(request, question_id):
    #request直接接收資訊
    question = get_object_or_404(Question, pk=question_id)
    try:
        #用get來查詢是否存在choice,並且raise自己定義的error_message
        select_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:
        #更新資料並且重定向到result頁面
        select_choice.vote += 1
        select_choice.save()
        return HttpResponseRedirect(
            reverse('polls:results', args=(question.id,))
        )

 上述程式碼中有一些需要解釋的程式碼:

  1. request.post是一個字典型的資料,可以通過key name來訪問傳入的資料,需要注意的是其中的資料皆為string型別
  2. request.post['choice']如果沒有在傳入資料中有有效值的情況下,會raise一個keyerror錯誤,當出現這個錯誤的時候,本程式碼的處理方式就是重新返回detail.html並給出錯誤資訊。
  3. 當處理完成post資料後,一般都會使用HttpResponseRedirect()來重定位頁面。這裡使用了reserve這個函式來定向到result這個view並且給出了question_id,也就是說這裡的reserve('polls:result', args=(question.id))相當於重定向到了"polls/question.id/results"頁面。

 重寫一下result view來滿足需求:

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

 新增一個result的頁面:

<body>
<h1>{{ question.question_text }}</h1>
<ur>
    {% for choice in question.choice_set.all %}
    <li>
        {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
    </li>
    {% endfor %}
</ur>
<a href="{ url 'polls:detail' question.id %}">Vote again?</a>
</body>

 上述程式碼每次就會顯示每個choice的選擇情況。值得注意學習的是這裡的{{ choice.votes|pluralize }}是根據votes的情況來加複數"s"。  需要補充的是,這個程式是有問題的,當兩個人同時使用這個vote時,會導致資料的錯誤,可以使用F()函式來避免這一問題,詳情見F()

step2:通用view

 我們可以看到在detail中和results中的view的程式碼大同小異,此時我們就可以來簡化程式碼,所以我們可以利用Django來做一個通用類的template來簡化程式碼。  我們需要經歷以下步驟:

  1. 改變urls的配置
  2. 刪除老的無用程式碼
  3. 使用新的generic views

 那麼首先,需要對polls/urls.py進行修改:

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.views import generic


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        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'

 上述程式碼利用了兩個generic view,分別是Listview和DetailView。他們分別用於顯示一個物件的list和顯示一個具體型別的物件的detail頁面。  每一個generic都需要知道它所作用的model,所以提供了model屬性來指明model。  DetailView需要一個叫做pk的主鍵,所以在urls.py中我們使用pk來代替question_id。  在DetailView中,通常會使用"< app bane>/< model name>_detail.html"不過我們這裡用template_name來指定了頁面,同理ListView。  在DetailView中,當我們使用了model:Question,就會自動命名object的名字是question,然後我實驗了ListView好像也是自動提供的,(把question改名程question2就不行了)。同時DetailView還會自動提供context的名字為question_list不過我們可以自己用context_objetc_name來改變名字。