0%

1
2
前端		后端		数据库
#后端学不好 工作不好找

[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四层

img

每层运行常见物理设备

img

img

4.五层协议

4.1物理层

物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
单层的电信号没有意义

4.2数据链路层:ethernet以太网协议

4.2.1Ethernet规定(以太网规定):

  • 一组电信号构成一个数据包,叫做‘帧’

  • 每一数据帧分成:报头head和数据data两部分

    head data
  • head包含:(固定18个字节)

    1. 发送者/源地址,6个字节
    2. 接收者/目标地址,6个字节
    3. 数据类型,6个字节

    data包含:(最短46字节,最长1500字节)

    • 网络层发过来的整体内容

    head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送

  • mac地址(用来标识局域网的一台机器):

    head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址

    mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)

    img

    img

注:以太网协议的工作方式是广播

4.3网络层:IP协议

img

上图结论:必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,如果是就采用广播的方式发送,如果不是,

就采用路由的方式(向不同广播域/子网分发数据包),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报文

img

tcp三次握手和四次挥手

img

4.5应用层

自定义协议需要注意的问题
1.两大组成部分=头部+数据部分
头部:放对数据的描述信息
比如:数据发给谁,数据的类型,数据的长度

​ 2.头部的长度必须固定
​ 因为接收端要通过头部获取所接接收数据的详细信息

socket介绍

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

img

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种”打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个”文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

要能解决问题

软件开发架构

1
2
3
cs架构
bs架构
#本质bs也是cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#HTTP协议
'''
HTTP协议 数据传输是明文
HTTPS协议 数据传输是密文
websocket 数据传输是密文
'''


四大特性
1.基于请求响应
2.基于TCP、IP作用与应用层之上的协议
3.无状态
4.短/无链接
数据格式
1.请求首行
2.请求头
3.请求体
响应状态码
1xx:指示信息–表示请求已接收,继续处理。
2xx:成功–表示请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进行更进一步的操作。
4xx:客户端错误–请求有语法错误或请求无法实现。
5xx:服务器端错误–服务器未能实现合法的请求。

基于wsgiref模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from wsgiref.simple_server import make_server

def run(env,response):
current_path = env.get('PATH_INFO')
if current_path == '/index':
return [b'index']
elif current_path =='/login'
return [b'login']
return [b'404 ']
response('200 ok',[])
return [b'hello wsgiref']
'''
:param env:请求相关的所有数据
:param response:响应相关的所有数据
:return:返回给浏览器的数据
'''

#wsgiref会帮你处理好http格式的数据,封装成字典给让你更加方便操作
if __name__=='__main__':
server=make_server('127.0.0.1',8080,run)
"""会实时监听127.0.0.1:8080地址,只要有客户端来了
都会交给run函数处理
"""
server.server_forever() #启动服务端

借助于wsgiref模块

urls.py 路由与属兔对应函数
views.py 视图函数
template文件夹 专门用来存储html文件

根据功能的不同拆分之后,后续添加功能只需要在urls.py注册对应关系然后再views.py书写业务逻辑即可

动静态网页

静态网页
页面数据是写死的
动态网页
数据是实时获取的
eg:
1.后端获取当前实践展示到html页面上
2.数据是才能数据库中获取展示到页面上

模板语法之jinja2模块

1
2
3
4
5
6
7
8
9
10
# 将字典传给html文件
from jinja2 import Template
def get_dict(env):
user_dic = {'username':'jason','age':18,'hobby':'read'}
with open(r'templates/04 get_dict.html','r',encoding='utf-8') as f:
data = f.read()
tmp = Template(data)
res = tmp.render(user=user_dic)
# 给get_dict.html传递了一个值 页面上通过变量名user就能够拿到user_dict
return res

简单web请求流程图

image-20211115111825710

wsgiref模块
1.请求来的时候解析http格式的数据,封装成大字典
2.响应走的时候给数据打包成符合http格式,再返回给浏览器

python三大主流web框架

django

特点:大而全,自带的功能特别多,类似于航空母舰
缺点: 有时候过于笨重
socket部分:wsgiref模块
路由与视图函数对应关系:自己写的
模块语法:用的是自己的

flask

特点:小而精 自带的功能特别少
第三方模块特别多,如果将flask第三方的模块完全可以盖过django
不足之处:
比较依赖于第三发的开发者,会碰到兼容性的问题
socket部分: werkzeug(内部还是wsgiref模块)
路由与视图函数对应关系:自己写的
模块语法: jinja2

tornado

特点:异步非阻塞 支持高并发
甚至开发游戏服务器
不足之处:
暂时你不会?
socket部分: 自己写的
路由与视图函数对应关系:自己写的
模块语法: 自己写的

注意事项

如何让你的计算机正常启动django项目

  1. 计算机的名称不能有英文
  2. 一个pycharm窗口只开一个项目
  3. 项目里面的所有文件不要出现中文

django版本问题

  1. x 2.x 3.x(忽略)
  2. x和2.x本身差距也不大,以1.x为例

django安装

pip3 install jango==1.11.11

验证是否安装成功
终端输入django-admin看有没有反应

django基本操作

命令行操作

  1. 创建django项目
    django-admin startproject mysite

    创建完app一定要在settings.py中的INSTALLED_APPS下注册

  2. 启动django项目

    一定要切到项目目录下
    python manage.py runserver

  3. 创建应用

    应用

    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操作

  1. new project -> django

  2. 启动django项目

    命令行 或者 绿色箭头运行

  3. 创建应用

    命令行

    tools -> run manage.py task -> start app02(有提示,前期不要用)

  4. 修改端口号以及创建server

    edit -> config

命令行与pycharm创建的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1 命令行创建django项目时,不仅要手动创建templates文件夹,还需要自己配置路径。
# -> settings.py
# pycharm创建
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
]
# 命令行创建
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
]
意味着你用命令行创建django的时候不仅需要创建template文件夹还要去配置文件里路径(settings.py)

django必会三板斧

Httpresponse

​ 返回字符串类型的数值

render

​ 返回html文件

redirect

​ 重定向
​ return redirect(‘https://www.baidu.com/')
​ return redirect(‘/home/‘)

静态文件访问令牌

1
2
3
4
5
6
7
8
9
10
# -> settings.py
STATIC_URL = '/static/' # 访问静态文件的令牌

# 需要自己创建STATICFILES_DIRS,配置静态文件所在文件夹
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1')
os.path.join(BASE_DIR,'static2')

]

动态解析令牌

1
2
3
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

我们将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
2
3
4
5
6
7
request.post	#获取post请求数据
hobby = request.POST.get('hobby') #获取hobby列表最后一个元素
hobby = request.POST.getlist('hobby') #获取整个列表

request.GET #获取get请求数据
#用法和POST一样
#二者区别是: get请求有大小数据限制,因为它提供的是url,post没有大小限制,它提供的是json文件

Django模型层

测试脚本

  • 当只是想测试django项目中的某一个.py文件内容,那么可以不用书写前后端交互的形式,而是直接写一个测试脚本即可。
  • 脚本代码无论是写在应用下的tests.py,还是单独开设.py文件都可以。
  • django中的文件默认不会暴露出来,需要准备测试环境才能进行测试。
1
2
3
4
5
6
7
8
9
10
11
PYTHON
# 测试环境的准备:1. 在manage.py中拷贝前四行代码;2. 额外增加两行代码
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoProject.settings") # 这里要改成相应的项目名
import django
django.setup()

# 测试代码
  • from app01 import models写在main之外时,可以通过勾选”使用Python控制台运行”来解决 https://blog.csdn.net/weixin_44393803/article/details/89739066
  • 当不准备环境配置时,可以在运行配置中勾选 Python控制台启动 。这样会忽略脚本中的配置环境代码。(推荐)

数据库链接

django链接数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
# -> settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test',
'USER': 'root',
'PASSWORD':'123456',
'HOST':'127.0.0.1',
'PORT': 3306,
'CHARSET': 'utf8'
}
}

​ django默认用的是mysqldb模块
​ 但是该模块的兼容性不好 需要手动改为用pymysql链接
​ #在项目名下的inint或者任一应用文件中写以下代码都可以

1
2
3
# -> 在项目名下或任意应用名下的init文件
import pymysql
pymysql.install_as_MySQLdb()

Django ORM

ORM,对应关系映射
作用:让一个不用sql语句的小白也能用python面向对象的代码简单快捷操作数据库
不足之处:封装程度高,有时候sql语句效率太低,需要自己写sql语句

image-20211118212957177

创建模型表

1
2
3
4
5
6
7
8
9
#	1.先去models.py中写一个类
class User(models.Model):
# id int primary_key auto_increment
id = models.AutoField(primary_key=True,verbose_name='主键')
# versbose_name该参数是所有字段都有的,就是用来对字段的解释
# name varchar(10)
name = models.CharField(max_length=32)
# age int
age = models.IntegerField()
1
2
3
4
#	2.数据库迁移命令
python manage.py makemigrations 将操作记录在小本本上(migration文件夹)
python manage.py migrate 将操作真正的数据库中
# 只要你修改了models.py中跟数据相关的代码 就必须重新执行上述的两条命令

字段增删改

1
2
3
4
5
6
7
8
9
10
11
#字段的增加
1.可以在终端内直接给出默认值
2.该字段也可以为空
info = models.CharField(max_length=32,null=True)
3.直接给字段设置默认值
hobby = models.CharField(max_length=32,default='study')
#字段的修改
直接修改代码然后执行两条命令
#字段的删
直接注释对应的字段并执行数据库迁移的两条命令
执行完毕后对应的数据也没了

创建表关系

  • A表外键字段 = models.ForeignKey(to=B表)
  • 关系表外键字段 = models.ManyToManyField(to=B表)
  • A表外键字段 = models.OneToOneField(to=B表)
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
from django.db import models


# Create your models here.
# 创建表关系 先把基表创建出来,再添加外键字段
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2) # 总共八位 小数点后面占两位
# 图书和出版社是一对多,并且书是多的一方,所以外键字段放在书表里面
publish = models.ForeignKey(to='Publish') # 默认就是与出版社的主键字段做关联
# 如果该字段是ForeignKey,则orm会自动在字段的后面加 _id -> publish_id
# 因此在定义ForeignKey的时候就不要加 _id

# 图书和作者是多对多的关系,外键字段建在任意一方均可,但是推荐建在查询频率较高的一方
authors = models.ManyToManyField(to='Author')
# authors是一个虚拟字段 主要是用来告诉orm 书籍表和作者表是多对多关系
# orm将自动创建第三张关系表


class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)


class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
# 作者与作者详情是一对一的关系 外键字段建在任意一方都可以 但是推荐你建在查询频率较高的表中
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE) # django2.x之后要手动添加级联删除
# OneToOneField()也会自动给字段 author_detail 加 _id 后缀
# on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET(value)五个可选择的值


class AuthorDetail(models.Model):
phone = models.BigIntegerField() # 直接用字符类型更好
addr = models.CharField(max_length=32)

单表的增删改查

增删改查

  • 在视图函数中,from app01 import models首先导入对应的app下的model
  • django自带的sqlite3数据库对日期格式不是很敏感,处理的时候容易出错。
  • pk会自动查找到当前表的主键字段,指代的就是当前表的主键字段,避免区分当前表的主键字段名uid/pid/sid

返回值先看成是列表套数据对象的格式
支持索引取值、切片操作 但是不支持负数索引
user_obj = models.User.objects.filter(username=username).first

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
def login(request):
if request.method == 'POST':
# 获取用户的用户名和密码 然后利用orm操作数据 校验数据是否正确
username = request.POST.get('username')
password = request.POST.get('password')

# 去数据库中查询数据
from app01 import models
# select * from user where username='jason';
# filter相当于where,括号内可以携带多个参数,参数与参数之间是and关系
result = models.User.objects.filter(username=username)
print(type(result)) # <QuerySet [<User: User object>]>
# 支持索引取值、切片操作,但是不支持负数索引
user_obj = reslt.first() # 从queryset拿出一个个数据对象
print(user_obj.username) # 直接调用字段

# 比对密码是否一致
if user_obj:
if password == user_obj.password:
return HttpResponse("登陆成功")
else:
return HttpResponse("密码错误")
else:
return HttpResponse("用户不存在")
return render(request, 'login.html')

注:

  • models.User.objects.filter()等效于models.User.objects.all()
  • .first()等效于[0]

数据展示:

1
2
3
4
5
6
# -> views.py
def userlist(request):
# 查询出用户表里面所有的数据
user_queryset = models.User.objects.all()
# return render(request,'userlist.html',{'user_queryset':user_queryset})
return render(request,'userlist.html', locals()) # 返回当前的命名空间

展示界面

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ user_queryset }}
</body>
<h1 class="text-center">数据展示</h1>
<div class="container">
<div class="col-md-offset-2">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>username</th>
<th>age</th>
</tr>
</thead>
<tbody>
{% for foo in user_queryset %}
<tr>
<td>{{ foo.id }}</td>
<td>{{ foo.name }}</td>
<td>{{ foo.age }}</td>
<td>
<a href="/edit_user/?user_id={{ foo.id }}" class="btn btn-primary btn-xs">编辑</a>
<a href="" class="btn btn-danger btn-xs">删除</a>
</td>
</tr>
{% endfor %}

</tbody>
</table>
</div>
</div>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -> views.py
def reg(request):
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
from app01 import models
# 第一种方式(推荐使用)
reslt = models.User.objects.create(username=username, password=password)
print(res, res.username, res.password) # 返回值就是当前被创建的对象本身
# 第二种方式
user_obj = models.User(username=username, password=password)
user_obj.save() # 保存数据

# 先给用户返回一个注册页面
return render(request, 'reg.html')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -> views.py
def edit(request):
# 获取url问好后面的参数
edit_id = request.GET.get('user_id')
if request.method == "POST":
name = request.POST.get('name')
age= request.POST.get('age')
# 改 1 批量更新filter查询出来的列表中所有对象。只修改被修改的字段
models.User.objects.filter(id=edit_id).update(name=name, age=age)
return redirect('/userlist/')

# 改 2 单条数据更新。无论该字段是否被修改,重写所有字段(当字段特别多时,效率很低)
# edit_obj.name = name
# edit_obj.age = age
# edit_obj.save()
# 查询当前用户想要编辑的数据对象
edit_obj = models.User.objects.filter(id=edit_id).first()
return render(request,'edit_user.html',locals())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# edit_user界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 class="text-center">编辑</h1>
<div class="container">
<div class="row">
<div class="col-md-offset-2">
<form action="" method="post">
<p>name:<input type="text" name="name" class="form-control" value="{{ edit_obj.name }}"> </p>
<p>age:<input type="text" name="age" class="form-control" value="{{ edit_obj.age }}"></p>
<input type="submit" class="btn btn-danger btn-block">
</form>
</div>
</div>
</div>
</body>
</html>

1
2
3
4
5
6
7
8
9
10
11
def delete_user(request):
# 获取用户想要删除的数据id值
delete_id = request.GET.get('user_id')
# 删 1 批量删除数据库中找到对应的数据
res = models.User.objects.filter(id=delete_id).delete()
print(res) # (1, {'app01.User': 1})
# 删 2
user_obj = models.User.objects.filter(pk=delete_id).first()
user_obj.delete()

return redirect('/userlist/')

必知必会13条

  1. all():查询所有数据

  2. filter():带有过滤条件的查询

  3. get():直接拿数据对象,但是条件不存在直接报错

  4. first():拿queryset里面第一个元素

  5. last():拿queryset里面最后一个元素

  6. values():可以指定获取的数据字段,相当于select name,age from …

    1
    2
    3
    PYTHON
    res = models.User.objects.values('name','age')
    print(res) # 列表套字典 <QuerySet [{'name': 'jason', 'age': 18}, {'name': 'egonPPP', 'age': 84}]>
  7. values_list():

    1
    2
    3
    PYTHON
    res = models.User.objects.values_list('name','age')
    print(res) # 列表套元组 <QuerySet [('jason', 18), ('egonPPP', 84)]>

    和values()仅仅只是封装格式不一样,sql查询语句是一样的。

  8. distinct():去重

    1
    2
    3
    PYTHON
    res = models.User.objects.values('name','age').distinct()
    print(res)
    • 去重一定要是(在虚表中)一模一样的数据。

      例如:.all()的数据带有主键,因此无法去重

  9. order_by():

    1
    2
    3
    4
    PYTHON
    res = models.User.objects.order_by('age') # 默认升序
    res = models.User.objects.order_by('-age') # 降序
    print(res)
  10. reverse():

    1
    2
    3
    4
    PYTHON
    res = models.User.objects.all() # 反转
    res1 = models.User.objects.order_by('age').reverse() # 必须先排序
    print(res, res1)
  11. count():统计当前数据的个数

    1
    2
    3
    PYTHON
    res = models.User.objects.count() # 统计当前数据的个数
    print(res)
  12. exclude():排除在外

    1
    2
    3
    PYTHON
    res = models.User.objects.exclude(name='jason')
    print(res)
  13. exists():基本用不到因为数据本身就自带布尔值 返回的是布尔值

    1
    2
    3
    PYTHON
    res = models.User.objects.filter(pk=10).exists()
    print(res)

    查看内部sql语句的方式

  14. 只有QuerySet类对象可以使用.query查看内部封装的sql语句

  15. 在配置文件中配置logging,查看所有的sql语句

  16. 需要在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
2
3
4
5
6
7
8
9
10
11
12
13
14
PYTHON
# 一对多增删改查
# 1. 增
# 直接写实际字段 id
models.Book.objects.create(title='三国演义', price=123.23, publish_id=1)
# 投放虚拟字段 对象
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title="红楼梦", price=666.23, publish=publish_obj)
# 2. 删
models.Publish.objects.filter(pk=1).delete() # 会按照on_delete处理对应外键
# 3. 改
models.Book.objects.filter(pk=1).update(publish_id=2)
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)

多对多外键增删改查

多表查询

  • 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
    43
    PYTHON
    # 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
    43
    PYTHON
    # 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
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
43
PYTHON
# 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)

联表查询(基于双下划线的跨表查询)

  • QuerySet要先用int取字典,再用key取对应的数据
  • 可以放多个查询的键
  • __就是用来获得表下的一个字段
  • 而写到__之前则表示已经进入了另外一个表
  • 这里非对象时都不需要_set
  • 可以 __ id 或者 __pk,是等价的
  • 可以无限制的跨表,正向、反向
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
PYTHON
# 1. 查询jason的手机号和作者姓名
# 有字段 -> 正向;
res = models.Author.objects.filter(name='jason').values("author_detail__phone", "name")
print(res[0]["author_detail__phone"])
# 反向
res = models.AuthorDetail.objects.filter(author__name='jason') # 拿作者姓名是jason的作者详情
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone', 'author__name')
print(res)

# 2.查询书籍主键为1的出版社名称和书的名称
# 正向,写到publish时就已经到了publish表,主需要__来取这个表下的任意字段
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)
# 反向
res = models.Publish.objects.filter(book__pk=1).values('name', 'book__title')
print(res)

# 3.查询书籍主键为1的作者姓名
# 正向
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)
# 反向
res = models.Author.objects.filter(book__id=1).values('name')
print(res)

# 4. 查询书籍主键是1的作者的手机号
res = models.Book.objects.filter(pk=1).values("authors__author_detail__phone")
print(res)

聚合查询(aggregate)

  • 只要是跟数据库相关的模块,基本上都在django.db.models里面,如果没有则应该在django.db里面。
1
2
3
4
5
6
PYTHON
from app01 import models
from django.db.models import Max,Min,Sum,Count,Avg
res = models.Book.objects.aggregate(Max('price'),Min('price'),Sum('price'),Count('pk'),Avg('price'))
print(res)
# {'price__max': Decimal('899.23'), 'price__min': Decimal('123.23'), 'price__sum': Decimal('2466.58'), 'pk__count': 5, 'price__avg': Decimal('493.316000')}

分组查询(annotate)

  • MySQL分组查询特点:分组之后默认只能获取到分组的依据,组内其他字段都无法直接获取了(严格模式,ONLY_FULL_GROUP_BY)
  • author和author__pk是等价的,个人认为写全更好
  • models后面点什么,就是按什么分组
  • author_num是自己定义的临时字段,用来存储统计出来的每本书对应的作者个数
  • 在使用values创建虚表时,依然可以放入多个字段
  • 只要orm语句得出的结果还是一个queryset对象,那么就可以继续无限制的点queryset对象封装的方法
  • queryset对象就代表一个虚表
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
PYTHON
# 1. 统计每一本书的作者个数
# 只需要写author就可以了
# models后面点什么,就是按什么分组
# author_num是我们自己定义的字段,用来存储统计出来的每本书对应的作者个数
# 在使用values创建虚表时,依然可以放入多个字段
res = models.Book.objects.annotate(author_num=Count("author__pk")).values("author_num", 'title', 'author_num')
print(res)

# 2. 统计每个出版社卖的最便宜的书的价格
# 按publish分组,反向找book表中price的最小值,作为publish表的min_price临时字段
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)

# 3. 统计不止一个作者的图书
# 先按照图书分组,求每一本书对应的作者个数
# 过滤出不止一个作者的图书
res = models.Book.objects.annotate(author_num=Count("author_id")) \
.filter(author__num__gt=1) \
.values('title', 'author_num')
# 只要orm语句得出的结果还是一个queryset对象,那么就可以继续无限制的点queryset对象封装的方法
# queryset对象就代表一个虚表
print(res)

# 4. 查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', "sum_price")
print(res)

# 5. 按照指定的字段分组,本质也是创造虚表
models.Book.objects.values('price').annotate()

F与Q查询

F

  • 能够直接获取到表中某个字段对应的数据
  • F不能够直接操作字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PYTHON
# 1. 查询卖出数大于库存数的书籍
from django.db.models import F
res = models.Book.objects.filter(sale__gt=F('store'))
print(res)

# 2. 将所有书籍的价格提升500块
models.Book.objects.update(price=F('price') + 500)

# 3. 将所有书的名称后面加上爆款两个字
# 在操作字符类型的数据的时候,F不能够直接做到字符串的拼接
from django.db.models.functions import Concat
from django.db.models import Value
# models.Book.objects.update(title=F('title') + '爆款') # 所有的名称会全部变成空白
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))

Q

  • 扩展搜索表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PYTHON
# 1.查询卖出数大于100或者价格小于600的书籍
# res = models.Book.objects.filter(maichu__gt=100,price__lt=600) # filter括号内多个参数是and关系
from django.db.models import Q
res = models.Book.objects.filter(Q(maichu__gt=100), Q(price__lt=600)) # Q包裹逗号分割 还是and关系
res = models.Book.objects.filter(Q(maichu__gt=100) | Q(price__lt=600)) # | or关系
res = models.Book.objects.filter(~Q(maichu__gt=100) | Q(price__lt=600)) # ~ not关系
print(res) # <QuerySet []>

# 2. Q的高阶用法,将查询条件的左边变成字符串形式
q = Q()
q.connector = 'or' # 默认是and关系
q.children.append(('maichu__gt', 100))
q.children.append(('price__lt', 600))
res = models.Book.objects.filter(q)
print(res)

django中如何开启事务

  • ACID
    • 原子性
      • 不可分割的最小单位
    • 一致性
      • 跟原子性是相辅相成
    • 隔离性
      • 事务之间互相不干扰
    • 持久性
      • 事务一旦确认永久生效
  • 事务的回滚
    • rollback
  • 事务的确认
    • commit
1
2
3
4
5
6
7
8
9
10
11
PYTHON
# Django中如何简单地开启事务
from django.db import transaction
try:
with transaction.atomic():
# sql1
# sql2
...
# 在with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
print(e)

orm字段及参数

常用字段及参数

  1. AutoField:主键字段

    • primary_key=True

    一般会自动创建,不需要手动设置

  2. CharField:varchar

    • verbose_name=字段的注释
    • max_length=长度
    • null=True可以为空
    • blank=True可以存空字符串(这两个有区别)
    • unique=True唯一
  3. IntegerField:int

  4. BigIntegerField:bigint

  5. DecimalField

    • max_digits=允许的最大位数
    • decimal_places=小数的最大位数
  6. EmailFiled:varchar(254)

  7. DateField:date

    DateTimeField:datetime

    • auto_now=每次修改数据时更新当前时间
    • auto_now_add=只在创建数据时记录当前时间
  8. BooleanField:Field

    该字段传布尔值(False/True),数据库里面存0/1

  9. TextField:Field

    该字段可以用来存大段内容(文章、博客…),没有字数限制

  10. FileField:Field

    • upload_to = “/data/{file}”

      给该字段传一个文件对象,会自动将文件保存到/data目录下,并将文件路径保存到数据库中

更多字段,直接参考博客:https://www.cnblogs.com/Dominic-Ji/p/9203990.html,后续补全

自定义字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PYTHON
# 定义字段
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
# 调用父类的init方法
super().__init__(max_length=max_length, *args, **kwargs) # 一定要是关键字的形式传入

def db_type(self, connection):
"""
返回真正的数据类型及各种约束条件
:param connection:
:return:
"""
return 'char(%s)' % self.max_length


# 自定义字段使用
myfield = MyCharField(max_length=16, null=True)

外键字段及参数

  1. unique=True

    ForeignKey(unique=True)等价于OneToOneField()

  2. db_index=True

    为此字段设置索引

  3. to

    设置要关联的表

  4. to_field

    设置要关联的表的字段,默认关联另外一张的主键字段,并且自动添加_id后缀

  5. on_delete

    当删除关联表中的数据时,当前表与其关联的行的行为。

  6. related_name

    反向查询使用的字段名,即代替 字段名__set

  7. limit_choices_to

    限制选择条件

数据库查询优化

all()

  • orm语句的特点:惰性查询

    如果仅仅只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别,直接不执行。

1
2
3
PYTHON
res = models.Book.objects.all() # 此时并没有查询数据库
print(res) # 要用数据了才会走数据库

only()

只取目标字段

  • 拿到数据对象,这些对象本身只有only中提到的字段参数
  • 如果调用没有包含的字段,则会重新回到数据库查询
1
2
3
4
5
6
7
8
9
10
11
PYTHON
# 获取书籍表中所有书的名字
res = models.Book.objects.values('title')
for d in res:
print(d.get('title'))
# 实现获取数据对象,点title就能够拿到书名,并且没有其他字段
res = models.Book.objects.only('title')
print(res) # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>, <Book: 论语爆款>, <Book: 聊斋爆款>, <Book: 老子爆款>]>
for i in res:
print(i.title) # 点only括号内的字段,不会走数据库
print(i.price) # 点only括号内没有的字段,会重新走数据库查询而all()不需要走了

defer()

  • defer与only刚好相反
  • defer括号内放的字段不在查询出来的对象里面,查询该字段需要重新走数据库
  • 而如果查询的是非括号内的字段,则不需要走数据库了
1
2
3
4
PYTHON
res = models.Book.objects.defer('title') # 对象除了没有title属性之外其他的都有
for i in res:
print(i.price)

跨表操作

  • select_related内部先将book与publish连起来,然后一次性将大虚表的所有数据封装给查询出来的对象

    这个时候对象无论是点击book表的数据还是publish表的数据都无需再走数据库查询了

  • select_related括号内只能放外键字段:一对多、一对一

    不可以多对多

1
2
3
4
5
6
7
8
PYTHON
res = models.Book.objects.all()
for i in res:
print(i.publish.name) # 每循环一次就要走一次数据库查询

res = models.Book.objects.select_related('authors') # INNER JOIN
for i in res:
print(i.publish.name) # 每循环一次就要走一次数据库查询
  • prefetch_related内部其实就是子查询

    指把内部查询的结果作为外层查询的比较条件

  • 两次查询,但不一定就比select_related差,要看实际情况

1
2
3
4
5
PYTHON
res = models.Book.objects.prefetch_related('publish') # 子查询

for i in res:
print(i.publish.name)

批量插入

当批量插入数据的时候,使用orm的bulk_create方法能够大幅减少操作时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PYTHON
def ab_pl(request):
# 1. 逐条插入
for i in range(10000):
models.Book.objects.create(title='第%s本书' % i)

# 2. 批量插入
book_list = []
for i in range(100000):
book_list.append(models.Book(title='第%s本书' % i))
models.Book.objects.bulk_create(book_list)

book_queryset = models.Book.objects.all()
return render(request, 'ab_pl.html', locals())

分页器

减少同一时间的数据展示量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PYTHON
def ab_pl(request):
# 分页
try:
cur_page = int(request.GET.get('cur_page', default=1)) # 如果获取不到当前页码,就展示第一页
per_page = int(request.GET.get('per_page', default=10)) # 每页展示多少条
start_page = int((cur_page - 1) * per_page) # 起始位置
end_page = int(cur_page * per_page) # 终止位置
except Exception:
start_page = 0
end_page = 10

book_list = models.Book.objects.all()

page_queryset = book_list[start_page:end_page]
response = serializers.serialize('json', page_queryset)
return HttpResponse(response)

choices参数

  • 只要某个字段的内容是可以枚举完全的,则可以采用choices参数:
    • 学历
    • 客户来源
    • ……
  • 元组套元组
  • 需要保证数据库表中字段类型跟key的数据类型一致
  • 在数据库中存的是key,value在orm中
  • 不存在的key直接输出key,存在的key可以通过get_字段_display()转换成value输出
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
PYTHON
# -> models.py
class User(models.Model):
username = models.CharField(max_length=32)
age = models.IntegerField()
# 性别
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(choices=gender_choices)

score = models.CharField(choices=(
('A', '优秀'),
('B', '良好'),
('C', '及格'),
('D', '不合格'),
), null=True) # 保证字段类型跟列举出来的元组第一个数据类型一致

# -> test.py
# 存的时候,没有列举出来的数字也能存,范围还是按照字段类型决定
models.User.objects.create(username='jason', age=18, gender=1)
models.User.objects.create(username='egon', age=85, gender=2)
models.User.objects.create(username='tank', age=40, gender=3)
models.User.objects.create(username='tony', age=45, gender=4)

# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender) # 数据库中存储的key
print(user_obj.get_gender_display()) # 对于choices参数的字段,通过get_字段名_display()获取对应的值
user_obj = models.User.objects.filter(pk=4).first()
print(user_obj.get_gender_display()) # 如果没有对应关系,那么返回字段存储的原key

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
2
3
4
5
6
7
8
PYTHON
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')

class Author(models.Model):
name = models.CharField(max_length=32)

纯手动

  • 优点:
    • 第三张表完全取决于你自己进行额外的扩展
  • 不足之处:
    • 需要写的代码较多
    • 不能够使用orm提供的简单方法

不建议使用该方式。

1
2
3
4
5
6
7
8
9
10
11
PYTHON
class Book(models.Model):
name = models.CharField(max_length=32)

class Author(models.Model):
name = models.CharField(max_length=32)

class Book2Author(models.Model):
book_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')

半自动

  • 可以使用orm的正反向查询
  • 但是没法使用add、set、remove、clear这四个方法
  • 多对多表关系可以放在任何一方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PYTHON
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',
through='Book2Author',
through_fields=('book','author')
) # 将当前表名放在元组第一位

class Author(models.Model):
name = models.CharField(max_length=32)
# books = models.ManyToManyField(to='Book',
# through='Book2Author',
# through_fields=('author', 'book')
# )

class Book2Author(models.Model):
book = models.ForeignKey(to='Book') # 外键默认加id,这里不要加,包括through_fields
author = models.ForeignKey(to='Author')

Django路由层

Django请求生命周期流程图(重点)

  • wsgiref模块能够支持的并发量很小,上线之后换成uwsgi
  • wsgi、wsgiref、uwsgi之间的关系
    • wsgi是协议
    • wsgiref和uwsgi是实现该协议的功能模块

202011130830231.png

扩展知识点

缓存数据库
提前已经把数据准备好了,直接拿来用,可以提高效率

路由匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 路由匹配
url(r'test',view.test),
url(r'testadd',view.testadd)

url方法第一个参数是正则表达式
只要第一个参数正则表达式能匹配到内容,就会停止往下匹配
直接执行对应的视图函数

你在输入url的时候会默认加斜杠
django内容帮你重定向
一次匹配不行
url后面加斜杠再来一次

如果要取消加斜杠,只需要在setting.py中加入以下代码
APPEND_SLASH = False

强制url(r'test',view.test)访问test
需要改成url(r'^test$',view.test)

首页url,要写成
url(r'^$',views.home)

无名分组

  • 无名分组就是将括号内正则表达式匹配到的内容当作位置参数传递给后面的视图函数
1
2
3
4
5
6
7
8
PYTHON
# -> urls.py
re_path(r'^test/(\d+)/', views.test)

# -> views.py
def test(request, xx):
print(xx)
return HttpResponse('test')

有名分组

  • 有名分组就是将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数
  • 需要有一个对应的形参来接收
1
2
3
4
5
6
7
8
PYTHON
# -> urls.py
re_path(r'^testadd/(?P<year>\d+)', views.testadd)

# -> views.py
def testadd(request, year):
print(year)
return HttpResponse('testadd')

无名有名是否可以混合使用

  • 不能混用无名、有名分组
  • 但是只使用一种分组时可以使用多次/传多个形参
1
2
3
4
5
6
7
8
9
PYTHON
# -> urls.py
re_path(r'^index/(\d+)/(\d+)/(\d+)/', views.index),
re_path(r'^index/(?P<year>\d+)/(?P<age>\d+)/(?P<month>\d+)/', views.index)

# -> views.py
def testadd(request, *args, **kwargs):
print(args, kwargs)
return HttpResponse('testadd')

反向解析

  • 通过一些方法得到一个结果 该结果可以直接访问对应的url触发视图函数
1
2
3
4
5
6
7
8
9
10
11
12
13
CODE
# -> urls.py
# 给路由与视图函数起一个别名
re_path(r'^urls/', views.func, name='name')

# -> views.py
# 后端反向解析
from django.shortcuts import reverse
reverse('name')

# -> xxx.html
# 前端反向解析
<a href="{% url 'name' %}">string</a>

无名分组反向解析

分组的解析值一般就是数据的主键值。

1
2
3
4
5
6
7
8
9
10
11
12
13
CODE
# -> urls.py
# 无名分组反向解析
re_path(r'^edit/(\d+)/', views.edit, name='xxx')

# -> views.py
# 后端
def edit(request, edit_id):
reverse('xxx', args=(edit_id, ))

# -> xxx.html
# 前端
<a href="{% url 'xxx' user_obj.id %}">编辑</a>

有名分组反向解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CODE
# -> urls.py
# 有名分组反向解析
re_path(r'^func/(?P<year>\d+)/', views.func, name='ooo')

# -> views.py
# 后端
# 有名分组反向解析
print(reverse('ooo', kwargs={'year':123}))
# 简便的写法,和无名一样
print(reverse('ooo', args=(111,)))

# -> xxx.html
# 前端
<a href="{% url 'ooo' year=123 %}">111</a> # 了解
<a href="{% url 'ooo' 123 %}">222</a> # 记忆

如何理解分组反向解析

  • 分组是基于正则表达式匹配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
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
PYTHON
# 总路由
from django.urls import include, re_path
# from app01 import urls as app01_urls
# from app02 import urls as app02_urls

urlpatterns = [
re_path(r'^admin/', admin.site.urls),
# 路由分发
# url(r'^app01/', include(app01_urls)), # 只要url前缀是app01开头,全部交给app01处理
# url(r'^app02/', include(app02_urls)) # 只要url前缀是app02开头,全部交给app02处理
# 推荐写法
re_path(r'^app01/', include('app01.urls')),
re_path(r'^app02/', include('app02.urls'))
# 注意事项: 总路由里面的url千万不能加$结尾
]


# 子路由
# -> app01 -> urls.py
from django.urls import include, re_path
from app01 import views

urlpatterns = [
re_path(r'^reg/', views.reg)
]

# -> app02 -> urls.py
from django.urls import include, re_path
from app02 import views

urlpatterns = [
re_path(r'^reg/', views.reg)
]

名称空间

  • 当多个应用出现了相同的别名,反向解析只能识别后缀而不能识别前缀,例如 https://app/xxx/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PYTHON
# 总路由
# -> urls.py
urlpatterns = [
re_path(r'^app01/', include('app01.urls', namespace='app01')),
re_path(r'^app02/', include('app02.urls', namespace='app02'))
]
# 子路由
# -> app01 -> urls.py
urlpatterns = [
re_path(r'^reg/', views.reg, name='reg')
]
# -> app02 -> urls.py
urlpatterns = [
re_path(r'^reg/', views.reg, name='reg')
]

# 反向解析
# -> views.py
reverse('app01:reg')
reverse('app02:reg')
# -> .html
{% url 'app01:reg' %}
{% url 'app02:reg' %}
  • 利用名称空间可以区分不同app的相同别名,但一般只需要在别名之前添加所属应用作为前缀,就大可不必使用名称空间。
1
2
3
4
5
6
7
8
PYTHON
# -> urls.py
urlpatterns = [
re_path(r'^reg/', views.reg, name='app01_reg')
]
urlpatterns = [
re_path(r'^reg/', views.reg, name='app02_reg')
]

伪静态

  • 静态网页:数据是写死的 万年不变
  • 伪静态:将一个动态网页伪装成静态网页
  • 伪装的目的:
    • 增大本网站的seo查询力度
    • 增加搜索引擎收藏本网上的概率
1
2
3
4
5
PYTHON
# 仅将url地址改为.html结尾,假装只是返回html文件,实际上还是经过视图函数,可以做动态处理
urlpatterns = [
re_path(r'^reg.html', views.reg, name='app02_reg')
]

虚拟环境

  • 在正常开发中,通常每一个项目配备一个独有的解释器环境,只有该项目用到的模块,用不到一概不装。
  • 但是较多的虚拟环境会消耗更多的硬盘空间。
  • 一般通过requirement.txt来标识项目的虚拟环境需要的模块。

Django各版本区别

1.re_path

Django2.0中的re_path与django1.0的url一样,传入的第一个参数都是正则表达式

1
2
3
4
5
6
7
8
Copyfrom django.urls import re_path # django2.0中的re_path
from django.conf.urls import url # 在django2.0中同样可以导入1.0中的url

urlpatterns = [
# 用法完全一致
url(r'^app01/', include(('app01.urls','app01'))),
re_path(r'^app02/', include(('app02.urls','app02'))),
]

2.django3默认支持一下5种转换器(Path converters)

1
2
3
4
5
Copystr,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
1
2
3
4
5
6
7
8
# -> urls.py
# 将匹配内容转成相应类型,作为形参传递给后面的视图函数
path('index/<int:id>/', index)

# -> views.py
def index(request,id):
print(id, type(id))
return HttpResponse('index')

3。可以自定义转化器

  1. 自定义转换器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PYTHON
# -> path_converts.py
class MonthConverter:
regex='\d{2}' # 属性名必须为regex

def to_python(self, value):
return int(value)

def to_url(self, value):
return value # 匹配的regex是两个数字,返回的结果也必须是两个数字

# -> urls.py
from django.urls import path, register_converter
from path_converts import MonthConverter
from app01 import views

# 先注册转换器
register_converter(MonthConverter, 'mon')

urlpatterns = [
path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'),
]

Django视图层

三板斧

  1. HttoResponse

    返回字符串类型

  2. Render

    返回html页面并在返回浏览器之前还可以给html文件传值

  3. redirect

    重定向

JsonResponse

  • 前后端数据交互需要使用json作为过渡,实现跨语言传输数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PYTHON
def ab_json(request):
user_dict = {'username': 'jason好帅哦,我好喜欢!', 'password': '123', 'hobby': 'girl'}
l = [111,222,333,444,555]

# import json # 先转成json格式字符串
# json_str = json.dumps(user_dict, ensure_ascii=False) # 禁止内部转码
# return HttpResponse(json_str) # 将该字符串作为参数,返回HttpResponse对象

from django.http import JsonResponse
return JsonResponse(user_dict, json_dumps_params={"ensure_ascii": False})

# 报错:In order to allow non-dict objects to be serialized set the safe parameter to False.
# return JsonResponse(l, safe=False)
  • 指将字典打散成关键字的形式
  • 默认只能序列化字典,序列化其他需要加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 中:

  1. method 必须指定成 post
  2. enctype 必须换成 multipart/formdata
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -> form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="POST" enctype="multipart/form-data">
<p>username:<input type="text" name="username"></p>
<p>file:<input type="file" name="file"></p>
<input type="submit">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
# -> app01.views
def ab_file(request):
if request.method == 'POST':
print(request.FILES) # 获取文件数据
file_obj = request.FILES.get('file') # 文件对象
print(file_obj.name)
with open(file_obj.name,'wb') as f:
for line in file_obj.chunks(): #推荐加上chunks方法
f.write(line)
return render(request, 'form.html')

request对象方法

  • get请求携带的数据是有大小限制的,而post请求则没有限制。
1
2
3
4
5
6
PYTHON
def login(request):
# 视图函数必须要接受一个形参request
if request.method == 'POST': # 返回请求方式 并且是全大写的字符串形式 <class 'str'>
return HttpResponse("收到了 宝贝")
return render(request, 'login.html')
  • 对于get请求和post请求应该有不同的处理机制
  • post做特殊处理,而将request放置在if围绕之外,提高可阅读性
  1. request.method:返回请求方式,并且是全大写的字符串形式 <class ‘str’>
  2. request.POST:获取用户post请求提交的普通数据不包含文件
  3. request.POST.get():只获取列表最后一个元素
  4. request.POST.getlist():直接将列表取出
  5. request.GET:获取用户提交的get请求数据
  6. request.GET.get():只获取列表最后一个元素
  7. request.GET.getlist():直接将列表取出
  8. request.FILES.get():用键取出对应的文件,是以字典形式封装好的
  9. request.body:发过来的原生二进制数据
  10. request.path_info:获取url(例如/app01/ab_file/),和request.path是等效的
  11. request.get_full_path():获取完整的url及问号后面的参数

FBV与CBV

视图函数既可以是 函数 FBV(function base views),也可以是 类 CBV(class base views)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PYTHON
# FBV
def index(request):
return HttpResponse('index')
# FBV路由
path("login/", views.login)

# CBV
from django.views import View
class MyLogin(View):
def get(self,request):
return render(request, 'form.html')

def post(self,request):
return HttpResponse('post方法')
# CBV路由
url(r'^login/', views.MyLogin.as_view())

CBV源码剖析

  • 首先,不建议自己修改源码
  • 函数名/方法名,加括号执行优先级最高,例如views.MyLogin.as_view()会立刻执行as_view()方法
  • CBV与FBV在路由匹配上本质是一样的,都是路由对应函数内存地址。
  • 在没有实例的情况下使用函数,则只可能使用@staicmethod或者@classmethod
  • 在看python源码的时候,一定要时刻提醒自己面向对象属性方法查找顺序:
    • 先从对象自己找
    • 再去产生对象的类里面找
    • 之后再去父类找
  • 反射 getattr()
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
43
44
45
46
47
48
49
50
51
52
53
54
PYTHON
# 突破口在urls.py
url(r'^login/', views.MyLogin.as_view())
url(r'^login/', views.view)
# CBV与FBV在路由匹配上本质是一样的,都是路由对应函数内存地址
"""
函数名/方法名 加括号执行优先级最高
猜测:
as_view()
要么是被@staicmethod修饰的静态方法
要么是被@classmethod修饰的类方法 正确

@classonlymethod
def as_view(cls, **initkwargs):
pass
"""

@classonlymethod
def as_view(cls, **initkwargs):
"""
cls就是我们自己写的类 MyCBV
Main entry point for a request-response process.
"""
def view(request, *args, **kwargs):
self = cls(**initkwargs) # cls是我们自己写的类
# self = MyLogin(**initkwargs) 产生一个我们自己写的类的对象
return self.dispatch(request, *args, **kwargs)
"""
以后你们会经常需要看源码 但是在看python源码的时候 一定要时刻提醒自己面向对象属性方法查找顺序
先从对象自己找
再去产生对象的类里面找
之后再去父类找
...
总结:看源码只要看到了self点一个东西,一定要问你自己当前这个self到底是谁
"""
return view

# CBV的精髓
def dispatch(self, request, *args, **kwargs):
# 获取当前请求的小写格式 然后比对当前请求方式是否合法
# get请求为例
# post请求
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
"""
反射:通过字符串来操作对象的属性或者方法
handler = getattr(自己写的类产生的对象, 'get',当找不到get属性或者方法的时候就会用第三个参数)
handler = 我们自己写的类里面的get方法
"""
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs) # 调用get方法

# 要求掌握到不看源码也能够描述出CBV的内部执行流程

Django模块层

模板语法传值

传值-变量相关-逻辑相关

  • 传递函数名会自动加括号调用,但是模版语法不支持给函数传额外的参数 -
  • 传类名的时候也会自动加括号调用(实例化) ,内部将自动判断出当前的变量名是否可以加括号调用,如果可以就会自动执行。针对的是函数名和类名,不针对对象的call方法。
  • 对象被展示到html页面上,就类似于执行了打印操作也会触发str方法
  • django模版语法的取值只能采用句点符.,即可以点键也可以点索引,也可以两者混用
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
# -> views.py
def index(request):
# 模版语法可以传递的后端python数据类型
n = 123
f = 11.11
s = '我也想奔现'
b = True
l = ['小红', '姗姗', '花花', '茹茹']
t = (111,222,333,444)
d = {'username':'jason', 'age':18, 'info':'这个人有点意思'}
se = {'晶晶', '洋洋', '嘤嘤'}

def func():
print('我被执行了')
return '你的另一半在等你'

class MyClass(object):
def get_self(self):
return 'self'

@staticmethod
def get_func():
return 'func'

@classmethod
def get_class(cls):
return 'cls'

# 对象被展示到html页面上,就类似于执行了打印操作也会触发__str__方法
def __str__(self):
return '到底会不会?'

obj = MyClass()

# return render(request,'index.html',{}) # 一个个传
return render(request,'index.html',locals())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -> .html
<p>{{ n }}</p>
<p>{{ f }}</p>
<p>{{ s }}</p>
<p>{{ b }}</p>
<p>{{ l }}</p>
<p>{{ d }}</p>
<p>{{ t }}</p>
<p>{{ se }}</p>
<p>{{ func }}</p>
<p>{{ MyClass }}</p>
<p>{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_func }}</p>
<p>{{ obj.get_class }}</p>
# django模版语法的取值 是固定的格式 只能采用“句点符” .
<p>{{ d.username }}</p>
<p>{{ l.0 }}</p>
<p>{{ d.hobby.3.info }}</p>

过滤器

  • 过滤器就类似于是模版语法的内置方法 - 基本语法:数据|过滤器:参数 - 过滤器只能最多有两个参数 - 在全栈项目的时候,前端代码不一定非要在前端页面书写,也可以现在先在后端写好,然后传递给前端页面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HTML
<h1>过滤器</h1>
<p>统计长度:{{ s|length }}</p>
<p>默认值(True则展示|前的值, False则展示default值):{{ b|default:'啥也不是' }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>日期格式化:{{ current_time|date:'Y-m-d H:i:s' }}</p>
<p>切片操作(支持步长):{{ l|slice:'0:4:2' }}</p>
<p>切取字符(包含三个点):{{ info|truncatechars:9 }}</p>
<p>切取词段(不包含三个点 按照空格切):{{ egl|truncatewords:9 }}</p>
<p>切取词段(不包含三个点 按照空格切):{{ info|truncatewords:9 }}</p>
<p>移除特定的字符:{{ msg|cut:' ' }}</p>
<p>拼接操作:{{ l|join:'$' }}</p>
<p>拼接操作(加法):{{ n|add:10 }}</p>
<p>拼接操作(加法):{{ s|add:msg }}</p>
<p>转义:{{ hhh|safe }}</p>
<p>转义:{{ sss|safe }}</p>
<p>转义:{{ res }}</p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def index(request):
# 模版语法可以传递的后端python数据类型
n = 123
f = 11.11
s = '我也想奔现'
b = False
l = ['小红', '姗姗', '花花', '茹茹', '敏敏', '新新']
t = (111, 222, 333, 444)
d = {'username': 'jason', 'age': 18, 'info': '这个人有点意思', 'hobby': [111, 222, 333, {'info': 'NB'}]}
se = {'晶晶', '洋洋', '嘤嘤'}
lll = []
file_size = 123123112
import datetime
current_time = datetime.datetime.now()
info = '本 文 始 发 于 个 人 公 众 号: ...'
egl = 'my name is jason my age is 18 and i am from China'
msg = 'I Love You And You?'
hhh = '<h1>敏敏</h1>'
sss = '<script>alert(123)</script>'
# 后端转义字符串
from django.utils.safestring import mark_safe
res = mark_safe('<h1>新新</h1>')
return render(request, 'index.html', locals()) # 返回所有变量的命名空间

标签

for循环

1
2
3
4
5
6
{#for循环#}
{% for foo in l %}
<p>{{ forloop }}</p>
{# {'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 6, 'revcounter0': 5, 'first': True, 'last': False}#}
<p>{{ foo }}</p>
{% endfor %}

if判断

1
2
3
4
5
6
7
8
{#if判断#}
{% if b %}
<p>baby</p>
{% elif s %}
<p>都来把</p>
{% else %}
<p>老baby</p>
{% endif %}

for与if混合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
for与if混合使用
{#for与if混合使用#}
{% for foo in lll %}
{% if forloop.first %}
<p>这是我的第一次</p>
{% elif forloop.last %}
<p>这是最后一次啊</p>
{% else %}
<p>{{ foo }}</p>
{% endif %}
{% empty %}
<p>for循环的可迭代对象内部没有元素,根本没法循环</p>
{% endfor %}

处理字典其他方法

1
2
3
4
5
6
7
8
9
10
{#处理字典其他方法#}
{% for foo in d.keys %}
<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.values %}
<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.items %}
<p>{{ foo }}</p>
{% endfor %}

with起别名

1
2
3
4
5
{#with起别名#}
{% with d.hobby.3.info as nb %}
<p>{{ nb }}</p>
<p>{{ d.hobby.3.info }}</p>
{% endwith %}

自定义过滤器、标签、Inclusion_tag

  1. 在应用下创建一个名字必须叫 templatetags 文件夹
  2. 在该文件夹内创建任意名称的py文件 eg: mytag.py
  3. 在该py文件内必须先书写下面两行代码
1
2
from django import template
register = template.Library()
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
# 1. 自定义过滤器(参数只能两个)
@register.filter(name='baby')
def my_sum(v1, v2):
return v1 + v2
# 使用
{% load mytag %} # 只需要load一次!
<p>{{ n|baby:666 }}</p>

# 2. 自定义标签(参数可以有多个),类似于自定义函数
@register.simple_tag(name='plus')
def index(a,b,c,d):
return '%s-%s-%s-%s'%(a,b,c,d)
# 使用,标签多个参数彼此之间空格隔开
<p>{% plus 'jason' 123 123 123 %}</p>

# 3. 自定义inclusion_tag
"""
内部原理
先定义一个方法
在页面上调用该方法,并且可以传值
该方法会生成一些数据然后传递给一个指定的html页面
之后将渲染好的结果放到调用的位置
"""
@register.inclusion_tag('left_menu.html')
def left(n):
data = ['第{}项'.format(i) for i in range(n)]
# 第一种
# return {'data':data} # 将data传递给left_menu.html
# 第二种
return locals() # 将data传递给left_menu.html
# 使用
{% left 5 %}

模板的继承

当html页面某一个地方的页面需要传参数才能够动态的渲染出来,并且在多个页面上都需要使用到该局部 那么就考虑将该局部页面做成inclusion_tag形式。例如:bbs ### 模版的继承 - 有些网站不同url的页面整体类似,只是某一些局部在做变化。例如:导航栏不变,主干部分在变化。

1
2
3
4
5
6
7
8
9
10
11
12
# 先设计好父类模版页面
{% extends 'home.html' %}

# 在父类模板中划定可以被修改的区域
{% block name %}
模版内容
{% endblock %}

# 在子页面声明想要修改的区域内容
{% block name %}
子页面内容
{% endblock %}

中间件与Auth模块

[TOC]

Django中间件

中间件简介

  • django自带七个中间件,并且支持程序员自定义中间件
  • 只要是涉及到全局相关的功能都可以使用中间件完成
  • 全局用户身份、权限校验
    • 全局访问频率校验
  • django中间件是django的门户
    • 请求需要先经过中间件才能到达django后端
    • 响应也需要经过中间件才能发送出去

自定义中间件

  • 中间件可以在项目名或者应用名下任意名称的文件夹中定义

  • 自定义中间件类必须继承于MiddlewareMixin类

  • 需要将类的路径以字符串的形式注册到配置文件中才能生效

  • 中间件将暴露五个可以自定义的方法,按需求重写

    • process_request(self, request)

      1. 请求来的时候需要经过每一个中间件里面的process_request方法

      2. 顺序是按照配置文件中注册的中间件从上往下的顺序依次执行

      3. 如果中间件里面没有定义该方法,则跳过

      4. 如果该方法返回了HttpResponse对象,则请求将不再继续往后执行,而是从同级别的process_reponse向上原路返回

        flask中即使中途返回数据,但依然经过所有中间件

      process_request方法可以用来做全局相关的所有限制功能

    • process_response(self, request, response)

      1. 响应走的时候需要经过每一个中间件里面的process_response方法
      2. 顺序是按照配置文件中注册了的中间件从下往上依次经过
      3. 如果中间件里面没有定义该方法,则跳过
      4. 该方法必须返回一个HttpResponse对象,默认返回的就是形参response
    • process_view(self, request, view_func, view_args, view_kwargs)

      1. 触发时间在路由匹配成功之后,执行视图函数之前
      2. 顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
    • process_template_response(self, request, response)

      1. 返回的HttpResponse对象有render属性的时候才会触发
      2. 顺序是按照配置文件中注册了的中间件从下往上依次经过
    • process_exception(self, request, exception)

      1. 当视图函数中出现异常的情况下触发
      2. 顺序是按照配置文件中注册了的中间件从下往上依次经过
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
PYTHON
from django.utils.deprecation import MiddlewareMixin


class MD1(MiddlewareMixin):
def process_request(self, request):
print("MD1里面的 process_request")

def process_response(self, request, response):
print("MD1里面的 process_response")
return response

def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__)

def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
return HttpResponse(str(exception))

def process_template_response(self, request, response):
print("MD1 中的process_template_response")
return response

csrf跨站请求伪造

使用cookie完成csrf校验

  • 为了确保请求来源是本服务器发送出去的
  • 为每一次发出去的页面提供唯一标识,如果校验标识失败则直接拒绝(403 forbbiden)
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
JS
<script src="get请求url"></script>

// Ajax CSRF in cookies start
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
// Ajax CSRF in cookies end
  1. 如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值。
  2. 如果你的视图渲染的HTML文件中没有包含 csrf_token ,Django可能不会设置CSRFtoken的cookie。
  3. 这个时候需要使用ensure_csrf_cookie()装饰器强制设置Cookie。
1
2
3
4
5
6
PYTHON
django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def login(request):
pass

csrf相关装饰器

  • @csrf_protect:需要校验
  • @csrf_exempt:忽视校验
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
PYTHON
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.utils.decorators import method_decorator

# @csrf_exempt
# @csrf_protect
def transfer(request):
if request.method == 'POST':
username = request.POST.get('username')
target_user = request.POST.get('target_user')
money = request.POST.get('money')
print('%s给%s转了%s元'%(username,target_user,money))
return render(request,'transfer.html')


from django.views import View

# @method_decorator(csrf_protect, name='post') # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt, name='post') # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt, name='dispatch')
class MyCsrfToken(View):
# @method_decorator(csrf_protect) # 针对csrf_protect 第三种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第三种方式可以
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfToken, self).dispatch(request, *args, **kwargs)

def get(self,request):
return HttpResponse('get')

# @method_decorator(csrf_protect) # 针对csrf_protect 第一种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第一种方式不可以
def post(self,request):
return HttpResponse('post')

跨域请求

  • 对于前后端分离的报错:has been blocked by CORS policy: NO 'Access-Control-Allow-Origin' header is present on the reauested resource.

同源策略

  • 浏览器的同源策略:浏览器发现ip或端口是不一样的,就会认为存在风险,会进行拦截。
  • 推荐的方法是使用Nginx进行反向代理。

添加响应头解决跨域

  • 解决思路就是告诉浏览器:不进行拦截。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PYTHON
# -> app01/middlewares/Mycors.py
from django.utils.deprecation import MiddlewareMixin

class MyCors(MiddlewareMixin):
def process_response(self, request, response):
response["Access-Control-Allow-Origin"] = "*"
if request.method == "OPTIONS":
response["Access-Control-Allow-Headers"] = "Content-Type"
response["Access-Control-Allow-Methods"] = "DELETE, PUT, POST"
return response

# -> DjangoProject/settings.py
MIDDLEWARE = [
...
'app01.middlewares.Mycors.MyCors',
]

配置文件与反射

importlib模块

使用importlib,以字符串形式导入模块

1
2
3
4
5
6
7
8
PYTHON
from myfile import b
print(b)

import importlib
res = 'myfile.b'
ret = importlib.import_module(res) # 该方法最小只能到py文件名
print(ret)

实例展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PYTHON
import settings
import importlib

def send_all(content):
for path_str in settings.NOTIFY_LIST: # 'notify.email.Email'
module_path,class_name = path_str.rsplit('.', maxsplit=1)
# module_path = 'notify.email' class_name = 'Email'
# 1 利用字符串导入模块
module = importlib.import_module(module_path) # from notify import email
# 2 利用反射获取类名
cls = getattr(module, class_name) # Email、QQ、Wechat
# 3 生成类的对象
obj = cls()
# 4 利用鸭子类型直接调用send方法
obj.send(content)

Auth模块

使用auth模块要用就用全套

Django的admin路由对应的就是就是auth_user表,必须是管理员用户才能进入。

创建超级用户(管理员):python3 manage.py createsuperuser

常用方法

  • auth.authenticate(request, username, password):根据用户名在表中查询加密后的密码,并将请求中的密码加密比对是否正确。返回用户对象,不匹配则返回None

  • auth.login(request, user_obj):保存用户状态,建立session。执行该方法后,可以在任何地方通过request.user获取到当前登陆的用户对象

  • request.user

  • request.user.is_authenticated():判断当前用户是否登陆

  • ```
    @login_required

    1
    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
2
3
4
5
6
7
8
9
10
11

### 扩展auth_user表

- 面向对象继承,自由增加字段,对象可以直接访问字段
- 如果继承了`AbstractUser`,那么在执行数据库迁移命令的时候`auth_user`表就不会再创建出来了,而是替换成定义的子类
- auth模块的功能还是照常使用
- 前提:
1. 在继承之前没有执行过数据库迁移命令、auth_user没有被创建
2. 继承的类里面不要覆盖`AbstractUser`里面的字段名,只扩展额外字段
3. 需要在配置文件中添加`AUTH_USER_MODEL = 'app01.UserInfo'`

PYTHON
class UserInfo(AbstractUser):
phone = models.BigIntegerField()

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

## BBS实战

### 表设计

1. 用户表(AbstractUser)

- phone:电话号码
- avatar:用户头像
- create_time:创建时间
- 一对一:个人站点表

2. 个人站点表

- site_name:站点名称
- site_title:站点标题
- site_theme:站点样式

3. 文章标签表

- name:标签名
- 一对多:个人站点

4. 文章分类表

- name:分类名
- 一对多:个人站点

5. 文章表

- title:文章标题

- desc:文章简介

- content:文章内容

- create_time:发布时间

数据库字段设计优化:虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率

- up_num:点赞数

- down_num:点踩数

- comment_num:评论数

- 一对多个人站点

- 多对多文章标签

- 一对多文章分类

6. 点赞点踩表(记录哪个用户给哪篇文章点了赞还是点了踩)

- user:ForeignKey(to=”User”)
- article:ForeignKey(to=”Article”)
- is_up:BooleanField()

7. 文章评论表

- user:ForeignKey(to=”User”)

- article:ForeignKey(to=”Article”)

- content:CharField()

- comment_time:DateField()

- parent:ForeignKey(to=”self/Comment”,null=True)

根评论子评论,评论当前发布的内容,根评论与子评论是一对多的关系

# 数据交互



## 前后端交互

### 发送请求的方式

1. 浏览器地址栏直接输入url回车 (GET)
2. a标签href属性 (GET)
3. form表单 (GET/POST)
4. ajax (GET/POST)

## Ajax

### 简介

- Ajax 最大的优点是异步提交,局部刷新。
- AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
- Ajax 在这里只学习jQuery封装之后的版本,而不是原生的。所以在前端页面使用ajax的时候需要确保导入了jQuery。
- 并不只有jQuery能够实现ajax,其他的框架也可以,原理是一样的。

demo:

HTML

Title
<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
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

- 针对后端如果是用HttpResponse返回的数据,回调函数不会自动反序列化
- 解决方式1:在前端JSON.parse()
- 解决方式2:在ajax里面配置一个参数
- 如果后端直接用的是JsonResponse返回的数据,回调函数会自动反序列化

### 编码格式(Content-Type)

前后端传输数据的编码格式:

1. urlencoded(例如:username=jason&password=123)
2. formdata
3. json
4. ……

在form表单中:

- 默认的数据编码格式是urlencoded,推荐都写成formdata。
- django针对符合urlencoded编码格式的数据都解析封装到request.POST中。
- 编码格式设定为formdata时,普通的键值对还是解析到request.POST中,文件将解析到request.FILES中(文件在urlencoded格式下只会解析文件名)。
- form表单是没有办法发送json格式数据的。

Ajax:

- 默认的编码格式也是urlencoded
- 对符合urlencoded编码格式的数据都解析封装到request.POST中

### Ajax发送json

前后端传输数据的时候一定要确保编码格式跟数据真正的格式是一致的。

ajax传输json格式数据注意点:

1. contentType参数指定成:application/json
2. 数据是原始的json格式数据
3. django后端不会封装处理json格式数据,需要自己在request.body获取二进制数据并处理

HTML
1
2
3
4
5

- request.is_ajax():判断当前请求是否是ajax请求,返回布尔值
- request.body:获取请求体,这里是未经处理的二进制数据
- 处理json文件:json.loads(request.body)

PYTHON def ab_ajax(request): if request.method == 'POST': print(request.is_ajax()) # 判断当前请求是否是ajax请求 print(request.body) # 获取请求体 json_bytes = request.body
    # 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
2
3
4
5
6
7

### Ajax发送文件

1. ajax发送文件需要借助于js内置对象FormData
2. 需要指定两个关键性的参数 contentType:false processData:false
3. django能直接识别formdata对象,并将内部的普通键值对自动解析封装到request.POST中,文件数据自动解析封装到request.FILES中

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
2
3
4
5
6

### Django自带的序列化组件

- 使用`django.core.serializers.serialize('指定数据格式', 数据本体)`快速完成序列化
- 后续还有更多的序列化组件,例如:drf

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
2
3
4
5

### Ajax结合SweetAlert

- 每个元素都可以自定义属性,可以用来传值

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

## forms组件

forms组件能够完成的事情:

1. 渲染html代码
2. 校验数据
3. 展示提示信息

数据校验前端可有可无,但是后端必须要有!

因为前端的校验是弱不禁风的,可以直接修改,或者利用爬虫绕过前端页面直接朝后端提交数据。

### 定义forms组件

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
2
3
4
5
6

### 校验数据

- 可以直接在 python console 中测试
- 默认情况下数据可以多传但是绝不可能少传

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
2
3
4
5
6

### 渲染标签

- forms组件只会自动渲染获取用户输入的标签(input select radio checkbox),不能渲染提交按钮
- 设置form表单让浏览器不做校验`<form action="" method="post" novalidate>`

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
2
3

### 自定义错误信息

PYTHON class MyForm(forms.Form): # username字符串类型最小3位最大8位 username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length': '用户名最少3位', 'max_length': '用户名最大8位', 'required': "用户名不能为空" }) # password字符串类型最小3位最大8位 password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={ 'min_length': '密码最少3位', 'max_length': '密码最大8位', 'required': "密码不能为空" }) # email字段必须符合邮箱格式 xxx@xx.com email = forms.EmailField(label='邮箱', error_messages={ 'invalid': '邮箱格式不正确', 'required': "邮箱不能为空" })
1
2
3
4
5
6
7
8

### 钩子函数(HOOK)

- 钩子函数将书写在Form类中,能够让我们自定义校验规则
- 在forms组件中有两类钩子
1. 局部钩子:给单个字段增加校验规则
2. 全局钩子:给多个字段增加校验规则

PYTHON class MyForm(forms.Form): ...... # 局部钩子 def clean_username(self): # 获取用户名 username = self.cleaned_data.get('username') if '666' in username: # 1. 校验用户名中不能含有666,添加错误信息 self.add_error('username', '光喊666是不行滴~') # 将钩子函数钩去出来得特定数据放回去 return username
# 全局钩子
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
2
3
4
5
6
7
8
9
10
11

### forms字段其他参数与样式渲染

- label:字段名
- error_messages:自定义报错信息
- initial:默认值
- required:控制字段是否必填
- widget:type类型
- attrs:以字典类型定义属性
- validators:可以使用`RegexValidator`添加正则表达式校验

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
2
3

### choice字段实时更新

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
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

## Cookie与Session

目标:

- 为了保持用户登陆状态。

Cookie:

- 登陆之后,将用户的信息返回给用户浏览器,保存在本地,之后访问网站的时候携带保存在浏览器上的用户信息发送给服务端,自动验证。
- 服务端保存在客户端浏览器上的信息都可以称之为cookie。
- 它的表现形式一般都是k:v键值对(可以有多个)。

Session:

- 登陆之后,服务端保存kv键值对形式的数据。k为随机字符串,交由客户端浏览器保存,v为用户信息。
- 数据保存在服务端,并且表现形式一般也是k:v键值对。
- Session是基于Cookie工作的(包括其他保存用户操作的状态都需要使用到cookie)。

Token:

- 为了减轻Session给服务端带来的压力:随着连接数增多,服务端保存的数据量越大。
- 登陆之后,将用户信息进行公司内独有的加密处理,并将多段数据拼接(jwt认证),整体返回给浏览器保存。
- 浏览器之后访问的时候带着密文,服务端自动解密并比对。

### Cookie操作

- 虽然cookie是服务端要求客户端需要保存内容,但是客户端可以选择拒绝保存,如果禁止,那么只要是需要记录用户状态的网站功能都无法使用了。
- 如果想要操作cookie,就不得不使用HttpResponse对象。

Cookie方法:

1. `HttpResponseObject.set_cookie(key,value)`:设置cookie

2. `request.COOKIES.get(key)`:获取cookie

3. ```
HttpResponseObject.set_cookie(key, value, max_age, expires)

:在设置cookie的时候可以添加一个超时时间

  • max_age、expires 都是设置超时时间的,并且都是以秒为单位,针对IE浏览器需要使用expires。
  1. HttpResponseObject.delete_cookie(key):清除Cookie
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
PYTHON
# 校验用户是否登陆的装饰器
def login_auth(func):
def inner(request, *args, **kwargs):
target_url = request.get_full_path() # 能够获取到用户上一次想要访问的url
if request.COOKIES.get('username'):
return func(request, *args, **kwargs)
return redirect('/login/?next=%s' % target_url)
return inner


def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
# 获取用户上一次想要访问的url
target_url = request.GET.get('next') # 这个结果可能是None
if target_url:
obj = redirect(target_url)
else:
obj = redirect('/home/')
# 让浏览器记录cookie数据,保存用户登陆状态,超时时间3秒到期
obj.set_cookie('username', 'jason666', max_age=3)
# 跳转到一个需要用户登陆之后才能看的页面
return obj
return render(request, 'login.html')


@login_auth
def home(request):
return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")


@login_auth
def logout(request):
obj = redirect('/login/')
obj.delete_cookie('username')
return obj

对装饰器的详细解释: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方法:

  1. request.session['key'] = value:设置session

  2. request.session.get('key'):获取session

  3. ```
    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的key

    PYTHON
    def set_session(request):
    request.session[‘hobby’] = ‘girl’
    request.session.set_expiry(5)
    “””

    1. django内部自动生成一个随机字符串
    2. django内部自动将随机字符串和对应的数据存储到django_session表中
      2.1 先在内存中产生操作数据的缓存
      2.2 在响应结果经过django中间件时才真正地操作数据库
       django.contrib.sessions.middleware.SessionMiddleware
      
    3. 将产生的随机字符串返回给客户端浏览器保存
      “””
      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
2
3

#### 利用session实现登陆验证

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
2
3
4
5

### CBV添加装饰器

#### 加在CBV的get或post方法上

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
2
3
4
5

#### 加在dispatch方法上

因为CBV中首先执行的就是dispatch方法,所以直接作用于当前类里面的所有的方法

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
2
3
4
5
6
7

#### 加在视图类上

如果get方法和post方法都需要登录校验的话就写两个装饰器。

但method_decorator必须传 name 关键字参数

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/")

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment