admin 管理员组文章数量: 1184232
文章目录
- 1. 初识web框架
- 1.1 http协议
- 1.2 socket简介
- 1.3 socket服务端概述
- 1.4 自己写web框架
- 1.5 web框架的分类
- 2. 初识django
- 3. django程序目录
- 4. 第一个django请求
- 5. 静态文件以及模板的配置
- 5.1 静态文件路径的配置
- 5.2 HttpResponse与render函数
- 5.3 模板路径的配置
- 6. 创建程序步骤
- 6.1 创建project
- 6.2 配置模板路径
- 6.3 配置静态文件目录
- 6.4 额外配置
- 7. 用户登录示例
- 8. request.GET与 request.POST
- 8.1 request.GET
- 8.2 request.POST
- 9. django模板语言特殊标记(重点)
- 9.1 取字符串的值
- 9.2 取列表的值
- 9.3 取字典的值
- 9.4 取嵌套于列表中字典的值
- 10. 学生信息管理系统
- 11. 初识cookie
- 12. 基于cookie的登录验证
- 13. django操作cookie
- 14. 设置cookie的签名
- 18. 三大web框架的区别与联系
- 19. Django非主流操作到主流操作
- 20. Django程序目录
- 21. 路由系统
- 1. 静态路由
- 2. 动态路由
- 3. 路由分发
- 4. 反向生成路由
- 22. ORM
- 1. ORM的功能
- 2. 数据库设置
- 3. makemigrations和migrate
- 4. ORM创建数据表
- 5. ORM单表CURD
- 23. CBV
- 1. CBV的引入
- 2. CBV的应用
- 3. dispatch方法
- 4. CBV中添加装饰器
- 24. 内置分页
- 1. 后台分页逻辑
- 2. 前台分页展示
- 25. 自定义分页
- 1. 自定义分页效果
- 2. 自定义分页的实现
- 3. 分页插件
- 26. 解决跨站脚本攻击XSS
- 1. Django处理XSS攻击
- 2. 前端标记字符串
- 3. 后端标记字符串
- 27. 中间件
- 1. 中间件执行流程
- 2. 中间件原理浅析
- 3. 中间件的应用
- 28. Form组件
- 1. Form组件的功能
- 2. 数据校验
- 3. 常用字段和参数
- 4. 保留上次输入内容
1. 初识web框架
1.1 http协议
http协议是无状态,短连接的。客户端连接服务器,发送请求,服务器响应请求后断开连接。
1.2 socket简介
所有的网络请求都是基于socket,浏览器是socket客户端,网站是socket服务端。
1.3 socket服务端概述
根据url的不同返回给用户不同的内容,使用路由系统,路由系统是url与函数的对应关系。返回给用户的内容本质是字符串,基本上返回的内容是动态的,所以需要使用到模板渲染。模板渲染实际上是把html充当模板,自己创造任意数据替换模板中的特殊字符,比如替换特殊字符为数据库中的数据。
1.4 自己写web框架
- 静态应用
# coding:utf-8
import socket
def f1(request):
'''
处理用户请求,并返回相应的内容
:param request:用户请求的所有信息
:return:返回相应的内容
'''
return b'f1'
def f2(request):
'''
处理用户请求,并返回相应的内容
:param request:
:return:
'''
f = open('index.html', 'rb')
data = f.read()
f.close()
return data
def f3(request):
'''
处理用户请求,并返回相应的内容
'''
f = open('news.html', 'rb')
data = f.read()
f.close()
return data
routers = [
('/user', f1),
('/', f2)
]
def run():
sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(5)
while True:
conn, addr = sock.accept()
'''
有用户来连接,
获取用户发送的数据
'''
data = conn.recv(8096)
print(data)
'''请求头:
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1\
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Purpose: prefetch
Accept-Encoding: gzip, deflate,
Accept-Language: zh-CN,zh;q=0.9
'''
# 解析请求头,目标:获取请求头中的url,并根据url向服务端发送请求
data = str(data, encoding='utf-8')
headers, bodys = data.split('\r\n\r\n')
headers_list = headers.split('\r\n')
methods, url, protocal = headers_list[0].split(' ')
func_name = None
for item in routers:
if item[0] == url:
func_name = item[1]
break
if func_name:
response = func_name(data)
else:
response = '404'
# if url == '/user':
# conn.send(b'user page')
# else:
# conn.send(b'404 is not found!')
# conn.send(b"HTTP/1.1 200 OK\r\n\r\n") # 响应头
# conn.send(b"hello thanlon!") # 相应体
conn.close()
if __name__ == '__main__':
run()
- 动态应用示例一
# coding:utf-8
import socket
def f1(request):
'''
处理用户请求,并动态返回相应的内容
:param request:
:return:
'''
f = open('news.html', 'r', encoding='utf-8')
data = f.read()
f.close()
import time
ctime = time.time()
data = data.replace('%', str(ctime))
return bytes(data, encoding='utf-8')
routers = [
('/user', f1),
]
def run():
sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(5)
while True:
conn, addr = sock.accept()
'''
有用户来连接,
获取用户发送的数据
'''
data = conn.recv(8096)
print(data)
'''请求头:
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1\
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Purpose: prefetch
Accept-Encoding: gzip, deflate,
Accept-Language: zh-CN,zh;q=0.9
'''
# 解析请求头,目标:获取请求头中的url,并根据url向服务端发送请求
data = str(data, encoding='utf-8')
headers, bodys = data.split('\r\n\r\n')
headers_list = headers.split('\r\n')
methods, url, protocal = headers_list[0].split(' ')
func_name = None
for item in routers:
if item[0] == url:
func_name = item[1]
break
if func_name:
response = func_name(data)
else:
response = '404'
# if url == '/user':
# conn.send(b'user page')
# else:
# conn.send(b'404 is not found!')
# conn.send(b"HTTP/1.1 200 OK\r\n\r\n") # 响应头
# conn.send(b"hello thanlon!") # 相应体
conn.close()
if __name__ == '__main__':
run()
- 动态应用示例二
# coding:utf-8
import socket
def f1(request):
import pymysql
# 创建连接
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='wwwnxl', db='test')
# 创建游标
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 执行sql语句,并返回受影响的行数
cursor.execute("select id,name,passwd from userinfo")
user_list = cursor.fetchall()
cursor.close()
conn.close()
# print(user_list)
content_list = []
for row in user_list:
tp = '<tr>%s</tr><tr>%s</tr><tr>%s</tr>' % (row['id'], row['name'], row['passwd'],)
content_list.append(tp)
content = ''.join(content_list)
f = open('userlist.html', 'r', encoding='utf-8')
template = f.read()
f.close()
data = template.replace('{{content}}', content)
print(data)
return bytes(data, encoding='utf-8')
routers = [
('/user', f1),
]
def run():
sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(5)
while True:
conn, addr = sock.accept()
'''
有用户来连接,
获取用户发送的数据
'''
data = conn.recv(8096)
# print(data)
'''请求头:
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1\
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Purpose: prefetch
Accept-Encoding: gzip, deflate,
Accept-Language: zh-CN,zh;q=0.9
'''
# 解析请求头,目标:获取请求头中的url,并根据url向服务端发送请求
data = str(data, encoding='utf-8')
headers, bodys = data.split('\r\n\r\n')
headers_list = headers.split('\r\n')
methods, url, protocal = headers_list[0].split(' ')
func_name = None
for item in routers:
if item[0] == url:
func_name = item[1]
break
if func_name:
response = func_name(data)
else:
response = b'404'
conn.send(response)
conn.close()
if __name__ == '__main__':
run()
1.5 web框架的分类
为了方便开发者开发web应用,web框架应用而生。有的web框架帮助开发者构建好了socket服务端,有的web框架帮助开发者写好了模板渲染。总之,借助web框架可以减轻了开发者的工作量。flask框架只有路由系统,没有socket服务端和模板引擎,socket服务端使用是python第三方模块,如wsgiref。模板引擎使用的也是第三方模块jinjia2。django框架有路由系统、模板引擎,但是没有socket服务端,socket服务端使用的是python的第三方内置模块wsgiref,wsgiref把请求交给django做处理。另外,还有一种叫Tornado的框架,Tornado框架包含socket服务端、路由系统、模板引擎。可以将web框架这样分类,django框架和其它框架。因为django框架提供了很多特殊的功能,如缓存、分布式。其它框架是轻量级的web框架。
2. 初识django
安装django:pip3 install django
创建django程序:django-admin startproject 项目名称
运行django程序:python manager.py runserver 127.0.0.1:8080(如果不指定,默认运行在8000端口)
3. django程序目录
manager.py:对当前django程序所有操作可以基于python manager.py runserver
settings.py:django配置文件
url.py:路由系统,url->函数
wsgi.py:用于定义django使用什么socket服务端,如wsgiref,uwsgi(wsgiref性能比较低)
4. 第一个django请求
usr.py:
from django.shortcuts import HttpResponse
# 处理请求的函数
def login(request): #
'''
处理用户请求,返回相响应结果
:param request:用户请求的相关信息(不是字节,是对象)
:return:
'''
pass
return HttpResponse('login!')
# url
urlpatterns = [
# path('admin/', admin.site.urls),
path('login/', login),
]
5. 静态文件以及模板的配置
5.1 静态文件路径的配置
创建静态文件目录也需要配置:
修改settings.py:
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject/en/2.2/howto/static-files/
'''
只要是使用/static/的前缀,就在这个目录(static目录)下找静态文件
'''
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
5.2 HttpResponse与render函数
- 返回字符串
return HttpResponse(‘login!’)
return HttpResponse(’< input type=“text”>’) - 返回模板
render函数默认是在“templates”中自动找文件,读取文件内容后返回给用户。
return render(request, ‘xxx.html’)
render函数本质上是调用HttpResponse。
5.3 模板路径的配置
模板名称需要与配置文件设定的模板名字一致,
6. 创建程序步骤
6.1 创建project
django-admin startproject project名,也可以在pycharm中选择Django,创建project
6.2 配置模板路径
创建templates目录,然后修改配置文件:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]#BASE_DIR指当前路径
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
6.3 配置静态文件目录
创建static目录,然后修改配置文件:
'''
只要是使用/static/的前缀,就在这个目录下找静态文件
'''
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
6.4 额外配置
将 django.middleware.csrf.CsrfViewMiddleware注释掉:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middlewaremon.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
7. 用户登录示例
urls.py:
from django.contrib import admin
from django.urls import path
from django.shortcuts import HttpResponse, render, redirect
def login(request): #
'''
处理用户请求,返回相响应结果
:param request:用户请求的相关信息(不是字节,是对象)
:return:
'''
if request.method == 'GET':
return render(request, 'login.html') # 本质上是调用HttpResponse,自动找到login.html文件,读取内容并返回给用户
else:
# print(request.POST) # 用户POST提交的数据(请求体)<QueryDict: {'name': ['thanlon'], 'pwd': ['123']}>
# user = request.POST['username']#直接索引,如果没有username会报错
username = request.POST.get('username') # 如果没有username不会报错,返回None
pwd = request.POST.get('pwd') # 如果没有username不会报错,返回None
if username == 'thanlon' and pwd == '123456':
return redirect('https://www.blueflags')
else:
return render(request, 'login.html', {'msg': '用户名或密码错误!'}) # django内部做模板渲染
urlpatterns = [
# path('admin/', admin.site.urls),
path('login/', login),
]
login.html:
<form action="/login/" method="POST" name="loginForm">
<div class="form-group">
<label for="name">用户名</label> <input type="text" class="form-control" name="username" placeholder="请输入用户名">
</div>
<div class="form-group">
<label for="">密码</label> <input type="password" class="form-control" name="pwd" placeholder="请输入密码">
<div style="color: red;font-weight: bold">{{ msg }}</div>
</div>
<button type="submit" class="btn btn-primary" onclick="return checkForm()">登录</button>
</form>
登录效果:
8. request.GET与 request.POST
8.1 request.GET
request.GET是从请求头的url中获取值
8.2 request.POST
request.POST是从请求体中获取值。GET请求时,只有request.GET可以获取值。但POST请求时,request.POST和request.GET都可能获取值。
<form action="/login/?page=1" method="POST" name="loginForm">……
可以通过request.GET获取url中的page
9. django模板语言特殊标记(重点)
9.1 取字符串的值
def index(request):
return render(request, 'index/index.html', {'username': '一问奈何'})
<p>{{ username }}</p> # 一问奈何
9.2 取列表的值
def index(request):
# return render(request, 'index/index.html', {'username': '一问奈何'})
return render(request, 'index/index.html', {'username': ['thanlon','Kiku']})
- 直接通过索引
{#<p>{{ username }}</p>#}
{{ username }}
{{ username.0 }}
{{ username.1 }}
- 通过循环遍历
{% for item in username %}
{{ item }}
{% endfor %}
9.3 取字典的值
def index(request):
return render(request, 'index/index.html', {
'user_dict': {'name': '一问奈何', 'age': 23}
})
<body>
{{ user_dict.name }}
{{ user_dict.age }}
<body>
9.4 取嵌套于列表中字典的值
def index(request):
return render(request, 'index/index.html', {
'user_list_dict': [
{'id': 1, 'name': 'thanlon'},
{'id': 2, 'name': 'kuku'},
]
})
- 通过索引取值
{{ user_list_dict.0.id}}--{{ user_list_dict.0.name}}
{{ user_list_dict.1.id}}--{{ user_list_dict.0.name}}
- 通过循环取值
{% for row in user_list_dict %}
{{ row.id }}--{{ row.name }}
{% endfor %}
10. 学生信息管理系统
Django实战项目 ------ 学生信息管理系统
11. 初识cookie
cookie是保存在浏览器端的特殊"键值对",服务端可以向浏览器端写cookie,客户端每次发送请求时需要携带本地cookie,此时cookie放到请求头中。服务端回写cookie会放到响应头中,render函数和redirect以及HttpResponse函数都可以在向服务端浏览器返回响应体的时候回写cookie。cookie的应用:可以用于投票,也可以用于用户登录。
12. 基于cookie的登录验证
views.py:
def classes(request):
'''
查询班级id、班级名称
:param request:对象相关的数据
:return:渲染后的模板
'''
ticket = request.COOKIES.get('ticket')
if not ticket:
return redirect('/login/')
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='test')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select id,title from class")
classes_list = cursor.fetchall()
cursor.close()
conn.close()
return render(request, 'classes.html', {'classes_list': classes_list})
def login(request):
'''
登录
:param request:
:return:
'''
username = request.POST.get('username')
pwd = request.POST.get('pwd')
if request.method == 'POST':
if username == 'thanlon' and pwd == '123456':
obj = redirect('/classes/')
# obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM')
return obj
else:
return render(request, 'login.html')
读所有的cookie使用request.COOKIES
13. django操作cookie
(1) 设置超时时间有两种方式,下面是设置cookie的函数源码:
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',domain=None, secure=False, httponly=False, samesite=None):
第一种方式:
def login(request):
username = request.POST.get('username')
pwd = request.POST.get('pwd')
if request.method == 'POST':
if username == 'thanlon' and pwd == '123456':
obj = redirect('/classes/')
obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM',max_age=10)
return obj
else:
return render(request, 'login.html')
max_age=10表示超时时间是10秒,10秒过后不管客户端有没有操作,都会跳转到登录页面。
第二种方式:
def login(request):
username = request.POST.get('username')
pwd = request.POST.get('pwd')
if request.method == 'POST':
if username == 'thanlon' and pwd == '123456':
obj = redirect('/classes/')
from datetime import timedelta
import datetime
ct = datetime.datetime.utcnow()
v = timedelta(seconds=10) # 加10s
value = ct + v
obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM', expires=value)
return obj
else:
return render(request, 'login.html')
使用第一种方式和第二种方式效果是一样的,但推荐使用第一种方式。因为第一种方式设置max_age后,内部会转换成datatime类型,把expires也设置一遍,所以推荐使用第一种方式。
(2) 设置在某个url上读取cookie
下面设置只有/class/的url才可以读取到cookie:
obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM', path='/class/')
默认
path=/,表示所有url都可以读取到cookie
(3) 设置不同的域名访问cookie
obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM',domain=)
默认是当前域名,当然也可以设置子域名,表示只有子域名才可以获取到,一般用不到。
(4) 设置安全相关
obj.set_cookie('ticket', 'O2UikIFGGvHxcUQD7rIbgxedinIpEoMjStxoO579rgY8NeQM',httponly=False,secure=False)
httponly表示写的cookie只能通过http来回发送,如果为True,cookie是写到浏览器上了,但是通过javascript找不到,没有权限对其进行操作,当然可以通过抓包获取。secure与https相关的,网站如果是https的,要把secure设置为True。
14. 设置cookie的签名
对cookie进行签名是指对cookie的值进行操作,使用set_signed_cookie方法。Django中默认使用的是时间戳来签名,当然也可以自定制签名,只需要自己定义一个类继承TimestampSigner,然后重写sign和unsign方法。最后在配置文件文件中指定自己定义的类:SIGNING_BACKEND=Python文件名.类名就可以了。下面是对cookie进行签名的案例:
def classes(request):
ticket = request.get_signed_cookie('ticket', salt='123456')
if not ticket:
return redirect('/login/')
……
def login(request):
username = request.POST.get('username')
pwd = request.POST.get('pwd')
if request.method == 'POST':
if username == 'thanlon' and pwd == '123456':
obj = redirect('/classes/')
obj.set_signed_cookie('ticket', 'thanlon', salt='123456')
return obj
else:
return render(request, 'login.html')
18. 三大web框架的区别与联系
Django、Flask和Tornado框架都有路由、视图和模板,但是Tornado的模板是自己写的,Flask的模板不是自己写的,采用的是第三方组建(Jinjia2)。Django自带ORM,其它两个框架中没有。Django中在写sql的时候不应该再使用pymysql,而应该使用ORM。其实ORM只是做了类和对象的映射,类对应表,对象对应数据行。Django中把类和对象转换成sql语句,但是本质上还是需要pymysql去连接数据库。对于Flask框架,可以使用pymysql,也可以使用SqlAchemy,Tornado也是一样的。其实,对于Django,一般使用的是ORM,但也可以使用pymysql,但是这是非主流的。对于非常复杂的sql语句,ORM完成不了,只能写原生sql。Django中ORM也支持原生sql,所以没必要使用pymysql。
19. Django非主流操作到主流操作
以上Django的使用中至少有两点是非主流的:第一点体现在创建app上;第二点体现在数据库操作上。以后,我们将使用Django的主流操作来进行开发。在创建app方面,使用命令的方式来创建。在操作数据库方面,不再使用pymysql或者SqlAchemy,而是使用ORM。
20. Django程序目录
创建一个完整的Django程序:
# 创建Django工程
thanlon@thanlon-master:~$ django-admin startproject mysite
# 进入工程目录
thanlon@thanlon-master:~$ cd mysite/
# 创建Django应用
thanlon@thanlon-master:~/mysite$ python3 manage.py startapp app01
主要目录介绍:
migrations:数据库相关
admin.py:Django自带后台管理相关配置
models.py:写类,根据类创建数据库表
test.py:单元测试
views.py:写试图函数的,用于做业务处理(不一定只有一个views.py,可以在app目录下单独创建一个views的目录用存放不同的视图函数)
21. 路由系统
1. 静态路由
路由就是用户访问网站资源的路径,在Django中路由要和视图函数对应起来,路由和视图的对应关系有一对一和多对一。静态路由是比较常见的路由,不使用尖括号和正则表达式定义的路由,与视图函数是一对一关系。如默认的 admin/ 和我们自己定义的 /login/、edit/1/:
urlpatterns = [
# 匹配admin/
path('admin/', admin.site.urls),
# 匹配login/
path('login/', views.login),
# 匹配edit/1
path('edit/1', views.add_user),
# 匹配edit/1/,和不加/是有区别的
path('edit/1/', views.add_user),
]
注意:最后加不加 / 匹配到的路径是不同的。
2. 动态路由
使用尖括号定义的路由是动态的,可以定义传入变量并且指定变量的类型,其它类型还有字符串,用 str 表示:
urlpatterns = [
# 匹配任意整型,视图函数中必须用名为id的变量接收,当然也可以使用万能的**kwargs来接收,转换成字典后再找到索引id的值即为传入的参数
path('add-user/<int:id>', views.add_user),
# 匹配任意整型,和不加/是有区别的
path('add-user/<int:id>/', views.add_user),
# 匹配任意字符串类型
path('add-user/<str:str_name>', views.add_user),
# 匹配任意字符串类型,和不加/是有区别的
path('add-user/<str:str_name>/', views.add_user),
]
PS:尖括号的方式下也可以使用转换器,可以自定义转换器,暂时用不到就不说。
使用正则表达式定义的路由也是动态的,同样可以实现多个路由对应一个视图函数。Django3.0版本使用正则表达式定义路由需要使用到 re_path:
from django.urls import path, re_path
urlpatterns = [
# 如果只想匹配add-user,可以在最后加上终止符号$
re_path('^add-user$', views.add_user),
# 可以匹配到add-user/
re_path('^add-user/', views.add_user),
# 可以匹配到add-user,也可以匹配到add-user/
re_path('^add-user', views.add_user),
# 匹配任意一位数字
re_path('^add-user/(\d)/', views.add_user),
# 匹配任意数字,数字会被当作传参传递,add_user方法中必须有形式参数接收
re_path('^add-user/(\d+)/', views.add_user),
# 匹配任意一个字符(大小写),字符会被当作传参传递,add_user方法中必须有形式参数接收
re_path('^add-user/([A-Za-z])/', views.add_user),
# 伪静态匹配数字.html,数字部分会被当作传参传递,add_user方法中必须有形式参数接收
re_path('^add-user/(\d+).html$', views.add_user),
# 伪静态匹配任意字符串.html,字符串中也可以只有一个字符,字符串部分会被当作传参传递,add_user方法中必须有形式参数接收
re_path('^add-user/(\w+).html$', views.add_user),
# 位置传参,所以视图函数中形参必须有a和b接收,a、b的位置随意。注意如果是'^add-user/(\d+)/(?P<b>\d+),视图是不接收(\d+)部分的参数。
re_path('^add-user/(?P<a>\d+)/(?P<b>\d+)', views.add_user),
]
注意:Django中的路径匹配是顺序匹配。也就是从上到下匹配,上面的路由匹配成功就停止匹配下面的路由。
3. 路由分发
多个app时可以使用路由分发,可以实现不同应用使用不同的路由,方便管理路由。可以在app01目录下新建 urls.py 文件用作app01应用的路由:
urlpatterns = [
path('login/', views.login),
]
在主要的 urls.py 文件中要导入include函数才能实现路由的分发:
from django.urls import path, include
urlpatterns = [
# 注意路由后面必须加上/
path('app01/', include('app01.urls'))
]
下面就可以通过 http://127.0.0.1:8000/app01/login/ 来访问app01下的视图函数login,这样就实现了路由的分发。
4. 反向生成路由
在写路由的时候,给路由命个名字,以后可使用这个名字找到这个路由,在存储用户路由信息的信息也可以存储路由的名字,节约空间,方便使用。可以在Python代码中反向生成路由,也可以在模板中反向生成路由。
① 在Python代码中使用。需要设置路由、业务逻辑:
urls.py:
urlpatterns = [
path('index/', views.index, name="index"),
]
views.py:
def index(request, id):
v = reverse('index_id', kwargs={'id': id})
print(v) # /index/1/
return HttpResponse('成功执行!')
② 在模板中使用。需要设置路由、写视图逻辑和模板:
urls.py:
urlpatterns = [
path('index/', views.index, name="index"),
]
views.py:
def index(request):
return render(request, 'index.html')
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% url 'index' %} #
</body>
</html>
如果路由中没有传值,模板中可以使用上面的方式。但是,如果路由中有传值,如:
urls.py:
urlpatterns = [
path('index/<int:id>/', views.index, name="index"),
]
views.py:
def index(request,id):
# v = reverse('index_id', kwargs={'id': id})
# print(v) # /index/1/
# print(id)
# 把id传到模板中
return render(request,'index.html',{'id':id})
模板中反向生成路由的方式:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% url 'index' id %}
</body>
</html>
注意:如果有多个参数,在id后面追加就可以,使用空格隔开,如:{% url 'index' id page %}
22. ORM
1. ORM的功能
Django的ORM可以操作数据库的表结构和数据行。操作表结构体现在创建表、修改表和删除表,操作数据行则体现在对表的CURD操作,也就是增删改查。Django的ORM本身不支持连接数据库,实质上使用的是第三方工具如 pymysql 或者是 sqlalchemy 来连接数据库。
2. 数据库设置
Django默认使用的是SQLite,也是Django中自带的数据库,但是我们一般不会使用。我们通常会使用SQLite之外的数据库,如mysql、oracle、postgresql等。所以,需要在Django中修改默认使用的SQLite为其它的数据库。在 settings.py 中找到 DATABASES 变量:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
如果使用的是mysql数据,修改为:
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
'ENGINE': 'django.db.backends.mysql',
'NAME': '数据库的名称',
'USER': '用户名',
'PASSWORD': '密码',
'HOST': '数据库地址',
'PORT': '数据库端口'
}
}
设置ORM使用 pymysql 连接数据库,只需要在与 settings.py 同级目录下的 __init__.py 这个空文件中写入:
import pymysql
pymysql.install_as_MySQLdb()
配置文件 setting.py 中的 INSTALLED_APPS 下包含了在Django实例中激活的所有Django应用程序的名称,这些应用程序都是随Django一起提供的:
django.contrib.admin:管理站点
django.contrib.auth:认证系统
django.contrib.contenttypes :内容类型的框架
django.contrib.sessions:会话框架
django.contrib.messages:消息传递框架
django.contrib.staticfiles:用于管理静态文件的框架
这些应用中的一些应用程序 至少使用一个数据库表,因此我们需要在数据库中创建表之后才能使用它们,执行下面的命令来创建:
(django_test) thanlon@thanlon:~/projects/PycharmProjects/django_test$ python manage.py migrate
可以在数据库看到创建的数据表:
mysql> show tables;
+----------------------------+
| Tables_in_django_test |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
10 rows in set (0.00 sec)
migrate命令获取所有尚未应用的迁移(Django使用名为的特殊表在数据库中跟踪应用了哪些迁移django_migrations),并针对数据库运行它们。本质上,将你对模型所做的更改与数据库。迁移功能非常强大,可以在开发项目时随时间更改模型,而无需删除数据库或表并创建新的模型。它专门用于实时升级数据库而不会丢失数据。
3. makemigrations和migrate
接下来需要到模型中创建类和字段,创建完成后需要先使用 python manage.py makemigrations 命令来为数据表的更改创建迁移,然后使用 python manage.py migrate 将更改应用到数据库。之所以使用单独的命令来进行和应用迁移,是因为你会将迁移提交到版本控制系统,并随应用程序一起交付。这些单独的命令不仅使您的开发更加容易,而且还可以被其他开发人员和生产环境使用。
4. ORM创建数据表
在 models.py 文件中写模型,这里分别创建员工表和部门表:
from django.db import models
class Depart(models.Model):
"""
创建部门表
"""
title = models.CharField(max_length=32, unique=True)
class EmpInfo(models.Model):
"""
创建员工信息表
"""
# 主键默认是id,写与不写名字都是id,类型是int且为主键和自动递增
nid = models.AutoField(primary_key=True)
username = models.CharField(max_length=32)
pwd = models.CharField(max_length=64)
# 'max_length' is ignored when used with IntegerField.
age = models.IntegerField()
# 会自动创建dp_id字段
dp = models.ForeignKey("Depart", on_delete=models.CASCADE, null=True)
执行创建迁移和应用更改的命令后会创建两张表:
部门表:
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| title | varchar(32) | NO | UNI | NULL | |
+-------+-------------+------+-----+---------+----------------+
员工表:
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| nid | int | NO | PRI | NULL | auto_increment |
| username | varchar(32) | NO | | NULL | |
| pwd | varchar(64) | NO | | NULL | |
| age | int | NO | | NULL | |
| dp_id | int | YES | MUL | NULL | |
+----------+-------------+------+-----+---------+----------------+
生成迁移记录和应用迁移有问题可以在评论区留言!
5. ORM单表CURD
数据的增加:
from django.shortcuts import render, HttpResponse, reverse
from app01 import models
# Create your views here.
def index(request):
models.Depart.objects.create(title='技术部')
models.Depart.objects.create(title='运维部')
models.EmpInfo.objects.create(username='Thanlon', pwd='123456', age=23, dp_id=3)
return render(request, 'index.html')
部门表:
+----+-----------+
| id | title |
+----+-----------+
| 2 | 技术部 |
| 1 | 运维部 |
+----+-----------+
员工表:
+-----+----------+--------+-----+-------+
| nid | username | pwd | age | dp_id |
+-----+----------+--------+-----+-------+
| 1 | Thanlon | 123456 | 23 | 1 |
+-----+----------+--------+-----+-------+
数据的查询:
from django.shortcuts import render, HttpResponse, reverse
from app01 import models
def index(request):
# 查询所有部门信息
dp_list = models.Depart.objects.all()
print(dp_list) # <QuerySet [<Depart: Depart object (2)>, <Depart: Depart object (1)>]>
for item in dp_list:
print(item.id, item.title)
"""
2 技术部
1 运维部
"""
# 查询满足条件的数据行
# emp_list_id = models.EmpInfo.objects.filter(name='thanlon').first()
# dp_list_id = models.Depart.objects.filter(id=1)
# dp_list_id = models.Depart.objects.filter(id__lt=2)
dp_list_id = models.Depart.objects.filter(id__gt=1)
for item in dp_list_id:
print(item.id, item.title) # 2 技术部
return render(request, 'index.html')
数据的删除:
from django.shortcuts import render, HttpResponse, reverse
from app01 import models
# Create your views here.
def index(request):
# 删除id=2的部门
models.Depart.objects.filter(id=2).delete()
return render(request, 'index.html')
+----+-----------+
| id | title |
+----+-----------+
| 1 | 运维部 |
+----+-----------+
数据表的修改:
from django.shortcuts import render, HttpResponse, reverse
from app01 import models
# Create your views here.
def index(request):
# 修改id=1的部门信息
models.Depart.objects.filter(id=1).update(title="人事部")
return render(request, 'index.html')
+----+-----------+
| id | title |
+----+-----------+
| 1 | 人事部 |
+----+-----------+
23. CBV
1. CBV的引入
FBV 即 Function-Based-View,是 通过函数来处理请求。CBV 即 Class-Based-View,是 通过类来处理请求。相对于传统的FBV方式,CBV的优点体现在:① 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承);② 可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性。
2. CBV的应用
登录功能的实现,定义一个Login类用于处理用户的登录请求。路由URL匹配成功后,开始匹配请求的方法method。如果是GET请求,执行Login类的get方法,获取到登录页面。如果是POST请求,将登录表单的数据发送到后台验证。无须使用if语句来判断请求的方法是什么:
urls.py:
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('login.html/', views.Login.as_view()),
]
views.py:
class Login(View):
"""
get:查询
post:创建
put:更新
delete:删除
"""
def get(self, request):
"""
请求的方法是get时执行
:param request:
:return:
"""
return render(request, 'login.html')
def post(self, request):
"""
请求的方法是post时执行
:param request:
:return:
"""
username = request.POST.get('username')
print(username)
return HttpResponse('登录成功!')
login.html:
...
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-danger">
<div class="panel-heading" style="font-weight: bold">用户登录
<button type="button" class="close" id="close_add_modal"><span aria-hidden="true">×</span>
</button>
</div>
<div class="panel-body">
<form method="post">
<div class="form-group">
<label for="">用户名</label>
<input type="text" name="username" class="form-control" id="name" placeholder="请输入用户名"
autofocus>
</div>
<div class="form-group">
<label for="">密码</label>
<input type="password" name="pwd" class="form-control" id="pwd" placeholder="请输入密码">
</div>
<input type="submit" class="btn" value="登录" id="add_student" style="background: #f2dede;">
</form>
</div>
</div>
</div>
</div>
</div>
...
表单只支持POST和GET方式提交!AJAX支持所有的请求!
3. dispatch方法
请求的方法有很多,如果使用 if 来一一判断就要写很多判断语句。但是,实际上使用的并不是条件判断,而是 反射。用户发送请求,URL会匹配到类。找类中的方法时,使用getattr方法判断请求的方法是否对应类中的方法。查看源码的时候发现在执行get和post方法之前,还执行了dispatch方法。dispatch方法里执行就是这个操作:
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:
# 在当前对象中找用户提交的方法request.method.lower(),handler就是找到的方法名
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# handler加括号就是执行这些方法
return handler(request, *args, **kwargs)
dispatch方法的功能类似 装饰器 的功能,在执行请求的方法之前和之后做其它操作:
class Login(View):
def dispatch(self, request, *args, **kwargs):
print('Before')
obj = super(Login, self).dispatch(request, *args, **kwargs)
print('After')
return obj
def get(self, request):
print('get')
return render(request, 'login.html')
def post(self, request):
print('post')
return HttpResponse('登录成功!')
执行记录:
[06 / Jun / 2020 01: 32:33] "GET /login.html/ HTTP/1.1" 200 1813
Before
get
After
[06 / Jun / 2020 01: 32:38] "GET /login.html/ HTTP/1.1" 200 1813
[06 / Jun / 2020 01: 32:41] "POST /login.html/ HTTP/1.1" 200 15
Before
post
After
如果要对get和post等请求方法做批量操作时,没必要在每个方法中都写一遍,只要在dispatch写一遍就可以了。
4. CBV中添加装饰器
CBV中添加装饰器有两种方法,分别是在指定的方法中添加装饰器和在类上添加装饰器。在方法中添加装饰器:
views.py:
from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
def wrapper(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
class Login(View):
@method_decorator(csrf_protect)
def get(self, request):
pass
@method_decorator(wrapper)
def post(self, request):
pass
在类上添加装饰器不可以直接把装饰器放在类上,这样时错误的,如:
from django.views import View
from django.views.decorators.csrf import csrf_protect
@csrf_protect
class Login(View):
def get(self, request):
pass
def post(self, request):
pass
在类上加装饰器,需要用到 @method_decorator:
from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
# 对类中所有的方法添加csrf_protect装饰器
@method_decorator(csrf_protect)
class Login(View):
def get(self, request):
pass
def post(self, request):
pass
还可以通过类方法名称来指定方法添加装饰器:
from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_protect, name='get')
@method_decorator(csrf_protect, name='post')
class Login(View):
def get(self, request):
pass
def post(self, request):
pass
csrf的装饰器在使用CBV的情况下不能加到类方法上!
以上两种方法都具有为所有的函数加装饰器的功能,不过如果 想为所有类方法添加装饰器显得比较麻烦! 这里还有一种方法可以 为所有的方法加上某个装饰器,那就是把装饰器加到dispatch方法上:
from django.views import View
from django.views.decorators.csrf import csrf_protect
from django.utils.decorators import method_decorator
def wrapper(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(wrapper, name='dispatch')
class Login(View):
def get(self, request):
pass
def post(self, request):
pass
24. 内置分页
1. 后台分页逻辑
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
def student(request):
# 获取所有的结果集
emp_list = models.StuInfo.objects.all() # <QuerySet [<StuInfo: StuInfo object (1)>]>
"""
emp_list也可以是使用pymysql拿到的列表:student_list = cursor.fetchall()
[{'id': 1, 'name': '超越'}, {'id': 2, 'name': '奈何'}] <class 'list'>
"""
# 创建Paginator对象,可直接设置每页显示的条目数量
pagenator = Paginator(emp_list, 3)
"""
pagenator.page(1):显示第1页
pagenator.per_page:每页显示的条目数量
pagenator.count:数据总个数
pagenator.num_pages:总页数
pagenator.page_range:总页数的索引范围,如(1,10)
"""
# 获取传入的页码
current_page = request.GET.get('page')
try:
posts = pagenator.page(current_page)
except PageNotAnInteger as e:
posts = pagenator.page(1) # 如果传入的页码不是整型,则默认显示第一页,如传入的是/student.html/?page=abc,/student.html/
"""
posts.has_next:是否有下一页
posts.next_page_number:下一页页码
posts.has_previous:是否有上一页
posts.previous_page_number:上一页页码
posts.object_list:分页之后的数据列表
posts.number:当前页
posts.paginator:paginator对象
"""
# 如果传入的页码不是int类型,如传入的是/student.html/?page=abc
except EmptyPage as e:
posts = pagenator.page(1) # 如果传入的页码是空页,则默认显示第一页,如传入的是/student.html/?page=-10
return render(request, 'student.html', {'posts': posts})
2. 前台分页展示
<table class="table table-hover text-center" style="background: white;margin-bottom: 0">
<tr>
<th class="text-center">ID</th>
<th class="text-center">姓名</th>
<th class="text-center">年龄</th>
</tr>
{% for row in posts.object_list %}
<tr>
<td>{{ row.id }}</td>
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
</tr>
{% endfor %}
</table>
<nav aria-label="Page navigation" class="text-left">
<ul class="pagination">
{% if posts.has_previous %}
<li>
<a href="/student.html?page={{ posts.previous_page_number }}">上一页</a>
</li>
{% endif %}
{% if posts.has_next %}
<li>
<a href="/student.html?page={{ posts.next_page_number }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
25. 自定义分页
1. 自定义分页效果
可以实现查找 首页和最后一页,上一页和下一页,以及指定页:
2. 自定义分页的实现
模型Model部分的代码:
models.py:
from django.db import models
class UserType(models.Model):
"""
用户类型表
"""
title = models.CharField(max_length=32)
class UserInfo(models.Model):
"""
用户表
"""
name = models.CharField(max_length=16)
age = models.IntegerField()
ut = models.ForeignKey('UserType', on_delete=models.CASCADE)
控制器Controller部分的代码:
vews.py:
from django.shortcuts import render, HttpResponse
from app01 import models
from utils.pagger import PageInfo
class PageInfo:
def __init__(self, current_page, per_page, count_sum, base_url, show_page=11):
"""
:param current_page:当前的页码
:param per_page:每页显示的数据量
:param count_sum:总的数据量
"""
try:
self.current_page = int(current_page)
except Exception as e:
self.current_page = 1
self.per_page = per_page
self.count_sum = count_sum
a, b = divmod(count_sum, per_page)
if b:
a += 1
# page_num:所有页码的数量
self.sum_page_num = a
self.show_page = show_page
self.base_url = base_url
def start(self):
return (self.current_page - 1) * self.per_page
def end(self):
return self.current_page * self.per_page
def pager(self):
half = int((self.show_page - 1) / 2)
# 如果总页数小于11
if self.sum_page_num < self.show_page:
begin = 1
stop = self.sum_page_num + 1
else:
if self.current_page <= half:
begin = 1
stop = self.show_page + 1
else:
if self.current_page + half > self.sum_page_num:
begin = self.sum_page_num - self.show_page + 1
stop = self.sum_page_num + 1
else:
begin = self.current_page - half
stop = self.current_page + half + 1
page_num_list = []
index_page = '<li><a href="%s?page=1"><span aria-hidden="true">首页</span></a></li>' % (self.base_url)
page_num_list.append(index_page)
if self.current_page <= 1:
prev_page = '<li><a href="#"><span aria-hidden="true">«</span></a></li>'
else:
prev_page = '<li><a href="%s?page=%s"><span aria-hidden="true">«</span></a></li>' % (
self.base_url, self.current_page - 1,)
page_num_list.append(prev_page)
for i in range(begin, stop):
if i == self.current_page:
page_num_list.append(
"<li class='active'><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
else:
page_num_list.append("<li><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
if self.current_page >= self.sum_page_num:
next_page = '<li><a href="#"><span aria-hidden="true">»</span></a></li>'
else:
next_page = '<li><a href="%s?page=%s"><span aria-hidden="true">»</span></a></li>' % (
self.base_url, self.current_page + 1,)
page_num_list.append(next_page)
final_page = '<li><a href="%s?page=%s"><span aria-hidden="true"></span>最后一页</a></li>' % (
self.base_url, self.sum_page_num)
page_num_list.append(final_page)
return ''.join(page_num_list)
def index(request):
count_sum = models.UserInfo.objects.count()
page_info = PageInfo(request.GET.get('page'), 1, count_sum, '/index.html')
user_info = models.UserInfo.objects.all()[page_info.start():page_info.end()]
return render(request, 'index.html', {'user_info': user_info, 'page_info': page_info})
视图View部分的代码:
...
<table class="table table-bordered">
<tr>
<th>用户名</th>
<th>年龄</th>
</tr>
{% for item in user_info %}
<tr>
<td> {{ item.name }}</td>
<td> {{ item.age }}</td>
</tr>
{% endfor %}
</table>
<nav aria-label="Page navigation">
<ul class="pagination">
{{ page_info.pager|safe }}
</ul>
</nav>
...
3. 分页插件
可以把控制器部分的PageInfo类放在单独的Python文件中,可以复用。新建utils目录,然后将其放在utils目录下:
pager.py
class PageInfo:
def __init__(self, current_page, per_page, count_sum, base_url, show_page=11):
"""
:param current_page:当前的页码
:param per_page:每页显示的数据量
:param count_sum:总的数据量
"""
try:
self.current_page = int(current_page)
except Exception as e:
self.current_page = 1
self.per_page = per_page
self.count_sum = count_sum
a, b = divmod(count_sum, per_page)
if b:
a += 1
# page_num:所有页码的数量
self.sum_page_num = a
self.show_page = show_page
self.base_url = base_url
def start(self):
return (self.current_page - 1) * self.per_page
def end(self):
return self.current_page * self.per_page
def pager(self):
half = int((self.show_page - 1) / 2)
# 如果总页数小于11
if self.sum_page_num < self.show_page:
begin = 1
stop = self.sum_page_num + 1
else:
if self.current_page <= half:
begin = 1
stop = self.show_page + 1
else:
if self.current_page + half > self.sum_page_num:
begin = self.sum_page_num - self.show_page + 1
stop = self.sum_page_num + 1
else:
begin = self.current_page - half
stop = self.current_page + half + 1
page_num_list = []
index_page = '<li><a href="%s?page=1"><span aria-hidden="true">首页</span></a></li>' % (self.base_url)
page_num_list.append(index_page)
if self.current_page <= 1:
prev_page = '<li><a href="#"><span aria-hidden="true">«</span></a></li>'
else:
prev_page = '<li><a href="%s?page=%s"><span aria-hidden="true">«</span></a></li>' % (
self.base_url, self.current_page - 1,)
page_num_list.append(prev_page)
for i in range(begin, stop):
if i == self.current_page:
page_num_list.append(
"<li class='active'><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
else:
page_num_list.append("<li><a class='active' href='%s?page=%s'>%s</a></li>" % (self.base_url, i, i))
if self.current_page >= self.sum_page_num:
next_page = '<li><a href="#"><span aria-hidden="true">»</span></a></li>'
else:
next_page = '<li><a href="%s?page=%s"><span aria-hidden="true">»</span></a></li>' % (
self.base_url, self.current_page + 1,)
page_num_list.append(next_page)
final_page = '<li><a href="%s?page=%s"><span aria-hidden="true"></span>最后一页</a></li>' % (
self.base_url, self.sum_page_num)
page_num_list.append(final_page)
return ''.join(page_num_list)
26. 解决跨站脚本攻击XSS
1. Django处理XSS攻击
Django本身默认可以处理XSS攻击,例如:
views.py:
def test(request):
ret = '<script>alert("XSS")</script>'
return render(request, 'test.html', {'ret': ret})
test.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ ret }}
</body>
</html>
并没有解析传入的字符串,当作js脚本来执行。
但是当传入到模板的数据是我们自己的并且是安全的,我们想要告诉系统这是安全的应该如何去做呢!有两种方法,分别是后端标记字符串和前端标记字符串。
2. 前端标记字符串
在模板上写 |safe 的方法可以标记字符串是安全的,可以去解析执行:
test.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ ret|safe }}
</body>
</html>
3. 后端标记字符串
后台把字符串标记成安全的需要在传入模板之前就操作,使用到的是 django.utils.safestring 模块中的 mark_safe 函数:
views.py:
def test(request):
from django.utils.safestring import mark_safe
ret = '<script>alert("XSS")</script>'
ret = mark_safe(ret)
return render(request, 'test.html', {'ret': ret})
test.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ ret }}
</body>
</html>
与前端标记一样:
27. 中间件
1. 中间件执行流程
Django 1.10之前,请求到达第一个中间件,会找到最后一个中间件的 process_response 返回:
而Django 1.10之后,会找到自己的 process_response 返回:
中间件是有序执行的!
2. 中间件原理浅析
中间件是类,首先查看Django源码中的中间件:
所以,我们写的中间件也要继承MiddlewareMixin类,这里把中间件类写在middleware.py,并放置在与manage.py同级目录下:
middleware.py:
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
print('m1.process_request')
def process_response(self, request, response):
print('m1.process_response')
return response
class M2(MiddlewareMixin):
def process_request(self, request):
print('m2.process_request')
def process_response(self, request, response):
print('m2.process_response')
return response
"""
m1.process_request
m2.process_request
test
m2.process_response
m1.process_response
"""
还要在配置文件 settings.py 中注册中间件:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middlewaremon.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'middleware.M1',
'middleware.M2'
]
可以看到 先执行process_request再执行process_response。还有个process_view方法:
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
print('m1.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('m1.process_view')
# return callback(*callback_args, **callback_kwargs)
def process_response(self, request, response):
print('m1.process_response')
return response
class M2(MiddlewareMixin):
def process_request(self, request):
print('m2.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('m2.process_view')
# return callback(*callback_args, **callback_kwargs)
def process_response(self, request, response):
print('m2.process_response')
return response
"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
test
m2.process_response
m1.process_response
"""
process_request有返回值会直接执行自己的process_response。如果执行的是process_view有返回值,会跳过下一个中间件的process_view,执行视图函数(这里是自动调用的)。接下来,不是执行自己的process_response返回给用户,而是 最开始 的process_response(把所有的process_response都执行一遍):
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
print('m1.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
# print(callback, callback_args, callback_kwargs) #<function test at 0x7fd39b986040> () {}
print('m1.process_view')
response = callback(request, *callback_args, **callback_kwargs)
return response
def process_response(self, request, response):
print('m1.process_response')
return response
class M2(MiddlewareMixin):
def process_request(self, request):
print('m2.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('m2.process_view')
def process_response(self, request, response):
print('m2.process_response')
return response
"""
m1.process_request
m2.process_request
m1.process_view
test
m2.process_response
m1.process_response
"""
中间件的类中还有 process_exception 方法,这是关于异常的方法。为了进行测试首先在视图函数中使代码产生异常:
def test(request):
print('test')
a = int('erics')
return HttpResponse()
通过下面的代码测试发现,请求经过所有的process_request、process_view到达视图函数,没有错误的情况下会执行process_response,出现错误会执行process_exception在执行process_response:
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self, request):
print('m1.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
# print(callback, callback_args, callback_kwargs) #<function test at 0x7fd39b986040> () {}
print('m1.process_view')
# response = callback(request, *callback_args, **callback_kwargs)
# return response
#
def process_response(self, request, response):
print('m1.process_response')
return response
def process_exception(self, request, exception):
print('m1.process_exception')
class M2(MiddlewareMixin):
def process_request(self, request):
print('m2.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('m2.process_view')
def process_response(self, request, response):
print('m2.process_response')
return response
def process_exception(self, request, exception):
print('m2.process_exception')
"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
test
m2.process_exception
m1.process_exception
m2.process_response
m1.process_response
"""
在process_exception使用HttpResponse函数返回异常,可以看这个时候中间件方法的执行顺序:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
def process_request(self, request):
print('m1.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
# print(callback, callback_args, callback_kwargs) #<function test at 0x7fd39b986040> () {}
print('m1.process_view')
# response = callback(request, *callback_args, **callback_kwargs)
# return response
#
def process_response(self, request, response):
print('m1.process_response')
return response
def process_exception(self, request, exception):
print('m1.process_exception')
class M2(MiddlewareMixin):
def process_request(self, request):
print('m2.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('m2.process_view')
def process_response(self, request, response):
print('m2.process_response')
return response
def process_exception(self, request, exception):
# print('m2.process_exception')
return HttpResponse('出现异常!')
"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
test
m2.process_response
m1.process_response
"""
异常被M2的process_exception处理了,跳过M1的process_exception,直接执行process_response。页面也不没有报错了:
中间件中还有process_template_response方法,但是这个方法在视图函数的返回值中有render方法才会执行,没有render方法不会被执行,所以这里修改视图函数:
class Foo:
def __init__(self, req):
self.req = req
def render(self):
return HttpResponse()
def test(request):
obj = Foo(request)
return obj
接下来可以看到process_template_response方法被执行,
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
def process_request(self, request):
print('m1.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('m1.process_view')
def process_response(self, request, response):
print('m1.process_response')
return response
def process_exception(self, request, exception):
print('m1.process_exception')
def process_template_response(self, request, response):
"""
视图函数的返回值中有render方法才会执行
:param request:
:param response:
:return:
"""
print('m1.process_template_response')
return response
class M2(MiddlewareMixin):
def process_request(self, request):
print('m2.process_request')
def process_view(self, request, callback, callback_args, callback_kwargs):
print('m2.process_view')
def process_response(self, request, response):
print('m2.process_response')
return response
def process_exception(self, request, exception):
return HttpResponse('出现异常!')
def process_template_response(self, request, response):
print('m2.process_template_response')
return response
"""
m1.process_request
m2.process_request
m1.process_view
m2.process_view
m2.process_template_response
m1.process_template_response
m2.process_response
m1.process_response
"""
process_template_response方法的使用,可以帮助我们把一些可以复用的功能组件拆分出来,如JSON序列化:
class Foo:
def __init__(self, req, status, msg):
self.req = req
self.status = status
self.msg = msg
def render(self):
import json
ret = {
'status': self.status,
'msg': self.msg
}
return HttpResponse(json.dumps(ret))
def test(request):
return Foo(request, True, '错误信息!')
在视图函数中打造功能,改造另外一个对象,让这个对象把我们的数据封装起来:
class JsonResponse:
"""
内部帮助我们序列化,
"""
def __init__(self, req, status, msg):
self.req = req
self.status = status
self.msg = msg
def render(self):
import json
ret = {
'status': self.status,
'msg': self.msg
}
return HttpResponse(json.dumps(ret))
def test(request):
return JsonResponse(request, True, '错误信息!')
视图函数返回的对象且对象中有render方法才会执行process_template_response方法。
3. 中间件的应用
中间件可以对 所有请求或一部分请求做批量处理。使用缓存时,需要对 所有请求进行判断,缓存中如果有就把缓存中的数据返回,没有就执行视图函数。对所有的请求进行判断就需要使用到中间件。
28. Form组件
1. Form组件的功能
对用户提交表单数据进行校验、保留上次输入内容。
2. 数据校验
Form组件对Form表单的方式提交的数据进行校验:
login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
</head>
<body>
<div class="container" style="margin-top: 10px">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-primary">
<div class="panel-heading">
登录
</div>
<div class="panel-body">
<form action="/login.html/" method="POST" name="loginForm">
<div class="form-group">
<label for="name">用户名</label>
<input type="text" class="form-control" name="name" placeholder="请输入用户名">
<div style="color: red;font-weight: bold">{{ obj.errors.name.0 }}</div>
</div>
<div class="form-group">
<label for="">密码</label>
<input type="password" class="form-control" name="pwd" placeholder="请输入密码">
<div style="color: red;font-weight: bold">{{ obj.errors.pwd.0 }}</div>
</div>
{% csrf_token %}
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
class LoginForm(Form):
name = fields.CharField(
required=True,
min_length=6,
max_length=20,
error_messages={
'required': '用户名不能为空!',
'min_length': '用户名长度不能小于6!',
'max_length': '用户名长度不能大于20!'
}
)
pwd = fields.CharField(
required=True,
min_length=6,
max_length=20,
error_messages={
'required': '密码不能为空!',
'min_length': '密码长度不能小于6!',
'max_length': '密码长度不能大于20!'
}
)
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
obj = LoginForm(request.POST)
"""
1、LoginForm实例化时
self.field={'name':正则表达式,'pwd':正则表达式}
2、循环self.field
for k,v in self.field.items():
input_value = request.POST.get(k)
正则表达式和input_value进行匹配
"""
if obj.is_valid():
# 校验通过的数据
print(obj.cleaned_data)
else:
print(obj.errors, type(obj.errors)) # <class 'django.forms.utils.ErrorDict'>
"""
<ul class="errorlist">
<li>name
<ul class="errorlist">
<li>用户名不能为空</li>
</ul>
</li>
<li>pwd
<ul class="errorlist">
<li>用户名不能为空</li>
</ul>
</li>
</ul>
"""
return render(request, 'login.html', {'obj': obj})
上面是Form组件校验以Form表单的方式的提交数据,下面是以AJAX的方式提交数据:
login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
</head>
<body>
<div class="container" style="margin-top: 10px">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-primary">
<div class="panel-heading">
登录
</div>
<div class="panel-body">
<form id="form" action="" method="POST" name="loginForm" novalidate>
<div class="form-group">
<label for="name">用户名</label>
<input type="text" class="form-control" name="name" placeholder="请输入用户名">
<div style="color: red;font-weight: bold">{{ obj.errors.name.0 }}</div>
</div>
<div class="form-group">
<label for="">密码</label>
<input type="password" class="form-control" name="pwd" placeholder="请输入密码">
<div style="color: red;font-weight: bold">{{ obj.errors.pwd.0 }}</div>
</div>
<div class="form-group">
<label for="">邮箱</label>
<input type="email" class="form-control" name="email" placeholder="请输入密码">
<div style="color: red;font-weight: bold">{{ obj.errors.email.0 }}</div>
</div>
<div class="form-group">
<label for="">手机号</label>
<input type="text" class="form-control" name="phone" placeholder="请输入密码">
<div style="color: red;font-weight: bold">{{ obj.errors.phone.0 }}</div>
</div>
{% csrf_token %}
<button id="loginBtn" type="button" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>
$(function () {
ajaxLogin();
})
function ajaxLogin() {
$('#loginBtn').click(function () {
var formData = $('#form').serialize();//会忽略字段左右的空格
console.log(formData)//name=thanlon&pwd=123456&email=thanlon@sina&phone=18523332005&csrfmiddlewaretoken=xxx
$.ajax({
url: '/login.html/',
type: 'post',
data: formData,
dataType: 'json',
success: function (arg) {
console.log(arg);//{"status": 0, "msg": null}
if (arg.status) {
//location.href = 'https://www.baidu'
} else {
//清除之前的错误提示信息
$('.clearTag').remove();
console.log(arg.msg)//对象类型
$.each(arg.msg, function (index, value) {
/**
* alert(index);//错误字段,pwd
* alert(value);//错误消息,如This field is required.
* alert(typeof (index)) //string
* alert(typeof (value))//object
* alert(value[0])
*/
var tag = document.createElement('span');
tag.innerHTML = value[0];
tag.className = 'clearTag';
$('#form').find('input[name="' + index + '"]').after(tag);
})
}
}
})
})
}
</script>
</html>
views.py:
from django.forms import Form, fields
from django.shortcuts import render, HttpResponse
from app01 import models
import json
class LoginForm(Form):
name = fields.CharField()
pwd = fields.CharField()
email = fields.EmailField()
phone = fields.RegexField('185\d+')
def ajax_login(request):
if request.method=='GET':
return render(request,'login.html')
ret = {'status': True, 'msg': None}
print(type(json.dumps(ret))) # <class 'str'>
obj = LoginForm(request.POST)
if obj.is_valid():
print(obj.cleaned_data)#{'name': 'thanlon', 'pwd': '123456', 'email': 'thanlon@sina', 'phone': '18523332005'}
else:
print(obj.errors) # obj.errors是对象
ret['status'] = False
ret['msg'] = obj.errors
return HttpResponse(json.dumps(ret)) # <class 'str'>
AJAX仅用来验证功能,Form可以验证功能也可以生成HTML标签。
3. 常用字段和参数
Field:
required = True, # 是否允许为空
label = None, # 用于生成Label标签
initial = None, # 初始值
help_text = '', # 帮助信息(在标签旁边显示)
error_messages = {}, # 错误信息{'invalid':'格式错误'}
show_hidden_initial = None, # 是否在当前插件后面再加上一个隐藏的且具有默认值的插件(可用于检验两次输入是否一致)
validators = [], # 自定义验证规则
localize = False, # 是否支持本地化
disabled = False, # 是否可以编辑
label_suffix = None, # Label内容后缀
widget = None, # HTML插件
CharField类和IntegerField 类都继承Field类,但是除了具有Field中的所有属性之外还有其它属性:
CharField(Field):
min_length = None,
max_length = None,
strip = True, # 是否移除空白
IntegerField(Field):
min_length = None,
max_length = None,
DecimalField(IntegerField):
max_value=None, # 最大值
min_value=None, # 最小值
max_digits=None, # 总长度
BaseTemporalField(Field)
input_formats=None, # 时间格式化
BaseTemporalField(Field)
input_formats=None, # 时间格式化
DateField(BaseTemporalField): # 格式:2020-10-12
TimeField(BaseTemporalField): # 格式:12:50
DateTimeField(BaseTemporalField): # 格式:2020-10-12 12:50
DurationField(Field): # 时间间隔:%d %H:%M:%S.%f
RegexField(CharField):
regex='185\d+',
max_length=None,
min_length=None,
error_messages={}
EmailField(CharField)
URLField(CharField)
SlugField(CharField)
GenericIPAddressField(CharField)
4. 保留上次输入内容
Form表单的方式提交数据,页面会刷新会失去提交的内容。AJAX方式提交时页面不刷新,上次输入内容自动保存。使用Form表单提交数据保存上次输入的内容也是可以实现的,需要应用到Form组件提供的第二个功能 生成HTML标签。只需要在 input 框中加入验证通过的数据 obj.cleaned_data:
views.py:
class LoginForm(Form):
name = fields.CharField(
label='用户名',
)
pwd = fields.CharField(
label='密码',
)
email = fields.EmailField(
label='邮箱'
)
phone = fields.RegexField(
label='手机号',
regex='185\d+',
)
def ajax_login(request):
if request.method == 'GET':
obj = LoginForm()
return render(request, 'login.html',{'obj': obj})
else:
obj = LoginForm(request.POST)
if obj.is_valid():
print(obj.cleaned_data)
else:
print(obj.errors)
return render(request, 'login.html', {'obj': obj})
login.html:
...
<form id="form" action="" method="POST" name="loginForm" novalidate>
<!--{{ obj.as_p }}-->
<div class="form-group">
{{ obj.name.label }}
<input type="text" class="form-control" name="name" placeholder="请输入用户名" value="{{ obj.cleaned_data.name }}">
<div style="color: red;font-weight: bold">{{ obj.errors.name.0 }}</div>
</div>
<div class="form-group">
{{ obj.pwd.label }}
<input type="password" class="form-control" name="pwd" placeholder="请输入密码" value="{{ obj.cleaned_data.pwd }}">
<div style="color: red;font-weight: bold">{{ obj.errors.pwd.0 }}</div>
</div>
<div class="form-group">
{{ obj.email.label }}
<input type="email" class="form-control" name="email" placeholder="请输入邮箱" value="{{ obj.cleaned_data.email }}">
<div style="color: red;font-weight: bold">{{ obj.errors.email.0 }}</div>
</div>
<div class="form-group">
{{ obj.phone.label }}
<input type="text" class="form-control" name="phone" placeholder="请输入手机号" value="{{ obj.cleaned_data.phone }}">
<div style="color: red;font-weight: bold">{{ obj.errors.phone.0 }}</div>
</div>
{% csrf_token %}
<button id="loginBtn" type="submit" class="btn btn-primary">登录</button>
</form>
...
上一次验证通过的数据才会被保存:
版权声明:本文标题:Django入门到实战 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1763827892a3273829.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论