1 | 前端 后端 数据库 |
[TOC]
后端基础
1.CS架构与BS架构
CS架构
CS指 Client<——>Server
客户端软件send 服务端软件recv
操作系统 操作系统
计算机硬件 计算机硬件
客户端发起系统调用把数据发给操作系统,操作系统调用网卡把数据发给对方的计算机硬件,对方的计算机硬件再把数据交给操作系统,操作系统把数据交给服务端软件.
BS架构
BS指 Browser(浏览器)<—–>Server
2.网络通信
网络存在的意义就是跨地域数据传输=》称之为通信
网络=物理链接介质+互联网通信协议
3. OIS七层协议
五层协议
应用层
传输层 tcp\udp
网络层 IP
数据传输层 Enternet
物理层
ip+port(端口)=》可以标识全世界范围内独一无二的一个基于网络通信的应用程序
协议: 规定数据的组织格式
格式: 头部+数据部分
封包:数据外加头
解包:拆解头获取数据
互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
每层运行常见物理设备
4.五层协议
4.1物理层
物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
单层的电信号没有意义
4.2数据链路层:ethernet以太网协议
4.2.1Ethernet规定(以太网规定):
一组电信号构成一个数据包,叫做‘帧’
每一数据帧分成:报头head和数据data两部分
head data head包含:(固定18个字节)
- 发送者/源地址,6个字节
- 接收者/目标地址,6个字节
- 数据类型,6个字节
data包含:(最短46字节,最长1500字节)
- 网络层发过来的整体内容
head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
mac地址(用来标识局域网的一台机器):
head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址
mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)
注:以太网协议的工作方式是广播
4.3网络层:IP协议
上图结论:必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,如果是就采用广播的方式发送,如果不是,
就采用路由的方式(向不同广播域/子网分发数据包),mac地址是无法区分的,它只跟厂商有关
网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
4.3.1IP协议:
- 规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示
- 范围0.0.0.0-255.255.255.255
- 一个ip地址通常写成四段十进制数,例:172.16.10.1
ip地址分成两部分
- 网络部分:标识子网
- 主机部分:标识主机
注意:单纯的ip地址段只是标识了ip地址的种类,从网络部分或主机部分都无法辨识一个ip所处的子网
4.3.2子网掩码
所谓”子网掩码”,就是表示子网络特征的一个参数。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP地址172.16.10.1,如果已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。
知道”子网掩码”,我们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。
比如,已知IP地址172.16.10.1和172.16.10.2的子网掩码都是255.255.255.0,请问它们是否在同一个子网络?两者与子网掩码分别进行AND运算,
172.16.10.1:10101100.00010000.00001010.000000001
255255.255.255.0:11111111.11111111.11111111.00000000
AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0
172.16.10.2:10101100.00010000.00001010.000000010
255255.255.255.0:11111111.11111111.11111111.00000000
AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0
结果都是172.16.10.0,因此它们在同一个子网络。
总结一下,IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。
ip数据包
ip数据包也分为head和data部分,无须为ip包定义单独的栏位,直接放入以太网包的data部分
head:长度为20到60字节
data:最长为65,515字节。
而以太网数据包的”数据”部分,最长只有1500字节。因此,如果IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。
以太网头 | ip 头 | ip数据 |
---|
一个合法的ipv4地址组成部分=ip地址/子网掩码地址
172.16.10.1/255.255.255.0
4.3.3ARP协议
arp协议由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送,在谈及以太网协议时候,我门了解到
通信是基于mac的广播方式实现,计算机在发包时,获取自身的mac是容易的,如何获取目标主机的mac,就需要通过arp协议
arp协议功能:广播的方式发送数据包,获取目标主机的mac地址
协议工作方式:每台主机ip都是已知的
例如:主机172.16.10.10/24访问172.16.10.11/24
一:首先通过ip地址和子网掩码区分出自己所处的子网
场景 | 数据包地址 |
---|---|
同一子网 | 目标主机mac,目标主机ip |
不同子网 | 网关mac,目标主机ip |
二:分析172.16.10.10/24与172.16.10.11/24处于同一网络(如果不是同一网络,先发一个目标ip是网关的arp包来获取网关的mac,然后发送下表,下表中目标ip为172.16.10.1,通过arp获取的是网关的mac)
源mac | 目标mac | 源ip | 目标ip | 数据部分 | |
---|---|---|---|---|---|
发送端主机 | 发送端mac | FF:FF:FF:FF:FF:FF | 172.16.10.10/24 | 172.16.10.11/24 | 数据 |
注:IP地址分私网地址和公网地址,私网地址和公网地址呈映射关系,一台计算机暴露给外界的是公网地址,这个地址能被想访问它的人拿到,上表种的目标ip是私网地址,被接受网关拿到后经过映射转为私网地址,同时,源ip地址也会变成对外的公网地址
三:这个包会以广播的方式在发送端所处的自网内传输,所有主机接收后拆开包,发现目标ip为自己的,就响应,返回自己的mac
注:IP地址用来定义一个子网,用来标识你的计算机在全国的哪一个子网内
ip地址+mac地址=》标识全世界范围内独一无二的一台计算机
4.4传输层
tcp/udp =》基于端口
端口范围0-65535,0-1023是系统占用端口
传输层的由来:网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,
那么我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口,端口即应用程序与网卡关联的编号。
传输层功能:建立端口到端口的通信
补充:端口范围0-65535,0-1023为系统占用端口
tcp协议:
可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。
以太网头 | ip 头 | tcp头 | 数据 |
---|
udp协议:
不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。
以太网头 | ip头 | udp头 | 数据 |
---|
tcp报文
tcp三次握手和四次挥手
4.5应用层
自定义协议需要注意的问题
1.两大组成部分=头部+数据部分
头部:放对数据的描述信息
比如:数据发给谁,数据的类型,数据的长度
2.头部的长度必须固定
因为接收端要通过头部获取所接接收数据的详细信息
socket介绍
能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种”打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个”文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
要能解决问题
软件开发架构
1 | cs架构 |
1 | #HTTP协议 |
基于wsgiref模块
1 | from wsgiref.simple_server import make_server |
借助于wsgiref模块
urls.py 路由与属兔对应函数
views.py 视图函数
template文件夹 专门用来存储html文件
根据功能的不同拆分之后,后续添加功能只需要在urls.py注册对应关系然后再views.py书写业务逻辑即可
动静态网页
静态网页
页面数据是写死的
动态网页
数据是实时获取的
eg:
1.后端获取当前实践展示到html页面上
2.数据是才能数据库中获取展示到页面上
模板语法之jinja2模块
1 | # 将字典传给html文件 |
简单web请求流程图
wsgiref模块
1.请求来的时候解析http格式的数据,封装成大字典
2.响应走的时候给数据打包成符合http格式,再返回给浏览器
python三大主流web框架
django
特点:大而全,自带的功能特别多,类似于航空母舰
缺点: 有时候过于笨重
socket部分:wsgiref模块
路由与视图函数对应关系:自己写的
模块语法:用的是自己的
flask
特点:小而精 自带的功能特别少
第三方模块特别多,如果将flask第三方的模块完全可以盖过django
不足之处:
比较依赖于第三发的开发者,会碰到兼容性的问题
socket部分: werkzeug(内部还是wsgiref模块)
路由与视图函数对应关系:自己写的
模块语法: jinja2
tornado
特点:异步非阻塞 支持高并发
甚至开发游戏服务器
不足之处:
暂时你不会?
socket部分: 自己写的
路由与视图函数对应关系:自己写的
模块语法: 自己写的
注意事项
如何让你的计算机正常启动django项目
- 计算机的名称不能有英文
- 一个pycharm窗口只开一个项目
- 项目里面的所有文件不要出现中文
django版本问题
- x 2.x 3.x(忽略)
- x和2.x本身差距也不大,以1.x为例
django安装
pip3 install jango==1.11.11
验证是否安装成功
终端输入django-admin看有没有反应
django基本操作
命令行操作
创建django项目
django-admin startproject mysite创建完app一定要在settings.py中的INSTALLED_APPS下注册
启动django项目
一定要切到项目目录下
python manage.py runserver创建应用
应用
django是一款专门用来开发app的web框架
app就雷士与大学的各个学院
比如开发淘宝
订单相关
投诉相关
选课系统
一个app就是一个独立的功能模块python manage.py startapp app01
#app应该做到见名知意1
2*****************创建出来的应用第一步去配置文件中注册*******
ps:用pycharm创建项目时候,它可以帮你创建一个app并自动注册
主要文件
-mysite项目文件夹
–mysite文件夹
—settings.py 配置文件
—urls.py 路由与视图函数对应关系(视图层)
—wsgi.py wsgiref模块(不考虑)
—manage.py django的入口文件
—db.sqlite.py django自带的sqlite数据库(小型数据库,功能不多bug多)
—app01文件夹
—admin.py django后台管理
—apps.py 注册使用
—migration文件夹 数据迁移记录
—models.py 数据库相关的模型类
—tests.py 测试文件
—views.py 视图函数(视图层)
pycharm操作
new project -> django
启动django项目
命令行 或者 绿色箭头运行
创建应用
命令行
tools -> run manage.py task -> start app02(有提示,前期不要用)
修改端口号以及创建server
edit -> config
命令行与pycharm创建的区别
1 | # 1 命令行创建django项目时,不仅要手动创建templates文件夹,还需要自己配置路径。 |
django必会三板斧
Httpresponse
返回字符串类型的数值
render
返回html文件
redirect
重定向
return redirect(‘https://www.baidu.com/')
return redirect(‘/home/‘)
静态文件访问令牌
1 | # -> settings.py |
动态解析令牌
1 | {% load static %} |
我们将html文件默认放在template文件夹下
我们将网站所占用的静态文件默认放在static文件下
静态文件
前端写好的
网站写好的js文件
网站好的css文件
网站用到的图片文件
第三方框架
‘’’
拿来就可以用的
#Django不会自动创建static文件夹 需要手动创建
在浏览器输入也url能看到对应资源,是因为后端提前开设了该资源的接口
静态文件配置
STATICFILES_DIRS=[
os.path.join(BASE_DIR,’static’)
]
想要调用静态文件,则文件地址必须以 令牌(STATIC_URL
) + 文件名 形式。
然后将在 STATICFILES_DIRS
注册的文件夹列表中从上到下查找。
Request
get请求拿界面,post请求拿数据
request.method #返回请求方式,并且全是大写的字符串形式
1 | request.post #获取post请求数据 |
Django模型层
测试脚本
- 当只是想测试django项目中的某一个.py文件内容,那么可以不用书写前后端交互的形式,而是直接写一个测试脚本即可。
- 脚本代码无论是写在应用下的tests.py,还是单独开设.py文件都可以。
- django中的文件默认不会暴露出来,需要准备测试环境才能进行测试。
1 | PYTHON |
- 当
from app01 import models
写在main之外时,可以通过勾选”使用Python控制台运行”来解决 https://blog.csdn.net/weixin_44393803/article/details/89739066 - 当不准备环境配置时,可以在运行配置中勾选 Python控制台启动 。这样会忽略脚本中的配置环境代码。(推荐)
数据库链接
django链接数据库
1 | # -> settings.py |
django默认用的是mysqldb模块
但是该模块的兼容性不好 需要手动改为用pymysql链接
#在项目名下的inint或者任一应用文件中写以下代码都可以
1 | # -> 在项目名下或任意应用名下的init文件 |
Django ORM
ORM,对应关系映射
作用:让一个不用sql语句的小白也能用python面向对象的代码简单快捷操作数据库
不足之处:封装程度高,有时候sql语句效率太低,需要自己写sql语句
创建模型表
1 | # 1.先去models.py中写一个类 |
1 | # 2.数据库迁移命令 |
字段增删改
1 | #字段的增加 |
创建表关系
A表外键字段 = models.ForeignKey(to=B表)
关系表外键字段 = models.ManyToManyField(to=B表)
A表外键字段 = models.OneToOneField(to=B表)
1 | from django.db import models |
单表的增删改查
增删改查
- 在视图函数中,
from app01 import models
首先导入对应的app下的model - django自带的sqlite3数据库对日期格式不是很敏感,处理的时候容易出错。
- pk会自动查找到当前表的主键字段,指代的就是当前表的主键字段,避免区分当前表的主键字段名uid/pid/sid
查
返回值先看成是列表套数据对象的格式
支持索引取值、切片操作 但是不支持负数索引
user_obj = models.User.objects.filter(username=username).first
1 | def login(request): |
注:
models.User.objects.filter()
等效于models.User.objects.all()
.first()
等效于[0]
数据展示:
1 | # -> views.py |
展示界面
1 |
|
增
1 | # -> views.py |
改
1 | # -> views.py |
1 | # edit_user界面 |
删
1 | def delete_user(request): |
必知必会13条
all():查询所有数据
filter():带有过滤条件的查询
get():直接拿数据对象,但是条件不存在直接报错
first():拿queryset里面第一个元素
last():拿queryset里面最后一个元素
values():可以指定获取的数据字段,相当于select name,age from …
1
2
3PYTHON
res = models.User.objects.values('name','age')
print(res) # 列表套字典 <QuerySet [{'name': 'jason', 'age': 18}, {'name': 'egonPPP', 'age': 84}]>values_list():
1
2
3PYTHON
res = models.User.objects.values_list('name','age')
print(res) # 列表套元组 <QuerySet [('jason', 18), ('egonPPP', 84)]>和values()仅仅只是封装格式不一样,sql查询语句是一样的。
distinct():去重
1
2
3PYTHON
res = models.User.objects.values('name','age').distinct()
print(res)去重一定要是(在虚表中)一模一样的数据。
例如:.all()的数据带有主键,因此无法去重
order_by():
1
2
3
4PYTHON
res = models.User.objects.order_by('age') # 默认升序
res = models.User.objects.order_by('-age') # 降序
print(res)reverse():
1
2
3
4PYTHON
res = models.User.objects.all() # 反转
res1 = models.User.objects.order_by('age').reverse() # 必须先排序
print(res, res1)count():统计当前数据的个数
1
2
3PYTHON
res = models.User.objects.count() # 统计当前数据的个数
print(res)exclude():排除在外
1
2
3PYTHON
res = models.User.objects.exclude(name='jason')
print(res)exists():基本用不到因为数据本身就自带布尔值 返回的是布尔值
1
2
3PYTHON
res = models.User.objects.filter(pk=10).exists()
print(res)查看内部sql语句的方式
只有QuerySet类对象可以使用.query查看内部封装的sql语句
在配置文件中配置logging,查看所有的sql语句
需要在Python控制台中运行才能看见
一对多外键增删改查
增 create(): publish_id=num或者publish=publish_obj
删 filter(pk=pk).delete(): 会按照on_delete处理对应外键
改 filter(pk=pk).update(): publish_id=num或者publish=publish_obj
这是批量修改,在first()返回的对象中没有update这个方法
1 | PYTHON |
多对多外键增删改查
多表查询
models.表名.objects.filter(pk=条件).first().多对多关系.add()
方法支持传递主键,也可以传递对象
models.表名.objects.filter(pk=条件).first().多对多关系.remove()
这只是在第三张关系表中删除记录
models.表名.objects.filter(pk=条件).first().多对多关系.set()
括号内必须给一个可迭代对象
如果不符合要求,则先删除,后新增
models.表名.objects.filter(pk=条件).first().多对多关系.clear()
在第三张关系表中清空某个书籍与作者的绑定关系
正反向
- 当a表中拥有b表的外键字段,则a查b为正向,否则反向。
- 一对一和多对多正反向的判断也是如此
外键字段要放在查询频率高的表中。
- 正向查询按
字段
- 反向查询
- 查询一对多、多对多时按
表名小写+_set
- 查询一对一时按
表名小写
- 查询一对多、多对多时按
子查询(基于对象的跨表查询)
在书写orm语句的时候跟写sql语句一样的:
- 不要企图一次性将orm语句写完,如果比较复杂,就写一点看一点
正向什么时候需要加.all():
- 查询 多对多 和 一对多 关系时需要加
- 查询 一对一 和 多对一 关系时不需要
- 当为需要all()的情况时,即使结果只有一个,也会返回集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43PYTHON
# 1. 查询主键为1的书籍的出版社
# 有字段 -> 正向;查询多对一关系中一的一方 -> 不用.all()
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.publish
print(res.name)
# 2. 查询主键为2的书籍的作者
# 有字段 -> 正向;多对多关系 -> 要.all()
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.authors
print(res)
# app01.Author.None
print(res.all())
# <QuerySet [<Author: Author object (1)>, <Author: Author object (3)>]>
print(models.Book.objects.filter(pk=3).first().authors.all())
# <QuerySet [<Author: Author object (1)>]>
# 3. 查询作者jason的电话号码
# 有字段 -> 正向;一对一关系 -> 不用.all()
author_obj = models.Author.objects.filter(name="jason").first()
res = author_obj.author_detail
print(res.phone, res.addr)
# 4. 查询出版社是东方出版社出版的书
# 无字段 -> 反向;查询一对多关系中多的一方 -> 要.all()
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
res = publish_obj.book_set # app01.Book.None
res = publish_obj.book_set.all()
print(res)
# 5. 查询作者jason写过的书
# 无字段 -> 反向;多对多关系 -> 要.all()
author_obj = models.Author.objects.filter(name='jason').first()
res = author_obj.book_set # app01.Book.None
res = author_obj.book_set.all()
print(res)
# 6.查询手机号是110的作者姓名
# 无字段 -> 反向;一对一关系 -> 不用.all(),不用_set
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
res = author_detail_obj.author
print(res.name)正反向
- 当a表中拥有b表的外键字段,则a查b为正向,否则反向。
- 一对一和多对多正反向的判断也是如此
外键字段要放在查询频率高的表中。
- 正向查询按
字段
- 反向查询
- 查询一对多、多对多时按
表名小写+_set
- 查询一对一时按
表名小写
- 查询一对多、多对多时按
子查询(基于对象的跨表查询)
在书写orm语句的时候跟写sql语句一样的:
- 不要企图一次性将orm语句写完,如果比较复杂,就写一点看一点
正向什么时候需要加.all():
- 查询 多对多 和 一对多 关系时需要加
- 查询 一对一 和 多对一 关系时不需要
- 当为需要all()的情况时,即使结果只有一个,也会返回集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43PYTHON
# 1. 查询主键为1的书籍的出版社
# 有字段 -> 正向;查询多对一关系中一的一方 -> 不用.all()
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.publish
print(res.name)
# 2. 查询主键为2的书籍的作者
# 有字段 -> 正向;多对多关系 -> 要.all()
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.authors
print(res)
# app01.Author.None
print(res.all())
# <QuerySet [<Author: Author object (1)>, <Author: Author object (3)>]>
print(models.Book.objects.filter(pk=3).first().authors.all())
# <QuerySet [<Author: Author object (1)>]>
# 3. 查询作者jason的电话号码
# 有字段 -> 正向;一对一关系 -> 不用.all()
author_obj = models.Author.objects.filter(name="jason").first()
res = author_obj.author_detail
print(res.phone, res.addr)
# 4. 查询出版社是东方出版社出版的书
# 无字段 -> 反向;查询一对多关系中多的一方 -> 要.all()
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
res = publish_obj.book_set # app01.Book.None
res = publish_obj.book_set.all()
print(res)
# 5. 查询作者jason写过的书
# 无字段 -> 反向;多对多关系 -> 要.all()
author_obj = models.Author.objects.filter(name='jason').first()
res = author_obj.book_set # app01.Book.None
res = author_obj.book_set.all()
print(res)
# 6.查询手机号是110的作者姓名
# 无字段 -> 反向;一对一关系 -> 不用.all(),不用_set
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
res = author_detail_obj.author
print(res.name)
子查询(基于对象的跨表查询)
在书写orm语句的时候跟写sql语句一样的:
- 不要企图一次性将orm语句写完,如果比较复杂,就写一点看一点
正向什么时候需要加.all():
- 查询 多对多 和 一对多 关系时需要加
- 查询 一对一 和 多对一 关系时不需要
- 当为需要all()的情况时,即使结果只有一个,也会返回集合
1 | PYTHON |
联表查询(基于双下划线的跨表查询)
- QuerySet要先用int取字典,再用key取对应的数据
- 可以放多个查询的键
- __就是用来获得表下的一个字段
- 而写到__之前则表示已经进入了另外一个表
- 这里非对象时都不需要_set
- 可以 __ id 或者 __pk,是等价的
- 可以无限制的跨表,正向、反向
1 | PYTHON |
聚合查询(aggregate)
- 只要是跟数据库相关的模块,基本上都在django.db.models里面,如果没有则应该在django.db里面。
1 | PYTHON |
分组查询(annotate)
- MySQL分组查询特点:分组之后默认只能获取到分组的依据,组内其他字段都无法直接获取了(严格模式,ONLY_FULL_GROUP_BY)
- author和author__pk是等价的,个人认为写全更好
- models后面点什么,就是按什么分组
- author_num是自己定义的临时字段,用来存储统计出来的每本书对应的作者个数
- 在使用values创建虚表时,依然可以放入多个字段
- 只要orm语句得出的结果还是一个queryset对象,那么就可以继续无限制的点queryset对象封装的方法
- queryset对象就代表一个虚表
1 | PYTHON |
F与Q查询
F
- 能够直接获取到表中某个字段对应的数据
- F不能够直接操作字符串
1 | PYTHON |
Q
- 扩展搜索表达式
1 | PYTHON |
django中如何开启事务
- ACID
- 原子性
- 不可分割的最小单位
- 一致性
- 跟原子性是相辅相成
- 隔离性
- 事务之间互相不干扰
- 持久性
- 事务一旦确认永久生效
- 原子性
- 事务的回滚
- rollback
- 事务的确认
- commit
1 | PYTHON |
orm字段及参数
常用字段及参数
AutoField:主键字段
- primary_key=True
一般会自动创建,不需要手动设置
CharField:varchar
- verbose_name=字段的注释
- max_length=长度
- null=True可以为空
- blank=True可以存空字符串(这两个有区别)
- unique=True唯一
IntegerField:int
BigIntegerField:bigint
DecimalField
- max_digits=允许的最大位数
- decimal_places=小数的最大位数
EmailFiled:varchar(254)
DateField:date
DateTimeField:datetime
- auto_now=每次修改数据时更新当前时间
- auto_now_add=只在创建数据时记录当前时间
BooleanField:Field
该字段传布尔值(False/True),数据库里面存0/1
TextField:Field
该字段可以用来存大段内容(文章、博客…),没有字数限制
FileField:Field
upload_to = “/data/{file}”
给该字段传一个文件对象,会自动将文件保存到/data目录下,并将文件路径保存到数据库中
更多字段,直接参考博客:https://www.cnblogs.com/Dominic-Ji/p/9203990.html,后续补全
自定义字段
1 | PYTHON |
外键字段及参数
unique=True
ForeignKey(unique=True)等价于OneToOneField()
db_index=True
为此字段设置索引
to
设置要关联的表
to_field
设置要关联的表的字段,默认关联另外一张的主键字段,并且自动添加_id后缀
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
related_name
反向查询使用的字段名,即代替 字段名__set
limit_choices_to
限制选择条件
数据库查询优化
all()
orm语句的特点:惰性查询
如果仅仅只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别,直接不执行。
1 | PYTHON |
only()
只取目标字段
- 拿到数据对象,这些对象本身只有only中提到的字段参数
- 如果调用没有包含的字段,则会重新回到数据库查询
1 | PYTHON |
defer()
- defer与only刚好相反
- defer括号内放的字段不在查询出来的对象里面,查询该字段需要重新走数据库
- 而如果查询的是非括号内的字段,则不需要走数据库了
1 | PYTHON |
select_related()
跨表操作
select_related内部先将book与publish连起来,然后一次性将大虚表的所有数据封装给查询出来的对象
这个时候对象无论是点击book表的数据还是publish表的数据都无需再走数据库查询了
select_related括号内只能放外键字段:一对多、一对一
不可以多对多
1 | PYTHON |
prefetch_related()
prefetch_related内部其实就是子查询
指把内部查询的结果作为外层查询的比较条件
两次查询,但不一定就比select_related差,要看实际情况
1 | PYTHON |
批量插入
当批量插入数据的时候,使用orm的bulk_create
方法能够大幅减少操作时间
1 | PYTHON |
分页器
减少同一时间的数据展示量
1 | PYTHON |
choices参数
- 只要某个字段的内容是可以枚举完全的,则可以采用choices参数:
- 学历
- 客户来源
- ……
- 元组套元组
- 需要保证数据库表中字段类型跟key的数据类型一致
- 在数据库中存的是key,value在orm中
- 不存在的key直接输出key,存在的key可以通过
get_字段_display()
转换成value输出
1 | PYTHON |
MTV与MVC模型
- MTV:Django号称是MTV模型
- M:models
- T:templates
- V:views
- MVC:其实django本质也是MVC
- M:models
- V:views
- C:controller
- vue框架:MVVM模型
多对多三种创建方式
全自动
- 利用orm自动帮我们创建第三张关系表
- 优点:
- 不需要写代码,非常方便
- 支持orm提供操作第三张关系表的方法
- 不足之处:
- 第三张关系表的扩展性极差:没有办法额外添加字段
1 | PYTHON |
纯手动
- 优点:
- 第三张表完全取决于你自己进行额外的扩展
- 不足之处:
- 需要写的代码较多
- 不能够使用orm提供的简单方法
不建议使用该方式。
1 | PYTHON |
半自动
- 可以使用orm的正反向查询
- 但是没法使用add、set、remove、clear这四个方法
- 多对多表关系可以放在任何一方
1 | PYTHON |
Django路由层
Django请求生命周期流程图(重点)
- wsgiref模块能够支持的并发量很小,上线之后换成uwsgi
- wsgi、wsgiref、uwsgi之间的关系
- wsgi是协议
- wsgiref和uwsgi是实现该协议的功能模块
扩展知识点:
缓存数据库
提前已经把数据准备好了,直接拿来用,可以提高效率
路由匹配
1 | # 路由匹配 |
无名分组
- 无名分组就是将括号内正则表达式匹配到的内容当作位置参数传递给后面的视图函数
1 | PYTHON |
有名分组
- 有名分组就是将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数
- 需要有一个对应的形参来接收
1 | PYTHON |
无名有名是否可以混合使用
- 不能混用无名、有名分组
- 但是只使用一种分组时可以使用多次/传多个形参
1 | PYTHON |
反向解析
- 通过一些方法得到一个结果 该结果可以直接访问对应的url触发视图函数
1 | CODE |
无名分组反向解析
分组的解析值一般就是数据的主键值。
1 | CODE |
有名分组反向解析
1 | CODE |
如何理解分组反向解析
- 分组是基于正则表达式匹配url,即不同的url能够进入相同的视图函数,多存在于
https://xxx/id/
类似,这部分会在id不同时发生变化。 - 反向解析基于别名,在.html和.py中动态获得
https://xxx/id/
的xxx部分,这部分可能会在重构url地址时发生变化。 - 分组反向解析则是通过上述两条结合,动态地获得
https://xxx/id/
这条有很多种可能的url,以方便进入相应的视图函数。 - 分组的解析值一般就是数据的主键值,相当于是在视图函数中增加形参,来代替在request中获取的部分数据。
路由分发
include()
- django的每一个应用都可以有自己的templates、urls.py、static(需要自己新建)。这样能够很好地做到分组开发,最终利用路由分发进行整合。
- 当django项目中的url特别多的时候,总路由urls.py代码非常冗余不好维护,这时也可以利用路由分发来减轻总路由的压力。
- 使用路由分发之后,总路由不再将url与视图函数直接对应,而是识别分类当前url所属应用,并分发给对应的应用去处理。
1 | PYTHON |
名称空间
- 当多个应用出现了相同的别名,反向解析只能识别后缀而不能识别前缀,例如
https://app/xxx/
1 | PYTHON |
- 利用名称空间可以区分不同app的相同别名,但一般只需要在别名之前添加所属应用作为前缀,就大可不必使用名称空间。
1 | PYTHON |
伪静态
- 静态网页:数据是写死的 万年不变
- 伪静态:将一个动态网页伪装成静态网页
- 伪装的目的:
- 增大本网站的seo查询力度
- 增加搜索引擎收藏本网上的概率
1 | PYTHON |
虚拟环境
- 在正常开发中,通常每一个项目配备一个独有的解释器环境,只有该项目用到的模块,用不到一概不装。
- 但是较多的虚拟环境会消耗更多的硬盘空间。
- 一般通过requirement.txt来标识项目的虚拟环境需要的模块。
Django各版本区别
1.re_path
Django2.0中的re_path与django1.0的url一样,传入的第一个参数都是正则表达式
1 | Copyfrom django.urls import re_path # django2.0中的re_path |
2.django3默认支持一下5种转换器(Path converters)
1 | Copystr,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式 |
1 | # -> urls.py |
3。可以自定义转化器
- 自定义转换器
1 | PYTHON |
Django视图层
三板斧
HttoResponse
返回字符串类型
Render
返回html页面并在返回浏览器之前还可以给html文件传值
redirect
重定向
JsonResponse
- 前后端数据交互需要使用json作为过渡,实现跨语言传输数据。
1 | PYTHON |
- 指将字典打散成关键字的形式
- 默认只能序列化字典,序列化其他需要加safe参数
json库的一些用法
json.dumps() 将python对象编码成Json字符串
json.loads() 将Json字符串解码成python对象
json.dump() 将python中的对象转化成json储存到文件中
json.load() 将文件中的json的格式转化成python对象提取出来
JSON.stringify() 把 JavaScript 对象转换为字符串。
JSON.parse() 方法将数据转换为 JavaScript 对象。
上传文件及后端如何操作
文件上传
使用form表单上传
在 .html 中:
- method 必须指定成 post
- enctype 必须换成 multipart/formdata
1 | # -> form.html |
1 | # -> app01.views |
request对象方法
get
请求携带的数据是有大小限制的,而post
请求则没有限制。
1 | PYTHON |
- 对于
get
请求和post
请求应该有不同的处理机制- 对
post
做特殊处理,而将request
放置在if
围绕之外,提高可阅读性
request.method
:返回请求方式,并且是全大写的字符串形式 <class ‘str’>request.POST
:获取用户post请求提交的普通数据不包含文件request.POST.get()
:只获取列表最后一个元素request.POST.getlist()
:直接将列表取出request.GET
:获取用户提交的get请求数据request.GET.get()
:只获取列表最后一个元素request.GET.getlist()
:直接将列表取出request.FILES.get()
:用键取出对应的文件,是以字典形式封装好的request.body
:发过来的原生二进制数据request.path_info
:获取url(例如/app01/ab_file/
),和request.path是等效的request.get_full_path()
:获取完整的url及问号后面的参数
FBV与CBV
视图函数既可以是 函数 FBV(function base views),也可以是 类 CBV(class base views)。
1 | PYTHON |
CBV源码剖析
- 首先,不建议自己修改源码
- 函数名/方法名,加括号执行优先级最高,例如views.MyLogin.as_view()会立刻执行as_view()方法
- CBV与FBV在路由匹配上本质是一样的,都是路由对应函数内存地址。
- 在没有实例的情况下使用函数,则只可能使用@staicmethod或者@classmethod
- 在看python源码的时候,一定要时刻提醒自己面向对象属性方法查找顺序:
- 先从对象自己找
- 再去产生对象的类里面找
- 之后再去父类找
- …
- 反射 getattr()
1 | PYTHON |
Django模块层
模板语法传值
传值-变量相关-逻辑相关
- 传递函数名会自动加括号调用,但是模版语法不支持给函数传额外的参数 -
- 传类名的时候也会自动加括号调用(实例化) ,内部将自动判断出当前的变量名是否可以加括号调用,如果可以就会自动执行。针对的是函数名和类名,不针对对象的call方法。
- 对象被展示到html页面上,就类似于执行了打印操作也会触发str方法
- django模版语法的取值只能采用句点符
.
,即可以点键也可以点索引,也可以两者混用
1 | # -> views.py |
1 | # -> .html |
过滤器
- 过滤器就类似于是模版语法的内置方法 - 基本语法:数据|过滤器:参数 - 过滤器只能最多有两个参数 - 在全栈项目的时候,前端代码不一定非要在前端页面书写,也可以现在先在后端写好,然后传递给前端页面。
1 | HTML |
1 | def index(request): |
标签
for循环
1 | {#for循环#} |
if判断
1 | {#if判断#} |
for与if混合使用
1 | for与if混合使用 |
处理字典其他方法
1 | {#处理字典其他方法#} |
with起别名
1 | {#with起别名#} |
自定义过滤器、标签、Inclusion_tag
- 在应用下创建一个名字必须叫 templatetags 文件夹
- 在该文件夹内创建任意名称的py文件 eg: mytag.py
- 在该py文件内必须先书写下面两行代码
1 | from django import template |
1 | # 1. 自定义过滤器(参数只能两个) |
模板的继承
当html页面某一个地方的页面需要传参数才能够动态的渲染出来,并且在多个页面上都需要使用到该局部 那么就考虑将该局部页面做成inclusion_tag形式。例如:bbs ### 模版的继承 - 有些网站不同url的页面整体类似,只是某一些局部在做变化。例如:导航栏不变,主干部分在变化。
1 | # 先设计好父类模版页面 |
中间件与Auth模块
[TOC]
Django中间件
中间件简介
- django自带七个中间件,并且支持程序员自定义中间件
- 只要是涉及到全局相关的功能都可以使用中间件完成
- 全局用户身份、权限校验
- 全局访问频率校验
- …
- django中间件是django的门户
- 请求需要先经过中间件才能到达django后端
- 响应也需要经过中间件才能发送出去
自定义中间件
中间件可以在项目名或者应用名下任意名称的文件夹中定义
自定义中间件类必须继承于MiddlewareMixin类
需要将类的路径以字符串的形式注册到配置文件中才能生效
中间件将暴露五个可以自定义的方法,按需求重写
process_request(self, request)
:请求来的时候需要经过每一个中间件里面的process_request方法
顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
如果中间件里面没有定义该方法,则跳过
如果该方法返回了HttpResponse对象,则请求将不再继续往后执行,而是从同级别的process_reponse向上原路返回
flask中即使中途返回数据,但依然经过所有中间件
process_request方法可以用来做全局相关的所有限制功能
process_response(self, request, response)
:- 响应走的时候需要经过每一个中间件里面的process_response方法
- 顺序是按照配置文件中注册了的中间件从下往上依次经过
- 如果中间件里面没有定义该方法,则跳过
- 该方法必须返回一个HttpResponse对象,默认返回的就是形参response
process_view(self, request, view_func, view_args, view_kwargs)
:- 触发时间在路由匹配成功之后,执行视图函数之前
- 顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
process_template_response(self, request, response)
:- 返回的HttpResponse对象有render属性的时候才会触发
- 顺序是按照配置文件中注册了的中间件从下往上依次经过
process_exception(self, request, exception)
:- 当视图函数中出现异常的情况下触发
- 顺序是按照配置文件中注册了的中间件从下往上依次经过
1 | PYTHON |
csrf跨站请求伪造
使用cookie完成csrf校验
- 为了确保请求来源是本服务器发送出去的
- 为每一次发出去的页面提供唯一标识,如果校验标识失败则直接拒绝(403 forbbiden)
1 | JS |
- 如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值。
- 如果你的视图渲染的HTML文件中没有包含 csrf_token ,Django可能不会设置CSRFtoken的cookie。
- 这个时候需要使用ensure_csrf_cookie()装饰器强制设置Cookie。
1 | PYTHON |
csrf相关装饰器
- @csrf_protect:需要校验
- @csrf_exempt:忽视校验
1 | PYTHON |
跨域请求
- 对于前后端分离的报错:
has been blocked by CORS policy: NO 'Access-Control-Allow-Origin' header is present on the reauested resource.
同源策略
- 浏览器的同源策略:浏览器发现ip或端口是不一样的,就会认为存在风险,会进行拦截。
- 推荐的方法是使用Nginx进行反向代理。
添加响应头解决跨域
- 解决思路就是告诉浏览器:不进行拦截。
1 | PYTHON |
配置文件与反射
importlib模块
使用importlib,以字符串形式导入模块
1 | PYTHON |
实例展示
1 | PYTHON |
Auth模块
使用auth模块要用就用全套
Django的admin路由对应的就是就是auth_user表,必须是管理员用户才能进入。
创建超级用户(管理员):python3 manage.py createsuperuser
常用方法
auth.authenticate(request, username, password)
:根据用户名在表中查询加密后的密码,并将请求中的密码加密比对是否正确。返回用户对象,不匹配则返回Noneauth.login(request, user_obj)
:保存用户状态,建立session。执行该方法后,可以在任何地方通过request.user
获取到当前登陆的用户对象request.user
:request.user.is_authenticated()
:判断当前用户是否登陆```
@login_required1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
:校验登录装饰器
- 局部配置:`@login_required(login_url='/login/')`
- 全局配置:`settings.py -> LOGIN_URL = '/login/'`
- `request.user.check_password(old_password)`:将请求中的密码加密,并比对原密码
- `request.user.set_password(new_password)`:修改用户对象的密码
- `request.user.save()`:将修改密码后的用户对象写入数据库
- `auth.logout(request)` :注销,清除双方session
- `User.objects.create_user(username=username,password=password)`:注册用户。注意不能使用`create`方法,该方法不对密码进行加密。PYTHON
def login(request):
if request.method == ‘POST’:username = request.POST.get('username') password = request.POST.get('password') user_obj = auth.authenticate(request, username=username, password=password) print(user_obj) # 用户对象,不存在则返回None print(user_obj.username) # jason print(user_obj.password) # pbkdf2_sha256$36000$zeNDf8CkZj7y$b+e/CjzZoAnbBIpvUWgz25ybBDqDzRTmYAHPytxqRYQ= # 判断当前用户是否存在 if user_obj: auth.login(request, user_obj) # 保持用户状态,类似于request.session[key] = user_obj # 执行该方法后,可以在任何地方通过request.user获取到当前登陆的用户对象 return redirect('/home/')
return render(request, ‘login.html’)
@login_required(login_url=’/login/‘)
def home(request):
print(request.user) # 在django_session中判断用户是否登陆,没有登录则为AnonymousUser匿名用户
print(request.user.is_authenticated())
return HttpResponse(‘home’)
@login_required
def set_password(request):
if request.method == ‘POST’:
old_password = request.POST.get(‘old_password’)
new_password = request.POST.get(‘new_password’)
confirm_password = request.POST.get(‘confirm_password’)
# 校验两次密码是否一致、老密码是否正确
if new_password == confirm_password and request.user.check_password(old_password):
request.user.set_password(new_password) # 仅仅是在修改对象的密码属性
request.user.save() # 这一步才是真正的操作数据库
return redirect(‘/login/‘)
return render(request, ‘set_password.html’, locals())
@login_required
def logout(request):
auth.logout(request) # 类似于request.session.flush()
return redirect(‘/login/‘)
def register(request):
if request.method == ‘POST’:
username = request.POST.get(‘username’)
password = request.POST.get(‘password’)
User.objects.create_user(username=username, password=password) # 创建普通用户
return render(request, ‘register.html’)
1 |
|
PYTHON
class UserInfo(AbstractUser):
phone = models.BigIntegerField()
1 |
|
HTML
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
+ =
PYTHON def ab_ajax(request): if request.method == 'POST': print(request.POST) i1 = int(request.POST.get('i1')) i2 = int(request.POST.get('i2')) return JsonResponse({"i3": i1 + i2}) # 无法用 HttpResponse + json.dumps 代替 return render(request, 'index.html')
1 |
|
1 |
|
# 1. 手动处理二进制流
json_str = json_bytes.decode('utf-8')
json_dict = json.loads(json_str)
# 2. json.loads如果传入二进制数据则自动解码再反序列化
json_dict = json.loads(json_bytes)
return JsonResponse(json_dict)
return render(request, 'index.html')
1 |
|
HTML
PYTHON
def ab_file(request):
if request.is_ajax():
if request.method == ‘POST’:
print(request.POST)
print(request.FILES)
return render(request,’ab_file.html’)
1 |
|
PYTHON
需求:在前端获取后端用户表里面所有的数据,列表套字典
from django.http import JsonResponse
from django.core import serializers
def ab_ser(request):
user_queryset = models.User.objects.all()
# user_list = []
# for user_obj in user_queryset:
# tmp = {
# ‘pk’:user_obj.pk,
# ‘username’:user_obj.username,
# ‘age’:user_obj.age,
# ‘gender’:user_obj.get_gender_display()
# }
# user_list.append(tmp)
# return JsonResponse(user_list, safe=False)
# 序列化
res = serializers.serialize('json', user_queryset)
return HttpResponse(res)
1 |
|
HTML
1 |
|
PYTHON
from django import forms
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3, max_length=8, label=’用户名’)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3, max_length=8)
# email字段必须符合邮箱格式 xxx@xx.com
email = forms.EmailField()
1 |
|
PYTHON
from app01 import views
1 将带校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({‘username’:’jason’,’password’:’123’,’email’:’123’})
2 判断数据是否合法:注意该方法只有在所有的数据全部合法的情况下才会返回True
form_obj.is_valid() # False
3 查看所有校验通过的数据
form_obj.cleaned_data # {‘username’: ‘jason’, ‘password’: ‘123’}
4 查看所有不符合校验规则以及不符合的原因
form_obj.errors # {‘email’: [‘Enter a valid email address.’]}
5 只校验类中出现的字段,多传的字段直接忽略
form_obj = views.MyForm({‘username’:’jason’,’password’:’123’,’email’:‘123@qq.com‘,’hobby’:’study’})
form_obj.is_valid() # True
6 默认情况下,类里面所有的字段都必须传值
form_obj = views.MyForm({‘username’:’jason’,’password’:’123’})
form_obj.is_valid() # False
1 |
|
PYTHON
def index(request):
# 1. 先生成一个空对象
form_obj = MyForm()
if request.method == ‘POST’:
“””
1. 多个字段的数据获取繁琐
2. 校验数据需要构造成字典的格式传入
3. request.POST就是一个字典
4. 对于提供多余的字段不在意
“””
# 3. 校验数据
form_obj = MyForm(request.POST)
# 4. 判断数据是否合法
if form_obj.is_valid():
# 5. 合法,操作数据库存储数据
return HttpResponse(‘OK’)
# 5. 不合法,有错误。此时可以基于上一次结果进行修改
# 2. 直接将该空对象传递给html页面
return render(request, ‘index.html’, locals())
HTML
第一种渲染方式:代码书写极少,封装程度太高,不便于后续的扩展,一般情况下只在本地测试使用
第二种渲染方式:可扩展性很强 但是需要书写的代码太多,一般情况下不用
:
:
:
第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高
1 |
|
1 |
|
# 全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not confirm_password == password:
# 2. 校验密码和确认密码是否一致
self.add_error('confirm_password', '两次密码不一致')
# 将钩子函数钩出来的所有数据放回去
return self.cleaned_data
1 |
|
PYTHON
class MyForm(forms.Form):
username = forms.CharField(min_length=3, max_length=8, label=’用户名’, initial=’jason’, required=False,
widget=forms.widgets.TextInput(attrs={‘class’: ‘form-control’}),
error_messages={
‘min_length’: ‘用户名最少3位’,
‘max_length’: ‘用户名最大8位’,
‘required’: “用户名不能为空”
})
password = forms.CharField(min_length=3, max_length=8, label=’密码’,
widget=forms.widgets.PasswordInput(attrs={‘class’: ‘form-control c1 c2’}),
error_messages={
‘min_length’: ‘密码最少3位’,
‘max_length’: ‘密码最大8位’,
‘required’: “密码不能为空”
})
confirm_password = forms.CharField(min_length=3, max_length=8, label=’确认密码’,
widget=forms.widgets.PasswordInput(attrs={‘class’: ‘form-control’}),
error_messages={
‘min_length’: ‘确认密码最少3位’,
‘max_length’: ‘确认密码最大8位’,
‘required’: “确认密码不能为空”
})
email = forms.EmailField(label=’邮箱’, widget=forms.widgets.EmailInput(attrs={‘class’: ‘form-control’}),
error_messages={
‘invalid’: ‘邮箱格式不正确’,
‘required’: “邮箱不能为空”
})
phone = forms.CharField(validators=[
RegexValidator(r’^[0-9]+$’, ‘请输入数字’),
RegexValidator(r’^159[0-9]+$’, ‘数字必须以159开头’)
])
# radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
# 单选select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选select
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
1 |
|
PYTHON
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
# 方式一
user = fields.ChoiceField(
# choices=((1, ‘上海’), (2, ‘北京’),),
initial=2,
widget=widgets.Select
)
def init(self, *args, **kwargs):
super(MyForm,self).init(*args, **kwargs)
# self.fields[‘user’].choices = ((1, ‘上海’), (2, ‘北京’),)
# 或
self.fields[‘user’].choices = models.Classes.objects.all().values_list(‘id’, ‘caption’)
# 方式二
authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选
# authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
1 |
|
:在设置cookie的时候可以添加一个超时时间
- max_age、expires 都是设置超时时间的,并且都是以秒为单位,针对IE浏览器需要使用expires。
HttpResponseObject.delete_cookie(key)
:清除Cookie
1 | PYTHON |
对装饰器的详细解释:https://www.runoob.com/w3cnote/python-func-decorators.html
Session操作
Session数据是保存在服务端的,给客户端返回的是一个key为sessionid、value为随机字符串的键值对。
在使用Session之前要使用数据库迁移命令,因为Session的数据是存储在django_session表中的。
Django默认session的过期时间是14天。
django_session表中的数据条数是取决于 user-agent ,同一个浏览器在期限内只会有一条数据被写入在表中。
当session过期时,可能会出现多条数据对应一个IP地址,但是该现象不会持续很久,内部会自动清除过期的数据,也可以通过代码手动清除。
session保存位置可以有多种选择:1. MySQL 2. 文件 3. redis 4. memcache
Session方法:
request.session['key'] = value
:设置sessionrequest.session.get('key')
:获取session```
request.session.set_expiry()1
2
3
4
5
6
7
8
9
10
11
12
13
14
:设置过期时间
- 非零正数:持续的秒数
- datetime或timedelta日期对象:指定日期失效
- 0:一旦浏览器窗口关闭则失效
- None:session会依赖全局session失效策略
4. `request.session.delete()`:清除session,只删服务端的,客户端的不删
5. `request.session.flush()`:清除session,浏览器和服务端都清空(推荐使用)
6. `request.session.session_key`:会话session的keyPYTHON
def set_session(request):
request.session[‘hobby’] = ‘girl’
request.session.set_expiry(5)
“””- django内部自动生成一个随机字符串
- django内部自动将随机字符串和对应的数据存储到django_session表中
2.1 先在内存中产生操作数据的缓存
2.2 在响应结果经过django中间件时才真正地操作数据库django.contrib.sessions.middleware.SessionMiddleware
- 将产生的随机字符串返回给客户端浏览器保存
“””
return HttpResponse(‘嘿嘿嘿’)
def get_session(request):
if request.session.get(‘hobby’):
print(request.session)
print(request.session.get(“hobby”))
“””
1. 先从请求中获取sessionid对应的随机字符串
2. 拿着该随机字符串去django_session表中查找对应的数据
2.1 如果比对上了,则将对应的数据取出并以字典的形式封装到request.session中
2.2 如果比对不上,则request.session.get()返回None
“””
return HttpResponse(“哈哈哈”)
return HttpResponse(“大爷 关门了 明晚再来吧”)
1 |
|
PYTHON
from functools import wraps
def check_login(func):
@wraps(func)
def inner(request, *args, **kwargs):
next_url = request.get_full_path()
if request.session.get(“user”):
return func(request, *args, **kwargs)
else:
return redirect(“/login/?next={}”.format(next_url))
return inner
def login(request):
if request.method == “POST”:
user = request.POST.get(“user”)
pwd = request.POST.get(“pwd”)
if user == “alex” and pwd == “alex1234”:
# 设置session
request.session[“user”] = user
# 获取跳到登陆页面之前的URL
next_url = request.GET.get(“next”)
# 如果有,就跳转回登陆之前的URL
if next_url:
return redirect(next_url)
# 否则默认跳转到index页面
else:
return redirect(“/index/“)
return render(request, “login.html”)
@check_login
def logout(request):
# 删除所有当前请求相关的session
request.session.flush()
return redirect(“/login/“)
@check_login
def index(request):
current_user = request.session.get(“user”, None)
return render(request, “index.html”, {“user”: current_user})
1 |
|
PYTHON
from django.utils.decorators import method_decorator
class HomeView(View):
def get(self, request):
return render(request, “home.html”)
@method_decorator(check_login)
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
1 |
|
PYTHON
from django.utils.decorators import method_decorator
class HomeView(View):
@method_decorator(check_login)
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, "home.html")
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
1 |
|
PYTHON
from django.utils.decorators import method_decorator
@method_decorator(check_login, name=”get”)
@method_decorator(check_login, name=”post”)
class HomeView(View):
def get(self, request):
return render(request, “home.html”)
def post(self, request):
print("Home View POST method...")
return redirect("/index/")