Django4.0 開始-編寫你的第一個Django應(yīng)用,第3部分

2022-03-16 18:05 更新

概況

Django 中的視圖的概念是「一類具有相同功能和模板的網(wǎng)頁的集合」。比如,在一個博客應(yīng)用中,你可能會創(chuàng)建如下幾個視圖:

  • 博客首頁——展示最近的幾項內(nèi)容。
  • 內(nèi)容“詳情”頁——詳細(xì)展示某項內(nèi)容。
  • 以年為單位的歸檔頁——展示選中的年份里各個月份創(chuàng)建的內(nèi)容。
  • 以月為單位的歸檔頁——展示選中的月份里各天創(chuàng)建的內(nèi)容。
  • 以天為單位的歸檔頁——展示選中天里創(chuàng)建的所有內(nèi)容。
  • 評論處理器——用于響應(yīng)為一項內(nèi)容添加評論的操作。

而在我們的投票應(yīng)用中,我們需要下列幾個視圖:

  • 問題索引頁——展示最近的幾個投票問題。
  • 問題詳情頁——展示某個投票的問題和不帶結(jié)果的選項列表。
  • 問題結(jié)果頁——展示某個投票的結(jié)果。
  • 投票處理器——用于響應(yīng)用戶為某個問題的特定選項投票的操作。

在 Django 中,網(wǎng)頁和其他內(nèi)容都是從視圖派生而來。每一個視圖表現(xiàn)為一個 Python 函數(shù)(或者說方法,如果是在基于類的視圖里的話)。Django 將會根據(jù)用戶請求的 URL 來選擇使用哪個視圖(更準(zhǔn)確的說,是根據(jù) URL 中域名之后的部分)。
URL 樣式是 URL 的一般形式 - 例如:?/newsarchive/<year>/<month>/?。
為了將 URL 和視圖關(guān)聯(lián)起來,Django 使用了 '?URLconfs?' 來配置。URLconf 將 URL 模式映射到視圖。

編寫更多視圖

現(xiàn)在讓我們向 ?polls/views.py? 里添加更多視圖。這些視圖有一些不同,因為他們接收參數(shù):

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)

把這些新視圖添加進(jìn) ?polls.urls? 模塊里,只要添加幾個 ?url()? 函數(shù)調(diào)用就行:

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'),
]

然后看看你的瀏覽器,如果你轉(zhuǎn)到 "/polls/5/" ,Django 將會運行 ?detail()? 方法并且展示你在 URL 里提供的問題 ID。再試試 "/polls/5/results/" 和 "/polls/5/vote/" ——你將會看到暫時用于占位的結(jié)果和投票頁。
當(dāng)某人請求你網(wǎng)站的某一頁面時——比如說, "/polls/5/" ,Django 將會載入 ?mysite.urls? 模塊,因為這在配置項 ?ROOT_URLCONF中設(shè)置了。然后 Django 尋找名為 ?urlpatterns變量并且按序匹配正則表達(dá)式。在找到匹配項 ?'polls/'?,它切掉了匹配的文本(?"polls/"?),將剩余文本——?"5/"?,發(fā)送至 'polls.urls' URLconf 做進(jìn)一步處理。在這里剩余文本匹配了 ?'<int:question_id>/'?,使得我們 Django 以如下形式調(diào)用 ?detail()?:

detail(request=<HttpRequest object>, question_id=34)

問題 ?question_id=34? 來自 ?<int:question_id>?。使用尖括號 "獲得" 網(wǎng)址部分后發(fā)送給視圖函數(shù)作為一個關(guān)鍵字參數(shù)。字符串的 ?question_id部分定義了要使用的名字,用來識別相匹配的模式,而 ?int ?部分是一種轉(zhuǎn)換形式,用來確定應(yīng)該匹配網(wǎng)址路徑的什么模式。冒號 (?:?) 用來分隔轉(zhuǎn)換形式和模式名。

寫一個真正有用的視圖

每個視圖必須要做的只有兩件事:返回一個包含被請求頁面內(nèi)容的 ?HttpResponse ?對象,或者拋出一個異常,比如 ?Http404 ?。
你的視圖可以從數(shù)據(jù)庫里讀取記錄,可以使用一個模板引擎(比如 Django 自帶的,或者其他第三方的),可以生成一個 PDF 文件,可以輸出一個 XML,創(chuàng)建一個 ZIP 文件,你可以做任何你想做的事,使用任何你想用的 Python 庫。
Django 只要求返回的是一個 ?HttpResponse,或者拋出一個異常。
因為 Django 自帶的數(shù)據(jù)庫 API 很方便,我們曾在 教程第 2 部分 中學(xué)過,所以我們試試在視圖里使用它。我們在 ?index()? 函數(shù)里插入了一些新內(nèi)容,讓它能展示數(shù)據(jù)庫里以發(fā)布日期排序的最近 5 個投票問題,以空格分割:

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)

# Leave the rest of the views (detail, results, vote) unchanged

這里有個問題:頁面的設(shè)計寫死在視圖函數(shù)的代碼里的。如果你想改變頁面的樣子,你需要編輯 Python 代碼。所以讓我們使用 Django 的模板系統(tǒng),只要創(chuàng)建一個視圖,就可以將頁面的設(shè)計從代碼中分離出來。
首先,在你的 ?polls ?目錄里創(chuàng)建一個 ?templates ?目錄。Django 將會在這個目錄里查找模板文件。
你項目的 ?TEMPLATES ?配置項描述了 Django 如何載入和渲染模板。默認(rèn)的設(shè)置文件設(shè)置了 ?DjangoTemplates ?后端,并將 ?APP_DIRS ?設(shè)置成了 True。這一選項將會讓 ?DjangoTemplates ?在每個 ?INSTALLED_APPS ?文件夾中尋找 "templates" 子目錄。這就是為什么盡管我們沒有像在第二部分中那樣修改 ?DIRS ?設(shè)置,Django 也能正確找到 polls 的模板位置的原因。
在你剛剛創(chuàng)建的 ?templates目錄里,再創(chuàng)建一個目錄 ?polls?,然后在其中新建一個文件 ?index.html? 。換句話說,你的模板文件的路徑應(yīng)該是 ?polls/templates/polls/index.html? 。因為?``app_directories``? 模板加載器是通過上述描述的方法運行的,所以 Django 可以引用到 ?polls/index.html? 這一模板了。

將下面的代碼輸入到剛剛創(chuàng)建的模板文件中:

{% 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視圖來使用模板:

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

上述代碼的作用是,載入 ?polls/index.html? 模板文件,并且向它傳遞一個上下文(context)。這個上下文是一個字典,它將模板內(nèi)的變量映射為 Python 對象。
用你的瀏覽器訪問 "/polls/" ,你將會看見一個無序列表。

一個快捷函數(shù): render()

「載入模板,填充上下文,再返回由它生成的 ?HttpResponse ?對象」是一個非常常用的操作流程。于是 Django 提供了一個快捷函數(shù),我們用它來重寫 ?index() ?視圖:

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)

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

拋出404錯誤

現(xiàn)在,我們來處理投票詳情視圖——它會顯示指定投票的問題標(biāo)題。下面是這個視圖的代碼:

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 所對應(yīng)的問題不存在,這個視圖就會拋出一個 ?Http404 ?異常。
我們稍后再討論你需要在 ?polls/detail.html? 里輸入什么,但是如果你想試試上面這段代碼是否正常工作的話,你可以暫時把下面這段輸進(jìn)去:

{{ question }}

這樣你就能測試了。

一個快捷函數(shù): get_object_or_404()

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

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()? 函數(shù)將 Django 模型作為其第一個參數(shù)和任意數(shù)量的關(guān)鍵字參數(shù),并將其傳遞給模型管理器的 ?get()? 函數(shù)。 如果對象不存在,它會引發(fā) ?Http404?。

也有 ?get_list_or_404()? 函數(shù),工作原理和 ?get_object_or_404()? 一樣,除了 ?get()? 函數(shù)被換成了 ?filter()? 函數(shù)。如果列表為空的話會拋出 ?Http404異常。

使用模板系統(tǒng)

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

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

模板系統(tǒng)統(tǒng)一使用點符號來訪問變量的屬性。在示例 ?{{ question.question_text }}? 中,首先 Django 嘗試對 ?question ?對象使用字典查找(也就是使用 ?obj.get(str)? 操作),如果失敗了就嘗試屬性查找(也就是 ?obj.str? 操作),結(jié)果是成功了。如果這一操作也失敗的話,將會嘗試列表查找(也就是 ?obj[int]? 操作)。
在 ?{% for %}? 循環(huán)中發(fā)生的函數(shù)調(diào)用:?question.choice_set.all? 被解釋為 Python 代碼 ?question.choice_set.all()? ,將會返回一個可迭代的 ?Choice ?對象,這一對象可以在? {% for %} ?標(biāo)簽內(nèi)部使用。

去除模板中的硬編碼URL

還記得嗎,我們在 ?polls/index.html里編寫投票鏈接時,鏈接是硬編碼的:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

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

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

這個標(biāo)簽的工作方式是在 ?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'),
...

為URL名稱添加命名空間

教程項目只有一個應(yīng)用,?polls。在一個真實的 Django 項目中,可能會有五個,十個,二十個,甚至更多應(yīng)用。Django 如何分辨重名的 URL 呢?舉個例子,?polls應(yīng)用有 ?detail視圖,可能另一個博客應(yīng)用也有同名的視圖。Django 如何知道 ?{% url %}? 標(biāo)簽到底對應(yīng)哪一個應(yīng)用的 URL 呢?
答案是:在根 URLconf 中添加命名空間。在 ?polls/urls.py? 文件中稍作修改,加上? app_name? 設(shè)置命名空間:

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'),
]

現(xiàn)在,編輯 ?polls/index.html? 文件,從:

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

修改為指向具有命名空間的詳細(xì)視圖:

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


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號