Django 中傳遞參數(shù)給視圖函數(shù)
Django 框架中推薦使用一個單獨(dú)的 python 模塊配置 URL 和視圖函數(shù)或者視圖類的映射關(guān)系,通常稱這個配置模塊為 URLconf,該 Python 模塊通常命名為 urls.py
。一般而言,每個應(yīng)用目錄下都會有一個 urls.py
文件,總的映射關(guān)系入口在項(xiàng)目目錄下的 urls.py
中,而這個位置又是在 settings.py
文件中指定的。
本小節(jié)中將會學(xué)習(xí) Django 中的路由系統(tǒng)、URLconf 的配置,以及如何將請求參數(shù),如表單數(shù)據(jù)、文件等傳遞給視圖函數(shù)。
1. 測試環(huán)境準(zhǔn)備
這里的實(shí)驗(yàn)環(huán)境會采用前面創(chuàng)建的第一個 Django 工程(first_django_app) 來進(jìn)行測試。在 first_django_app 中,我們創(chuàng)建了第一個 django app 應(yīng)用:hello_app?,F(xiàn)在按照如下步驟準(zhǔn)備實(shí)驗(yàn)環(huán)境:
在 hello_app 應(yīng)用目錄下的 views.py
中添加一個視圖函數(shù):
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 應(yīng)用目錄下新建 urls.py
文件,里面內(nèi)容如下:
from django.urls import path
from . import views
urlpatterns = [
# 資產(chǎn)查詢接口,根據(jù)我們自己的機(jī)器命名
path('world/', views.hello_view, name='hello_view'),
]
在 settings.py
文件中注冊應(yīng)用:
# 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',
# 注冊應(yīng)用
'hello_app'
]
...
在 URLconf 的總?cè)肟谖恢檬褂?django 提供的 include 方法將應(yīng)用中的 urls.py
中的 urlpatterns 添加進(jìn)來:
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!” 字符了。后面的測試將在應(yīng)用的 urls.py
和 views.py
中進(jìn)行。
2. Django 中的傳參方式
Django 中傳遞參數(shù)給視圖函數(shù)的方式主要可分為以下兩種形式:URL 傳參和非 URL 傳參兩種。第一種基于 Django 中的 URLconf 配置,可以通過 URL 路徑將對應(yīng)匹配的參數(shù)傳到視圖函數(shù)中;而另外一種就是屬于HTTP 請求攜帶的參數(shù)了,請求參數(shù)可以放到 URL 中以 ?
格式加到 URL 的最后面,也可以將參數(shù)放到請求 body 中,最后統(tǒng)一由視圖函數(shù)中的 request 參數(shù)保存并傳到視圖函數(shù)中。
2.1 動態(tài) URL 傳參
在 url 的路徑 (path)部分可以作為動態(tài)參數(shù),傳遞給視圖函數(shù),如下面幾種寫法:
# 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),
]
注意上面的定義了匹配三個動態(tài) URL 的映射,每個動態(tài) URL 會匹配一個至多個參數(shù),每個動態(tài)值使用 <> 符號匹配,采用 <type:name>
這樣的形式。我們對應(yīng)的視圖函數(shù)如下:
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))
對于動態(tài)的 URL 表達(dá)式中,匹配到的值,比如上面的 year,month 和 title 可以作為函數(shù)的參數(shù)放到對應(yīng)的視圖函數(shù)中,Django 會幫我們把匹配到的參數(shù)對應(yīng)的放到函數(shù)的參數(shù)上。這里參數(shù)的位置可以任意寫,但是名字必須和 URL 表達(dá)式中的對應(yīng)。
[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 個動態(tài)參數(shù),在視圖函數(shù)中只寫上兩個參數(shù)接收也是沒問題的,因?yàn)槭O碌膮?shù)會被傳到 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))
# 啟動服務(wù),再次請求后
[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'}
上述介紹的動態(tài) URL 匹配格式 <type:name>
中,Django 會對捕捉到的 URL 參數(shù)進(jìn)行強(qiáng)制類型裝換,然后賦給 name 變量,再傳到視圖函數(shù)中。其中 Django 框架中支持的轉(zhuǎn)換類型有:
-
str:匹配任意非空字符,不能匹配分隔符 “/”;
-
int:匹配任意大于0的整數(shù);
-
slug:匹配任意 slug 字符串, slug 字符串可以包含任意的 ASCII 字符、數(shù)字、連字符和下劃線等;
-
uuid:匹配 UUID 字符串;
-
path:匹配任意非空字符串,包括 URL 的分隔符 “/”。
2.2 自定義URL參數(shù)類型轉(zhuǎn)換器
除了 Django 定義的簡單類型,我們還可以自定義參數(shù)類型轉(zhuǎn)換器來支持更為復(fù)雜的 URL 場景。比如前面的 int 類型并不支持負(fù)整數(shù),我希望開發(fā)一個能匹配正負(fù)數(shù)的類型,具體的步驟如下:
在 hello_app/urls.py
中定義一個 SignInt 類。該類有一個固定屬性 regex,用于匹配動態(tài)值;兩個固定方法:to_python()
方法和 to_url()
方法:
# hello_app/urls.py
class SignInt:
regex = '-*[0-9]+'
def to_python(self, value):
# 將匹配的value轉(zhuǎn)換成我們想要的類型
return int(value)
def to_url(self, value):
# 反向生成url時候回用到
return value
注冊該定義的轉(zhuǎn)換器,并給出一個簡短的名字,比如 sint:
# hello_app/urls.py
from django.urls import converters, register_converter
register_converter(SignInt, 'sint')
...
最后,我們就可以在 URLconf 中使用該類型來配置動態(tài)的 URL 表達(dá)式:
# 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, 自定義類型轉(zhuǎn)換器,獲取參數(shù)={}\n'.format(signed_num))
啟動 Django 服務(wù)后,執(zhí)行相關(guān)請求,可以看到 Django 的路由系統(tǒng)能成功匹配到帶負(fù)數(shù)和正數(shù)的 URL:
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/
hello, 自定義類型轉(zhuǎn)換器,獲取參數(shù)=1998
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/-1998/
hello, 自定義類型轉(zhuǎn)換器,獲取參數(shù)=-1998
2.3 使用正則表達(dá)式
上面是比較簡單的 URLconf 配置形式,Django 框架中可以使用正則表達(dá)式來進(jìn)一步擴(kuò)展動態(tài) URL 的配置,此時 urlpatterns 中的不再使用 path 方法而是支持正則表達(dá)式形式的 re_path 方法。此外,在 Python 的正則表達(dá)式中支持對匹配結(jié)果進(jìn)行重命名,語法格式為:(?P<name>pattern)
,其中 name 為該匹配的名稱,pattern 為匹配的正則表達(dá)式。 這樣我們可以有如下的 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),
]
注意:這里使用正則表達(dá)式的 URL 匹配和前面的普通的動態(tài) URL 匹配有一個非常重要的區(qū)別,基于正則表達(dá)式的URL 匹配一旦匹配成功就會直接跳轉(zhuǎn)到視圖函數(shù)進(jìn)行處理,而普通的動態(tài) URL 匹配則會找到最長匹配的動態(tài) URL,然后再進(jìn)入相應(yīng)的視圖函數(shù)去處理:
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/12/test
hello, 1998 archive
可以看到,這里并沒有匹配到第三個 re_path 的 URL 配置,而是直接由第一個 re_path 的視圖函數(shù)進(jìn)行了處理。
2.4 URLconf 傳遞額外參數(shù)
在前面的 URLconf 配置中,我們的 re_path 方法中只傳遞兩個參數(shù),分別是設(shè)計(jì)的路由以及對應(yīng)的視圖函數(shù)。我們可以看看 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是函數(shù)
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 參數(shù)(比較少用)。其中 name 參數(shù)表示的是 route 匹配到的 URL 的一個別名,而 kwargs 是我們可以額外傳給視圖函數(shù)的參數(shù):
# 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, 額外參數(shù)={}\n'.format(kwargs))
啟動 Django 服務(wù)后,我們請求對應(yīng)的服務(wù),可以看到除了 URL 中匹配的 year 參數(shù)外,還有 re_path 中額外傳遞的參數(shù),最后都被視圖函數(shù)中的 **kwargs
接收:
[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/
hello, year archive, 額外參數(shù)={'year': '1998', 'hello': 'app'}
2.5 從 HttpRequest 中獲取參數(shù)
從 HttpRequest 中獲取參數(shù)是我們進(jìn)行 Web 開發(fā)中最常用的一種方式。對于 Django 的視圖函數(shù)來說,HTTP 請求的數(shù)據(jù)被 HttpRequest 實(shí)例化后傳到了視圖函數(shù)的第一個參數(shù)中。為了能觀察相關(guān)信息,我們修改請求的視圖函數(shù):
@csrf_exempt
def hello_view(request, *args, **kwargs):
# 在第三次使用表單上傳包括文件數(shù)據(jù)時,需要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 請求 和帶文件參數(shù)的請求,結(jié)果如下:
[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操作語句,不然請求會報(bào)錯
[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)>]}>
可以看到,跟在 “?” 后的參數(shù)數(shù)據(jù)會保存到 request.GET
中,這也是 GET 請求帶參數(shù)的方式。對于 POST 請求的傳參,數(shù)據(jù)一般會保存在 request.POST
和 request.body
中。對于最后發(fā)送的上傳文件請求,可以看到,文件內(nèi)容的內(nèi)容數(shù)據(jù)是保存到了 request.body
中。
3. 小結(jié)
本節(jié)內(nèi)容我們主要介紹了如何向視圖函數(shù)傳送參數(shù),包括兩種方式:
-
通過動態(tài)的 URLconf 配置傳遞參數(shù)到視圖函數(shù)中;
-
通過 http 請求帶參數(shù)傳遞給視圖函數(shù)。至此,Django 中的路由系統(tǒng)和視圖函數(shù)已經(jīng)介紹完畢。接下來會介紹 Django 中的模板系統(tǒng)。