Django路由層

先生發表於2021-06-08

一、MVC和MTV框架

MVC

  • M 代表模型(Model)
  • V 代表檢視(View)
  • C 代表控制器(Controller)

Web伺服器開發領域裡著名的MVC模式,所謂MVC就是把Web應用分為模型(M),控制器(C)和檢視(V)三層,他們之間以一種外掛式的、鬆耦合的方式連線在一起,模型負責業務物件與資料庫的對映(ORM),檢視負責與使用者的互動(頁面),控制器接受使用者的輸入呼叫模型和檢視完成使用者的請求。

MTV

Django 的 MTV 模式本質上和MVC是一樣的,也是為了各元件間保持鬆耦合關係,只是定義上有些許不同,Django 的MTV分別是值:

  • M 代表模型(Model): 負責業務物件和資料庫的關係對映(ORM)。
  • T 代表模板 (Template):負責如何把頁面展示給使用者(html)。
  • V 代表檢視(View): 負責業務邏輯,並在適當時候呼叫Model和Template。

除了以上三層之外,還需要一個URL分發器,它的作用是將一個個URL的頁面請求分發給不同的View處理,View 再呼叫相應的 Model 和 Template

二、URL配置

URL配置:它的本質是URL與要為該URL呼叫的檢視函式之間的對映表。

Django 1.x版本

url()方法:普通路徑和正則路徑均可使用,需要自己手動新增正則首位限制符號。

注意:

  1. urlpatterns中的元素按照書寫順序從上往下逐一匹配,一旦匹配成功則不在繼續
  2. 若要從URL中捕獲一個值,只需要在它周圍放置一對圓括號(分組匹配)。
# urls.py檔案
from django.contrib import admin
from django.conf.urls import url		# 2.x之後,用 url 需要引用
from app01 import views					# 需要自行匯入檢視檔案 views.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),		# 新增一條
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render

# 新增檢視函式
def index(request):		# request 中包含請求資訊
    return render(request, 'index.html')	# 返回的 HTML 頁面

Django 2.x之後版本

path:用於普通路徑,不需要自己手動新增正則首位限制符號,底層已經新增。

re_path:用於正則路徑,需要自己手動新增正則首位限制符號。

# urls.py檔案
from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),                # 新增-普通路徑
    re_path(r'^books/\d{4}/$', views.books)     # 新增-正則路徑
]

# -------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, HttpResponse	# 匯入HttpResponse,用來生成響應資訊

def index(request):
    return render(request, 'index.html')		# 返回的 HTML 頁面

def books(request):
    return HttpResponse('Hello world')			# 響應資訊

三、分組

分組就是需要直接從路徑中取出引數,這就用到了正規表示式的分組功能了。

分組分為兩種:無名分組與有名分組

無名分組

無名分組按照位置傳參,需要一 一對應。

捕獲URL中的值並以位置引數形式傳遞給檢視

views 中除了request,其他形參的數量要與urls中的分組數量一致。

# ruls.py檔案
from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    re_path(r'^admin/', admin.site.urls),
    re_path(r'^books/(\d{4})/', views.books),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, HttpResponse

def books(request, year):
    print(year)			# 一個形參代表路徑中一個分組的內容,按順序匹配
    return HttpResponse(f'Hello world {year}')

有名分組

語法:

(?P<組名>正規表示式)

捕獲URL中的值並以關鍵字引數形式傳遞給檢視。

有幾個有名分組就要有幾個關鍵字引數

# urls.py檔案
from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    re_path(r'^admin/', admin.site.urls),
    # 匹配成功的分組部分會以關鍵字引數(name_id = 匹配成功的數字)的形式傳給檢視函式。
    re_path(r'^books/(?P<name_id>\d{2})/', views.books),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, HttpResponse

# 需要額外增加一個形參,形參名必須為 name_id
def books(request, name_id):
    return HttpResponse(f'Hello world {name_id}')

總結:有名分組和無名分組都是為了獲取路徑中的引數,並傳遞給檢視函式,區別在於無名分組是以位置引數的形式傳遞,有名分組是以關鍵字引數的形式傳遞。

四、路由分發(include)

存在的問題:Django專案裡有多個app共用一個 urls 容易造成混淆,後期維護不方便。

解決:使用路由分發(include),讓每個app目錄都單獨擁有自己的 urls。

步驟:

1、在各自 app 目錄下的建立 urls.py檔案, 並對 urls.py 檔案和 views.py 檔案中寫各自的路由和檢視函式。

app01下的 urls.py 檔案和 views.py 檔案

# urls.py檔案
from django.urls import path, re_path
from app01 import views		# 匯入app01的views


urlpatterns = [
    re_path(r'^index/', views.index),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, HttpResponse

def index(request):
    return HttpResponse('app01頁面')

app02下的 urls.py 檔案和 views.py 檔案

# urls.py檔案
from django.urls import path, re_path
from app02 import views		# 匯入app02的views


urlpatterns = [
    re_path(r'^index/', views.index),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, HttpResponse

def index(request):
    return HttpResponse('app02頁面')

2、在專案名稱目錄下的 urls 檔案裡面,統一將路徑分發給各個 app 目錄。

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

# 總路由表
urlpatterns = [
    re_path(r'^admin/', admin.site.urls),

    # 新增兩條路由,不能以$結尾
    # include函式就是做分發操作的。
    re_path(r'^app01/', include('app01.urls')),
    re_path(r'^app02/', include('app02.urls')),
]

五、反向解析

如果路由 層的 url 發生變化,就需要取更改對應的檢視層和模板層的 url ,非常麻煩,不便於維護。

所以可以利用反向解析,當路由層 url 發生變化,在檢視層和模組層動態反向解析出更改後的 url ,避免修改操作

反向解析一般用在模板中的超連結以及檢視中的重定向。

普通反向解析

登入成功跳轉到 index.html 頁面:如果 urls.py 檔案中的 index 路徑有所變化,views.py 檔案中不需要更改index路徑。

# urls.py檔案
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    # 路徑 login/的別名為login_page
    re_path(r'^login/', views.login, name='login_page'),
    # 路徑 index/的別名為index_page
    re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, reverse, redirect, HttpResponse

def login(request):
    # 當為GET請求時就返回登入頁面
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        # post 請求時,就提取出請求資料。
        user = request.POST.get('username')
        pwd = request.POST.get('password')
        if user == 'xiaoyang' and pwd == '123':
            # 會對別名反向解析成路徑/index/
            url = reverse('index_page')
            # 然後哦重定向到別名解析的路徑
            return redirect(url)
        else:
            return HttpResponse('登入失敗')

def index(request):
    return HttpResponse('登入成功!!!')

登入成功 HTML頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登入頁面</h1>

<!-- 這裡的 "{% url 'login_page' %}" 相當於路徑/login/-->
<form action="{% url 'login_page' %}" method="post">
    {% csrf_token %}    <!-- post提交需要做CSRF驗證 -->
    使用者名稱:<input type="text" name="username">
    密碼:<input type="password" name="password">
    <input type="submit">
</form>

</body>
</html>

分組反向解析

如果路徑存在分組的反向解析使用:

無名分組:reverse ( "路由別名", arges = ( 符合正則匹配的引數 ) )

有名分組:reverse ( "路由別名", arges = { "分組名":"符合正則匹配的引數" } )

# urls.py檔案
from django.urls import path, re_path
from app01 import views


urlpatterns = [
    # 無名分組
    re_path(r'^books/(\d{2})/', views.books, name='books_page'),
    # 有名分組
    re_path(r'^years/(?P<year>\d{4})/', views.years, name='years_page'),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, reverse, redirect, HttpResponse

# 有名分組的反向解析
def years(request, year):
    url = reverse('years_page', kwargs={'year': year})
    
    # HTML 檔案中:{% url 'years_page' 'year'=1234 %}
    return HttpResponse(url)

# 無名分組的反向解析
def books(request, ret):
    url = reverse('books_page', args=(ret,))
    
    # HTML 檔案中:{% url 'books_page' 34 %}
    return HttpResponse(url)

六、名稱空間

Django專案裡有多個app,當在不同的 app 目錄下的 urls.py 檔案中定義了相同的路由別名 name 時,那麼在反向解析時則會出現覆蓋。

例如:

不管輸入:http://127.0.0.1:8000/app01/index/ 還是 http://127.0.0.1:8000/app02/index/ 都得到的是 /app02/index/ app02路徑的別名覆蓋了app01 路徑的別名

app01下的 urls.py 檔案和 views.py 檔案

# urls.py檔案
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, reverse, HttpResponse

def index(request):
    url = reverse('index_page')
    return HttpResponse(url)

app02下的 urls.py 檔案和 views.py 檔案

# urls.py檔案
from django.urls import path, re_path
from app02 import views

urlpatterns = [
    re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, reverse, HttpResponse

def index(request):
    url = reverse('index_page')
    return HttpResponse(url)

專案名稱目錄下的 urls 檔案裡面,統一將路徑分發給各個 app 目錄。

# urls.py檔案
from django.urls import path, re_path, include

# 總路由表
urlpatterns = [
    re_path(r'^app01/', include('app01.urls')),
    re_path(r'^app02/', include('app02.urls')),
]

對於這種問題的解決方法就是避免使用相同的別名,如果要使用相同的別名,那就需要將別名放到不同的名稱空間中去,這樣就避免了即使出現了重複,彼此也不會衝突。

解決方法:

格式:

# 檢視中的名稱空間的方向解析
url=reverse('名稱空間的名字:待解析的別名')
# 模板中的名稱空間的反向解析
<a href="{% url '名稱空間的名字:待解析的別名'%}">小楊</a>

1、在 urls.py 路由分發時指定名稱空間

專案名稱目錄下的 urls 檔案裡面,統一將路徑分發給各個 app 目錄。

# url.py檔案
from django.urls import path, re_path, include

# 總路由表
urlpatterns = [
    # 給include傳遞一個元組,第一個是路由分發的地址,第二個是我們自定義的名稱空間名字
    re_path(r'^app01/', include(('app01.urls', 'app01'))),
    re_path(r'^app02/', include(('app02.urls', 'app02'))),
]

2、修改每個app下的view.py中檢視函式,對不同名稱空間的別名做反向解析

app01下的 urls.py 檔案和 views.py 檔案

# urls.py檔案
from django.urls import path, re_path
from app01 import views

urlpatterns = [
        re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, HttpResponse, reverse

def index(request):
    # 解析的是app01下的別名‘index_page’
    url = reverse('app01:index_page')
    return HttpResponse(url)

app02下的 urls.py 檔案和 views.py 檔案

# urls.py檔案
from django.urls import path, re_path
from app02 import views

urlpatterns = [
    re_path(r'^index', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py檔案
from django.shortcuts import render, reverse, HttpResponse

def index(request):
    # 解析的是app02下的別名‘index_page’
    url = reverse('app02:index_page')
    return HttpResponse(url)

相關文章