一、获取表单提交的数据

在 [Python自学] day-18 (2) (MTV架构、Django框架)中,我们使用过以下方式来获取表单数据:

user = request.POST.get('username', None)

这种获取方式可以获取来自表单的单个数据,例如<input type='text'/>的数据。

除了以上这种最简单的数据获取方式,我们还需要获取例如<input type='checkbox' />、<input type='file' />、<select>等标签的数据:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MyPage</title>
<style>
p{
border: 1px solid #dddddd;
display: inline-block;
}
</style>
</head>
<body>
<form action="/mypage" method="post" enctype="multipart/form-data">
<!-- radio单选 -->
<p>性别:</p>
<div>
男:<input type="radio" name="gender" value="1"/>
女:<input type="radio" name="gender" value="2"/>
</div>
<!-- checkbox多选 -->
<p>喜好:</p>
<div>
足球:<input type="checkbox" name="favor" value="11"/>
篮球:<input type="checkbox" name="favor" value="22"/>
游泳:<input type="checkbox" name="favor" value="33"/>
</div>
<!-- 单选select -->
<p>来自哪个城市:</p>
<div>
<select name="city">
<option value="cd">成都</option>
<option value="bj">北京</option>
<option value="sh">上海</option>
</select>
</div>
<!-- 多选select -->
<p>喜欢哪些城市:</p>
<div>
<select name="favorcity" multiple>
<option value="cd">成都</option>
<option value="bj">北京</option>
<option value="sh">上海</option>
</select>
</div>
<!-- 上传文件 -->
<div>
<input type="file" name="filetrans"/>
</div>
<div style="height: 48px;line-height: 48px;">
<input type="submit" value="提交"/>
</div>
</form>
</body>

在views.py中,我们可以通过以下方式来获取对应的数据:

def mypage(request):
if request.method == 'POST':
print(request.POST.get('gender', None)) # 获取radio单选数据,打印单个数据,例如'2'表示"女"
print(request.POST.getlist('favor', None)) # 获取checkbox的多选数据,打印value组成的列表 ['11','22']
print(request.POST.get('city', None)) # 获取select的单选数据,打印单个数据,例如'cd'表示"成都"
print(request.POST.getlist('favorcity', None)) # 获取multiple select标签的多选数据,打印列表['cd','sh'] # 获取文件对象
recv_file = request.FILES.get('filetrans', None)
print(recv_file.name)
# 从obj.chunks()中循环获取文件的块,并写入同名文件
with open(os.path.join('upload', recv_file.name), 'wb') as f:
for i in recv_file.chunks():
f.write(i) return render(request, 'mypage.html')

特别注意:在上传文件的时候,<form>表单必须要有 enctype="multipart/form-data" 属性,否则会将文件当做字符串提交(也就是说后台只能收到文件的名称)。

二、FBV和CBV

FBV:Function base view,基于函数的视图。

CBV:Class base view,基于类的视图。

1.FBV

在之前的章节中,我们在APP的views.py中写了很多请求处理函数(视图函数),使用函数来处理请求,就叫做FBV。

2.CBV

如果我们使用一个类来处理一个URL,则称为CBV,例如在APP的views.py中定义一个处理类:

from django.views import View

# 处理类必须继承自View类
class MyPage(View):
# get方法专门处理GET请求
def get(self, request):
print(request.method)
return render(request, 'mypage.html') # post方法专门处理POST请求
def post(self, request):
print(request.method)
return render(request, 'mypage.html')

我们查看View类的源码,可以看到:

class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
""" http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] ......
......

我们可以定义 http_method_names 列表中所列出的所有请求类型对应的方法。

3.CBV中的执行过程

我们查看View父类的源码:

    def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)

我们可以看到使用反射的时候,参数中将请求方式转化为小写。所以,我们实现的方法名必须是小写的。例如get()、post()、put()。如果请求的方法不在允许的列表中,则返回405错误(参考源码中的 self.http_method_not_allowed方法)。

4.重写父类中的dispatch方法,实现自定义功能

# 处理类必须继承自View类
class MyPage(View): # 重写父类方法
def dispatch(self, request, *args, **kwargs):
print('before')
result = super(MyPage, self).dispatch(request, *args, **kwargs)
print('after')
return result # get方法专门处理GET请求
def get(self, request):
print(request.method)
return render(request, 'mypage.html') # post方法专门处理POST请求
def post(self, request):
obj = request.FILES.get('filetrans')
if obj is not None:
with open(os.path.join('upload',obj.name),'wb') as f:
for item in obj.chunks():
f.write(item) return render(request, 'mypage.html')

在调用父类dispatch方法前后可以实现自定义功能。

总结:FBV和CBV用哪个更好?

FBV和CBV没有哪个好,哪个不好。在生产中都可以使用。

三、实现详情页面(动态url)

在后台管理页面中,我们经常看到一个列表(例如用户列表),点击其中一条,可以跳转到详情页面。

1.用户列表页面html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>UserList</title>
</head>
<body>
<ul>
{% for key,value in user_dict.items %}
<li><a href="/details/?nid={{ key }}"> {{ value.name }} </a></li>
{% endfor %}
</ul>
</body>
</html>

2.添加urls.py映射关系

from cmdb import views

urlpatterns = [
path('admin/', admin.site.urls),
path('login', views.login),
path('home', views.home), # 后台管理页面映射,映射到cmdb.views.home方法
path('mypage', views.MyPage.as_view()),
path('users', views.user_page),
]

3.用户列表视图函数

USER_DICT = {
'': {'name': 'Alex', 'email': 'Alex@163.com'},
'': {'name': 'Jone', 'email': 'Jone@163.com'},
'': {'name': 'Leo', 'email': 'Leo@163.com'},
'': {'name': 'Eric', 'email': 'Eric@163.com'}
} # /users页面的视图函数
def user_page(request):
return render(request, 'users.html', {'user_dict': USER_DICT})

4.用户列表页面实现效果:

5.用户详情页面html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Details</title>
</head>
<body>
<h1>详细信息</h1>
<h6>用户名: {{ detail_info.name }}</h6>
<h6>邮箱: {{ detail_info.email }}</h6>
</body>
</html>

6.添加urls.py中的映射关系

from cmdb import views

urlpatterns = [
path('admin/', admin.site.urls),
path('login', views.login),
path('home', views.home), # 后台管理页面映射,映射到cmdb.views.home方法
path('mypage', views.MyPage.as_view()),
path('users', views.user_page),
path('details/', views.details),
]

7.用户详情视图函数

# /details页面的视图函数
def details(request):
detail_info = {}
# 如果从GET数据中获取到nid,则取相应用户的详情
if request.method == 'GET':
nid = request.GET.get('nid')
detail_info = USER_DICT[nid]
return render(request, 'details.html', {'detail_info': detail_info})

8.详情页面实现效果

四、伪静态URL方式实现详情页面

在第三节中,我们使用了"detail/?nid=3"这种形式的参数传递方式,可以从GET中获取相应的数据。但这种形式的URL为动态URL,在SEO中权重很低。

所以目前比较流行的做法是,使用"detail-3.html"这种方式才传递参数"3"。这种方式被SEO看做是静态URL,具有比较高的排名权重。

1.修改users.html文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>UserList</title>
</head>
<body>
<ul>
{% for key,value in user_dict.items %}
<li><a href="/details-{{ key }}.html"> {{ value.name }} </a></li>
{% endfor %}
</ul>
</body>
</html>

2.修改urls.py映射

from django.contrib import admin
from django.urls import path
from django.urls import re_path from cmdb import views urlpatterns = [
path('admin/', admin.site.urls),
path('login', views.login),
path('home', views.home), # 后台管理页面映射,映射到cmdb.views.home方法
path('mypage', views.MyPage.as_view()),
path('users', views.user_page),
re_path('details-(\d+).html', views.details),
]

这里要使用正则表达式(导入re_path模块)来进行映射匹配,Django会自动将"()"中匹配到的字符串作为参数传递给views.details()

3.修改视图函数

# /details页面的视图函数
def details(request, nid):
# 取相应用户的详情
detail_info = USER_DICT[nid]
return render(request, 'details.html', {'detail_info': detail_info})

4.伪静态URL详情页面实现效果

五、基于正则的URL

前面第四节我们已经使用了基于正则的URL映射。如下代码所示:

from django.contrib import admin
from django.urls import path
from django.urls import re_path from cmdb import views urlpatterns = [
path('admin/', admin.site.urls),
path('login', views.login),
path('home', views.home), # 后台管理页面映射,映射到cmdb.views.home方法
path('mypage', views.MyPage.as_view()),
path('users', views.user_page),
re_path('details-(\d+).html', views.details),
]

要使用正则,必须使用re_path模块。

如果正则中有两个分组

re_path('details-(\d+)-(\d+).html', views.details)

那么对应视图函数就应该是:

def details(request, param1, param2):
pass

正则表达式中匹配到的数据会按顺序传递给details()函数。

我们可以使用以下方式,让其指定传递的参数名:(推荐使用)

re_path('details-(?P<nid>\d+)-(?P<uid>\d+).html', views.details)

"<>"中的参数名就表示正则表达式匹配到的数据指定传递给哪个参数,所以我们的视图函数参数顺序可以任意:

def details(request, nid, uid):
pass def details(request, uid, nid):
pass

参数的顺序变了,但值都能传递正确。

当我们不确定参数个数,或者为了方便,可以将视图函数写成:

def details(request, *args, **kwargs):
pass

当我们使用前面那种按顺序传递参数的方式,参数就会被传递到"*args"中(元组)。

当后者按名称传递的方式,参数就会被传递到"**kwargs"中(字典)。

六、修改URL的便捷方式({% url 'url_name' %})

当我们对urls.py中的某个映射进行修改时,使用这个URL的地方也要修改:

例如修改urls.py中的其中一条映射:

urlpatterns = [
path('admin/', admin.site.urls),
path('login', views.login),
path('home', views.home),
# path('mypage', views.MyPage.as_view()),
path('mypage12376sjhdfjnwjer', views.MyPage.as_view()),
path('users', views.user_page),
re_path('details-(\d+).html', views.details),
]

我们此时需要修改html中表单提交的目的地址:

<form action="/mypage12376sjhdfjnwjer" method="post" enctype="multipart/form-data">

Django为我们提供了一种便捷的方式(其他框架可能没有):

我们在定义urls.py中的映射关系时,不管匹配字符串是什么我们都可以为其定义一个"name":

urlpatterns = [
path('admin/', admin.site.urls),
path('login', views.login),
path('home', views.home),
# path('mypage', views.MyPage.as_view()),
path('mypage12376sjhdfjnwjer', views.MyPage.as_view(), name='mypage'),
path('users', views.user_page),
re_path('details-(\d+).html', views.details),
]

在html中,我们就不需要写匹配字符串了,而是如下:

<form action="{% url 'mypage' %}" method="post" enctype="multipart/form-data">

此时,访问对应的页面,可以正常提交:

Django提供name的目的:

让我们可以根据name来构建我们需要的URL,例如在上面的例子中,name="mypage"代表着URL "/mypage12376sjhdfjnwjer"。。

当我们的表单提交目的URL为这个URL时,我们可以直接使用{% url 'mypage' %}。

思考一个场景,当我们处于一个分页页面,例如第10页,该页面对应的URL映射为:

re_path('mypage12376sjhdfjnwjer/(\d+)', views.MyPage.as_view(), name='mypage')

我们处在的页面URL为:http://127.0.0.1/mypage12376sjhdfjnwjer/10/

此时,假设该页面右上角有登录按钮,我们点击进行登录。页面会进行跳转,我们可以把{% url 'mypage' 10 %}串到登录按钮的href中。例如"/login/mypage12376sjhdfjnwjer/10/".

这样,/login页面可以拿到我们点击登录时正处于的页面,在登录完成后,页面还可以跳转回之前浏览的页面。

从上面可以看出,当我们的URL是正则匹配时,我们可以使用传参的方式生成想要的URL:

re_path('mypage12376sjhdfjnwjer/(\d+)/', views.MyPage.as_view(), name='mypage')  # 对应{% url 'mypage' 10 %}
re_path('mypage12376sjhdfjnwjer/(\d+)/(\d+)/', views.MyPage.as_view(), name='mypage')  # 对应{% url 'mypage' 10 13 %}
re_path('mypage12376sjhdfjnwjer/(?P<nid>\d+)/', views.MyPage.as_view(), name='mypage')  # 对应{% url 'mypage' nid=10 %}
re_path('mypage12376sjhdfjnwjer/(?P<nid>\d+)/(?P<uid>\d+)/', views.MyPage.as_view(), name='mypage')  # 对应{% url 'mypage' nid=10 uid=13 %}

当然,我们也可以在登录页面的视图函数中直接生成:

# 登录操作,登录完毕后跳转到某页
def login(request, *args, **kwargs):
# 登录操作
# todo.. # 直接字符串拼接
url = '/mypage12376sjhdfjnwjer/10/13/'
# 使用django提供的函数实现拼接
from django.urls import reverse
url = reverse('mypage', args=(10,)) # 对应正则:mypage12376sjhdfjnwjer/(\d+)/
url = reverse('mypage', args=(10, 13,)) # 对应正则:mypage12376sjhdfjnwjer/(\d+)/(\d+)/
url = reverse('mypage', kwargs={'nid': 10}) # 对应正则:mypage12376sjhdfjnwjer/(?P<nid>\d+)/
url = reverse('mypage', kwargs={'nid': 10, 'uid': 13}) # 对应正则:mypage12376sjhdfjnwjer/(?P<nid>\d+)/(?P<uid>\d+)/
# 登录完毕后跳转回某页
return redirect(url)

七、路由分发

目前,我们只有一个urls.py,位于Django工程目录。我们所有的APP的映射都写在一起,显得比较杂乱,也不便于多人协作。

我们可以使用Django提供的路由分发功能。

首先,我们在最上层urls.py(工程目录中的urls.py)中,进行修改:

from django.contrib import admin
from django.urls import path
from django.urls import include

urlpatterns = [
path('cmdb/', include("cmdb.urls")),
path('mgmt/', include("mgmt.urls")),
]

这个最上层urls.py会将所有http://127.0.0.1:8000/cmdb/xxx的URL分发给APP cmdb的urls.py进行处理。

将所有http://127.0.0.0:8000/mgmt/xxx的URL分发给APP mgmt的urls.py处理。

我们分别在两个APP文件夹中创建urls.py:

分别在cmdb和mgmt的urls.py写各自的路由映射,例如在cmdb的urls.py中有如下映射:

from django.contrib import admin
from django.urls import path
from django.urls import re_path from cmdb import views urlpatterns = [
path('admin/', admin.site.urls),
path('login', views.login),
path('home', views.home),
# path('mypage', views.MyPage.as_view()),
path('mypage12376sjhdfjnwjer', views.MyPage.as_view(), name='mypage'),
path('users', views.user_page),
re_path('details-(\d+).html', views.details),
]

那么访问 "mypage12376sjhdfjnwjer"这个页面的话,要使用 http://127.0.0.1:8000/cmdb/mypage12376sjhdfjnwjer:

这样就完成了按APP进行路由分发的功能。

[Python自学] day-19 (1) (FBV和CBV、路由系统)的更多相关文章

  1. Python学习(三十一)—— Django之路由系统

    转载自:http://www.cnblogs.com/liwenzhou/p/8271147.html Django的路由系统 Django 1.11版本 URLConf官方文档 URL配置(URLc ...

  2. python第一百零五天 ---Django 基础 路由系统 URL 模板语言 ORM 操作

    一 路由系统 URL 1 url(r'^index/',views.index) url(r'^home/', views.Home.as_view()) 2 url(r'^detail-(\d+). ...

  3. [Python自学] day-21 (2) (Cookie、FBV|CBV装饰器)

    一.什么是Cookie 1.什么是Cookie? Cookie是保存在客户端浏览器中的文件,其中记录了服务器让浏览器记录的一些键值对(类似字典). 当Cookie中存在数据时,浏览器在访问网站时会读取 ...

  4. python 视图 (FBV、CBV ) 、Request 和Response对象 、路由系统

    一.FBV和CBV1.基于函数的view,就叫FBV(Function Based View) 示例: def add_book(request): pub_obj=models.Publisher. ...

  5. python 全栈开发,Day84(django请求生命周期,FBV和CBV,ORM拾遗,Git)

    一.django 请求生命周期 流程图: 1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post, ...

  6. Python菜鸟之路:Django 路由补充1:FBV和CBV - 补充2:url默认参数

    一.FBV和CBV 在Python菜鸟之路:Django 路由.模板.Model(ORM)一节中,已经介绍了几种路由的写法及对应关系,那种写法可以称之为FBV: function base view ...

  7. day54_9_18视图层某内部原理(fbv和cbv)与模板层

    一.render内部原理. 在render中往往需要返回三个参数,request,模板和一些键值对. 键值对中存储的是需要对模板渲染的值. 如果手动实现可以如下: from django.templa ...

  8. django请求生命周期,FBV和CBV,ORM拾遗,Git

    一.django 请求生命周期 流程图: 1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post, ...

  9. django——FBV与CBV

    引言 FBV FBV(function base views) 就是在视图里使用函数处理请求. 在之前django的学习中,我们一直使用的是这种方式,所以不再赘述. CBV CBV(class bas ...

随机推荐

  1. Spring 加载项目外部配置文件

    背景 在项目的部署过程中,一般是打成 war 或者 jar 包,这样一般存在两种问题: 即使是配置文件修改,也还需要整个项目重新打包和部署. 整个项目只有一套环境,不能切换. 针对上面的问题,可以使用 ...

  2. java源码--ArrayList

    1.1.ArrayList概述 1)ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类. 2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capac ...

  3. S02_CH02_MIO实验Enter a post title

    S02_CH02_MIO实验 2.1 GPIO简介 Zynq7000系列芯片有54个MIO(multiuse I/O),它们分配在 GPIO 的Bank0 和Bank1隶属于PS部分,这些IO与PS直 ...

  4. shell脚本查询某一目录的某一部分文件并且拷贝到其他目录(有则跳过没有则拷贝)

    #!/bin/bash dir=`ls /root//*` for i in $dir do #basename 返回一个字符串参数的基本文件名称.(只剩下文件名除去路径名) a=`basename ...

  5. X86驱动:挂接SSDT内核钩子

    SSDT 中文名称为系统服务描述符表,该表的作用是将Ring3应用层与Ring0内核层,两者的API函数连接起来,起到承上启下的作用,SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用 ...

  6. VS219 没有.net core 3.0模板

    控制台命令 dotnet --info dotnet --version 都正常显示有安装3.0 需要升级VS 2019 16.3.3,本地版本为16.3.2

  7. C# 使用Emit实现动态AOP框架 (二)

    目  录 C# 使用Emit实现动态AOP框架 (一) C# 使用Emit实现动态AOP框架 (二) C# 使用Emit实现动态AOP框架 (三) C# 使用Emit实现动态AOP框架 进阶篇之异常处 ...

  8. js之数据类型(对象类型——单体内置对象——Math)

    Math是一个内置对象,它具有数学常数和函数的属性和方法.Math对象用于执行数学任务,和其它对象不同,Math只是一个静态对象并没有Math()构造函数,实际上,Math()只是一个由js设置的对象 ...

  9. WPF - 仿QQ2014

    声明:非原创.项目是网上发现的,以学习为目的重写了部分代码,合理地调整了下布局,巧妙地简化了下Style样式.重写还算是有价值的,并非完全复制. 效果: 获取项目源码:https://pan.baid ...

  10. 【pycharm】pycharm断点调试

    step into:单步执行,遇到子函数就进入并且继续单步执行(简而言之,进入子函数): step over:在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子函数整个执行完再停止, ...