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

2022-03-16 18:05 更新

概況

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

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

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

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

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

編寫更多視圖

現(xiàn)在讓我們向 ?polls/views.py? 里添加更多視圖。這些視圖有一些不同,因?yàn)樗麄兘邮諈?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? 模塊里,只要添加幾個(gè) ?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 將會(huì)運(yùn)行 ?detail()? 方法并且展示你在 URL 里提供的問(wèn)題 ID。再試試 "/polls/5/results/" 和 "/polls/5/vote/" ——你將會(huì)看到暫時(shí)用于占位的結(jié)果和投票頁(yè)。
當(dāng)某人請(qǐng)求你網(wǎng)站的某一頁(yè)面時(shí)——比如說(shuō), "/polls/5/" ,Django 將會(huì)載入 ?mysite.urls? 模塊,因?yàn)檫@在配置項(xiàng) ?ROOT_URLCONF中設(shè)置了。然后 Django 尋找名為 ?urlpatterns變量并且按序匹配正則表達(dá)式。在找到匹配項(xiàng) ?'polls/'?,它切掉了匹配的文本(?"polls/"?),將剩余文本——?"5/"?,發(fā)送至 'polls.urls' URLconf 做進(jìn)一步處理。在這里剩余文本匹配了 ?'<int:question_id>/'?,使得我們 Django 以如下形式調(diào)用 ?detail()?:

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

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

寫一個(gè)真正有用的視圖

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

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

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

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? 模板文件,并且向它傳遞一個(gè)上下文(context)。這個(gè)上下文是一個(gè)字典,它將模板內(nèi)的變量映射為 Python 對(duì)象。
用你的瀏覽器訪問(wèn) "/polls/" ,你將會(huì)看見(jiàn)一個(gè)無(wú)序列表。

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

「載入模板,填充上下文,再返回由它生成的 ?HttpResponse ?對(duì)象」是一個(gè)非常常用的操作流程。于是 Django 提供了一個(gè)快捷函數(shù),我們用它來(lái)重寫 ?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 ?。不過(guò)如果你還有其他函數(shù)(比如說(shuō) ?detail?, ?results?, 和 ?vote ?)需要用到它的話,就需要保持 ?HttpResponse ?的導(dǎo)入。

拋出404錯(cuò)誤

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

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

這里有個(gè)新原則。如果指定問(wèn)題 ID 所對(duì)應(yīng)的問(wèn)題不存在,這個(gè)視圖就會(huì)拋出一個(gè) ?Http404 ?異常。
我們稍后再討論你需要在 ?polls/detail.html? 里輸入什么,但是如果你想試試上面這段代碼是否正常工作的話,你可以暫時(shí)把下面這段輸進(jìn)去:

{{ question }}

這樣你就能測(cè)試了。

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

嘗試用 ?get()? 函數(shù)獲取一個(gè)對(duì)象,如果不存在就拋出 ?Http404 ?錯(cuò)誤也是一個(gè)普遍的流程。Django 也提供了一個(gè)快捷函數(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 模型作為其第一個(gè)參數(shù)和任意數(shù)量的關(guān)鍵字參數(shù),并將其傳遞給模型管理器的 ?get()? 函數(shù)。 如果對(duì)象不存在,它會(huì)引發(fā) ?Http404?。

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

使用模板系統(tǒng)

回過(guò)頭去看看我們的 ?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)一使用點(diǎn)符號(hào)來(lái)訪問(wèn)變量的屬性。在示例 ?{{ question.question_text }}? 中,首先 Django 嘗試對(duì) ?question ?對(duì)象使用字典查找(也就是使用 ?obj.get(str)? 操作),如果失敗了就嘗試屬性查找(也就是 ?obj.str? 操作),結(jié)果是成功了。如果這一操作也失敗的話,將會(huì)嘗試列表查找(也就是 ?obj[int]? 操作)。
在 ?{% for %}? 循環(huán)中發(fā)生的函數(shù)調(diào)用:?question.choice_set.all? 被解釋為 Python 代碼 ?question.choice_set.all()? ,將會(huì)返回一個(gè)可迭代的 ?Choice ?對(duì)象,這一對(duì)象可以在? {% for %} ?標(biāo)簽內(nèi)部使用。

去除模板中的硬編碼URL

還記得嗎,我們?cè)??polls/index.html里編寫投票鏈接時(shí),鏈接是硬編碼的:

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

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

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

這個(gè)標(biāo)簽的工作方式是在 ?polls.urls? 模塊的 URL 定義中尋具有指定名字的條目。你可以回憶一下,具有名字 ?'detail'? 的 URL 是在如下語(yǔ)句中定義的:

...
# 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名稱添加命名空間

教程項(xiàng)目只有一個(gè)應(yīng)用,?polls。在一個(gè)真實(shí)的 Django 項(xiàng)目中,可能會(huì)有五個(gè),十個(gè),二十個(gè),甚至更多應(yīng)用。Django 如何分辨重名的 URL 呢?舉個(gè)例子,?polls應(yīng)用有 ?detail視圖,可能另一個(gè)博客應(yīng)用也有同名的視圖。Django 如何知道 ?{% url %}? 標(biāo)簽到底對(duì)應(yīng)哪一個(gè)應(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)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)