Django路由層

一、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 時,那麼在反向解析時則會出現覆蓋。

例如:

不管輸入://127.0.0.1:8000/app01/index/ 還是 //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)

Tags: