学习视频网址:https://www.bilibili.com/video/BV1mW411G7g6?from=search&seid=11980423788703295979
1 web框架本质
1.1 socket服务端
HTTP协议
客户端和服务端交互遵循Http协议
发送:get请求 127.0.0.1/index?p=123
请求头
GET /index HTTP/1.1
Host: 127.0.0.1:8080\r\nConnection: keep-alive
sec-ch-ua: “Google Chrome”;v=”87”, “ Not;A Brand”;v=”99”, “Chromium”;v=”87”\r\nsec-ch-ua-mobile: ?0\r\nUpgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
请求体:
p=123
请求头第一行GET斜杠后面就是url,默认就是/ 如果是127.0.0.1/index 则请求头第一行应该是GET/index);
响应:
普通响应:页面直接显示
重定向响应:再发一次http请求
响应头
Status Code:200
age: 88788
ali-swift-global-savetime: 1614057834
content-encoding:gzip
content-length: 177480
content-type: text/html; charset=UTF-8
date: Tue, 23 Feb 2021 05:23:54 GMT
eagleid: 78dddf9b16141466222165103e
link: http://www.runoob.com/wp-json/; rel=”https://api.w.org/“
server: Tengine
timing-allow-origin: *
vary: Accept-Encoding
via: cache71.l2cn2623[0,200-0,H], cache80.l2cn2623[10,0], cache9.cn2429[0,200-0,H], cache7.cn2429[1,0]
x-cache: HIT TCP_MEM_HIT dirn:1:14559382
x-swift-cachetime: 86400
x-swift-savetime: Wed, 24 Feb 2021 00:30:22 GMT
响应体(response)
用户可以看到的页面内容(本质是字符串) 是经过浏览器渲染得到的
浏览器(socket客户端)
2.输入域名通过DNS服务器解析成要请求的ip,指定端口80
sk.socket()
sk.connect(ip)
sk.send(‘我想要…’)
5.接收
6.连接断开
博客园(socket服务端)
1.服务器运行:监听ip和端口
while True:
等待用户连接
3.收到’我想要…’
4.响应:’好’
断开连接
处理请求过程交给别人去做
socket服务端代码
第一版本
import socketdef handle_request(client):buf = client.recv(1024) # 获取用户请求buf = str(buf, encoding='utf-8')hearders, bodys = buf.split('\r\n\r\n')temp_list = hearders.split('\r\n')method, url, protocal = temp_list[0].split(' ')if url == '/':client.send(b"HTTP/1.1 200 OK\r\n\r\n")client.send(b"Hello, liuxuebin")# 服务端发送数据else:client.send(b"HTTP/1.1 404 NO\r\n\r\n")client.send(b'404 not found')def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 127.0.0.1 指的是自己的主机 不是公网ipsock.bind(('127.0.0.1', 8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
因为url较多,如果这样写就要写大量代码来判断url
第二版本
import socketdef f1(request):"""处理用户请求,并返回响应信息:param request: 用户请求信息:return:"""request = bytes(request, encoding='utf-8')return requestdef f2():return b'f2'routers = [('/xxx', f1),('/ooo', f2)]def handle_request(client):buf = client.recv(1024) # 获取用户发送的数据buf = str(buf, encoding='utf-8')hearders, bodys = buf.split('\r\n\r\n')temp_list = hearders.split('\r\n')method, url, protocal = temp_list[0].split(' ')func_name = Nonefor item in routers:if item[0] == url:func_name = item[1]breakif func_name:client.send(b"HTTP/1.1 200 OK\r\n\r\n")response = func_name(buf)else:client.send(b"HTTP/1.1 404 OK\r\n\r\n")response = b'404'client.send(response)def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 127.0.0.1 指的是自己的主机 不是公网ipsock.bind(('127.0.0.1', 8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
f1和f2函数不止可以返回字节还可以返回html文件或者是数据库的数据,返回的html本质就是字节类型的字符串
第三个版本
返回静态html
import socketdef f1(request):"""处理用户请求,并返回响应信息:param request: 用户请求信息:return:"""request = bytes(request, encoding='utf-8')return requestdef f2():f = open('index.html', 'rb')data = f.read()print(data)f.close()return datarouters = [('/xxx', f1),('/ooo', f2)]def handle_request(client):buf = client.recv(1024) # 获取用户发送的数据buf = str(buf, encoding='utf-8')hearders, bodys = buf.split('\r\n\r\n')temp_list = hearders.split('\r\n')method, url, protocal = temp_list[0].split(' ')func_name = Nonefor item in routers:if item[0] == url:func_name = item[1]breakif func_name:client.send(b"HTTP/1.1 200 OK\r\n\r\n")response = func_name()else:client.send(b"HTTP/1.1 404 OK\r\n\r\n")response = b'404'client.send(response)def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 127.0.0.1 指的是自己的主机 不是公网ipsock.bind(('127.0.0.1', 8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
但现在的网站是静态网站,数据是不变的。
第四版本
动态网站:html只是一个模板,里面的数据是变化的
import socketimport timedef f1(request):"""处理用户请求,并返回响应信息:param request: 用户请求信息:return:"""request = bytes(request, encoding='utf-8')return requestdef f2():f = open('index.html', 'r', encoding='utf-8')data = f.read()f.close()ctime = time.time()data = data.replace('@time@', str(ctime)) #数据是动态的return bytes(data, encoding='utf-8')routers = [('/xxx', f1),('/ooo', f2)]def handle_request(client):buf = client.recv(1024) # 获取用户发送的数据buf = str(buf, encoding='utf-8')hearders, bodys = buf.split('\r\n\r\n')temp_list = hearders.split('\r\n')method, url, protocal = temp_list[0].split(' ')func_name = Nonefor item in routers:if item[0] == url:func_name = item[1]breakif func_name:client.send(b"HTTP/1.1 200 OK\r\n\r\n")response = func_name()else:client.send(b"HTTP/1.1 404 OK\r\n\r\n")response = b'404'client.send(response)def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 127.0.0.1 指的是自己的主机 不是公网ipsock.bind(('127.0.0.1', 8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
第5版本
从数据库获取数据返回前端
import socketimport timeimport pymysqldef f1(request):"""处理用户请求,并返回响应信息:param request: 用户请求信息:return:"""request = bytes(request, encoding='utf-8')return requestdef f2():"""获取当前时间:return:"""f = open('index.html', 'r', encoding='utf-8')data = f.read()f.close()ctime = time.time()data = data.replace('@time@', str(ctime))return bytes(data, encoding='utf-8')def f3():"""连接数据库并获取查询结果:return:"""conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='362514', db='djangodata')# 创建游标cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)# 执行SQL,并返回收影响行数cursor.execute("select * from userlist")# 获取查询到的所有数据user_list = cursor.fetchall()# 提交,不然无法保存新建或者修改的数据conn.commit()# 关闭游标cursor.close()# 关闭连接conn.close()print(user_list) #[{'id': 1, 'username': 'ssda', 'password': '123'}, {'id': 2, 'username': 'acasc', 'password': '233'}, {'id': 3, 'username': 'wad', 'password': '231'}, {'id': 4, 'username': 'aada', 'password': '432'}, {'id': 5, 'username': '撒打算', 'password': '129'}]content_list = []for row in user_list:tp = '<tr><td>%s</td><td>%s</td><td>%s</td></tr>'%(row['id'],row['username'],row['password'])content_list.append(tp)content = ''.join(content_list)f = open('index.html', 'r', encoding='utf-8')template = f.read()f.close()# 模板渲染data = template.replace('@content@', content)return bytes(data, encoding='utf-8')routers = [('/xxx', f1),('/ooo', f2),('/userlist', f3)]def handle_request(client):buf = client.recv(1024) # 获取用户发送的数据buf = str(buf, encoding='utf-8')hearders, bodys = buf.split('\r\n\r\n')temp_list = hearders.split('\r\n')method, url, protocal = temp_list[0].split(' ')func_name = Nonefor item in routers:if item[0] == url:func_name = item[1]breakif func_name:client.send(b"HTTP/1.1 200 OK\r\n\r\n")response = func_name()else:client.send(b"HTTP/1.1 404 OK\r\n\r\n")response = b'404'client.send(response)def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 127.0.0.1 指的是自己的主机 不是公网ipsock.bind(('127.0.0.1', 8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
基于jinjia2模板渲染
不需要后端写html标签
import socketimport timeimport pymysqlfrom jinja2 import Templatedef f1():"""处理用户请求,并返回响应信息:param request: 用户请求信息:return:"""conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='362514', db='djangodata')# 创建游标cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)# 执行SQL,并返回收影响行数cursor.execute("select * from userlist")# 获取查询到的所有数据user_list = cursor.fetchall()# 提交,不然无法保存新建或者修改的数据conn.commit()# 关闭游标cursor.close()# 关闭连接conn.close()f = open('jinjia2模板渲染.html', 'r', encoding='utf-8')data = f.read()f.close()template = Template(data)data = template.render(user_list=user_list, user=user_list)print(data)return data.encode('utf-8')routers = [('/host.htm', f1)]def handle_request(client):buf = client.recv(1024) # 获取用户发送的数据buf = str(buf, encoding='utf-8')hearders, bodys = buf.split('\r\n\r\n')temp_list = hearders.split('\r\n')method, url, protocal = temp_list[0].split(' ')func_name = Nonefor item in routers:if item[0] == url:func_name = item[1]breakif func_name:client.send(b"HTTP/1.1 200 OK\r\n\r\n")response = func_name()else:client.send(b"HTTP/1.1 404 OK\r\n\r\n")response = b'404'client.send(response)def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 127.0.0.1 指的是自己的主机 不是公网ipsock.bind(('127.0.0.1', 8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
总结
自己写网站<br />a.socket服务端<br />b.根据不同url返回不同内容<br />路由系统:<br />url->函数<br />c.字符串返回给浏览器<br />模板引擎渲染:<br />html充当模板<br />自己创造任意数据
Http请求生命周期:请求头-》提取url-》路由关系匹配-》函数(模板+数据渲染)-》返回用户(响应体+相响应头)
web框架
框架种类:
功能包含:
-a,b,c —>Tornado
-b,c ,第三方a—>Django a用的是wsgiref
-b,第三方a,c —>Flask c用的是jinjia2 ,a用的是wsgiref
2 初识Django
2.1 创建和使用Django
用pycharm专业版新建项目-django项目(djangoProject1)
asgi.py: 一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
setting:配置文件
urls:路由系统:url->函数
wsgi:用于定义django用socket,wsgiref,uwsgi
manage:对当前django程序所有操作可以基于python manage.py runserver
views:写处理请求的函数
创建第一个项目教程:
https://www.runoob.com/django/django-first-app.htmlhttps://www.runoob.com/django/django-first-app.html
2.2 django静态模板以及配置
template下面创建一个html文件
views.py下的代码
from django.http import HttpResponsefrom django.shortcuts import renderdef hello(request):"""处理用户请求,并返回内容:param request: 用户请求相关的所有信息(对象):return:"""return HttpResponse("<input/> ")def login(request):# 自动找到模板路径下的login.html文件,读取内容并返回给用户return render(request, 'login.html')
urls.py添加对应路由
之所以可以找到login.html是因为配置文件setting中
引用css文件

要进行setting的配置
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'))
2.3 用户登录示例
views.py
from django.http import HttpResponsefrom django.shortcuts import render, redirectdef hello(request):"""处理用户请求,并返回内容:param request: 用户请求相关的所有信息(对象):return:"""return HttpResponse("<input/> ")def login(request):# print(request.GET) get请求携带的数据 <QueryDict: {'p': ['11233']}># 自动找到模板路径下的login.html文件,读取内容并返回给用户if request.method == 'GET':return render(request, 'login.html')else:# 用户POST提交的数据(请求体)# print(request.POST) # post请求携带的数据 <QueryDict: {'username': ['liuxuebin'], 'password': ['123']}>user = request.POST.get('username')password = request.POST.get('password')if user == 'liuxuebin' and password == '123':# 登陆成功,跳转到某个地址return redirect('https://www.baidu.com/')else:# 登陆失败,这里是将msg对应的值显示到前端,前端用特殊的语法,底层其实用了replace替换然后返回给前端字符串return render(request, 'login.html', {'msg': '用户名或密码错误!'})
login.html
总结
创建Django步骤
1.创建项目
2.配置
-模板路径
-静态文件路径
-注释一条语句
3.启动项目
需要注意:
post请求:request.post->来自请求体
get请求:request.get->来自请求头的url中
2.4 django模板语言
简单的特殊标记
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><h1>模板标记学习</h1><!-- 后台name的值--><p>{{ name }}</p><!-- 列表取索引值--><p>{{ users.0 }}</p><p>{{ users.1 }}</p><!-- 字典取索引值--><p>{{ user_dicts.k1 }}</p><h3>循环</h3><ul>{% for item in users %}<li>{{ item }}</li>{% endfor %}</ul><table border="1">{% for row in user_list_dict %} {# user_list_dict #} 是一个字典列表<tr><td>{{ row.id }}</td><td>{{ row.name }}</td></tr>{% endfor %}</table></body></html>
母版
最开始我们开发一个一个管理系统,如下列学院系统
classes.html文件
students.html文件
可以发现多个html文件的导航栏和左边菜单栏都是相同的,通常我们的做法是重复性的复制这段代码,加入到不同的html中,如果这段被复用的代码发生了修改,则使用这段代码的多个html都要进行修改,这样无疑是麻烦的,影响开发效率!
所以提出了一个好的方法呢就是——————-母版
示例
layout.html是母版
layout页面
classes是要继承母版的子版
执行这段代码过程:首先读取classes.html文件,看到extends就会把后面的html拿过来进行替换,classes发生变化,看到替换后的第一个block的时候会把子板里面的block(也就是此时classes.html里面的第二个block)里面的代码替换第一个block位置处的代码,生成全新html,返回给前端。
一般情况下母版会写3处block css body js各写一个 是为了子版扩充自己的css js body是使用的,所以说母版只放所有子版共用的东西,类似于父类子类,公共部分写在父类。
函数
模板自定义函数
-使用前五个步骤
-@register.filter
-@register.simple_tag —-常用
include
场景:有一个组件会在多个页面多个位置使用到
被使用组件:
如果页面里多处使用到,以往的方法就是在每个地方写一遍,这样代码可读性低,代码量太大,因为人们的需求就出现了include
未使用include
{% load xx %}<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><h3>组件</h3><div class="title">标题</div><div class="content">内容</div>{% for row in v %} {# v接收字典,但是for迭代的是键 #}{{ row }}{% endfor %}{# filter装饰器装饰的函数调用方式 #}{# 将接收到的字符串转为大写 相当于调用函数 #}{{ name|my_upper}}{# 将name的值和666拼接起来 #}{{ name|splice:'666'}}<h3>组件</h3><div class="title">标题</div><div class="content">内容</div><h2>tag</h2>{# simple_tag装饰器装饰的函数调用方式 #}{% my_lower 'ALEX' %}{% splice 'alex' 'nihao' 'woshihaoren' %}{# filter装饰的函数可以搭配if使用,而simple_tag不可以 #}{% if name|my_bool %}<h3>真</h3>{% else %}<h3>假</h3>{% endif %}<h3>组件</h3><div class="title">标题</div><div class="content">内容</div></body></html>
引入include
{% load xx %}<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body>{% include 'public_module.html' %}{% for row in v %} {# v接收字典,但是for迭代的是键 #}{{ row }}{% endfor %}{# filter装饰器装饰的函数调用方式 #}{# 将接收到的字符串转为大写 相当于调用函数 #}{{ name|my_upper}}{# 将name的值和666拼接起来 #}{{ name|splice:'666'}}{% include 'public_module.html' %}<h2>tag</h2>{# simple_tag装饰器装饰的函数调用方式 #}{% my_lower 'ALEX' %}{% splice 'alex' 'nihao' 'woshihaoren' %}{# filter装饰的函数可以搭配if使用,而simple_tag不可以 #}{% if name|my_bool %}<h3>真</h3>{% else %}<h3>假</h3>{% endif %}{% include 'public_module.html' %}</body></html>
<h3>组件</h3><div class="title">标题:{{ name }}div><div class="content">内容:{{ name }}</div>
在组件里面写入{{name}}页面也会渲染出来,因为在render执行的时候,先读取test.html发现include、extend特殊标记先进行关联替换,然后再对其他的特殊标记进行渲染,传给前端页面解析出来
3 学员管理
表:
班级
学生
老师
单表操作:classes表
增
删
改
查
一对多操作:students表
增
删
改
查
多对多操作:teacher_class表
增
删
改
查
3.1 学员管理之数据库表结构设计
3.2 学员管理之查看班级列表以及添加班级
功能实现分析:
1.首先 classes.html页面加一个添加标签,点击添加会跳转到另一个url,返回添加班级页面
2.添加班级页面
要有文本框输入和添加按钮
填写完表单,点击添加按钮会向数据库中添加一条记录,所以添加按钮要对应一个url发送的是post请求
返回添加班级页面和提交按钮返回的页面(提交按钮返回的页面是添加记录后的班级列表页面也就是classes.html)写在一个函数里,否则写两个函数,函数体太短,用请求方法来进行判断,并返回相应页面。
3.添加班级页面没有班级名称点击提交会报错,需要完善:当点击提交时提示用户!
3.3 学员管理之删除班级
功能实现分析:
1.每一条记录后面添加删除标签
2.点击删除标签进行url访问(get请求)要携带id参数,发送给后端,根据id进行在数据库中删除记录
3.后端拿到参数操作数据库进行删除记录
4.重定向/classes/
意思是再次访问/classes/路由,发出get请求,执行classes函数。
可以理解为return redirect(‘/classes/‘)等价于classes函数体,如果写return render(…)就要把classes函数体再写一遍。
return redirect(‘/classes/‘)就是后台返回响应头和响应体,响应头就是127.0.0.1/classes,发送给浏览器,浏览器再次访问这个路由地址
3.4 学员管理之编辑班级
功能实现分析:
1.添加编辑标签
2.点击编辑标签跳转到编辑页面,要存在被编辑内容
3.编辑完成,点击提交,重定向回查看班级页面
3.5 学员管理之查看学生列表
3.6 学员管理之添加学生信息
功能实现分析:
1.添加界面有学生姓名和学生班级,学生班级要用下拉框,因为输入时可能班级表里没有这个班级,也因为是外键所以用下拉框
2.所以不能只单单返回一个页面,要提前查询到班级表中的班级名称,用模板标记语言进行渲染再生成页面
3.这里提交的时候进行数据库插入操作,可是页面是插入班级名称,而数据库是插入班级id,所以后台要拿到的是班级id。
3.7 学员管理之编辑学生
功能实现分析:
难点在下拉框默认选择学生原来的班级
注意:添加、编辑需要在views中对用户提交的数据进行判断(django中有个form表单验证组件)
例如添加班级时表单里没有写班级就提交,要有相关处理
3.8 学员管理之基于Ajax添加班级
模态对话框:就是在屏幕上再加上两层,一层遮罩层,一层就是对话框了
下图就是模态对话框
form表单提交,页面会刷新,则模态对话框会消失
需求:点击提交,对话框不消失,并且可以判断当没有输入班级名称时,显示错误。
如果用form表单提交请求,是无法做到以上需求的。
完成这个需求就要用到Ajax来提交请求了
Ajax应用:网易邮箱登陆界面—当你输入密码或用户名错误时,提示输入错误,此时页面不会刷新,但已经把数据提交到后台了。
使用ajax:引入jquery
<script>function send(){$.ajax({url:'/modal_addClass/', //请求的urltype:'POST',data:{'title':'kkkkkkkk'}, //发送post请求的请求体success:function (data){//当服务端处理完成后,返回数据时,该函数自动调用//data时服务端返回的值console.log(data)}})}</script>
def modal_addClass(request):"""处理对话框提交添加班级请求:param request::return:"""print(request.POST)# class_name = request.POST.get('class_name')# if len(class_name) > 0:# sqlhelper.modify('insert into classes(classname) values(%s)', [class_name])# return redirect('/classes/')# else:# pass# print(class_name)return HttpResponse('ok')
服务端返回ok不会直接在前端页面渲染出来,发送给data保存起来,再执行函数内部逻辑,这个过程页面始终没有刷新。
如果用ajax发送请求,用 redirect(‘/classes/‘) 不会看到页面刷新,浏览器没有拿到响应体,会把页面内容放到data中,要想完成页面跳转,自己写js,如下图

而用form表单提交数据会看到response有页面内容,如下图
总结
模态对话框
一般都会和Ajax一起使用
使用场景:
少量输入框
少量数据
新url方式
操作多
大量数据以及输入框
3.9 学员管理之基于Ajax编辑班级
js阻止默认事件的发生
<a href="https://www.baidu.com/" onclick="showEditModal()">模态对话框编辑</a><script>//显示编辑模态对话框function showEditModal(){document.getElementById('shadow').classList.remove('hide');document.getElementById('modalEdit').classList.remove('hide');}</script>
这里a标签相当于绑定了两个事件,一个是自己定义的事件、一个是默认事件,会先执行自己定义的事件再去执行默认事件,页面效果就是先弹出对话框然后跳转到百度页面,如果想要用阻止默认事件的发生,修改代码后,如下:
<a href="https://www.baidu.com/" onclick="return showEditModal()">模态对话框编辑</a><script>//显示编辑模态对话框function showEditModal(){document.getElementById('shadow').classList.remove('hide');document.getElementById('modalEdit').classList.remove('hide');return false}</script>
难点:点击模态对话框编辑班级,对话框默认显示原来班级名称,不能像之前form表单那样做了。
解决方案:不再向后台发请求,用前端js完成
1.获取当前点击标签
2.获取当前标签的父标签,再找其上方标签
结构如下:
var v = $(ths).parent().prevAll();//获取当前标签父标签之前所有同级标签
3.10 学员管理值基于Ajax添加学生信息
与前面自己用js写事件绑定语法不同,这里基于jquery进行事件绑定
<a id="showAddModal">模态对话框添加</a><script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script><script>//基于jquary进行事件绑定$(function (){$('#showAddModal').click(function (){//事件处理})})</script>
同样可以阻止默认事件发生—最后加return false
这里点击模态对话框添加会弹出窗口,要求下拉框显示所有班级,此时的做法和之前请求新url添加学生信息不同,这里没有进行form表单提交,没有进行url请求,有一种做法就是请求students/这个url的时候就已经把班级名称都给拿到,再student函数中加入
相应前端做修改
3.11 学员管理之基于Ajax编辑学生信息
需求:对于编辑学生窗口,打开时学生班级默认选择原来的班级
<p>学生班级:<select id="editClassId">{% for row in classList %}<option value="{{ row.id }}">{{ row.classname }}</option>{% endfor %}</select></p>


通过dom操作获取当前下拉框中选中的班级id
因为每一个option都有value值
通过
来改变下拉框内的文本
3.12 学员管理之查看老师以及任课班级列表
涉及多对多的表查询,查询sql语句需要连表操作
查看结果如下
但是这里有一个缺点:如果同一个老师教了多个班级,这还要自己慢慢找这个老师所教的全部班级,如果让所教授班级显示在同一行就可以了,做法就是在后台数据库查询,拿到数据之后进行一些处理,处理图示
处理代码:
teacher_class_list = [{'teacherId': 6, 'name': '孙老师', 'classname': '计算机科学11班'},{'teacherId': 6, 'name': '孙老师', 'classname': '商务英语1班'},{'teacherId': 10, 'name': '王老师', 'classname': '商务英语1班'},{'teacherId': 9, 'name': '刘老师', 'classname': '环境艺术1班'},{'teacherId': 10, 'name': '王老师', 'classname': '环境艺术1班'},{'teacherId': 9, 'name': '刘老师', 'classname': '环境艺术2班'}]result = {# 把teacherId当作字典的键,如果键相同就往该键所对应的值中的classnames里面追加班级# 6:{'teacherId': 6, 'name': '孙老师', 'classnames': ['计算机科学11班','商务英语1班']},# 10:{'teacherId': 10, 'name': '王老师', 'classname': ['商务英语1班','环境艺术1班']},# 9: {'teacherId': 9, 'name': '刘老师', 'classname': ['环境艺术1班',环境艺术2班]},}for row in teacher_class_list:teacherId = row['teacherId']if teacherId in result:result[teacherId]['classnames'].append(row['classname'])else:result[teacherId] = {'teacherId': row['teacherId'], 'name': row['name'], 'classnames': [row['classname']]}for row in result.values():print(row)"""处理之后的结果{'teacherId': 6, 'name': '孙老师', 'classnames': ['计算机科学11班', '商务英语1班']}{'teacherId': 10, 'name': '王老师', 'classnames': ['商务英语1班', '环境艺术1班']}{'teacherId': 9, 'name': '刘老师', 'classnames': ['环境艺术1班', '环境艺术2班']}"""
3.13 学员管理之添加老师任课信息
在添加老师任课信息的时候,当前端页面添加老师姓名和选取多个任课班级,数据库要首先在老师表里插入一名新老师,然后在老师班级关系表中插入同一名老师id以及任课多个班级id,这个时候用如下代码可以完成
但是这会频繁的连接和关闭数据库,效率太低,故不采用!
3.14 学员管理之编辑老师任课信息
难点:点击编辑按钮后,默认选择老师原来任课班级
做法:后台查询出老师当前所任教的班级id,前台用in操作符来完成
<select multiple>{% for item in class_list %}{#如果item.id在当前老师任教的班级id中#}{% if item.id in teacher_class_id %}<option selected>{{ item.classname }}</option>{% else %}<option>{{item.classname }}</option>{% endif %}{% endfor %}</select>
teacher_class_id = obj.get_list('select teacher_class.classId from teacher_class,classes where teacher_class.classId=classes.id and teacherId=%s', [teacherId])#拿到的格式是[{'classId': 45}, {'classId': 46}],前台用in无法判断,要转换成列表,以下代码就是转换成列表的过程temp = []for i in teacher_class_id:temp.append(i['classId'])print(temp) #[45,46]return render(request, 'editteacher_class.html', {'teacher': teacher, 'class_list': class_list, 'teacher_class_id': temp})
4 初始Cookie
cookie:
a.保存在用户浏览器端的一个键值对
b.服务端可以给用户浏览器端写cookie
c.客户端每次发请求时,会携带cookie去
例子:类似于张三取登陆的时候输入用户名和密码,发送给后台进行数据库校验,然后成功登陆,然后过了一会张三要去加入购物车,但是此时因为http请求是无状态的,,后台已经无法确定这次申请加入购物车得请求是谁发的,所以需要张三再登陆一次才能加入,而解决办法就是张三登录成功之后会给张三一个纸条,作为张三登陆过的凭证,张三再次加入购物车的时候就给后台看这张凭证,后台就知道张三已经登陆过了,这个凭证其实就是cookie,张三就是浏览器端,所以说cookie是保存在浏览器端的。
作用:用于用户登录
5 Django程序目录介绍
创建app01:python manage.py startapp app01
project 项目文件夹
- app01:放业务代码,每个业务放到一个app
- migrations:数据库更新历史
- admin.py:django自带后台管理相关配置
- apps.py: 存放当前应用程序的配置
- models:写django里面orm用来做映射的类
- tests.py:单元测试
- url.py:二级路由文件
- views.py:视图函数(业务处理)
- project 与项目名称相同文件夹
- setting:该项目配置文件
- urls:一级路由文件
- wsgi:用于定义django用socket,wsgiref,uwsgi
- manage:对当前django程序所有操作可以基于python manage.py runserver
6 路由系统
6.1 路由系统之动态路由
以前对某个记录做编辑的时候,url会自动跳转并携带参数
这种url是不好的,因为权重小,比如百度搜索关键字,这种url会放到搜索内容的下面,而且不美观。

这里箭头所指实际上是正则表达式,如果不写/那么可以匹配index后面加人以字母的url,加了/只有index/才能匹配到,所以最好用index$这种形式,这样访问的时候不需要加/
以后用这种形式
#url(r'^index$', views.index), # $表示终止符#url(r'^edit/(\w+).html$', views.edit),#url(r'^edit/(\w+)/(\w+)', views.edit) # 动态路由,后面的正则,视图函数要用相同数目的用参数接收#url(r'^edit/(?P<a2>\w+)/(?P<a1>\w+)', views.edit) # 另一种动态路由方式,可以给指定参数传值def index(requests):user_list = ['liuxuebin', 'zhangsan', 'lisi']return render(requests, 'index.html', {'user_list': user_list})def edit(request, *args,**kwargs):""":param request::param a1: 正则表达式参数:param a2: 正则表达式参数:return:"""print(args)
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><ul>{% for i in user_list %}<li>{{ i }}|<a href="/edit/{{ i }}.html">编辑</a></li>{% endfor %}</ul></body></html>
6.2 路由系统之路由分发
当多个人协同开发的时候,每个人会写一个app文件,里面都有各自的views,每个人都会再urls里面写路由,写的过程中可能会出现相同的路由,所以需要一种机制,让每一个app都有各自的url,这样就不会出现相同的路由了,这种机制就是路由分发。
实现方法:
项目同名文件夹下的urls:
from django.conf.urls import url, includeurlpatterns = [url(r'^app01/', include('app01.urls')),url(r'^app02/', include('app02.urls')),
app01下的urls:
from django.conf.urls import urlfrom app01 import viewsurlpatterns = [url(r'^index.html$', views.index)]
app02下的urls:
from django.conf.urls import urlfrom app02 import viewsurlpatterns = [url(r'^index.html$', views.index),]
6.3 路由系统之别名URL
后端反生成url
urls.py下
from django.conf.urls import url, includefrom django.shortcuts import HttpResponsefrom app01 import viewsdef default(request):"""路由匹配失败,返回的页面:param request::return:"""return HttpResponse('路由匹配失败')urlpatterns = [url(r'^index/(\d+)/', views.index, name='n1'), # name='n1' 是根据名字反推url
app01/views.py下
from django.shortcuts import renderfrom django.shortcuts import HttpResponsefrom django.urls import reversedef index(requests, a1):user_list = ['liuxuebin', 'zhangsan', 'lisi']# url.py文件下url用的正则,后台想要反向生成url并重新将url修改,args的参数是多少,就生成多少v = reverse('n1', args=(1,))print(v)return render(requests, 'index.html', {'user_list': user_list})
前端反生成url
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body>{#根据名称反生成url#}<form method="post" action="{% url 'm1' %}"><input type="text"></form></body></html>
urls.py
from django.conf.urls import urlfrom app01 import viewsurlpatterns = [url(r'^login/', views.login, name='m1')
views.py
def login(request):return render(request, 'login.html')
index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><ul>{% for i in user_list %}<li>{{ i }}|<a href="{% url 'n2' i %}">编辑</a></li>{% endfor %}</ul></body></html>
模板渲染之后的结果
好处:不需要自己写url,特别是url特别长的时候,这种方法适用
应用场景:写用户的权限管理会用
7 ORM
ORM操作表:
创建表
修改表
删除表
ORM操作数据行:
增删改查
ORM要利用pymysql第三方工具连接数据库
默认:
连接mysql数据库使用的是mysqlDB(使用django的orm框架时,要修改连接mysql方式)
django默认连接的是sqllite
7.1 ORM操作之准备
1.创建数据库
2.配置文件setting
DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME':'testDB','USER': 'root','PASSWORD': '362514','HOST': 'localhost','PORT': '3306',}}
3.init.py下
import pymysqlpymysql.version_info = (1, 4, 13, "final", 0)pymysql.install_as_MySQLdb() # 使用pymysql代替mysqldb连接数据库
7.2 ORM操作之创建数据表
1.app01/models
from django.db import modelsclass UserInfo(models.Model):nid = models.BigAutoField(primary_key=True) # 可以不写,会默认生成id这一列且为主键可自增的userName = models.CharField(max_length=32)passWord = models.CharField(max_length=64)
2.注册app settings.py下
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','app01']
除了app01其他文件下面也有models,models下面也有类,也会映射到表
3.创建数据表命令
python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations
会读取models.py然后自动生成一个配置文件在migrations文件夹下
python manage.py migrate
根据配置文件生成数据库语句
记录了你对数据库操作的所有记录,配置文件自下而上是依赖关系
创建一对多表
from django.db import modelsclass UserGroup(models.Model):title = models.CharField(max_length=32)class UserInfo(models.Model):nid = models.BigAutoField(primary_key=True)userName = models.CharField(max_length=32)passWord = models.CharField(max_length=64)# age = models.IntegerField(null=True) 设置为空值age = models.IntegerField(default=1) # 设置默认值1userGroup = models.ForeignKey('UserGroup', null=True, on_delete=models.CASCADE) # 外键
现创建UserGroup和UserInfo是一对多关系的表,UserInfo里面添加外键,当执行ForeigKey命令时数据库生成的时候自动在外键名后加_id,同时UserGroup表会默认生成id字段
创建多对多表
第一种方式
class Classes(models.Model):className = models.CharField(max_length=32)class Teachers(models.Model):name = models.CharField(max_length=32)class TeacherClass(models.Model):teacher = models.ForeignKey('Teachers', null=True, on_delete=models.CASCADE)classRoom = models.ForeignKey('Classes', null=True, on_delete=models.CASCADE)
第二种方式
class Classes(models.Model):className = models.CharField(max_length=32)# 写Classes或者Teachers都可以,会生成一个新表classes_mm = models.ManyToManyField('Teachers')class Teachers(models.Model):name = models.CharField(max_length=32)
第二种方式的增删改查
# obj.m可以是一个ManyRelatedManager对象obj.m.add(2) # 增obj.m.remove(4) # 删obj.m.set([1, ]) # 重置obj.m.clear() # 清空
老师和班级是多对多关系
第二种方式只能生成3列(主键id,和两个表id),若需要加其他列,只能用第一种方式
7.3 ORM操作之修改表
1.修改字段名
直接在类里面修改属性,然后运行命令即可。
2.增加字段
要给定参数,是空值还是给一个默认值
7.4 ORM操作之单表增删改查
新增
from django.shortcuts import render, HttpResponsefrom app01 import models# 数据库相关操作def index(request):# 新增# models.UserGroup.objects.create(title='销售部')# models.UserInfo.objects.create(userName='张三', passWord='123', age=18, userGroup_id=1)
查询
无条件查询
from django.shortcuts import render, HttpResponsefrom app01 import models# 数据库相关操作def index(request):# 查询groupList = models.UserType.objects.all() # gropList类型为QuerySet,里面有一些对象groupList1 = models.UserType.objects.all()[0:2] # 相当于切片,取连续的某几条记录for row in groupList:print(row.id, row.title)# userList = models.UserInfo.objects.all()print(groupList) # <QuerySet [<UserGroup: UserGroup object (1)>]>
这里groupList是一个QuerySet对象,这个对象其实可以看做是列表,里面又装了一堆UserGroup对象。
条件查询
from django.shortcuts import render, HttpResponsefrom app01 import models# 数据库相关操作def index(request):# 条件查询userList = models.UserInfo.objects.filter(nid=1)# id>1# user = models.UserInfo.objects.filter(id__gt=1)# # id<1# user = models.UserInfo.objects.filter(id__lt=1)for row in userList:print(row.userName)
删除
# 删除models.UserGroup.objects.filter(id=2).delete()
更新
models.UserGroup.objects.filter(id=1).update(title='研发部')
7.5 多表连接
现在有三张表,且已经建立主外键关系
models.py
from django.db import modelsclass Foo(models.Model):caption = models.CharField(max_length=32)class UserInfo(models.Model):id = models.BigAutoField(primary_key=True)userName = models.CharField(max_length=32)passWord = models.CharField(max_length=64)# age = models.IntegerField(null=True) 设置为空值age = models.IntegerField(default=1) # 设置默认值1userType = models.ForeignKey('UserType', null=True, on_delete=models.CASCADE)class UserType(models.Model):title = models.CharField(max_length=32)foo = models.ForeignKey('Foo', null=True, on_delete=models.CASCADE)
正向操作
views.py
from app01 import modelsdef test(request):result = models.UserInfo.objects.all().first()print(result.userName, result.userType_id, result.userType.title, result.userType.foo.caption)
当前被查询表—UserInfo
关联关系:
UserInfo外键—userType
UserType外键—foo
通过当前被查询表.外键就相当于当前表与关联表做了左连接操作形成了一个新表
from app01 import modelsfrom django.shortcuts import renderdef test(request):# 正向操作 等价于 select app01_userinfo.id,app01_userinfo.userName,app01_usertype.title from app01_userinfo LEFT JOIN app01_usertype on app01_userinfo.userType_id=app01_usertype.idv1 = models.UserInfo.objects.values('id', 'userName', 'userType__title') # v1是一个Queryset对象,里面是一个个字典v2 = models.UserInfo.objects.values_list('id', 'userName', 'userType__title') # v2是一个Queryset对象,里面是一个个元组v3 = models.UserInfo.objects.values('id', 'userName', 'userType') # 不加双下划线默认输出idv4 = models.U
models.UserInfo.objects.values(‘id’, ‘userName’, ‘外键’)
def test(request):v4 = models.UserInfo.objects.filter(userType__title='普通用户').values('id', 'userName')print(v4)
反向操作
from app01 import models# 数据库相关操作def test(request):result = models.UserType.objects.all().first() # 查询第一条数据print('用户类型', result.title)# result.userinfo_set.all()是一个Queryset对象for row in result.userinfo_set.all():print(row.userName, row.age)
当前查询表UserType,与UserInfo有关联,但是外键在UserInfo表中,但是该表具有一个隐含属性,就是关联表的小写加上 _set 再加上.all()就可以获取关联表的所有对象
from app01 import modelsfrom django.shortcuts import renderdef test(request):# 反向操作 等价于 select app01_usertype.id,app01_usertype.title,app01_userinfo.id from app01_usertype LEFT JOIN app01_userinfo on app01_usertype.id=app01_userinfo.userType_idv2 = models.UserType.objects.values('id', 'title', 'userinfo')
v2 = models.UserType.objects.values(‘id’, ‘title’, ‘要跨的表名(小写)’) 只写表名取到的是这个表的id,要去某个属性,就加双下划线
def test(request):v2 = models.UserType.objects.filter(userinfo__userType_id=1).values('id', 'title', 'userinfo__userName')print(v2)
7.6 其他操作
排序
def test(request):v = models.UserInfo.objects.all().order_by('-id') # 根据id降序排列print(v)
分组
def test(request):# 等价于SELECT `app01_userinfo`.`userType_id`, COUNT(`app01_userinfo`.`id`) AS `xxxx` FROM `app01_userinfo` GROUP BY `app01_userinfo`.`userType_id` HAVING COUNT(`app01_userinfo`.`id`) > 2 ORDER BY NULLv = models.UserInfo.objects.values('userType_id').annotate(xxxx=Count('id')).filter(xxxx__gt=2)print(v.query)
def test(request):v = models.UserInfo.objects.filter(id__gt=2).values('userType_id').annotate(xxxx=Count('id'))print(v.query)
filter在annotate前面相当于where,在后面相当于having
in betwween and 大于小于
models.UserInfo.objects.filter(id__gt=1) # 大于models.UserInfo.objects.filter(id__lt=1) # 小于models.UserInfo.objects.filter(id__lte=1)# 小于等于models.UserInfo.objects.filter(id__gte=1)# 大于等于models.UserInfo.objects.filter(id__in=[1, 2, 3])#inmodels.UserInfo.objects.filter(id__range=[1, 2])#between andmodels.UserInfo.objects.filter(userName__startswith='阿')# likemodels.UserInfo.objects.filter(userName__contains='阿')# containsmodels.UserInfo.objects.exclude(id=1) # 不等于
7.7 高级操作
# F# from django.db.models import F# 让所有年龄加1# models.UserInfo.objects.update(age=F('age')+1)
# Q 常用于组合查询,构造复杂查询条件# con = Q() 创建父条件对象# q1 = Q() 创建子条件对象1# q1.connector = 'OR'# q1.children.append(('id', 1))# q1.children.append(('id', 10))# q1.children.append(('id', 9))# 生成子条件1 id=1 or id=10 or id=9# q2 = Q() 创建条件对象2# q2.connector = 'OR'# q2.children.append(('c1', 1))# q2.children.append(('c1', 10))# q2.children.append(('c1', 9))# 生成子条件2 c1=1 or c1=10 or c1=9# 子条件添加到父条件中# con.add(q1, 'AND')# con.add(q2, 'AND')# 生成父条件 (id=1 or id=10 or id=9) and (c1=1 or c1=10 or c1=9)# models.Tb1.objects.filter(con)
extra方法
用于额外查询条件以及相关表、排序
models.UserInfo.objects.values('id', 'userName').extra(select={'n': 'select count(1) from app01_usertype where id>%s'},select_params=1,where=['age>%s'],params=18,order_by=['-age'],tables=['app01_usertype'])# select id,userName,(select count(1) from app01_usertype where id>1) from app01_userinfo,app01_usertype# where age>18 order by age desc
models.UserInfo.objects.values('id', 'userName').extra(select={'n': 'select count(1) from app01_usertype where id>%s'},select_params=1,where=['age>%s'],params=18,order_by=['-age'],tables=['app01_usertype'])
select和select_params一起使用,用来做字段查询
# select userName,(select count(1) from app01_userinfo) as n from app01_userinfov = models.UserInfo.objects.all().extra(select={'n': "select count(1) from app01_userinfo"})for obj in v:print(obj.userName, obj.n)
# select userName,(select count(1) from app01_userinfo where id>1 and id<3) as n from app01_userinfov = models.UserInfo.objects.all().extra(select={'n': "select count(1) from app01_userinfo where id>%s and id<%s",'m':'select count(1) from app01_userinfo where id>%s and id<%s'},select_params=[1,3,4,7])for obj in v:print(obj.userName, obj.n)
where和params一起使用,用来做条件查询
# select userName from app01_userinfo where id=1 or id=2 and name='alex'models.UserInfo.objects.extra(where=['id=1 or id=%s', 'name=%s'],params=[2, 'alex'])
# select * from app01_userinfo,app01_usertypemodels.UserInfo.objects.extra(tables=['app01_usertype'])
7.8 django后台管理页面
EmailField(CharField)
IPAddressField(Field)
GenericIPAddressField(Field)
URLField(CharField)
SlugField(CharField)
CommaSeparatedIntegerField(CharField)
UUIDField(Field)
FilePathField(Field)
FileField(Field)
ImageField(FileField)
以上这些对应数据库都是把字段设置为字符串类型,只影响admin
通过python manage.py createsuperuser命令创建超级用户,然后登录后台管理页面
通过在admin.py里面写入admin.site.register(models.UserInfo),页面会产生你所注册的表
添加用户记录,发现email报错,是因为
会在django的admin页面进行验证是否符合正确格式
8 CBV和FBV
原来是一个url对应一个函数,现在是一个url对应一个类,类里面有多个函数,提交的http请求如果是get请求方式,则自动执行get函数,如果请求方式是post,则自动执行post函数
CBV—基于反射实现,根据请求方式不同,执行不同的方法
views
from django.shortcuts import render,HttpResponsefrom django.views import Viewclass Login(View):def get(self, request):return render(request, 'login.html')def post(self, request):return HttpResponse('post')def delete(self,request):return HttpResponse('delete')
url.py
url(r'^student',view.Login.as_view())
原理:a.从路由开始url->view.Login.as_view() as_view函数返回一个view,所以执行view函数—》调用dispach方法,-》反射
9 django自带分页
学员管理
views.py
from app01 import modelsfrom django.core.paginator import Paginator, Page, PageNotAnInteger, EmptyPagedef pagination(request,page):"""django内置分页:param request::param page: 当前页码:return:"""classList = models.Classes.objects.all()currentPage = pagepaginator = Paginator(classList, 10) # 每页显示10条数据post = paginator.page(currentPage) # 当前显示第几页return render(request, 'paginator.html', {'post': post})
index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><ul>{% for row in post.object_list %}<li>{{ row.className }}</li>{% endfor %}</ul><div>{% if post.has_previous %}<a href="/app01/pagination/{{ post.previous_page_number }}.html">上一页</a>{% endif %}{% for num in post.paginator.page_range %}<a href="/app01/pagination/{{ num }}.html">{{ num }}</a>{% endfor %}{% if post.has_next %}<a href="/app01/pagination/{{ post.next_page_number }}.html">下一页</a>{% endif %}</div></body></html>
缺点:因为page_range方法,会显示所有页码,所以数据越多,页码越多所以只适合于做上下页。
10 django视图之自定义分页
11 XSS攻击
例子:有人进行评论的时候写入了非法字符例如,后端接收之后,在前台没有进行过滤而直接渲染出来会造成危险,而django内部对此做出了预防
前端写评论,提交之后后台接收
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form method="post" action="/comment.html"><input type="text" name="content"><input type="submit" value="提交"></form>{% for row in msg %}<p>{{ row }}</p> # 接收后台发过来的数据,若没有加safe则自动注释非法字符{% endfor %}</body></html>
评论里写的是美女则渲染的时候自动用其他替换,发给前端
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form method="post" action="/comment.html"><input type="text" name="content"><input type="submit" value="提交"></form>{% for row in msg %}<p>{{ row|safe }}</p> # 加了safe 则不会过滤掉非法字符{% endfor %}</body></html>

渲染的时候会将输入不安全的内容渲染出来,发给前端
12 CSRF
下面是我的自己写的网站,如果我已经登陆了招商银行,本地存在Cookie,下面这个是转账的表单,点击就会完成转账
<form action="www.zhaoshangyinhang.com" method="post"><input type="text" value="987899" name="to"><input type="text" value="87890" name="money"><input type="submit" value="点击我"></form>
django这个中间件表示需要进行csrf验证,也就是说你发送post请求必须还要携带django提供的随机字符串,post请求才能被处理
比如说我现在注释掉这个中间件,
<form action="/csrf1/" method="post"><input type="text" value="987899" name="to"><input type="text" value="87890" name="money"><input type="submit" value="点击我"></form>
from django.shortcuts import render,HttpResponsedef csrf1(request):if request.method == "GET":return render(request,'csrf1.html')else:return HttpResponse('通过csrf验证')
会显示通过csrf验证
如果不注释这个中间件,会进行csrf验证,又因为post没携带随机字符串,所以会报错403!
想要生成随机字符串,就要使用以下代码{% csrf_token %}
再次发送get请求会生成一个隐藏的input框,里面有默认的随机字符串,这样发送post请求,就可以完成csrf验证
并且cookie也会有csrf_token
csrf具体讲解
14 Session
保存在服务器端的数据(本质是键值对)
应用:依赖于Cookie
作用:保持会话(web网站)
内部流程:用户登录,登陆成功后,服务器端会生成一个随机字符串自己保存一份作为key,然后返回到前端的cookie里面,每个用户对应一个随机字符串,然后继续发送请求会携带随机字符串过来,在服务器端比对,比对成功就返回相应的value。
优点:敏感信息不会直接给客户端
14.1 Session基本操作
14.2 Session配置文件和其它操作
14.3 Session数据源配置
15 WSGI拾遗
import socketdef handle_request(client):buf = client.recv(1024) # 获取用户请求buf = str(buf, encoding='utf-8')# 自己解析,各种splithearders, bodys = buf.split('\r\n\r\n')temp_list = hearders.split('\r\n')method, url, protocal = temp_list[0].split(' ')# 请求相关django#产出字符串client.send(产出字符串)def main():sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 127.0.0.1 指的是自己的主机 不是公网ipsock.bind(('127.0.0.1', 8080))sock.listen(5)while True:connection, address = sock.accept()handle_request(connection)connection.close()if __name__ == '__main__':main()
socket做的主要是给django请求之前的一系列处理(获取用户请求,进行请求的解析等)以及把django产生的字符串发给客户端
wsgiref使用
from wsgiref.simple_server import make_server# runserver 由wsgi服务器调用、函数对http请求与响应的封装、使得Python专注于处理# environ http 请求 (dist)# start_response 响应 (function)def runserver(environ, start_response):# django框架开始# 中间件# 路由系统# 视图函数# 响应请求头start_response('200 OK', [('Content-Type', 'text/html')])return [b'<h1>hi, web!</h1>']# 启动服务器 | 这个服务器负责与 wsgi 接口的 runserver 函数对接数据httpd = make_server('127.0.0.1', 8000, runserver)# 监听请求httpd.serve_forever()
django生命周期:
客户端请求->wsgi->中间件->视图函数->拿到数据库数据和模板渲染->回中间件->wsgi->客户端页面
16 MVC和MTV
from wsgiref.simple_server import make_serverdef index():return [b'index']def login():return [b'login']routers = [('/index', index),('/login', login)]def runserver(environ, start_response):func_name = Nonerouters = [('/index', index),('/login', login)]url = environ['PATH_INFO']print(url)for item in routers:if item[0] == url:func_name = item[1]breakif func_name:start_response('200 OK', [('Content-Type', 'text/html')])return func_name()else:start_response('404 OK', [('Content-Type', 'text/html')])return [b'404 not found']if __name__ == '__main__':httpd = make_server('127.0.0.1', 8080, runserver)# 监听请求httpd.serve_forever()
当视图函数很多时,代码都放在一个py文件会降低可读性,不方便管理,要分开放
MVC三个目录:
-models 数据库操作 模型
-views html模板
-contrller 视图函数
MTV三个目录:
-models 数据库操作 模型
-views 视图函数
-templates html模板
16 中间件
17 Form组件
问题1:
若一个页面进行登陆的时候,需要填写很多表单,现在的情况是你填写时,只要有一个写错了,因为网页无法记住你上次写入的数据,刷新页面之后,数据就消失了
问题2:
现在对用户名和密码提出一些要求:
用户名:不能为空,长度5-18,必须邮箱格式
密码:不能为空,长度5-18,必须包含字母数字下划线
可以采用传统方法—重复对用户数据进行校验:正则、长度、是否为空的判断,但是需要写好多重复代码,效率很低!
form组件功能:
1.数据校验
2.生成html标签
用户提交数据方式:
1.form表单提交(刷新,失去上次内容)
2.ajax提交(不刷新,保留上次内容)
第一版本
def login(request):if request.method == 'GET':return render(request, 'login.html')else:user = request.POST.get('user')# 用户名的规则校验pwd = request.POST.get('pwd')# 密码的规则校验if user == 'Alex' and pwd == '123':return redirect('/index/')else:return render(request, 'login.html',{'msg':'用户名或密码错误'})
17.1 Form验证流程
- 定义校验规则的类 LoginForm(Form)
- 创建对象 obj = LoginForm(用户提交的数据)
- obj.is_valid()
- obj.cleaned_data
- obj.errors
注意:
1.前端传来的数据要和规则个数相同
2.前端name属性值要和规则字段名相同
3.设置错误信息为中文的另一个方法:在setting中设置LANGUAGE_CODE = ‘zh-hans’
17.2 Form和ajax提交验证
17.3 ajax提交至显示错误信息
17.4 Form组件之常用字段
https://www.cnblogs.com/wupeiqi/articles/6144178.html武沛奇博客
初始化子类,被传递的参数可以是父类的,原因是如下:
class Person:def __init__(self, name, age):self.name = nameself.age = agedef study(self):print('%s,%s岁 在学习' % (self.name, self.age))class Worker(Person):def __init__(self, salary, **kargs):self.salary = salary# 调用父类构造方法,初始化worker对象的时候可以给父类的参数传参super().__init__(**kargs)def get_salary(self):print('salart')obj = Worker(salary=182321,name='xiaoming',age=19)obj.study()obj.get_salary()
17.5 Form组件之相关参数
用于表单验证required=True, 是否允许为空error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
用于生成html标签 模板里要写{{对象.as_p}}widget=None, HTML插件label=None, 用于生成Label标签或显示内容initial=None, 初始值help_text='', 帮助信息(在标签旁边显示)show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)validators=[], 自定义验证规则localize=False, 是否支持本地化disabled=False, 是否可以编辑label_suffix=None Label内容后缀
自动生成html标签代码
class LoginForm(Form):"""定义校验规则的类"""# 长度、是否为空的校验user = fields.CharField(max_length=18, label='asdasda', min_length=6, required=True, error_messages={'required': '用户名不能为空','max_lenth': '长度大于18','min_lenth': '长度小于6'})pwd = fields.CharField(max_length=16, required=True)def login_form(request):"""数据校验,提交方式是form:param request::return:"""if request.method == 'GET':obj = LoginForm()# print(obj.base_fields['user'].label) # 前端访问Field实例属性labelreturn render(request, 'login.html', {'obj': obj})
默认前端显示input框<p>{{ obj.user }}</p><p>{{ obj.pwd }}</p>
17.6 Form组件之保留上次输入内容
如果是用form组件自动生成的html标签,进行foorm表单提交的时候,自动保留上次输入内容
17.7 疑问
- checkbox、select使用Form组件
-默认值
-保留上次输入值
- 自定义验证规则

