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 中的模板系统。