10 Django 中傳遞引數給檢視函式
Django 框架中推薦使用一個單獨的 python 模組配置 URL 和檢視函式或者檢視類的對映關係,通常稱這個配置模組為 URLconf,該 Python 模組通常命名為 urls.py
。一般而言,每個應用目錄下都會有一個 urls.py
檔案,總的對映關係入口在專案目錄下的 urls.py
中,而這個位置又是在 settings.py
檔案中指定的。
本小節中將會學習 Django 中的路由系統、URLconf 的配置,以及如何將請求引數,如表單資料、檔案等傳遞給檢視函式。
1. 測試環境準備
這裡的實驗環境會採用前面建立的第一個 Django 工程(first_django_app) 來進行測試。在 first_django_app 中,我們建立了第一個 django app 應用:hello_app。現在按照如下步驟準備實驗環境:
在 hello_app 應用目錄下的 views.py
中新增一個檢視函式:
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def hello_view(request, *args, **kwargs):
return HttpResponse('hello, world!')
在 hello_app 應用目錄下新建 urls.py
檔案,裡面內容如下:
from django.urls import path
from . import views
urlpatterns = [
# 資產查詢介面,根據我們自己的機器命名
path('world/', views.hello_view, name='hello_view'),
]
在 settings.py
檔案中註冊應用:
# first_django_app/settings.py
...
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes' ,
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 註冊應用
'hello_app'
]
...
在 URLconf 的總入口位置使用 django 提供的 include 方法將應用中的 urls.py
中的 urlpatterns 新增進來:
from django.contrib import admin
from django.conf.urls import include, url
# 所有url入口
urlpatterns = [
url('admin/', admin.site.urls),
url('hello/', include('hello_app.urls')),
]
這樣之後,我們請求地址 /hello/world/
時,就能看到頁面顯示 “hello, world!” 字元了。後面的測試將在應用的 urls.py
和 views.py
中進行。
2. Django 中的傳參方式
Django 中傳遞引數給檢視函式的方式主要可分為以下兩種形式:URL 傳參和非 URL 傳參兩種。第一種基於 Django 中的 URLconf 配置,可以通過 URL 路徑將對應匹配的引數傳到檢視函式中;而另外一種就是屬於HTTP 請求攜帶的引數了,請求引數可以放到 URL 中以 ?
格式加到 URL 的最後面,也可以將引數放到請求 body 中,最後統一由檢視函式中的 request 引數儲存並傳到檢視函式中。
2.1 動態 URL 傳參
在 url 的路徑 (path)部分可以作為動態引數,傳遞給檢視函式,如下面幾種寫法:
# hello_app/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<slug:title>/', views.article_title),
]
注意上面的定義了匹配三個動態 URL 的對映,每個動態 URL 會匹配一個至多個引數,每個動態值使用 <> 符號匹配,採用 <type:name>
這樣的形式。我們對應的檢視函式如下:
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def year_archive(request, year, *args, **kwargs):
return HttpResponse('hello, {} archive\n'.format(year))
def month_archive(request, year, month, *args, **kwargs):
return HttpResponse('hello, month archive, year={}, moth={}!\n'.format(year, month))
def article_title(request, year, month, title, *args, **kwargs):
return HttpResponse('hello, title archive, year={}, month={}, title={}!\n'.format(year, month, title))
對於動態的 URL 表示式中,匹配到的值,比如上面的 year,month 和 title 可以作為函式的引數放到對應的檢視函式中,Django 會幫我們把匹配到的引數對應的放到函式的引數上。這裡引數的位置可以任意寫,但是名字必須和 URL 表示式中的對應。
[root@server first_django_app]# curl http://127.0.0.1:8881/hello/articles/1998/
hello, 1998 archive
[root@server first_django_app]# curl http://127.0.0.1:8881/hello/articles/1998/12/
hello, month archive, year=1998, moth=12!
[root@server first_django_app]# curl http://127.0.0.1:8881/hello/articles/1998/12/test/
hello, title archive, year=1998, month=12, title=test
比如 URL 中有 3 個動態引數,在檢視函式中只寫上兩個引數接收也是沒問題的,因為剩下的引數會被傳到 kwargs 中以 key-value 的形式儲存:
(django-manual) [root@server first_django_app]# cat hello_app/views.py
...
def article_title(request, year, month, *args, **kwargs):
return HttpResponse('hello, title archive, year={}, month={}, kwargs={}\n'.format(year, month, kwargs))
# 啟動服務,再次請求後
[root@server first_django_app]# curl http://127.0.0.1:8881/hello/articles/1998/12/test/
hello, title archive, year=1998, month=12, kwargs={'title': 'test'}
上述介紹的動態 URL 匹配格式 <type:name>
中,Django 會對捕捉到的 URL 引數進行強制型別裝換,然後賦給 name 變數,再傳到檢視函式中。其中 Django 框架中支援的轉換型別有:
-
str:匹配任意非空字元,不能匹配分隔符 “/”;
-
int:匹配任意大於0的整數;
-
slug:匹配任意 slug 字串, slug 字串可以包含任意的 ASCII 字元、數字、連字元和下劃線等;
-
uuid:匹配 UUID 字串;
-
path:匹配任意非空字串,包括 URL 的分隔符 “/”。
2.2 自定義URL引數型別轉換器
除了 Django 定義的簡單型別,我們還可以自定義引數型別轉換器來支援更為複雜的 URL 場景。比如前面的 int 型別並不支援負整數,我希望開發一個能匹配正負數的型別,具體的步驟如下:
在 hello_app/urls.py
中定義一個 SignInt 類。該類有一個固定屬性 regex,用於匹配動態值;兩個固定方法:to_python()
方法和 to_url()
方法:
# hello_app/urls.py
class SignInt:
regex = '-*[0-9]+'
def to_python(self, value):
# 將匹配的value轉換成我們想要的型別
return int(value)
def to_url(self, value):
# 反向生成url時候回用到
return value
註冊該定義的轉換器,並給出一個簡短的名字,比如 sint:
# hello_app/urls.py
from django.urls import converters, register_converter
register_converter(SignInt, 'sint')
...
最後,我們就可以在 URLconf 中使用該型別來配置動態的 URL 表示式:
# hello_app/urls.py
urlpatterns = [
path('articles/<sint:signed_num>/', views.signed_convert),
]
# hello_app/views.py
def signed_convert(request, signed_num, **kwargs):
return HttpResponse('hello, 自定義型別轉換器,獲取引數={}\n'.format(signed_num))
啟動 Django 服務後,執行相關請求,可以看到 Django 的路由系統能成功匹配到帶負數和正數的 URL:
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/
hello, 自定義型別轉換器,獲取引數=1998
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/-1998/
hello, 自定義型別轉換器,獲取引數=-1998
2.3 使用正則表示式
上面是比較簡單的 URLconf 配置形式,Django 框架中可以使用正則表示式來進一步擴充套件動態 URL 的配置,此時 urlpatterns 中的不再使用 path 方法而是支援正則表示式形式的 re_path 方法。此外,在 Python 的正則表示式中支援對匹配結果進行重新命名,語法格式為:(?P<name>pattern)
,其中 name 為該匹配的名稱,pattern 為匹配的正則表示式。 這樣我們可以有如下的 URLconf 配置:
# hello_app/urls.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path('articles/(?P<year>[0-9]{4})/', views.year_archive),
re_path('articles/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/', views.month_archive),
re_path('articles/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/(?P<title>[a-zA-Z0-9-_]+)/', views.article_title),
]
注意:這裡使用正則表示式的 URL 匹配和前面的普通的動態 URL 匹配有一個非常重要的區別,基於正則表示式的URL 匹配一旦匹配成功就會直接跳轉到檢視函式進行處理,而普通的動態 URL 匹配則會找到最長匹配的動態 URL,然後再進入相應的檢視函式去處理:
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/12/test
hello, 1998 archive
可以看到,這裡並沒有匹配到第三個 re_path 的 URL 配置,而是直接由第一個 re_path 的檢視函式進行了處理。
2.4 URLconf 傳遞額外引數
在前面的 URLconf 配置中,我們的 re_path 方法中只傳遞兩個引數,分別是設計的路由以及對應的檢視函式。我們可以看看 Django-2.2.10 中的 path 和 re_path 方法的原始碼:
# django/urls/conf.py
# ...
def _path(route, view, kwargs=None, name=None, Pattern=None):
if isinstance(view, (list, tuple)):
# For include(...) processing.
pattern = Pattern(route, is_endpoint=False)
urlconf_module, app_name, namespace = view
return URLResolver(
pattern,
urlconf_module,
kwargs,
app_name=app_name,
namespace=namespace,
)
elif callable(view):
# view是函式
pattern = Pattern(route, name=name, is_endpoint=True)
return URLPattern(pattern, view, kwargs, name)
else:
raise TypeError('view must be a callable or a list/tuple in the case of include().')
path = partial(_path, Pattern=RoutePattern)
re_path = partial(_path, Pattern=RegexPattern)
可以看到,除了route 和 view 外,我們還有 name、kwargs、Pattern 引數(比較少用)。其中 name 引數表示的是 route 匹配到的 URL 的一個別名,而 kwargs 是我們可以額外傳給檢視函式的引數:
# hello_app/urls.py
...
urlpatterns = [
re_path('articles/(?P<year>[0-9]{4})/', views.year_archive, {'hello': 'app'}),
]
# hello_app/views.py
def year_archive(request, *args, **kwargs):
return HttpResponse('hello, year archive, 額外引數={}\n'.format(kwargs))
啟動 Django 服務後,我們請求對應的服務,可以看到除了 URL 中匹配的 year 引數外,還有 re_path 中額外傳遞的引數,最後都被檢視函式中的 **kwargs
接收:
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/
hello, year archive, 額外引數={'year': '1998', 'hello': 'app'}
2.5 從 HttpRequest 中獲取引數
從 HttpRequest 中獲取引數是我們進行 Web 開發中最常用的一種方式。對於 Django 的檢視函式來說,HTTP 請求的資料被 HttpRequest 例項化後傳到了檢視函式的第一個引數中。為了能觀察相關資訊,我們修改請求的檢視函式:
@csrf_exempt
def hello_view(request, *args, **kwargs):
# 在第三次使用表單上傳包括檔案資料時,需要request.GET和request.POST操作,不然會拋異常
params = "request.GET={}\n".format(request.GET)
params += "request.POST={}\n".format(request.POST)
params += "request.body={}\n".format(request.body)
params += "request.FILES={}\n".format(request.FILES)
return HttpResponse(params)
我們測試如下 3 種 HTTP 請求,分別為 GET 請求、POST 請求 和帶檔案引數的請求,結果如下:
[root@server ~]# curl -XGET "http://127.0.0.1:8881/hello/world/?a=xxxx&b=yyyy"
request.GET=<QueryDict: {'a': ['xxxx'], 'b': ['yyyy']}>
request.POST=<QueryDict: {}>
request.body=b''
request.FILES=<MultiValueDict: {}>
[root@server ~]# curl -XPOST -d "username=shen&password=shentong" "http://127.0.0.1:8881/hello/world/?a=xxxx&b=yyyy"
request.GET=<QueryDict: {'a': ['xxxx'], 'b': ['yyyy']}>
request.POST=<QueryDict: {'username': ['shen'], 'password': ['shentong']}>
request.body=b'username=shen&password=shentong'
request.FILES=<MultiValueDict: {}>
# 本次請求中,需要去掉request.GET和request.POST操作語句,不然請求會報錯
[root@server ~]# curl -XPOST -F "username=shen&password=shentong" "http://127.0.0.1:8881/hello/world/?a=xxxx&b=yyyy" -F "files=@/root/upload_file.txt"
request.body=b'------------------------------68c9ede00e93\r\nContent-Disposition: form-data; name="username"\r\n\r\nshen&password=shentong\r\n------------------------------68c9ede00e93\r\nContent-Disposition: form-data; name="files"; filename="upload_file.txt"\r\nContent-Type: text/plain\r\n\r\nupload file test\n\r\n------------------------------68c9ede00e93--\r\n'
request.FILES=<MultiValueDict: {'files': [<InMemoryUploadedFile: upload_file.txt (text/plain)>]}>
可以看到,跟在 “?” 後的引數資料會儲存到 request.GET
中,這也是 GET 請求帶引數的方式。對於 POST 請求的傳參,資料一般會儲存在 request.POST
和 request.body
中。對於最後傳送的上傳檔案請求,可以看到,檔案內容的內容資料是儲存到了 request.body
中。
3. 小結
本節內容我們主要介紹瞭如何向檢視函式傳送引數,包括兩種方式:
-
通過動態的 URLconf 配置傳遞引數到檢視函式中;
-
通過 http 請求帶引數傳遞給檢視函式。至此,Django 中的路由系統和檢視函式已經介紹完畢。接下來會介紹 Django 中的模板系統。