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()方法:普通路徑和正則路徑均可使用,需要自己手動添加正則首位限制符號。
注意:
- urlpatterns中的元素按照書寫順序從上往下逐一匹配,一旦匹配成功則不在繼續
- 若要從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)