django官方教材(3, 4)

我們接着上一小節繼續

官方教程:投票應用

第3節:視圖和模板 | 第4節:表單和通用視圖

我們的投票應用中,我們需要下列幾個視圖:

問題索引頁——展示最近的幾個投票問題。
問題詳情頁——展示某個投票的問題和不帶結果的選項列表。
問題結果頁——展示某個投票的結果。
投票處理器——用於響應用戶爲某個問題的特定選項投票的操作。

  1. 編寫更多的視圖
polls/views.py

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

把這些新視圖添加進 polls.urls 模塊裏,只要添加幾個 url() 函數調用就行:

polls/urls.py

from django.urls import path
from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]
  1. 寫一個真正有用的視圖
    每個視圖必須要做的只有兩件事:返回一個包含被請求頁面內容的 HttpResponse 對象,或者拋出一個異常,比如 Http404 。
polls/views.py
from django.http import HttpResponse
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

這裏有個問題:頁面的設計寫死在視圖函數的代碼裏的。如果你想改變頁面的樣子,你需要編輯 Python 代碼。所以讓我們使用 Django 的模板系統,只要創建一個視圖,就可以將頁面的設計從代碼中分離出來。

首先,在你的 polls 目錄裏創建一個 templates 目錄。Django 將會在這個目錄裏查找模板文件。
在你剛剛創建的 templates 目錄裏,再創建一個目錄 polls,然後在其中新建一個文件 index.html 。換句話說,你的模板文件的路徑應該是 polls/templates/polls/index.html 。因爲 Django 會尋找到對應的 app_directories ,所以你只需要使用 polls/index.html 就可以引用到這一模板了。

polls/templates/polls/index.html

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

然後,讓我們更新一下 polls/views.py 裏的 index 視圖來使用模板:

polls/views.py

from django.http import HttpResponse
from django.template import loader
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))
  1. 一個快捷函數: render()
    「載入模板,填充上下文,再返回由它生成的 HttpResponse 對象」是一個非常常用的操作流程。於是 Django 提供了一個快捷函數,我們用它來重寫 index() 視圖:
polls/views.py

from django.shortcuts import render
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

注意到,我們不再需要導入 loader 和 HttpResponse 。不過如果你還有其他函數(比如說 detail, results, 和 vote )需要用到它的話,就需要保持 HttpResponse 的導入。

  1. 拋出404錯誤
    現在,我們來處理投票詳情視圖——它會顯示指定投票的問題標題。下面是這個視圖的代碼:
polls/views.py

from django.http import Http404
from django.shortcuts import render
from .models import Question

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

這裏有個新原則。如果指定問題 ID 所對應的問題不存在,這個視圖就會拋出一個 Http404 異常。

我們稍後再討論你需要在 polls/detail.html 裏輸入什麼,但是如果你想試試上面這段代碼是否正常工作的話,你可以暫時把下面這段輸進去:

polls/templates/polls/detail.html

{{ question }}

這樣你就能測試了。

  1. 一個快捷函數: get_object_or_404()
    嘗試用 get() 函數獲取一個對象,如果不存在就拋出 Http404 錯誤也是一個普遍的流程。Django 也提供了一個快捷函數,下面是修改後的詳情 detail() 視圖代碼:
polls/views.py

from django.shortcuts import get_object_or_404, render
from .models import Question

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

爲什麼我們使用輔助函數 get_object_or_404() 而不是自己捕獲 ObjectDoesNotExist 異常呢?還有,爲什麼模型 API 不直接拋出 ObjectDoesNotExist 而是拋出 Http404 呢?
===》因爲這樣做會增加模型層和視圖層的耦合性。指導 Django 設計的最重要的思想之一就是要保證鬆散耦合。一些受控的耦合將會被包含在 django.shortcuts 模塊中。

  1. 使用模板系統
    回過頭去看看我們的 detail() 視圖。它向模板傳遞了上下文變量 question 。下面是 polls/detail.html 模板里正式的代碼:
polls/templates/polls/detail.html

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
  1. 去除模板中的硬編碼 URL
    還記得嗎,我們在 polls/index.html 裏編寫投票鏈接時,鏈接是硬編碼的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

問題在於,硬編碼和強耦合的鏈接,對於一個包含很多應用的項目來說,修改起來是十分困難的。然而,因爲你在 polls.urls 的 url() 函數中通過 name 參數爲 URL 定義了名字,你可以使用 {% url %} 標籤代替它:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

這個標籤的工作方式是在 polls.urls 模塊的 URL 定義中尋具有指定名字的條目。你可以回憶一下,具有名字 ‘detail’ 的 URL 是在如下語句中定義的:

...
#the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...

如果你想改變投票詳情視圖的 URL,比如想改成 polls/specifics/12/ ,你不用在模板裏修改任何東西(包括其它模板),只要在 polls/urls.py 裏稍微修改一下就行:

...
#added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
  1. 爲 URL 名稱添加命名空間
    教程項目只有一個應用,polls 。在一個真實的 Django 項目中,可能會有五個,十個,二十個,甚至更多應用。Django 如何分辨重名的 URL 呢?舉個例子,polls 應用有 detail 視圖,可能另一個博客應用也有同名的視圖。Django 如何知道 {% url %} 標籤到底對應哪一個應用的 URL 呢?

答案是:在根 URLconf 中添加命名空間。在 polls/urls.py 文件中稍作修改,加上 app_name 設置命名空間:

polls/urls.py

from django.urls import path
from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

現在,編輯 polls/index.html 文件,從:

polls/templates/polls/index.html¶
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

修改爲指向具有命名空間的詳細視圖:

polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

4節 表單

  1. 編寫一個簡單的表單
    讓我們更新一下投票詳細頁面的模板 (“polls/detail.html”) ,讓它包含一個 HTML 元素:
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>

簡要說明:

  • 上面的模板在 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 %} 模板標籤。

現在,讓我們來創建一個 Django 視圖來處理提交的數據。記住,在 教程第 3 部分 中,我們爲投票應用創建了一個 URLconf ,包含這一行:

> polls/urls.py

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

我們還創建了一個 vote() 函數的虛擬實現。讓我們來創建一個真實的版本。 將下面的代碼添加到 polls/views.py :

> 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 而不是常用的 HttpResponse 、 HttpResponseRedirect 只接收一個參數:用戶將要被重定向的 URL(請繼續看下去,我們將會解釋如何構造這個例子中的 URL)。

    As the Python comment above points out, you should always return an HttpResponseRedirect after successfully dealing with POST data. This tip isn’t specific to Django; it’s just good Web development practice.

  • 在這個例子中,我們在 HttpResponseRedirect 的構造函數中使用 reverse() 函數。這個函數避免了我們在視圖函數中硬編碼 URL。它需要我們給出我們想要跳轉的視圖的名字和該視圖所對應的 URL 模式中需要給該視圖提供的參數。 在本例中,使用在 教程第 3 部分 中設定的 URLconf, reverse() 調用將返回一個這樣的字符串:

‘/polls/3/results/’

其中 3 是 question.id 的值。重定向的 URL 將調用 ‘results’ 視圖來顯示最終的頁面。

正如在 教程第 3 節 中提到的,HttpRequest 是一個 HttpRequest 對象。更多關於 HttpRequest 對象的內容,請參見 請求和響應的文檔 。

當有人對 Question 進行投票後, 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})

這和 教程第 3節 中的 detail() 視圖幾乎一模一樣。唯一的不同是模板的名字。 我們將在稍後解決這個冗餘問題。
現在,創建一個 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>
  1. 改良 URLconf¶
    首先,打開 polls/urls.py 這個 URLconf 並將它修改成:
polls/urls.py

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> 改爲 。

改良視圖

下一步,我們將刪除舊的 index, detail, 和 results 視圖,並用 Django 的通用視圖代替。打開 polls/views.py 文件,並將它修改成:

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.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章