Django進階之路由層和檢視層

Fredette發表於2024-03-10

Django的路由系統

【1】什麼是URL配置(URLconf)

  • URL排程器 | Django 文件 | Django (djangoproject.com)
  • URL配置(URLconf)就像Django 所支撐網站的目錄。
    • 它的本質是URL與要為該URL呼叫的檢視函式之間的對映表。
  • 你就是以這種方式告訴Django,對於這個URL呼叫這段程式碼,對於那個URL呼叫那段程式碼。

【2】基本語法

(1)Django1.x語法

from django.conf.urls import url
urlpatterns = [
     url(正規表示式, views檢視函式,引數,別名),
]
from django.conf.urls import url

urlpatterns = [
     url(r"^index/", views.index,引數,別名),
]

(2)Django2.x+

from django.urls import path
from django.urls import re_path
from . import views

urlpatterns = [
    re_path(r"^index/",views.index),
    path('articles/2003/', views.special_case_2003),
    path('articles/<int:year>/', views.year_archive),
    path('articles/<int:year>/<int:month>/', views.month_archive),
    path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
  • 注意
    • 要從 URL 中取值,使用尖括號。
    • 捕獲的值可以選擇性地包含轉換器型別。比如,使用 <int:name> 來捕獲整型引數。如果不包含轉換器,則會匹配除了 / 外的任何字元。
    • 這裡不需要新增反斜槓,因為每個 URL 都有。比如,應該是 articles 而不是 /articles

(3)引數說明

from django.conf.urls import url

urlpatterns = [
     path(路徑名, views檢視函式,引數,別名),
]
  • 路徑名:
    • 在瀏覽器埠後請求的路徑名
  • views檢視函式:
    • 一個可呼叫物件,通常為一個檢視函式或一個指定檢視函式路徑的字串
  • 引數:
    • 可選的要傳遞給檢視函式的預設引數(字典形式)
  • 別名:
    • 一個可選的name引數

【3】請求示例

  • /articles/2005/03/ 會匹配 URL 列表中的第三項。Django 會呼叫函式 views.month_archive(request, year=2005, month=3)
  • /articles/2003/ 將匹配列表中的第一個模式不是第二個,因為模式按順序匹配,第一個會首先測試是否匹配。請像這樣自由插入一些特殊的情況來探測匹配的次序。在這裡 Django 會呼叫函式 views.special_case_2003(request)
  • /articles/2003 不匹配任何一個模式,因為每個模式要求 URL 以一個斜線結尾。
  • /articles/2003/03/building-a-django-site/ 會匹配 URL 列表中的最後一項。Django 會呼叫函式 views.article_detail(request, year=2003, month=3, slug="building-a-django-site")

【4】使用正規表示式

  • 如果路徑和轉化器語法不能很好的定義你的 URL 模式,你可以可以使用正規表示式。如果要這樣做,請使用 re_path() 而不是 path()
  • 在 Python 正規表示式中,命名正規表示式組的語法是 (?P<name>pattern) ,其中 name 是組名,pattern 是要匹配的模式。
  • 這裡是先前 URLconf 的一些例子,現在用正規表示式重寫一下:
from django.urls import path, re_path

from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
]
  • 這實現了與前面示例大致相同的功能,除了:
    • 將要匹配的 URLs 將稍受限制。比如,10000 年將不在匹配,因為 year 被限制長度為4。
    • 無論正規表示式進行哪種匹配,每個捕獲的引數都作為字串傳送到檢視。
  • 當從使用 path() 切換到 re_path() (反之亦然),要特別注意,檢視引數型別可能發生變化,你可能需要調整你的檢視。

【一】路由匹配

【1】路徑引數相似

# 路由匹配
path('test',views.test),
path('testadd',views.testadd),

無法跳轉到 testadd

  • url方法第一個引數是路徑引數
  • 只要第一個引數能夠匹配到內容,就會立刻停止匹配,執行檢視函式

【2】解決路徑引數相似問題

# 路由匹配
path('test'/,views.test),
path('testadd/',views.testadd),
  • 在尾部加一個/
  • 在輸入url的時候會預設加一個/
  • 一次匹配不行,那就加一個/ 再嘗試一次

【3】路由系統自動新增 /

  • 在配置檔案中,有一個引數可以幫助我們幹這件事
APPEND_SLASH = True
  • 當設定為 True 時,如果請求的 URL 不符合 URLconf 中的任何模式,並且不以斜線結尾,則會發出一個 HTTP 重定向到相同的URL,並附加一個斜線。
  • 注意,重定向可能會導致 POST 請求中提交的任何資料丟失。

APPEND_SLASH 的配置只有在安裝了 CommonMiddleware 的情況下才會使用(參見 中介軟體)。也請參見 PREPEND_WWW

【4】完整的路由匹配

  • 這才是完整版的路由匹配格式
path('test/', views.test),
  • 匹配首頁的路由格式
path('', views.test),

【二】無名分組

  • 分組就是將某段正規表示式用()括起來
# 無名分組
re_path(r'^text/(\d+)/', views.test),
def test(request,data):
    print(data)
    return HttpResponse('test')
  • 無名分組就是將括號內正規表示式匹配到的內容當做位置引數傳給後面的檢視函式

【三】有名分組

  • 可以給正規表示式起一個別名
# 有名分組
re_path(r'^testadd/(?P<year>)\d+',views.testadd),
def testadd(request,year):
    print(year)
    
    return HttpResponse("testadd")
  • 有名分組就是將括號內正規表示式匹配到的內容當做關鍵字引數傳給後面的檢視函式

【四】無名有名混用

  • 無名分組和有名分組不能混用
# 無名有名混合使用
re_path(r'^index/(\d+)/(?P<year>\d+)/', views.index),

# 訪問路由獲取不到 (\d+) 的內容
  • 但是同一個分組可以使用多次
re_path(r'^test/(\d+)/(\d+)/',views.test),
re_path(r'^test/(?P<xxx>\d+)/(?P<year>\d+)/',views(r'^test/(?P<xxx>\d+)/(?P<year>\d+)/',viewsre_path.test),
def index(request,args,year):
    return HttpResponse("index")

【五】反向解析

【0】反向解析的本質

  • 先給路由起一個別名,然後,透過一些方法去解析別名,可以得到這個別名對應的路由地址

  • 先給路由與檢視函式起一個別名

    re_path(r'index/(\d+)/(\d+)/', views.func,name="index"),
    

【1】後端反向解析

from django.shortcuts import render, HttpResponse,reverse
def home(request):
  reverse('index', args=(1, 2)))

【2】前端反向解析

<a href="{% url 'index' 123 234 %}">111</a>

【3】無名分組的反向解析

  • 有名分組和無名分組的反向解析是一樣的
# 後端
reverse("路由對映的別名",args=(額外的引數,))

# 前端
<a href="{% url '路由對映的別名' 額外的引數 %}">跳轉連結</a>

【六】路由分發

【1】前言

  • Django每一個應用都可以擁有屬於自己的
    • templates資料夾
    • urls.py
    • static資料夾
  • 正是基於上述的特點,Django可以很好的做到自己的分組開發(每個人只寫自己的app)
  • 最後只需要將所有的app複製到新的檔案,並將這些APP註冊到配置檔案中,然後再利用路由分發的特點,將所有的APP整合起來
  • 當一個專案中的URL特別多的時候,總路由urls.py的程式碼非常冗餘而且不好維護,這個時候就可以利用路由分發來減輕總路由的壓力
  • 利用路由分發之後,總路由不再幹預路由與檢視函式的直接對應關係
    • 而是做一個分發處理
    • 識別當前的url是屬於哪個應用下的,直接分發給對應的應用去處理

【2】語法

  • 通常,我們會在每個app裡,各自建立一個urls.py路由模組,然後從根路由出發,將app所屬的url請求,全部轉發到相應的urls.py模組中。

(1)方式一:轉發app下的urls

  • 例如,下面是Django網站本身的URLconf節選。 它包含許多其它URLconf:
from django.urls import include, path

urlpatterns = [
    path('community/', include('aggregator.urls')),
    path('contact/', include('contact.urls')),
]
  • 路由轉發使用的是include()方法,需要提前匯入,它的引數是轉發目的地路徑的字串,路徑以圓點分割。
  • 每當Django 遇到include()時,它會去掉URL中匹配的部分並將剩下的字串傳送給include的URLconf做進一步處理,也就是轉發到二級路由去。

(2)方式二:轉發url列表

  • 另外一種轉發其它URLconf的方式是使用一個path()例項的列表。 例如,下面的URLconf:
from django.urls import include, path

from apps.main import views as main_views
from credit import views as credit_views

extra_patterns = [
    path('reports/', credit_views.report),
    path('reports/<int:id>/', credit_views.report),
    path('charge/', credit_views.charge),
]

urlpatterns = [
    path('', main_views.homepage),
    path('help/', include('apps.help.urls')),
    path('credit/', include(extra_patterns)),
]
  • 在此示例中,/credit/reports/URL將由credit_views.report()檢視處理。
  • 這種做法,相當於把二級路由模組內的程式碼寫到根路由模組裡一起了,不是很推薦。

【3】示例

(1)總路由

from django.contrib import admin
from django.urls import path, re_path, include

import app01
import app02

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/', include(app01.urls)),
    path('app02/', include(app02.urls)),
]

(2)子路由

# 自己的邏輯路徑
from django.contrib import admin
from django.urls import path, re_path, include
from app01 import login

urlpatterns = [
    path('login/', views.login),
]

【4】傳遞額外的引數給include()

  • 類似上面,也可以傳遞額外的引數給include()。引數會傳遞給include指向的urlconf中的每一行。
  • 例如,下面兩種URLconf配置方式在功能上完全相同:

django/urls.py

# 方式一:將所有的檢視函式都放在一個urls.py檔案下
from django.contrib import admin
from django.urls import path, include
from app01.views import register
from app01.views import register_two as register_two_01
from app02.views import register_two as register_two_02

urlpatterns = [
    path('admin/', admin.site.urls),
    # 註冊路由和檢視函式的對映關係
    path('register/', register),
    path('app01/register_two',register_two_01),
    path('app02/register_two',register_two_02)
]

方式二:
from django.urls.conf import include

urlpatterns = [
    path('app01/', include('app01.urls', namespace='app01')),
    path('app02/', include('app02.urls', namespace='app02')),
]

# app01
app_name ='app01'

urlpatterns =[
    path('register_two/',views.register_two)
]

# app02
app_name ='app02'

urlpatterns =[
    path('register_two/',views.register_two)
]

【七】名稱空間

【1】引入

  • 當多個應用出現相同的別名,反向解析不會自動識別應用字首
  • 正常情況下的反向解析是不能識別字首的

【2】使用

# 在主路由上註冊
path("app01/", include(("app01.urls", "app01"), namespace="app01")),
path("app02/", include(("app02.urls", "app02"), namespace="app02"))

# 在子路由 app01 
path("login/", views.login, name="login")

# app01/login/
# app02/login/

# 在前端解析路由地址的時候
{% url "app01:login" %}
{% url "app02:login" %}

【八】偽靜態

  • 靜態網頁
    • 資料是寫死的
  • 偽靜態
    • 將一個動態網頁偽裝成靜態網頁
  • 偽裝的目的在於增大本網站的seo查詢力度
    • 並且增加搜尋引擎收藏本網頁的機率
  • 搜尋引擎本質上就是一個巨大的爬蟲程式

總結:無論怎麼最佳化,怎麼處理,始終還是幹不過RMB玩家

【九】虛擬環境

  • 在正常開發中,我們會給每一個專案獨有的直譯器環境
  • 該環境內只有該專案用到的模組,用不到的一概不裝

【1】虛擬環境

  • 每建立一個虛擬環境就類似於重新下載了一個純淨的python直譯器
  • 但是虛擬器不建議下載太多,建立虛擬環境是需要消耗磁碟空間的

【2】模組管理檔案

  • 每個專案都需要用到很多模組,並且每個模組的版本可能是不一樣的
  • 這種情況下我們會給每一個專案配備一個requirements.txt檔案,裡面存放著我們這個專案所安裝的所有模組及版本
  • 只需要一條命令即可安裝所有模組及版本

【3】模組檔案匯出和安裝

(1)匯出專案模組檔案

pip freeze > requirements.txt
  • 這行命令的含義是 "freeze"(凍結)當前環境中的所有已安裝包及其版本資訊,並將其輸出重定向到 requirements.txt 檔案中。
  • 執行此命令後,requirements.txt 將列出你專案所需的全部依賴及其對應版本號。

(2)安裝專案模組檔案

pip install -r requirements.txt
  • 這將按照 requirements.txt 中列出的順序及版本要求安裝相應的 Python 包。

【十】Django版本的區別

【1】路由匹配規則

  • Django1.x路由層使用的是url方法
  • 在Django2.x版本以後在路由層使用的是path方法
    • url() 第一個引數支援正則
    • path() 第一個引數不支援正則,寫什麼就匹配到什麼

【2】正則匹配規則

  • 在Django2.x以後也可以使用正則表單式,但是使用的方法是re_path
from django.urls import path, re_path

re_path(r'^fuc/(?P<year>\d+)', views.func)

# 等價於

url(r'^fuc/(?P<year>\d+)', views.func)

【3】路徑轉換器

(1)初識

  • 雖然path不支援正則,但是其內部支援五種轉換器
path('index/<int:id>/',views.index)
# 將第二個路由裡面的內容先轉成整型,然後以關鍵字的形式傳遞給後面的檢視函式
from django.urls import path,re_path

from app01 import views

urlpatterns = [
    # 問題一的解決方案:
    path('articles/<int:year>/', views.year_archive), 
    # <int:year>相當於一個有名分組,其中int是django提供的轉換器,相當於正規表示式,專門用於匹配數字型別,而year則是我們為有名分組命的名,並且int會將匹配成功的結果轉換成整型後按照格式(year=整型值)傳給函式year_archive

    # 問題二解決方法:用一個int轉換器可以替代多處正規表示式
    path('articles/<int:article_id>/detail/', views.detail_view), 
    path('articles/<int:article_id>/edit/', views.edit_view),
    path('articles/<int:article_id>/delete/', views.delete_view),
]

(2)五種轉換器

  • str
    • 匹配除了 '/' 之外的非空字串。
    • 如果表示式內不包含轉換器,則會預設匹配字串。
  • int
    • 匹配 0 或任何正整數。返回一個 int 。
  • slug
    • 匹配任意由 ASCII 字母或數字以及連字元和下劃線組成的短標籤。
    • 比如,building-your-1st-django-site 。
  • uuid
    • 匹配一個格式化的 UUID 。為了防止多個 URL 對映到同一個頁面,必須包含破折號並且字元都為小寫。
    • 比如,075194d3-6885-417e-a8a8-6c931e272f00。返回一個 UUID 例項。
  • path
    • 匹配非空欄位,包括路徑分隔符 '/' 。
    • 它允許你匹配完整的 URL 路徑而不是像 str 那樣匹配 URL 的一部分。

【4】自定義轉換器

  • Django支援自定義轉換器

(1)初識

  • 轉換器是一個類,包含如下內容:
    • 字串形式的 regex 類屬性。
    • to_python(self, value) 方法,用來處理匹配的字串轉換為傳遞到函式的型別。如果沒有轉換為給定的值,它應該會引發 ValueErrorValueError 說明沒有匹配成功,因此除非另一個 URL 模式匹配成功,否則會向使用者傳送404響應。
    • 一個 to_url(self, value) 方法,它將處理 Python 型別轉換為字串以用於 URL 中。如果不能轉換給定的值,它應該引發 ValueErrorValueError 被解釋為無匹配項,因此 reverse() 將引發 NoReverseMatch,除非有其他 URL 模式匹配。

Changed in Django 3.1:

支援引發 ValueError 以表示沒有匹配項被新增。

(2)自定義轉換器示例

  • 在app01下新建檔案 path_ converters.py(檔名可以隨意命名)

    class FourDigitYearConverter:
        # 此屬性名為正規表示式(regex),用於匹配四位數的年份字串
        regex = r'[0-9]{4}' 
    
        def to_python(self, value):
            """
            將接收到的字串值解析為Python整數型別表示四位數的年份。
            示例:輸入 "2024" 會轉換為 2024
            """
            return int(value)
    
        def to_url(self, value):
            """
            根據給定四位數年份(value)將其格式化為URL安全的四位數字形式,例如 "2024" -> "2024".
            注意這裡的 "匹配的regex是四個數字" 應更改為 "此方法針對四位數的年份字串"
            
            例如:輸入 "2024" 會返回 "2024", 保持四位數且無前導零。
            """
            return '%04d' % value
    

(3)使用自定義轉換器

  • 在urls.py中,使用register_converter() 將其註冊到URL配置中:

    # 引入所需的模組和從當前目錄下的views.py匯入檢視函式
    from django.urls import path, register_converter
    from . import converters, views
    
    # 在Django應用中註冊自定義的日期轉換器類,即位於converters檔案中的FourDigitYearConverter
    # 併為其指定別名 'yyyy', 這個別名可以方便地在URL模式中引用該轉換器
    register_converter(converters.FourDigitYearConverter, 'yyyy')
    
    # 配置URL模式,其中:
    #   path('/articles/2003/', views.special_case_2003) - 特殊情況處理,當訪問 '/articles/2003/' 時呼叫 special_case_2003 檢視函式
    #   path('articles/<yyyy:year>/', views.year_archive) - 處理常規年份歸檔頁面請求,
    #       其中 '<yyyy:year>' 表示 URL 模板中 '<year>' 前面帶有 'yyyy' 轉換器別名,
    #       Django將在實際匹配到的 URL 字串中找到四位數的年份並傳遞給 views.year_archive 檢視函式作為引數
    
    urlpatterns = [
        path('articles/2003/', views.special_case_2003),
        path('articles/<yyyy:year>/', views.year_archive),
        ...
    ]
    

(4)轉換器使用示例

  • app01/path_converters.py
class MonthConverter:
    regex = '\d{2}'  # 屬性名必須為regex

    def to_python(self, value):
        return int(value)

    def to_url(self, value):
        return value  # 匹配的regex是兩個數字,返回的結果也必須是兩個數字
  • urls.py
from django.contrib import admin
from django.urls import path
from app01 import views

register_converter(converters.FourDigitYearConverter, 'yyyy')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('articles/<int:year>/<int:month>/<slug:other>/', views.article_detail)
    # 針對路徑http://127.0.0.1:8000/articles/2009/123/hello/,path會匹配出引數year=2009,month=123,other='hello'傳遞給函式article_detail
]
  • app01/views.py

    from django.shortcuts import render,HttpResponse,reverse
    from django.urls import reverse
    
    def article_detail(request, year, month, other):
        print(year, type(year))
        print(month, type(month))
        print(other, type(other))
        print(reverse('article_detail', args=(1988, 12, 'hello')))  # 反向解析結果/articles/1988/12/hello/
    
        '''
        2009 <class 'int'>
        12 <class 'int'>
        hello <class 'str'>
        /articles/1988/12/hello/
        '''
        return HttpResponse('xxxx')
    
  • 測試

    # 在瀏覽器輸入http://127.0.0.1:8000/articles/2009/12/hello/
    # path會成功匹配出引數year=2009,month=12,other='hello'傳遞給函式article_detail
    
    # 在瀏覽器輸入http://127.0.0.1:8000/articles/2009/123/hello/
    # path會匹配失敗,因為我們自定義的轉換器mon只匹配兩位數字,而對應位置的123超過了2位
    

【十一】Django返回JSON格式的資料兩種方式

【1】第一種方式json模組序列化

def register_two(request):
    data = {"username": "zhangsan", "password": "123321"}
    # 先用json序列化
    data = json.dumps(data)
    return HttpResponse(data)

image-20240303104932524

【2】第二種

def register_two(request):
    data = {"username": "zhangsan", "password": "123321"}
    resource = JsonResponse(data)
    return resource

image-20240303105413577

【十二】form表單上傳下載檔案資料

form表單上傳資料以及後端如何獲取

【1】資料準備

  • 路由urls.py
from app01.views import register, register_two
urlpatterns = [
    path('admin/', admin.site.urls),
    # 註冊路由和檢視函式的對映關係
    path('register/', register),
    path('app01/register_two/', register_two),
]
  • views
def register_two(request):
    if request.method == "POST":
        user_data = request.POST
        print(user_data)
        avatar = user_data.get("avatar")
        print(avatar,type(avatar))
        return HttpResponse('ok')
    return render(request,"register_two.html")
  • 前端register_two.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>這是主頁面</h1>
    <form action="" method="post">
        <div>username:  <input type="text" name="username"></div>
        <div>password:  <input type="password" name="password"></div>
        <div>avatar:    <input type="file" name="avatar"></div>
        <div><input type="submit"></div>
    </form>

</body>
</html>
# 最佳化-新增form表單引數
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>這是主頁面</h1>
    <form action="" method="post" enctype="multipart/form-data">
        <div>username:  <input type="text" name="username"></div>
        <div>password:  <input type="password" name="password"></div>
        <div>avatar:    <input type="file" name="avatar"></div>
        <div><input type="submit"></div>
    </form>

</body>
</html>
  • 後端沒有了avatar對應的鍵和值

【2】最佳化-後端獲取form的表單檔案資料

def register_two(request):
    if request.method == "POST":
        user_data = request.POST
        # file = request.FILES
        file_obj = request.FILES.get("avatar")
        # <QueryDict: {'username': ['zhang'], 'password': ['1']}>
        print(file_obj,type(file_obj))  # <MultiValueDict: {'avatar': [<TemporaryUploadedFile: 1709436763121.jpg (image/jpeg)>]}>
        # 1709436763121.jpg <class 'django.core.files.uploadedfile.TemporaryUploadedFile'>
        file_name = file_obj.name
        print(file_name)
        # 1709436763121.jpg
        # 儲存檔案到本地
        with open(f'{file_name}',mode='wb') as fp:
            for line in file_obj.chunks():
                fp.write(line)
        # 獲取檔名
        # file_name = file.name
        # print(user_data)
        avatar = user_data.get("avatar")
        return HttpResponse('ok')
    return render(request, "register_two.html")
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>這是主頁面</h1>
    <form action="" method="post" enctype="multipart/form-data">
        <div>username:  <input type="text" name="username"></div>
        <div>password:  <input type="password" name="password"></div>
        <div>avatar:    <input type="file" name="avatar"></div>
        <div><input type="submit"></div>
    </form>

</body>
</html>

【十三】request物件方法補充

【1】request.method

  • request.method:該方法返回客戶端用於發起請求的HTTP方法。
  • 例如,可以是'GET'、'POST'、'PUT'、'DELETE'等。
  • 您可以使用該方法來確定請求的型別,並相應地執行特定操作。

【2】request.POST

  • request.POST:該屬性是一個類似字典的物件,包含了請求中透過POST方法傳送的所有引數。
  • 這些引數通常是透過HTML表單傳送的。
  • 您可以使用引數的名字作為鍵來訪問單個引數,例如request.POST['username']

【3】request.GET

  • request.GET:類似於request.POST,該屬性包含了請求中透過GET方法傳送的所有引數。
  • 這些引數通常會附加在URL之後,以問號分隔。
  • 您可以使用引數的名字作為鍵來訪問單個引數,例如request.GET['page']

【4】request.FILES

  • request.FILES:該屬性是一個類似字典的物件,包含了請求中透過檔案上傳元件傳送的所有檔案。
  • 當表單中包含檔案上傳欄位時,透過request.FILES可以訪問上傳的檔案。
  • 您可以使用檔案的名字作為鍵來訪問單個檔案,例如request.FILES['file']

【5】request.path

只能獲取到路由地址,無法獲取到引數

  • request.path:該屬性表示請求URL中的路徑部分。
  • 它包含在域名之後,在任何查詢引數之前。
  • 例如,如果請求的URL是"http://example.com/foo/bar/",那麼request.path將為"/foo/bar/"。

【6】request.path_info

只能獲取到路由地址,無法獲取到引數

  • 用於表示請求URL的路徑部分,不包括域名和查詢引數。
  • request.path 相比,request.path_info 更加原始和未經解析。
  • 它保留了URL路徑中的任何編碼、特殊字元或斜槓等資訊。
  • 例如,對於以下請求URL:"http://example.com/foo/bar/?page=2",request.path_info 的值將是 "/foo/bar/"。
  • 通常情況下,您可以使用 request.path 來獲取丟棄域名後的路徑,而使用 request.path_info 來獲取原始的、未解析的路徑。這在某些情況下非常有用,例如當您需要對URL進行一些自定義操作或路由處理時。

【7】request.get_full_path()

即能獲取到路由地址又能獲取到完整的路由地址後面的引數

  • request.get_full_path():該方法返回請求URL的完整路徑,包括路徑部分和任何查詢引數。
  • 當您需要將完整URL作為字串使用時,這個方法非常有用。
  • 例如,如果請求的URL是"http://example.com/foo/bar/?page=2",request.get_full_path()將返回"/foo/bar/?page=2"。

【十四】CBV和FBV

  • 檢視函式既可以是函式也可以是類
  • 我們之前寫過的都是基於函式的view,就叫FBV。
    • 還可以把view寫成基於類的。

【1】FBV

# FBV版新增班級
# 匯入模組
from django.shortcuts import render, redirect
from . import models

# FBV版新增班級
def add_class(request):
    # 處理 POST 請求
    if request.method == "POST":
        # 從表單中獲取班級名稱
        class_name = request.POST.get("class_name")
        
        # 使用 Django 模型建立新的班級物件並儲存到資料庫
        models.Classes.objects.create(name=class_name)
        
        # 重定向到班級列表頁面
        return redirect("/class_list/")
    
    # 處理 GET 請求,渲染新增班級的表單頁面
    return render(request, "add_class.html")

【2】CBV

  • class類和路由之間的對映關係

(1)路由

  • 在檢視函式引入一個類
  • 在路由中註冊路由和檢視類之間的對映關係
# CBV 路由  -  根據請求方式的不同選擇不同的入口動作
path('login/', views.MyLogin.as_view())

(2)檢視

# 需要匯入類
from django.views import View

# 重寫一個類,繼承View
class Register(View):
    # get 請求
    def get(self, request, *args, **kwargs):
        print(request.GET)
        # 重寫get請求邏輯
        return render(request,'register_two.html')
    # post 請求
    def post(self, request, *args, **kwargs):
        # 處理POST請求攜帶的請求體資料
        print(request.POST)
        return HttpResponse("ok")

(3)小結

  • 選擇使用 FBV 還是 CBV 取決於具體的需求和個人偏好。
  • FBV 相對簡單直觀,適合編寫簡單的檢視邏輯;
  • 而 CBV 可以透過繼承和重寫類來實現程式碼複用和可擴充套件性,適用於複雜的檢視處理場景。
  • 在實際開發中,可以根據需求選擇適合的方式來編寫檢視處理函式或類。

【十五】CBV原始碼剖析

class View:
	# http_method_names : 存放了我們常用的請求方式
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
	
    # 繫結給類的函式,繫結方法
    @classonlymethod
    def as_view(cls, **initkwargs):
        # **initkwargs : 可變長關鍵字引數
        for key in initkwargs:
            # print(f"key :>>>> {key}") # key :>>>> pattern_name
            # 可變長關鍵字引數中的每一個鍵值對
            # 我們自己寫檢視類中沒有定義過 http_method_names ,只能從父類 View 裡面找
            if key in cls.http_method_names:
                # 
                raise TypeError(
                    'The method name %s is not accepted as a keyword argument '
                    'to %s().' % (key, cls.__name__)
                )
            # hasattr : 獲取到當前物件中的屬性
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))
		
        # 上面都沒有報錯,走到了 view 函式里面
        def view(request, *args, **kwargs):
            # 獲取到當前類的物件 這個 cls ---> Register
            self = cls(**initkwargs)
            # 獲取到了一個 當前的get方法
            self.setup(request, *args, **kwargs)
            # 必須接受一個位置引數叫request
            if not hasattr(self, 'request'):
                raise AttributeError(
                    "%s instance has no 'request' attribute. Did you override "
                    "setup() and forget to call super()?" % cls.__name__
                )
            # 觸發了 dispatch 方法 ---> 獲取到了get函式的返回值 
            # render
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

    def setup(self, request, *args, **kwargs):
        # self : 當前Register例項化出來的物件
        # 有GET方法 並且 沒有 head屬性
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            # self.head 變成了我自己寫的 GET 方法
            self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs
	
    # 走到了dispatch方法
    def dispatch(self, request, *args, **kwargs):
		# request.method.lower() :當前請求方法轉全小寫 
        if request.method.lower() in self.http_method_names:
            # 獲取到了當前物件中的get方法的函式記憶體地址
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        # get方法的函式記憶體地址呼叫
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return HttpResponseNotAllowed(self._allowed_methods())

    def options(self, request, *args, **kwargs):
        """Handle responding to requests for the OPTIONS HTTP verb."""
        response = HttpResponse()
        response.headers['Allow'] = ', '.join(self._allowed_methods())
        response.headers['Content-Length'] = '0'
        return response

    def _allowed_methods(self):
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]
  • 當我們啟動Django專案時
  • 會自動觸發路由中的方法,呼叫 as_view 方法並自執行
  • 在執行後我們檢視 as_view 方法的原始碼 發現
    • 在依次給我們的物件賦值後,最終返回了一個自執行的 dispatch 方法
  • 於是我們又去檢視了 dispatch 方法
    • 在 dispatch 內部 ,先是將請求方式轉換並進行校驗
    • 然後開始校驗需要呼叫的方法的呼叫位置,校驗成功並拿到需要執行的方法執行
  • 在自己寫的類中如果有相關的方法,會首先呼叫我們重寫的類方法,並返回執行結果
    • 如果自己的類裡面沒有該方法 會去自己的父類中呼叫 父類的方法
      • 如果父類 以及 基類 都找不到則報錯,丟擲異常

【十六】CBV新增裝飾器

【1】給FBV加裝飾器

  • FBV本身就是一個函式,所以和給普通的函式加裝飾器無差:
def timer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        print(f"總耗時:>>>>{time.time() - start_time}s")
        return res

    return inner


@timer
def register(request):
    time.sleep(2)
    return HttpResponse("ok")

【2】CBV新增裝飾器的三種方式

  • 類中的方法與獨立函式不完全相同,因此不能直接將函式裝飾器應用於類中的方法 ,我們需要先將其轉換為方法裝飾器。
  • Django中提供了method_decorator裝飾器用於將函式裝飾器轉換為方法裝飾器。

(1)第一種

from django.utils.decorators import method_decorator
def timer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        print(f"總耗時:>>>>{time.time() - start_time}s")
        return res

    return inner


@timer
def register(request):
    time.sleep(2)
    return HttpResponse("ok")

# 第一種
class Login(View):
    @timer
    def get(self, request, *args, **kwargs):
        time.sleep(2)
        return HttpResponse("Login")

(2)第二種

from django.utils.decorators import method_decorator
# 第二種方式 : 藉助Django內建的公共函式 method_decorator
class Login(View):
@method_decorator(timer)
def get(self, request, *args, **kwargs):
    time.sleep(2)
    return HttpResponse("Login")

(3)第三種

from django.utils.decorators import method_decorator
def timer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        print(f"總耗時:>>>>{time.time() - start_time}s")
        return res

    return inner

class Login(View):
    

@timer
def register(request):
    time.sleep(2)
    return HttpResponse("ok")


# 第三種方式:重寫 dispatch 方法做攔截
def get(self,request,*args,**kwargs):
    time.sleep(2)
    return HttpResponse("login")

def dispatch(self,request,*args,**kwargs):
    start_time = time.time()
    # 可以什麼都不寫
    obj = super().dispatch(request,*args,**kwargs)
    # 可以放自己的類名和自己的物件 self
    obj = super(Login,self).dispatch(request, *args, **kwargs)
    print(f"總耗時 :>>>> {time.time() - start_time} s ")
    return obj

相關文章