Django 框架
Django
HTTP协议:
Socket和Tcp协议关系:
-
socket本身并不是协议而是一个调用的接口,socket的出现使我们更方便的使用tcp协议,如socket的基本接口,listen send recv
HTTP协议概述:
-
HTTP存在应用层的超文本传输协议,协议规定了客户端和服务器之间的通信标准,采用的是请求和响应模型,客户端发送一个请求报文,服务端进行响应
-
HTTP优点:无连接:请求和响应,无状态:访问速度快,通信标准
-
请求格式和响应格式:
HTTP工作原理(重要):
-
第一步:客户端连接web服务端
-
客户端连接服务端,会建立一个TCP套接字连接,(套接字就是socket,客户端通常是浏览器)
-
-
第二步:发送http请求
-
通过TCP套接字,客户端向web服务端发送一个文本的请求报文,一个请求报文由请求行,请求头部,和请求数据组成
-
-
第三步:服务器接收请求并返回HTTP响应
-
响应就是服务端分析发送过来的请求,通过解析请求定位到资源,服务器将资源写到tcp套接字中,返回给客户端
-
-
第四步:释放tcp连接
-
如果connection模式为close时,服务端主动关闭tcp连接,客户端被动关闭连接,释放tcp,若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
-
-
第五步:客户端浏览器解析HTML内容
-
客户端浏览器首先解释状态行,查看请求的状态码是否成功,然后解析每一个响应头,客户端浏览器读取响应数据的HTML,在根据HTML语法在浏览器窗口中进行展示
-
浏览器地址栏输入URL,流程:
-
浏览器向DNS服务器请求解析该URL中的域名对应的IP地址
-
解析出IP地址,根据IP地址和默认端口,和服务器建立TCP连接
-
浏览器发送HTTP请求(包含url后面对应的文件路径),该请求报文由TCP第三次握手发送给服务器
-
服务器对浏览器做出相应,将对应的html返回给浏览器
-
释放TCP连接
-
浏览器将该html文本进行展示
HTTP 请求方法
-
HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源:
-
GET:
- 向指定资源发出显示请求,使用GET方法只用在读取数据,
-
POST:
- 向指定资源提交数据,请求服务器进行处理(如提交表单或者上传文件),数据被包含在请求文本中
-
HEAD:
-
与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据
-
-
其余请求:
-
put 向指定资源位置上传其最新内容
-
delete 请求服务器删除Request-URI所标识的资源。
-
trace 回显服务器收到的请求,主要用于测试或诊断
-
options 使服务器传回该资源所有http请求方法
-
connect HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
-
-
请求注意事项:
-
方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)
-
HTTP 状态码
-
1xx消息——请求已被服务器接收,继续处理
-
2xx成功——请求已成功被服务器接收、理解、并接受
-
3xx重定向——需要后续操作才能完成这一请求
-
4xx请求错误——请求含有词法错误或者无法被执行 404:没有内容 403:没有权限
-
5xx服务器错误——服务器在处理某个正确请求时发生错误
URL
-
超文本传输协议,统一资源定位获取五个基本元素,
https://www.sogou.com/web?query=新闻&_asf=www.sogou.com&_ast=&w=0 # http 传送协议 # // 层级URL表示符号,固定格式 # www.sogou.com 域名,服务器和:端口号 #/ 区分每个路径的的目录 #/web 页面路径 #?query=新闻 GET模式查询的窗口参数(?字符为起点,每个参数以&隔开,再以=分开参数名称和数据) # 锚点 请求(浏览器发给服务器的数据 request0 请求方法,路径,协议版本\r\n #请求行 k1:v1\r\n k2:v2\r\n #请求头 \r\n 请求数据 #get请求美哦与请求体 响应(服务端返回给浏览器的数据 respons) 格式: "协议版本" 状态码 状态描述 \r\n k1:v1\r\n k2:v2\r\n \r\n 响应数据(响应体)"html文本"
web框架
-
web框架的本质都是一个socket服务端,而用户浏览器就是一个socket客户端部,这样就实现了web框架
web框架的功能:
-
使用socket收发消息 (wsgiref wsgi模块也可以收发消息,uwsgi线上使用)
-
根据不同路径返回不同的内容
-
返回动态的数据(字符串的替换 模板的渲染 jinja2)
分类:
-
Django 2 3
-
Flask 2
-
tornado 1 2 3
自己写web框架:
-
框架示例:
import socket import time sk = socket.socket() # 创建一个socket对象 sk.bind(('127.0.0.1',667)) # 绑定ip和端口 sk.listen() # 监听 def index(url): with open('index.html', 'rb') as f: #读取index标签 return f.read() def home(url): ret = '欢迎回家! - {}'.format(url) return ret.encode('utf-8') def help_me(url): ret = '再等30年,你又是条好汉! - {}'.format(url) return ret.encode('utf-8') def timer(url): now = time.time() with open('time.html','r',encoding='utf-8') as f: ret = f.read() ret = ret.replace('@@time@@',str(now)) #将获取的时间在time中替换展示在页面上 return ret.encode('utf-8') list1 = [ ('/index', index), ('/home', home), ('/help_me', help_me), ('/time', timer),] while True: conn, addr = sk.accept() # 等待连接 data = conn.recv(1024) url = data.decode('utf-8').split()[1] func = None for i in list1: if url == i[0]: func = i[1] break if func: ret = func(url) else: ret = '被查办了!'.encode('utf-8') conn.send(b'HTTP/1.1 200 OK\r\ncontent-type: text/html; charset=utf-8\r\n\r\n') conn.send(ret) conn.close() # index页面: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1 style="color: forestgreen"> 欢迎光临! </h1> </body> </html> # time页面: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>当前时间是:@@time@@ </h1> </body> </html>
Django安装简单使用
安装Django:
-
命令行方法:
-
使用django必须是专业版
-
pip3 install django==1.11.23 -i https://pypi.tuna.tsinghua.edu.cn/simple (清华源速度快)
-
-
查看是否安装成功:
-
查看此目录是否有django-admin.exe
-
D:\Program Files (x86)\python3.6.8\Scripts
-
创建项目:
-
命令行创建:
- django-admin startproject 项目名称
-
pycharm创建:
-
flie _ new_project _ django _ 项目路径 选解释器
-
-
创建后的项目目录:
xiangmu/ ├── idea # pycharm环境 ├── manage.py # 管理文件 ├─— templates # html css样式目录 └── xiangmu # 项目目录 ├── __init__.py ├── settings.py # 配置 ├── urls.py # 路由 --> URL和函数的对应关系 └── wsgi.py # runserver命令就使用wsgiref模块做简单的web server settings.py配置文件: ALLOWED_HOSTS = ['*'] #设置可以访问的主机,*=所有 TEMPLATES-->DIRS #关联html css文件位置 urls.py URL路径地址:
启动项目:
-
命令行:
#进入python目录进行启动 python manage.py runserver 127.0.0.1:8000 python manage.py runserver 127.0.0.1:80 python manage.py runserver 0.0.0.0:80
-
pycharm:
-
pycharm 点绿三角, 不要右键运行文件
-
Django项目创建步骤:
-
下载
命令行:
pip install django==1.11.23
pip install django==1.11.23 -i 源的地址
pycharm
file——》 settings ——》解释器 ——》 点+号 ——》 输入django ——》 选择版本 ——》下载
-
创建项目
命令行:
切换到存项目的目录下
django-admin startproject 项目名
pycharm:
file ——》 new project ——》 django ——》 输入项目的路径 ——》 选择解释器 ——》 写一个APP的名字 ——》 create
-
启动项目
命令行:
切换进去到项目的根目录 manage.py
python manage.py runserver # 127.0.0.1:8000
python manage.py runserver 80 # 127.0.0.1:80
python manage.py runserver 0.0.0.0:80 # 0.0.0.0:80
pycharm:
点绿三角 启动 确定是django项目
可配置ip和端口
-
配置settings
静态文件
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR,’static‘),
os.path.join(BASE_DIR,’static1‘),
]
中间件
csrf 中间件注释掉 可以提交POST请求
INSTALLED_APPS app相关
数据库
模板 DIRS
-
APP
创建app
命令行:
python manage.py startapp app名称
pycharm:
tools ——> run manage.py task ——> startapp app名称
注册app
INSTALLED_APPS = [ # 'app01', 'app01.apps.App01Config', ]
-
urls.py
urlpatterns = [ url(r'^index/', views.index), url(r'^home/', views.home), url(r'^login/', views.login), ]
-
views.py
from django.shortcuts import HttpResponse, render, redirect def index(request,): return HttpResponse()
HttpResponse('字符串') 返回字符串
render(request,'html文件名') 返回一个html页面
redirect(’重定向的地址‘) 重定向
-
form表单
-
form标签的属性 action ='' 提交的地址 method=’post‘ 请求方式 novalidate 不校验
-
input标签要有name 有些需要value
-
有一个类型为submit的input或者 button
-
-
get 和post的分别
get 获取一个页面
没有请求体
?k1=v1&k2=v2
django 中获取 request.GET request.GET['k1'] request.GET.get('k1','xxxx')
post 提交数据 数据隐藏
django 中获取 request.POST request.POST['k1'] request.POST .get('k1','xxxx')
orm-增删改查:
-
__str__和__repr__区别
:有时候我们想让屏幕打印的结果不是对象的内存地址,而是它的值或者其他可以自定义的东西,以便更直观地显示对象内容,可以通过在该对象的类中创建或修改__str__()或__repr__()方法来实现(显示对应方法的返回值) # 两种触发方式: 使用print()时 使用%s和f'{}'拼接对象时 使用str(x)转换对象x时 在上述三种场景中,会优先调用对象的__str__()方法;若没有,就调用__repr__()方法;若再没有,则显示其内存地址。 # 特别地,对于下面两种场景: 用%r进行字符串拼接时 用repr(x)转换对象x时 则会调用这个对象的__repr__()方法;若没有,则不再看其是否有__str__()方法,而是显示其内存地址
-
创建表数据
from django.db import models class Publisher(models.Model): # Publisher:表名,继承models.Model类 pid = models.AutoField(primary_key=True) # 自增、主键 name = models.CharField(max_length=32,unique=True) def __str__(self): return "{}{}".format(self.pid,self.name) # 返回给调用者内容
-
查询展示数据:
#查询所有数据 all_publisher = models.Publisher.objects.all() #将数据返回到前端 return render(request,'publisher_list.html',{'all_publisher':all_publisher}) #展示数据 from django.shortcuts import render,HttpResponse,redirect from app import models #从数据库中展示数据 #从数据库中查询出所有的出版社 #从数据库中查询的数据展示到前端页面 def publisher_list(request): all_publisher = models.Publisher.objects.all().order_by('pid') return render(request,'publisher_list.html',{'all_publisher':all_publisher})
-
模板的语法:
{{ all_publishers }} 变量 {% for i in all_publishers %} #for循环 {{ forloop.counter }} #循环打印的内容 {{ i }} {% endfor %} #闭合
-
新增数据:
# 方式一:通过create将数据插入到数据库中(推荐) ret = models.Publisher.objects.create(name=pub_name) return redirect("/publisher_list") #方式二:先创建对象在save进行保存 obj = models.Publisher(name=pub_name) obj.save() #新增出版社 def publisher_add(request): pub_name,error='','' if request.method == "POST": #判断前端是POST类型还是GET类型 pub_name = request.POST.get('pub_name') #通过POST获取前端的出版社名称 if not pub_name: error = "输入不能为空" elif models.Publisher.objects.filter(name=pub_name): error = "数据已经存在" else: #通过create将数据插入到数据库中(推荐) models.Publisher.objects.create(name=pub_name) return redirect("/publisher_list") return render(request, 'publisher_add.html', {'pub_name': pub_name, 'error': error})
-
删除数据
pk = request.GET.get('pk') query = models.Publisher.objects.filter(pk=pk) # 对象列表 query.delete() # 通过queryset 删除 query[0].delete() # 通过单独的对象 删除 # 删除数据 def publisher_del(request): pk_id = request.GET.get('pk') # 获取前端URL数据 query = models.Publisher.objects.filter(pk=pk_id) if not query: return HttpResponse("要删除的数据不存在") query.delete() # 参数有多个删除多个,query[0].delete()#只删除一个 return redirect("/publisher_list")
-
编辑数据
obj = models.Publisher.objects.filter(pk=pk).first() # 对象列表中第一个对象 obj.name = pub_name # 内存中修改 obj.save() # 提交 #编辑数据 def publisher_edit(request): error="" pk_id = request.GET.get("pk") #url地址携带的参数,first()查出多个只取一个,没有不报错 obj = models.Publisher.objects.filter(pk=pk_id).first() if not obj: return HttpResponse("编辑的对象不存在") if request.method == "POST": # 获取新提交的数据,编辑原始的对象 pub_name = request.POST.get('pub_name') if not pub_name: error = "输入不能为空" elif models.Publisher.objects.filter(name=pub_name): error = "数据已经存在" else: obj.name = pub_name obj.save() return redirect("/publisher_list") return render(request,'publisher_edit.html',{'obj':obj,'error':error})
ORM一对多关系:
外键的设计
# ForeignKey 添加的外键('Publisher' 添加字符串通过反射进行查找)
on_delete=models.set(1) 唯一
default=11,on_delete=models.DEFERRED 默认值
null=True on_delete=models.SET_NULL 设置字段可有为空
on_delete=models.DO_NOTHING 什么操作都不做
# on_delete 2.0 必填 ,关联删除后的选项
class Book(models.Model):
id = models.AutoField(primary_key=True) # 自增、主键
title = models.CharField(max_length=32)
pid = models.ForeignKey('Publisher', on_delete=models.CASCADE) #默认关联主键
增删改查
-
查询:
all_books = models.Book.objects.all() print(all_books) for book in all_books: print(book) print(book.pk) print(book.title) print(book.pub,type(book.pub)) # 一对多关联的是对象 print(book.pub_id,type(book.pub_id)) # 所关联的对象的pk print('*' * 32)
-
新增
#pub=models.Publisher.objects.get(pk=pub_id) 只能等于一个对象 models.Book.objects.create(title=title,pub=models.Publisher.objects.get(pk=pub_id)) #第二种方法,pub外键必须等于一个对象,但是都要转换成id,所以第二种方法直接添加ID也可以 models.Book.objects.create(title=title, pub_id=pub_id)
-
删除
pk = request.GET.get('pk') models.Book.objects.filter(pk=pk).delete()
-
编辑
book_obj.title = title book_obj.pub_id = pub_id #第一种方法 book_obj.pub = models.Publisher.objects.get(pk=pub_id) book_obj.save() #第二种方法 book_obj.pub_id = new_pub_id #将数据保存 book_obj.save()
ORM 多对多关系:
多对多关系创建
-
第一种方法:django通过ManyToManyField自动创建第三张表
# 在上下两个表中添加都可以,只是顺序不一样 class Book(models.Model): title = models.CharField(max_length=32) pub = models.ForeignKey('Publisher', on_delete=models.CASCADE) # authors = models.ManyToManyField('Author') # 描述多对多的关系 不生成字段 生成关系表 def __repr__(self): return self.title __str__ = __repr__ class Author(models.Model): id = models.AutoField(primary_key=True) # 自增、主键 name = models.CharField(max_length=32) books = models.ManyToManyField('Book') # 描述多对多的关系 不生成字段 生成关系表
-
第二种方法:自己手动创建
class Book(models.Model): title = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) class Book_Author(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) author = models.ForeignKey(Author, on_delete=models.CASCADE) date = models.DateField() #自己创建表可以添加多个字段(时间字段)
-
第三种方法:自己创建 + ManyToManyField
# 第三张表包含了第一张和第二张表的使用方法 class Book(models.Model): title = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) #第三种方法:to book表字段,through=指定多对多关系 books = models.ManyToManyField(Book, through='Book_Author') class Book_Author(models.Model): #创建关系表 book = models.ForeignKey(Book, on_delete=models.CASCADE) author = models.ForeignKey(Author, on_delete=models.CASCADE) date = models.DateField()
多对多-增删改查
-
查询:
def author_list(request): all_author = models.Author.objects.all().order_by("id") for author in all_author: print(author) print(author.name) print(author.pk) print(author.books) #多对多拿到books是关系对照表 print(author.books.all()) #通过all拿到关联的所有对象 print("*"*32) return render(request,'author_list.html',{'all_author':all_author}) # 从book表中查询author作者数据,下面是获取到author对象 book.author_set.all # 前端展示示例: <tbody> {% for author in all_author %} <tr> <td>{{ forloop.counter }}</td> <td>{{ author.pk }}</td> <td>{{ author.name }}</td> <td> {% for book in author.books.all %} 《{{ book.title }}》 {% endfor %} </td> <td> <a href="">删除</a> <a href="">编辑</a> </td> </tr> {% endfor %} </tbody>
-
新增:
books = request.POST.getlist('books') # 获取多个元素 author_obj = models.Author.objects.create(name=name) # 新建作者 author_obj.books.set(books) #【id,id】,给作者和书籍绑定关系 #新增示例: def author_add(request): if request.method == "POST": name = request.POST.get('name') books = request.POST.getlist('books') #列表方式获取书籍ID ['1'] author_obj = models.Author.objects.create(name=name) #作者名写入数据库 author_obj.books.set(books) #写入关联方式 return redirect("/author_list/") all_books = models.Book.objects.all() return render(request,'author_add.html',{'all_books':all_books})
-
删除:
def author_del(request): pk_id = request.GET.get("pk") models.Author.objects.filter(pk=pk_id).delete() return redirect('/author_list/')
-
编辑:
def author_edit(request): pk_id = request.GET.get("pk") obj = models.Author.objects.filter(pk=pk_id).first() if request.method == "POST": name = request.POST.get('name') books = request.POST.getlist('books') #列表方式获取书籍 obj.name = name #修改作者名称 obj.save() obj.books.set(books) #添加作者关联的书籍地址,每次都是删除后在添加 return redirect('/author_list//') all_books = models.Book.objects.all() return render(request,'author_edit.html',{'obj':obj,'all_books':all_books}) # 前端示例 <form action="" method="post"> <p> 作者:<input type="text" name="name" value="{{ obj.name }}"> </p> <p> 著作:<select name="books" id="" multiple> {% for book in all_books %} {% if book in obj.books.all %} <option selected value="{{ book.pk }}">{{ book.title }}</option> {% else %} <option value="{{ book.pk }}">{{ book.title }}</option> {% endif %} {% endfor %} </select> </p> <button>提交</button> </form>
模板使用:
MVC 和 MTV
-
MVC:
-
mvc是软件中一种软件架构模式,把软件系统分为三个基本部分,具有耦合性低,重用性高,生命周期成本低
- M: model 模型 和数据库交互
- V:view 视图 展示页面 HTML
- C: controller 控制器 调度 业务逻辑
-
MTV:
-
M : model 模型 操作数据库 orm、
-
T : template 模板 HTML
-
V: view 视图 业务逻辑
-
常用语法:
-
{{ 变量 }} :两个大括号 表示变量,在模板渲染的时候替换成值
-
{{% 逻辑%}} : 括号中带有两个百分号表示逻辑相关操作
变量
-
{{ 变量名. }}
- .索引 .key .属性 .方法
- 列表.索引 字典.key 类.属性 类.方法
-
示例:
# 模板中的变量是通过views render后端返回到前端,前端通过{{}}显示到页面 # 注意事项:在字典中创建跟字典方法相同key 会优先调用字典中的key > 对象的属性或方法 > 列表的索引 {{ name_list.1 }} 拿到列表第一个索引 {{ dic.name }} 通过字典的key取值 {{ dic.keys }} 字典的数据类型,keys {{ dic.values }} 字典的数据类型,values {{ p1 }} 类中方法,在类中定义str返回值,返回所有值 {{ p1.name }} 返回name属性 {{ p1.talk }} 调用类中方法,不用加括号 #views def index(request): class Person(): def __init__(self,name,age): self.name = name self.age = age def talk(self): return "我太难了" def __str__(self): return f"Person obj:{self.name}{self.age}" booll = True num = 666 string = "小窝窝" name_list = ["海洋","俊丽","小宝宝","大宝宝"] dic = { "name":"小明", "age":"18", "sex":"18", 'hobby':["台球","篮球","足球",'羽毛球']} p1 = Person("胡海洋",18) context = { 'booll':booll, 'num':num, 'string':string, 'name_list':name_list, 'dic':dic, 'p1':p1} return render(request,'index.html',context) # templates <body> {{ booll }}<br> {{ num }}<br> {{ string }}<br> {{ name_list.1 }}<br> {{ dic.values }}<br> {{ p1}} {{ p1.name }} {{ p1.talk }} </body>
过滤器-Filters(管道)
-
修改变量的显示结果
-
语法
-
{{ value|filter_name }} {{ value|filter_name:参数 }}
-
-
default: 提供默认值
# 传过来的变量不存在或者为空 使用默认值,:后面不可以有空格!!! {{ qq|default:'默认值自己设置' }} # 后端变量如果无效可以增加提示 注:TEMPLATES的OPTIONS可以增加一个选项:string_if_invalid:'找不到',可以替代default的的作用。
-
列表切片
{{ name_list|slice:'::-1' }} 从后往前切片 {{ name_list|slice:'0:2' }} 切两个值
-
filesizeformat 文件大小格式化
-
将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)
{{ filesize|filesizeformat }} 后端:'filesize':1*1024*1024
-
-
**add **
数字的加法 字符串的拼接 列表的合并 {{ num|add:2 }} # 给数字进行加2,如果num为字符串类型也会进行相加 {{ string|add:"haha"}} # 通过add给string字符串进行拼接 {{ name_list|add:name_list}} # 列表拼接 {{ num|add:-2 }} 减法 {% widthratio num 1 4%} 乘法 {% widthratio num 2 1%} 除法 # 1放在中间时乘法,放在后面是除法
-
其他内置过滤器方法:
-
字符串
- 变小:{{ string|lower}}
- 变大:{{ string|upper}}
- 计数:{{ string|length}}
-
列表
-
取第一个元素: {{ name_list|first}}
-
取最后一个元素 : {{ name_list|last}}
-
字符串拼接列表: {{ name_list|join:'**'}}
-
按字符串长度取值: {{ string|truncatechars:'10'}} ,truncatewords按照单词区分
-
-
-
日期格式化:
{{ now|date:'Y-m-d H:i:s' }} 年月日时分秒 {{ now|date}} 设置settings后使用方法 {{ now|time}} # 后端格式 'now':datetime.datetime.now() # settings 配置 USE_L10N = False DATETIME_FORMAT = 'Y-m-d H:i:s' DATE_FORMAT = 'Y-m-d' TIME_FORMAT = 'H:i:s'
-
safe 告诉django不需要“”转义:
{{ js|safe }} {{ a|safe }} # 后端js,字符串在前端还是普通字符串,想要展示效果,添加safe不需要转义 'js':''' <script> for (var i = 0; i < 5; i++) { alert('11111') } </script> ''' 'a':'<a href="http://www.baidu.com">跳转</a>'
-
PY文件中不转义方法
# 第二种方法mark_safe 在后端进行处理,前端直接调用: {{ a }} # 调用 from django.utils.safestring import mark_safe 'a':mark_safe('<a href="http://www.baidu.com">跳转</a>')
自定义过滤器
-
在app下创建一个名为templatetags的python包(文件夹)
-
在包内创建py文件 —— 》 自定义 my_yags.py
-
在py文件中写入:
from django import template register = template.Library() # register不能变
-
定义函数 + 加装饰
@register.filter # new_upper(|后面的方法) value(原本的变量) arg最多有一个(冒号就后面的参数,可默认) def new_upper(value, arg=None): # arg 最多有一个 print(arg) return value.upper() # 不返回 前端结果为None
-
在模板中使用:
{% load my_yags %} # 加载写函数定义的文件 {{ string|new_upper:dic }} # string变量,new_upper函数方法,dic后端数据,可以返回到函数arg中
for 循环
-
forloop
# for 循环格式 {% for name in name_list %} <li> {{ forloop.counter0 }} - {{ name }}</li> {% endfor %} {{ forloop.counter0 }} 字典中都已经定义好了一下参数 {{ forloop.counter }} 当前循环的从1开始的计数 {{ forloop.counter0 }} 当前循环的从0开始的计数 {{ forloop.revcounter }} 当前循环的倒叙计数(到1结束) {{ forloop.revcounter0 }} 当前循环的倒叙计数(到0结束) {{ forloop.first}} 当前循环是否是第一次循环 布尔值 {{ forloop.last}} 当前循环是否是最后一次循环 布尔值 {{ forloop.parentloop }} 当前循环父级循环的forloop(多层循环中才可以查看到)
-
empty
{% for name in name_list %} {{ name }} {% empty %} 空的数据,循环过程没有数据返回此提示 {% endfor %}
if 判断
-
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。
# 判断列表中为偶数的变颜色,if里面可以使用过滤器 <table border="1"> <tbody> {% for name in name_list2 %} <tr> {% for new in name %} {% if forloop.counter|divisibleby:2 and forloop.parentloop.counter|divisibleby:2%} <td style="background-color: yellow">{{ new }}</td> {% else %} <td>{{ new }}</td> {% endif %} {% endfor %} </tr> {% endfor %} </tbody> </table> # 多层判断 {% if p1.age < 18 %} 宝宝 {% elif p1.age == 18 %} 刚成年 {% else %} 上班 {% endif %} # 不支持算数运算 {% if 1 + 2 == 3 %} {% if 1|add:2 == 3 %} 可以这样写 # 不支持连续判断 {% if 10 >5 > 1 %}
with和csrf_token
-
with示例:
# 取别名两种方式 {% with alex=person_list.1.name age=person_list.1.age %} {{ alex }} {{ age }} {{ alex }} {% endwith %} {% with person_list.1.name as junli %} {{ junli }} {% endwith %}
-
# {% csrf_token %}标签放在form标签中,不安全报错403 # form表单中有一个隐藏的input标签(name ='csrfmiddlewaretoken'),后端通过这个值进行验证 <form action="" method="post"> {% csrf_token %} </form>
母板和继承
-
母板就是将公共部分提取出来,不重复的定义block,之后再进行集成填充block块,减少重复代码,提高效率
-
母板:
-
html页面 提取多个页面的公共部分
-
定义多个block块,需要让子页面覆盖填写
# 找到重复的页面定义bolck块 # 第一种block块,设置母版 <div class="col-sm-3 col-md-2 sidebar"> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> {% block content %} <h1>母版</h1> {% endblock %} </div> # 第二种block可以设置为参数 <ul class="nav nav-sidebar"> <li class="{% block pub_active%}{% endblock %}"> <li class="{% block book_active%}{% endblock %}"> <li class="{% block author_active%}{% endblock %}"> </ul> # 每个页面的css样式,在继承中填写link {% block css%} {% endblock %}
-
-
继承母版:
-
重写block块 ,写在block内部
{% extends 'base.html' %} # 引用母版文件 {% block content %} <h3 class="sub-header"> 出版社列表 </h3> #填写block块,将代码覆盖添加到块中 <div class="table-responsive"> {% endblock %} {% block book_active %} # 重写block会覆盖block内的内容 active {% endblock %} {% block css %} # 重写每个页面文件的样式,这样避免重复,公共样式写在母版中 <link rel="stylesheet" href=""> {% endblock %}
-
注意:
-
注意事项
-
{% extends 'base.html' %} 带上引号 不带的话会当做变量
-
{% extends 'base.html' %} 上面不要写其他内容,否则会显示出来
-
要显示的内容写在block块中
-
母板可以定义多个block 定义 css js,每个子页面继承自己的css和js样式
-
-
组件
-
组件就是将一段常用HTML代码段存放单独的html中,想调用直接引用
-
include组件示例:
#组件其实就是将代码进行拆分 #新建一部分代码如导航栏,存放在 nav.html中 #拆分的组件可以使用include在母版中,也可以使用在单独的html中: {% include ‘nav.hmtl ’ %}
静态文件相关
-
引入静态文件:
# 从配置文件中自动找到static别名 # 第一种方式 {% load static %} <link rel="stylesheet" href="{% static 'css/dashboard.css' %}"> # 第二种方式 <link rel="stylesheet" href="{% get_static_prefix %}css/dashboard.css">
定义 filter simple_tag inclusion_tag
-
在app下创建templatetags的python包文件
-
在包内创建py文件 my_tags.py
-
在py文件中写代码:
from django import template register = template.Library()
-
定义函数 + 加装饰器
-
filter 过滤器方法
@register.filter def add_arg(value,arg): # 只能接受两个 return "{}_{}".format(value,arg) {% load my_yags %} # 加载写函数定义的文件,不可以嵌套,可以在for循环if里使用 {{ string|new_upper:"haha" }}
-
simple_tag 方法:
# 可以接受多个参数 @register.simple_tag def join_str(*args, **kwargs): return '_'.join(args) + "*".join(kwargs.values()) # 调用方法 {% load my_tags %} {% join_str "v1" "v2" k3="v3" k4="v4"%}
-
inclusion_tag 方法
# my_tags中填写 inclusion_tag函数 @register.inclusion_tag('page.html') # 必须制定页面,而且是动态,要不和组件一样 def page(num): return {'num':range(1,num+1)} # 返回必须是字典交给模板,接受HTML返回的参数,返回给page # 前端页面调用 page函数 {% load my_tags %} {% page 5%} # page.heml 页面添加一些分页功能,接受page发送刚过来的参数num </li> {% for i in num %} <li><a href="#">{{ i }}</a></li> {% endfor %} <li>
视图
-
视图系统,是一个简单的python函数,接受web传过来的请求,并向web进行响应,响应可以是html,重定向,错误提示啊,无论你的视图包含什么逻辑,都需要返回响应
-
FBV: function based view
-
CBV: class based view
-
cbv示例:
from django.views import View class AddPublisher(View): def get(self,request): #处理get请求 return xx def post(self,request): #处理post请求 return xx # urls调用 url(r'^publisher_add/',views.AddPublisher.as_view() ),
-
CBV的流程
-
看这个cbv流程主要是要知道request和get是怎么执行的
-
项目启动时,运行urls.py
url(r'^publisher_add/',views.AddPublisher.as_view() ) # 执行as_view() 函数 AddPublisher.as_view() 执行 ——> view函数 # 将此函数AddPublisher传给 as_view() # view函数 def as_view(cls, **initkwargs): # cls = AddPublisher def view(request, *args, **kwargs): # reqyest 前端的请求 self = cls(**initkwargs) # AddPublisher实例化对象self if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request # 类中使用的request就是self.request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) # 调用dispatch方法 view.view_class = cls view.view_initkwargs = initkwargs update_wrapper(view, cls, updated=()) update_wrapper(view, cls.dispatch, assigned=()) return view
-
请求到来时,实行view函数:
-
实例化AddPublisher ——> self
-
self.request = request
-
执行View中self.dispatch(request, *args, **kwargs)
def dispatch(self, request, *args, **kwargs): if request.method.lower() in self.http_method_names: # 判断请求是否被准许,方法有八种 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) # 反射,判断请求是否错误 else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # 执行 handler=get加括号 # 判断请求方式是否被允许 允许 通过反射获取请求方式对应的方法 ——> handler 不允许 self.http_method_not_allowed ——> handler # 返回的是错误代码信息 执行handler 获取到响应
-
视图加装饰器
-
装饰器函数:
# 视图添加装饰器,先创建好装饰器 def timer(func): def inner(*args,**kwargs): start = time.time() ret = func(*args,*kwargs) print(f"{time.time()-start}") return ret return inner
-
FBV 函数添加装饰器直接加:
@timer def publisher_edit(request): #编辑数据 pass
-
CBV 类中方法添加装饰器:
# 第一种在方法中添加: from django.utils.decorators import method_decorator class AddPublisher(View): @method_decorator(timer) def get(self, request): # 处理get请求 print('get') return render(request, 'publisher_add.html') # 第二种在dispatch上添加,这样所有请求都带有装饰器功能: @method_decorator(timer) def dispatch(self, request, *args, **kwargs): print("dispatch 前") ret = super().dispatch(request,*args,**kwargs) print("dispatch 后") return ret # 第三种加在类上面: @method_decorator(timer,name='post') @method_decorator(timer,name='get') @method_decorator(timer,name='dispatch') class AddPublisher(View): pass
request:
-
获取请求参数:
request.method # 请求方式 GET POST request.GET # url上携带的参数 {} get() request.POST # post请求提交的参数 {} get() request.META # http头部的信息,具体信息取决于客户端和服务器,获取页面request headers request.body # 请求体的数据 post也是在body里面取值的 request.path_info # 获取路径信息 不包含IP和端口 也不包含查询参数 request.FILES # 获取上传的文件 request.scheme # 获取前端是http或者是https字符串 request.COOKIES # cookie 记录状态 键值对存储在浏览器 request.session # session 记录状态,键存储在浏览器,值存储在数据库中,更安全些 request.get_full_path() # 完整的路径信息 包含查询参数 不包含IP和端口 request.is_ajax() # 判断是否是ajax请求
-
上传文件注意事项:
# form表单的属性 enctype="multipart/form-data" # input type = 'file' # request.FILES.get('f1') class File_upload(View): def get(self,request): return render(request,'file_upload.html') def post(self,request): file_obj = request.FILES.get('f1') with open(file_obj.name,'wb') as f: for i in file_obj.chunks(): f.write(i) return HttpRespose("ok") # 前端页面: <form action="" method="post" enctype="multipart/form-data"> <p> 上传文件:<input type="file" name="f1" multiple> </p> <button>上传</button> </form>
response
-
HttpResponse('字符串') # 返回字符串
-
render(request,'模板的文件名',{}) # 返回一个完整的页面
def render(request, template_name, context=None, content_type=None, status=None, using=None): # content loader.render_to_string 这里已经将模板页面进行渲染完成 content = loader.render_to_string(template_name, context, request, using=using) # 将content内容封装成httpresponse对象 return HttpResponse(content, content_type, status)
-
redirect('要跳转的地址') # 重定向 响应头 Location: url
-
301: url后面不添加/ 302:添加/会直接永久重定向
def redirect(to, *args, **kwargs): if kwargs.pop('permanent', False): redirect_class = HttpResponsePermanentRedirect # 301 暂时重定向 else: redirect_class = HttpResponseRedirect # 302 永久重定向 return redirect_class(resolve_url(to, *args, **kwargs))
-
前后端分离-JSON:
# 第一种json传参方法: import json def get_data(request): data = {'status':0,'data':{'k1':'v1'}} return HttpResponse(json.dumps(data),content_type='application/json') # 第二种简单方法(常用,前端自动进行反序列化): def get_data(request): data = {'status':0,'data':{'k1':'v1'}} return JsonResponse(data) # 不是字典,列表的话需要添加safe=False from django.http.response import JsonResponse def get_data(request): l1 = ["1",'2','3'] return JsonResponse(l1,safe=False)
路由
-
路由的本质就是,url与调用的视图函数之间的映射表,也就是说你访问我哪个url路由执行相对应的函数
-
路由匹配的只是路径,获取的参数永远只是字符串
-
urls的基本配置
from django.conf.urls import url from django.contrib import admin from app import views urlpatterns = [ url(正则表达式, views视图,参数,别名), ]
正则表达式
-
正则规则
-
以什么开头^ 结尾$ [0-9] [a-zA-Z]
-
. 数字:\d 数字和字母:\w 0个或者多个:? 至少一个:+ 0和或者无数个:*
-
-
从上到下的进行匹配 匹配到一个不再往下进行匹配,开头不加 / ,游览器后面/自动添加
urlpatterns = [ url(r'^admin/', admin.site.urls), # r转义,^以什么开头,前面不加/ url(r'^blog/$', views.bolg), # 如果上下第一层目录相同,添加$表示结尾 url(r'^blog/[0-9]{4}/[0-9]{2}/$', views.bolgs), # 简单的动态路由可以匹配多个 ]
分组和命名分组
-
从URL上获取到的参数的类型都是字符串,获取的参数按照位置参数传递给视图函数
# urls urlpatterns = [ url(r'^blog/([0-9]{4})/([0-9]{2})/$', views.bolgs),] # views def bolgs(request,year,month): #year和month按照顺序接受分组前端返回信息 print(year) print(month) return HttpResponse("首页")
-
命名分组:获取的参数按照关键字参数传递给视图函数,视图中实参可以指定默认值
urlpatterns = [ url(r'^blog/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.bolgs), ] def bolgs(request,year,month): #year和month按照顺序接受分组前端返回信息,前后端命名相同 print(year) print(month) return HttpResponse("首页")
路由分发include:
-
分发示例:
# luyou from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), # url(r'^',include('ap.urls')), # 匹配时为空,跟之前相同都是一级目录 url(r'^app/',include('app.urls')), # 匹配app中的目录 url(r'^app01/',include('app01.urls')) # 可以匹配多个app ] # app from django.conf.urls import url from app import views urlpatterns = [ url(r'^blog/$', views.bolg), #$表示结尾 url(r'^blog/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.bolgs), ]
命名URL和URL反向解析
-
命令url和反向解析,解决了路径写死问题,让路径修改更加灵活
-
命名url:
urlpatterns = [ url(r'^admin/', admin.site.urls,), url(r'^publisher_list/',views.publisher_list.as_view(),name='publisher'),]
-
反向解析:
-
模板
# 模板解释方法,通过命令url中的设置的name,获取到真实路径 <a href="{% url 'publisher' %}">出版社列表 </a>
-
py文件
# py文件中使用,也是通过name获取到路径 from django.urls import reverse def AddPublisher(request): return redirect(reverse('publisher')) #redirect('publisher') 相同
-
-
分组命名URL
-
分组命令url
# url命名动态路由 urlpatterns = [ url(r'^blogs/([0-9]{4})/([0-9]{2})/$', views.bolgs,name='blogs'), url(r'^home/$', views.home), ]
-
反向解析
-
模板
# 模板中反向解析 <a href="{% url 'blogs' '2019' '12' %}">xxx</a> # views def home(request): return render(request,'home.html')
-
py文件
def home(request): return redirect('blogs','2020','10')
-
-
-
命名分组URL(了解)
-
分组命名
url(r'^blogs/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.blogs,name='blogs'),
-
反向解析
-
模板
<a href="{% url 'blogs' year='2019' month='13' %}">xxx</a>
-
py文件
from django.urls import reverse reverse('blogs',args=('2019','09')) ——> /app01/blogs/2019/09/ reverse('blogs', kwargs={'year': '2019', 'month': '12'}) ——> /app01/blogs/2019/12/
-
-
namesapce 名称空间
-
名称空间示例,解决命名分组相同问题
# app,app01 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^app/',include('app.urls',namespace='app')), url(r'^app01/',include('app01.urls',namespace='app01')), ] # app urlpatterns = [ url(r'^home/$', views.home,name='home'),] def home(request): return render(request,'home.html') # app01 urlpatterns = [ url(r'^home/$', views.home,name='home'),] def home(request): return render(request,'home1.html')
-
反向解析
# app:blogs 起了相同的name时,根据名称空间来区分 <a href="{% url 'app:blogs' year='2019' month='13' %}">xxx</a>
示例:
-
html模板
<a href="{% url 'delete' 'author' author.pk %}">删除</a>
-
urls
url(r'(\w+)_del/(\d+)/$',views.delete,name='delete')
-
views
def delete(request, table, pk): print(table, pk) # 查询到对应的数据进行删除 model_cls = getattr(models, table.capitalize(),) model_cls.objects.filter(pk=pk).delete() # 重定向 return redirect(reverse(table))
ORM
-
orm对象关系映射,对象和关系型数据库的对应关系,将程序中的对象保存到数据库中
- 根据对象类型生成表结构
- 将对象操作转为sql语句,在将sql语句查询到的结果转换为对象
- 作用:减少了开发的工作量,不用面对数据库操作
-
注意不同的django和不同版本的mysql存在兼容性
- 类 表
- 属性 字段
- 对象 数据行
-
str
# str返回的只是显示出来信息 def __str__(self): return f"{self.pid}{self.name}{self.age}{self.bith}"
admin
-
创建一个超级用户
-
python36 manage.py createsuperuser admin
-
输入用户名和密码
-
注册
-
在app下的admin.py中写
from django.contrib import admin from app01 import models admin.site.register(models.Person) #注册 Person(model中的表名)
-
-
登陆网页http://127.0.0.1:8000/admin/(找到对应的表做增删改查)
-
修改账号密码:python manage.py changepassword admin
脚本运行
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lainxi.settings")
django.setup()
from app01 import models #放在最后面引入
ORM 常用字段
-
常用字段
-
AutoField 主键
#自增的整形字段,必填参数primary_key=True,则成为数据库的主键。无该字段时,django自动创建。 #一个model不能有两个AutoField字段。 pid = models.AutoField(primary_key=True)
-
CharField 字符串
#字符类型,必须提供max_length参数。max_length表示字符的长度。 name = models.CharField(max_length=32)
-
IntegerField 整型
#-21亿到正21亿 age = models.IntegerField()
-
DecimalField 十进制小数
max_digits=5 #总长度五位 decimal_places=2 #小数位长度 999.99 price = models.DecimalField(max_digits=5,decimal_places=2,default=0)
-
DateTimeField 日期时间
auto_now和auto_now_add和default参数是互斥的,不能同时设置 # auto_now=True 每次修改时间,都会变化 bith = models.DateTimeField(auto_now=True) # auto_now_add=True #新增数据,自动添加时间 bith = models.DateTimeField(auto_now_add=True)
-
-
其他字段
BooleanField(Field) #布尔值 NullBooleanField(Field) #可以为空的布尔值 BigIntegerField(IntegerField) #存入的数字更多 BigAutoField(AutoField) #自增存入的数字更多 SmallIntegerField(IntegerField) #小整数 -32768 ~32767 FloatField(Field) #浮点数 TextField #存储大字符串多内容 EmailField(CharField) #判断字符串是否是email格式 IPAddressField(Field) #判断字符是否是IPV4格式 URLField(CharField) #判断是否是正常的url地址 UUIDField(Field) #uuid格式校验 FilePathField(Field) #存储文件路径 FileField(Field) #存储文件 ImageField(FileField) #存储图片 DateField() #日期格式 TimeField() #时间格式
-
自定义char字段
class MyCharField(models.Field): # 自定义的char类型的字段类 def __init__(self, max_length, *args, **kwargs): #init可以自定义,不写源码里面也有 self.max_length = max_length super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs) def db_type(self, connection): #限定生成数据库表的字段类型为char,长度为max_length指定的值 return 'char(%s)' % self.max_length #调用: phone = MyCharField(max_length=11)
字段参数
-
null=True 数据库该字段可以为空
-
blank=True 校验时可以为空,from表单可以不填
-
default 默认值
-
unique 唯一索引,有重复的添加不了
-
verbose_name admin页面显示的名称
-
choices=((True, '男'), (False, '女')) 可选择的参数
# 单选,通过bool进行设置,true为1 false为0,默认为男1 gender = models.BooleanField(default=True,choices=((True,'男'),(False,'女'))) gender = models.BooleanField(max_length=32,choices=((1,'男'),(2,'女'),(3,'xxx')))
-
其他参数:
-
db_column 跟数据库关联时使用,关联数据库中的列名
-
db_index=True 数据库字段创建索引
-
editable=False 不可编辑,默认为True
-
help_text admin页面显示提示信息
-
表的参数
class Person(models.Model):
pid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32,db_column='username',db_index=True)
age = models.IntegerField(null=True,blank=True,editable=False)
bith = models.DateTimeField(auto_now=True)
phone = MyCharField(max_length=11,null=True,blank=True)
gender = models.BooleanField(default=True,choices=((True,'男'),(False,'女')))
class Meta:
db_table = "person" #数据库中生成表名称 默认app名称+下划线+类名
verbose_name = '个人信息' # admin中显示的表名称
verbose_name_plural = '所有用户信息' # admin中导航栏修改
index_together = [
("name", "age"), # 联合索引,两个存在的字段
]
unique_together = (("name", "age"),) # 联合唯一索引,不可重复
必会13条查询语句:
-
在文件中查询引入配置:
import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "untitled.settings") django.setup() from app01 import models
-
查询语句
# all 获取表中所有数据,结果是对象列表:QuerySet ret = models.Person.objects.all() # filter 获取所有满足条件的数据,结果也是对象列表:QuerySet ret = models.Person.objects.filter(pk=1) # first和last 获取第一个/最后一个 拿不到值为none,结果为对象 ret = models.Person.objects.filter().first() # get 获取到一个满足条件的数据,存在而且唯一,查不到或者多个报错,结果是对象 ret = models.Person.objects.get(pk=1) # exclude 获取不满足条件结果,取反,结果是对象列表:QuerySet ret = models.Person.objects.exclude(pk=1) #order_by 按照字段排序,默认升序,降序为'-pid', ret = models.Person.objects.all().order_by('pid') ret = models.Person.objects.all().order_by('age','-pid') #多个字段排序,先排前面 #reverse 只能对已经排序的QuerySet进行翻转 ret = models.Person.objects.all().order_by('pid').reverse() #values 获取对象的字段名和字段值,结果是对象列表:QuerySet,列表里面为字典 ret = models.Person.objects.all().values() ret = models.Person.objects.all().values('pid','name') #可以指定字段 ret = models.Person.objects.all().values_list('pid','name') #拿取字段值 #distinct 只能是一样的对象才可以去重,distinct中不可填写字段 ret = models.Person.objects.all().distinct() #只能去重相同对象 ret = models.Person.objects.values('age').distinct() #这样可以去重 #count 计数 ret = models.Person.objects.all().count() # exists 判断数据是否存在,结果为布尔值 ret = models.Person.objects.filter(pk=1).exists() print(ret) 返回queryset:all filter exculde values(字典) values_list() order_by reverse distinct 返回对象:get first last 返回布尔值:exists 返回数字:count
-
单表的双下划线(字段__过滤条件)
-
大于小于
ret = models.Person.objects.filter(pk__gt=1) #id大于1(greater than) ret = models.Person.objects.filter(pk__lt=5) #id小于5(less than) ret = models.Person.objects.filter(pk__gte=1) #id大于等于1 (greater than equal) ret = models.Person.objects.filter(pk__lte=5) #id小于等于5 (greater than equal)
-
查询范围
ret = models.Person.objects.filter(pk__range=[1,3]) ret = models.Person.objects.filter(pk__in=[1,3,5]) #in 查询多个
-
模糊查询(like),查当前字段中有g的数据
ret = models.Person.objects.filter(name__contains='g') #查询包含g的字段 ret = models.Person.objects.filter(name__icontains='g') #ignore 忽略大小写 ret = models.Person.objects.filter(name__startswith='h') #查询以h开头的 ret = models.Person.objects.filter(name__istartswith='h') #ignore 忽略大小写 ret = models.Person.objects.filter(name__endswith='g') #查询以g结尾的 ret = models.Person.objects.filter(name__iendswith='g') #ignore 忽略大小写
-
查询时间
ret = models.Person.objects.filter(bith__year='2020') #查看年份 ret = models.Person.objects.filter(bith__contains='09') #查看月份 ret = models.Person.objects.filter(bith__contains='2019-09') #查看年月
-
查询为空数据
ret = models.Person.objects.filter(age__isnull=True)
-
外键查询:
-
对象正向查询
#外键的正常查询可以取到书籍是哪个出版社出版的 book_obj = models.Book.objects.get(pk=1) print(book_obj) #关联的对象 print(book_obj.pub_id) #关联的对象的id print(book_obj.pub.name) #关联的对象的id,通过书籍查询到出版社
-
对象反向查询
pub_obj = models.Publisher.objects.get(pk=1) print(pub_obj.name) #当前出版社 print(pub_obj.book_set) #关系管理对象 不指定related_name print(pub_obj.book_set.all()) #关联的所有对象,根据出版社ID查询书籍 # 外键 related_name publisher = models.ForeignKey(to="Publisher", related_name='books') print(pub_obj.book.all()) #在models中指定related_name,替换book_set
-
基于字段查询
#表中有外键的话,直接通过外键__字段 ret = models.Book.objects.filter(pub__name="沙河出版社") #__跨表查询,根据出版社拿书 ret = models.Book.objects.filter(pub__name__contains="沙河") #表中没有外键,直接通过表名__字段 ret = models.Publisher.objects.filter(book__title='神雕侠侣') #根据书拿出版社 #指定related_name 使用类名小写,related_query_name='book' 后面覆盖前面 ret = models.Publisher.objects.filter(books__title='神雕侠')
外键关系管理对象:
-
SET不可用,set不能通过ID添加,只能添加对象
-
add
#外键添加数据,修改书籍的出版社 pub_obj = models.Publisher.objects.get(pk=1) pub_obj.book_set.add(*models.Book.objects.filter(pk__in=[2,4,6]))
-
remove
#外键删除,字段要为空 pub_obj.book_set.remove(*models.Book.objects.filter(pk__in=[2,4,6]))
-
clear
pub_obj.book_set.clear() #清空
-
create
pub_obj.book_set.create(title='小白',pub_id=2) #添加书籍和出版社,不填默认自己1
多对多
-
对象正向查询(跟外键使用方法一样)
auth_obj = models.Author.objects.get(pk=1) print(auth_obj.name) print(auth_obj.books) #关联管理对象 print(auth_obj.books.all()) #关联对象 通过作者找书籍
-
对象反向查找(跟外键使用方法一样)
book_obj = models.Book.objects.get(pk=1) print(book_obj.title) print(book_obj.author_set) #关联管理对象 print(book_obj.author_set.all()) #关联对象 通过书籍找作者
-
基于字段查询
ret = models.Book.objects.filter(author__name='俊丽') #通过作者找书名 ret = models.Author.objects.filter(books__title='少有人走的路') #通过书名找作者
多对多关系管理对象
-
set:添加之前会删除清空
author_obj = models.Author.objects.get(pk=1) author_obj.books.set([1,2]) #set [存放的是关联的ID,可以是多个],之前的会覆盖掉 author_obj.books.set(models.Book.objects.filter(pk__in=[1,2])) #set [也可以是对象]
-
add :新增新的关系,之前不变,重复添加之前的也不变
# add添加是不会覆盖删除,而是新增 author_obj = models.Author.objects.get(pk=1) author_obj.books.add(3) #添加ID author_obj.books.add(models.Book.objects.get(pk=4)) #添加对象 author_obj.books.add(*models.Book.objects.filter(pk__in=[3,4])) #添加多个对象,*打散
-
remove 删除
author_obj = models.Author.objects.get(pk=1) author_obj.books.remove(2) #跟add语法一样 author_obj.books.remove(*models.Book.objects.filter(pk__in=[3,4]))
-
clear 清空
# 将关系全部清空 author_obj = models.Author.objects.get(pk=1) author_obj.books.clear()
-
create 新增
#给作者添加书籍和出版社 author_obj = models.Author.objects.get(pk=2) author_obj.books.create(title='小红书',pub_id=1) #通过作者2对象,添加一本小红书和出版社1的id #给书籍添加作者 book_obj = models.Book.objects.get(pk=2) book_obj.author_set.create(name='小黑') #先查找书籍,在通过书籍对象反向写入作者
聚合
-
引入聚合函数
from django.db.models import Max,Min,Count,Avg,Sum
-
aggregate
#统计书中最高的价格,整个字段,aggregate为终止方法,后面不可在添加方法了 ret= models.Book.objects.aggregate(max=Max('price'),min=Min('price')) #先进行筛选,选择范围之后在使用聚合函数 ret=models.Book.objects.filter(pk__range[3,7]).aggregate(max=Max('price'),min=Min('price')) print(ret['max']) #返回的是字典,使用key来取值
分组
-
统计每一本数的作者个数
#book:按照book_id进行分组 annotate:填写聚合函数(将count结果注释到Book中) ret = models.Book.objects.annotate(count=Count('author')).values() for i in ret: print(i)
-
统计出每个出版社的最便宜书的价格 分组聚合
# 方法一 # 先使用出版社进行分组,annotate注释 填写聚合函数,将结果写入对象中,以出版社为准 ret= models.Publisher.objects.annotate(min=Min('book__price')).values() for i in ret: print(i['min']) # 方法二!!! #values分组条件 #以书籍为准 ret = models.Book.objects.values('pub__id').annotate(min=Min('price')) for i in ret: print(i['min'])
-
统计不止一个作者的图书-筛选
# 先分组聚合,之后进行筛选 ret = models.Book.objects.annotate(count=Count('author')).filter(count__gt=1) for i in ret: print(i.title)
-
根据作者数量,进行-排序
# 先分组聚合,之后排序 ret = models.Book.objects.annotate(count=Count('author')).order_by('-count') for i in ret: print(i.title)
-
查询各个作者书的总价格
#第一种方法:通过作者分组,反向取price 以作者表进行左连接 ret = models.Author.objects.annotate(sum=Sum('books__price')) for i in ret: print(i.name,i.sum) #第二种方法:通过作者分组,在通过钱来取值 以书进行左连接,所以会出现none情况 ret = models.Book.objects.values('author').annotate(sum=Sum('price')) for i in ret: print(i #转成sql过程 1.先把book表和authro进行left join连表 2.连完表之后进行group by分组 3.之后再进行函数取值
F和Q
-
save和update字段区别:
-
save会全部都保存一遍
-
update会只针对查询的数据进行保存,!!!
-
-
F 两个字段比较,跟子查询很像
F两个字段进行比较(里面不能进行聚合和models,只能使用字段)
from django.db.models import F #sale字段大于(gt)num字段的有哪些 ret = models.Book.objects.filter(sale__gt=F('num')) #筛选book表pk字段等于1,针对这个行数据的sale进行修改 ret = models.Book.objects.filter(pk=1).update(sale=F('sale') *2 + 10)
-
Q 或与非
from django.db.models import F,Q ret = models.Book.objects.filter(Q(pk__gt=5)|Q(pk__lt=2)) #大于5或者小于2 ret = models.Book.objects.filter(~Q(pk__gt=5)&Q(pk__lt=2)) #与 ret = models.Book.objects.filter(~Q(pk__gt=5)|Q(pk__lt=2)) #小于等于5或者小于2(~非) ret = models.Book.objects.filter(Q(~Q(pk__gt=5)&Q(pk__lt=2))|Q(pk__lt=2)) #设置多个匹配
事务
-
事务把一系列操作,可能是多条的,我去执行,要么成功要么都失败,当出现问题全部都要回滚回去,保证原子性
# 验证事务 from django.db import transaction try: with transaction.atomic(): book1 = models.Book.objects.get(pk=1) book2 = models.Book.objects.get(pk=2) book1.num -=50 book1.save() int("sssss") #默认是有事务,try int的错误 book2.num += 50 book2.save() except Exception as e: print(e)
Cookie
-
cookie就是保存在浏览器上的一组键值对,这个键值对是服务器发送出来存储在浏览器上,当浏览器在访问服务器时,会自动携带这些键值对,服务器接受后可以利用处理这些键值对
-
为什么要有cookie:HTTP协议是没有状态的,每次请求都是独立的,不会受前面的结果影响
cookies和session区别:
-
应为http本身是无状态的,所以使用cookies和session都是用来记录客户端的状态
-
cookies是以文本键值对存储在浏览器中,session是存储在服务端
-
cookies的存储数量有限只有4kb,而session是无限量的
-
还有就是安全问题,我们可以轻松访问到cookie值,但是我们无法直接访问会话值,因此session更安全
原理
-
首先客户端向服务端发送一个post请求,提交账号密码
-
服务器校验账号密码,正确向浏览器写入登录成功状态
-
浏览器再次请求时,会自动携带cookie的状态在发送给服务器,服务器在判断状态
-
特性:
-
服务器让浏览器进行设置的,键值对保存在浏览器
-
浏览器下次访问事自动携带对应的cookie
-
-
应用:
-
登陆,记录登录状态
-
投票,投票记录状态
-
记录网页的浏览习惯,分页多少条数据
-
django中操作cookies
-
设置
#设置cookie Set-Cookie: is_login=1; 添加响应头 ret.set_cookie('is_login','1000') #设置加密的cookie Set-Cookie: is_login=1 ret.set_signed_cookie('is_login', '1000', '盐')
-
获取
is_login = request.COOKIES.get('is_login') #读取cookies if is_login != '1000': #判断是否有cookies #获取加密盐的cookie request.get_signed_cookie('is_login',default='',salt='盐') #获取不到值,设置一个默认值
-
删除
def logout(request): ret = redirect('/login/') ret.delete_cookie('is_login') #删除cookie return ret
参数:
-
key, 键 value='', 值
-
expires=None, IE 浏览器的超时时间
-
max_age=None 浏览器超时时间,没有设置浏览器关闭就失效
ret.set_signed_cookie('is_login', '1000', '盐',max_age=10) #超过10秒cookei失效!!
-
path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
#针对某个目录设置cookie地址,没有cookie不能访问 ret.set_signed_cookie('is_login', '1000', '盐',path='/home/')
-
domain=None, Cookie生效的域名,默认对所有域名生效
-
secure=False, https传输 为true是有为https协议时候才传输cookei
-
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
Cookie应用示例:
-
网页登录:
from django.shortcuts import render,redirect,HttpResponse def login_required(func): def inner(request,*args,**kwargs): # is_login = request.COOKIES.get('is_login') #读取cookies is_login = request.get_signed_cookie('is_login', default='', salt='盐') if is_login != '1000': #没cookies执行login,url上添加当前路径 return redirect(f'/login/?returnurl={request.path_info}') ret = func(request,*args,**kwargs) #执行home或者index return ret return inner def login(request): if request.method == "POST": user = request.POST.get('user') pwd = request.POST.get('password') if user == 'haiyang' and pwd =='123': #效验登录状态 returnurl = request.GET.get('returnurl') #获取是否有要跳转的地址 if returnurl: ret = redirect(returnurl) else: ret = redirect('/home/') #登录成功要调转的地址 # ret.set_cookie('is_login','1000') #设置cookie Set-Cookie: is_login=1; 添加响应头 ret.set_signed_cookie('is_login', '1000', '盐',path='/home/') return ret return render(request,'login.html',{'error':'用户名错误'}) #密码错误返回 return render(request,'login.html') @login_required #访问home页面调用装饰器 def home(request): #home页面 return HttpResponse("home ok") @login_required def index(request): #index页面 return HttpResponse("index ok") def logout(request): #删除cookie ret = redirect('/login/') ret.delete_cookie('is_login') return ret #前端代码 <form action="" method="post"> {% csrf_token %} <p>用户名:<input type="text" name="user"></p> <p>密码:<input type="text" name="password"></p> <p style="color: red">{{ error }}</p> <button>登录</button> </form>
session
-
session会话控制,保存在服务器上一组组键值对 ,但是必须依赖于cookie。
-
为什么session要就与cookie,没有cookie,建立连接就生成一个session_id,那么打开几个页面就是几个session,使用cookie就可以把session_id保存在cookie中,每次访问携带
-
为什么要用session
-
session保存在服务器上,安全
-
cookie 保存在浏览器上 不安全
-
浏览器对cookie的大小有限制
-
-
session原理图
-
session流程:
- 首先客户端向服务端发送一个post请求,提交账号密码
- 服务器校验账号密码,效验正确,向浏览器返回
- 浏览器再次请求时,会自动携带session_id发送给服务器,服务器通过session_id拿取到值
-
数据库中session标识
-
session表名:django_session
-
session_id
-
session_data
-
expire_date:超时时间,默认两周
-
-
django中的操作:
-
设置session
request.session['is_login'] ='1000' request.session['is_login1'] ='1000' #可以设置多个键值对 request.session.setdefault('k1',123) #存在则不设置 del request.session['is_login'] #删除键值对
-
获取
#两种获取方式 request.session.get('is_login') request.session['is_login'] #可以当成字典使用 request.session.keys() request.session.values() request.session.items() #设置多个键值对可以都取到
-
删除
request.session.delete() # 删除session 数据 不删除cookie request.session.flush() # 删除session 数据 删除cookie def logout(request): #删除session ret = redirect('/login/') request.session.flush() return ret
-
session方法:
request.session.session_key #拿到数据库中的key request.session.clear_expired() #将所有Session失效日期小于当前日期的数据删除 request.session.exists("session_key") # 检查会话session的key在数据库中是否存在 request.session.set_expiry(value) # 设置会话Session和Cookie的超时时间 * 如果value是个整数,session会在些秒数后失效。 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 * 如果value是0,用户关闭浏览器session就会失效。 * 如果value是None,session会依赖全局session失效策略。
-
session设置:
#查看session配置 在settings中设置 from django.conf import global_settings #每次访问保存session,默认为flase 设置后在两周内在此访问,延长两周 SESSION_SAVE_EVERY_REQUEST = True SESSION_EXPIRE_AT_BROWSER_CLOSE = False #关闭浏览器cookie就失效了 1. 数据库Session SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认) 2. 缓存Session SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 SESSION_CACHE_ALIAS = 'default' 3. 文件Session SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() SESSION_FILE_PATH = None 4. 缓存+数据库 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎 5. 加密Cookie 把Session数据放在cookie里 SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎 其他公用设置项: SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认) SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认) SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认) SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认) SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认) SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
session代码示例:
-
网页登录
from django.shortcuts import render,redirect,HttpResponse def login_required(func): def inner(request,*args,**kwargs): is_login = request.session.get('is_login') # 通过session去数据库中拿取key print(is_login) if is_login != '1000': #没session执行login,url上添加当前路径 return redirect(f'/login/?returnurl={request.path_info}') ret = func(request,*args,**kwargs) #执行home或者index return ret return inner def login(request): request.session.clear_expired() # 设置清除已经失效的session数据 if request.method == "POST": user = request.POST.get('user') pwd = request.POST.get('password') if user == 'haiyang' and pwd =='123': #效验登录状态 returnurl = request.GET.get('returnurl') #获取是否有要跳转的地址 if returnurl: ret = redirect(returnurl) else: ret = redirect('/home/') #登录成功要调转的地址 #设置session,使用cookie将session_id结果传给浏览器 request.session['is_login'] = '1000' request.session.set_expiry(10) #设置超时时间 return ret return render(request,'login.html',{'error':'用户名错误'}) #密码错误返回 return render(request,'login.html') @login_required def home(request): return HttpResponse("home ok") @login_required def index(request): return HttpResponse("index ok") def logout(request): #删除session ret = redirect('/login/') request.session.delete() return ret
中间件
-
django的中间件是用来处理django请求和响应,框架级别的钩子(类似于装饰器,添加新的内容),他是一个轻量,低级别的插件系统,也就是,我是用插件就改变,每个中间件组件都负责做一些特定的功能
-
添加插件,影响的是全局,谨慎使用,使用不当会影响性能,注意
-
django中间件就是一个类,五个方法,四个特点
定义中间件
-
settings 中间件配置
-
配置中注册APP
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', #中间件没有返回值走下面 'app01.mymiddleware.MD1', 'app01.mymiddleware.MD2',] #执行urls #执行视图
-
process_request
-
执行时间:
- 在路由前面执行
-
参数:
- request:process_request中的request和views中接受的请求是同一个
-
顺序:
- 多个process_request,按照中间件的注册顺序,顺序执行
-
返回值:
-
None:为none是正常流程
-
HttpResponse:当前中间件中有return之后,路由匹配,视图函数都不执行了,如果有response,直接执行当前中间件process_response的方法,倒序执行之前的process_response方法,最终返回给浏览器
-
process_response
-
执行时间:
- 在视图函数后面执行
-
参数:
- request:process_request中的request和views中接受的请求是同一个
- response:视图返回的response对象
-
顺序:
- 按照中间件的注册顺序,倒序执行
-
返回值:
-
HttpResponse:必须return返回视图的Response对象,返回值也可以自己返回
def process_response(self,request,response): #response响应对象 print("MD2 process_response") # return response return HttpResponse('ok98')
-
process_view
-
执行时间:
- 在路由匹配之后,视图函数之前
-
参数:request response:
- request 请求都是同一个
- view_func:视图函数 (function index at 0x31435345242134)
- view_args,:位置参数,urls中的分组信息
- vies_kwargs:关键字参数,命名分组
-
顺序:
- 按照中间件的注册顺序,顺序执行
-
返回值:
-
None:正常流程
-
HttpResponse:当前中间件之后的process_view不执行了,视图函数都不执行了,倒序执行中间件中的process_response方法,最终返回给浏览器
-
process_exception
-
执行时间:
- 视图层有错误异常触发条件,执行
-
参数:request response:
- request 请求对象都是同一个
- exception:错误对象
-
顺序:
- 按照中间件的注册顺序,在视图函数之后都是倒序执行
-
返回值:
-
None:交给下一个中间件处理异常,所有的中间件都没有处理的话,django最后进行处理
-
HttpResponse:中间件之前的process_exception不执行了,直接执行最后一个方法,倒序执行中间件中的process_response方法,最终返回给浏览器
-
process_template_response
-
执行时间:
- 视图返回的reqponse是一个template_response 模板对象
-
参数:request response:
- request 请求都是同一个
- response:响应对象
-
顺序:
- 按照中间件的注册顺序,倒序执行
-
返回值:
-
HttpResponse:必须返回process_template_response对象,返回的结果,以最后一个为准,倒序执行中间件中的process_response方法,最终返回给浏览器
-
中间件示例
-
代码示例
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class MD1(MiddlewareMixin): def process_request(self,request): #处理请求,request和views请求一样 print("MD1 process_request") # return HttpResponse("md1") def process_response(self,request,response): #response响应对象 print("MD1 process_response") return response def process_view(self,request,view_func,view_args,vies_kwargs): # print(view_func) # print(view_args) print("MD1 process_view") return HttpResponse("dudu") def process_exception(self,request,exception): print("MD1 process_exception") # return None def process_template_response(self,request,response): print("MD1 process_template_response") response.template_name = "index1.html" #替换新的页面 response.context_data['user']='newhai' #更改返回的数据 return response class MD2(MiddlewareMixin): def process_request(self,request): #处理请求,request和views请求一样 print("MD2 process_request") def process_response(self,request,response): #response响应对象 print("MD2 process_response") return response def process_view(self,request,view_func,view_args,vies_kwargs): print("MD2 process_view") return HttpResponse("xiix") def process_exception(self,request,exception): print("MD2 process_exception") # return HttpResponse("处理完了") def process_template_response(self,request,response): print("MD2 process_template_response") response.template_name = "index1.html" #替换新的页面 response.context_data['user']='newhaiyang1' #更改返回的数据 return response #views from django.shortcuts import render,HttpResponse from django.template.response import TemplateResponse def index(request): print('indexa') # ret = HttpResponse("OK") # int("aaaaaa") # return ret return TemplateResponse(request,'index.html',{'uesr':'haiyang'}) #urls from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.index),] #template <body> <h1>index{{ uesr }}</h1> </body>
django-请求生命周期图
- 客户端发送HTTP请求,wsgi接受http请求,将http请求封装成request,传送给process_request -->路由urls -->process_vies ---> 视图 --->process-response
- 图
csrf
csrf装饰器:
-
csrf_exempt,csrf_protect使用
from django.shortcuts import render,redirect,HttpResponse from django.views.decorators.csrf import csrf_exempt,csrf_protect #csrf装饰器 @csrf_exempt #设置csrf,访问不需要进行csrf校验 @csrf_protect #不设置csrf,必须要进行校验 def form(request): return render(request,'form.html') #类中设置csrf from django.utils.decorators import method_decorator #引入装饰器 from django.views import View @method_decorator(csrf_exempt,name='dispatch') class From(View): def get(self,request): return render(request,'form.html') def post(self,request): return render(request,'form.html')
csrf的中间件流程:
-
查看中间件的方法:
#找到中间件进行引用 from django.middleware.csrf import CsrfViewMiddleware
-
想要能通过csrf校验的前提条件 必须要有csrftoken 的cookie
-
第一种:{% csrf_token %} 生成token
-
第二种:from django.views.decorators.csrf import ensure_csrf_cookie
@method_decorator(ensure_csrf_cookie) def get(self,request):
-
-
csrf流程:
-
从cookie获取csrftoken的值 与 POST提交的数据中的csrfmiddlewaretoken的值做对比
-
如果从request.POST中获取不到csrfmiddlewaretoken的值,会尝试从请求头中获取x-csrftoken的值,并且拿这个值与csrftoken的值做对比,对比成功也能通过校验。
-
-
源代码:
class CsrfViewMiddleware(MiddlewareMixin): def _get_token(self, request): if settings.CSRF_USE_SESSIONS: #第二步:判断session是否为flase try: return request.session.get(CSRF_SESSION_KEY) #获取session except AttributeError: else: try: #第三部:获取浏览器cookie对应的值 cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] except KeyError: return None csrf_token = _sanitize_token(cookie_token) #第四步:调用_sanitize_token if csrf_token != cookie_token: #判断cookie是否匹配 request.csrf_cookie_needs_reset = True #cookie不相等,重新设置 return csrf_token #第五步返回token def _sanitize_token(token): #判断cookie是否符合 if re.search('[^a-zA-Z0-9]', force_text(token)): return _get_new_csrf_token() #匹配token,获取到新的token elif len(token) == CSRF_TOKEN_LENGTH: #匹配长度是64的直接返回 return token elif len(token) == CSRF_SECRET_LENGTH: return _salt_cipher_secret(token) return _get_new_csrf_token() def process_request(self, request): #第一步:调用_get_token函数,从cookie中获取csrftoken的cookie值 csrf_token = self._get_token(request) if csrf_token is not None: request.META['CSRF_COOKIE'] = csrf_token #有结果,将获取的cookie值放在请求头部信息 def process_view(self, request, callback, callback_args, callback_kwargs): #csrf_processing_done等于true 返回none view为none正常流程 if getattr(request, 'csrf_processing_done', False): return None #在viws中函数加入csrf_exempt,值就会给true #callback从函数里面拿csrf_exempt豁免,如果等于true返回none, if getattr(callback, 'csrf_exempt', False): return None #不是这些请求的话返回,是post del if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): return self._accept(request) #self._accept 处理完成等于true返回none,正常流程 #不需要进行强制性效验 if getattr(request, '_dont_enforce_csrf_checks', False): if request.is_secure(): #如果发的是https请求 csrf_token = request.META.get('CSRF_COOKIE')#之前代码添加的,从字典里面获取cookie值 if csrf_token is None: #如果请求csrf_token没有值403 return self._reject(request, REASON_NO_CSRF_COOKIE) request_csrf_token = "" if request.method == "POST": #如果是post请求 #获取浏览器页面post提交的数据 request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') #比较加密过的数据是否效验成功 if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN) #从请求头中获取x-csrf_token,赋值html post数据 if request_csrf_token == "": request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '') return self._accept(request)
ajax
-
简介:
-
JAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。
-
-
ajax
-
js技术,给服务发送请求。
-
特点: 异步(发送请求,不等待结果)传输的数据量小 局部刷新(输入框返回来的不存在信息)
-
ajax请求方式:
- 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求
- 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求
-
请求方式-同步请求,发送一个请求,等待响应,在发送下一个
-
浏览器地址栏输入地址 get
-
a标签 get
-
form表单 get/post
-
发送请求:
-
发送代码示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> {% load static %} </head> <body> <input type="text" name="i1">+ <input type="text" name="i2">= <input type="text" name="i3"> <button id="b1">计算</button> <script src="{% static 'jquery-3.4.1.js' %}"></script> #使用ajax 引用js <script> $('#b1').click(function () { //发ajax请求 $.ajax({ url: '/calc/', #发送请求地址 type: 'post', #请求方式,默认get data:{ #发送的数据 'x1':$('[name="i1"]').val(), 'x2':$('[name="i2"]').val(), }, success:function (res) { #成功响应后的回调函数,res views响应return的数据 {#$('[name="i3"]').val(res)#} location.href=res #前端跳转配置 } }) }) </script> </body> </html> #urls urlpatterns = [ url(r'^calc/', views.calc), ] #views def calc(request): i1 = request.POST.get('x1') i2 = request.POST.get('x2') return HttpResponse(i3) #html <button id="b2">参数的测试</button> <script> $('#b2').click(function () { //发ajax请求 $.ajax({ url: '/test/', type: 'post', data:{ 'name':'haiyang', 'age':28, {#'hobby':['台球','吃饭','俊丽']#} 'hobby':JSON.stringify(['台球','吃饭','俊丽']) }, success:function (res) { {#console.log(res)#} console.log(res.status) }, error:function (res) { console.log(res) location.href=res #前端定义跳转月面,res为后端传送的路径 } }) }) </script>
使用json传值
import json
def test(request):
print(request.POST)
# hobby = request.POST.getlist('hobby[]')
hobby = json.loads(request.POST.get('hobby'))
print(hobby,type(hobby))
# return HttpResponse("ok")
# int("aaa") #错误时候调用
return JsonResponse({'status':'ok','msg':'xxx'}) #回应请求头,变成对象
### 使用ajax上传文件
* 上传文件代码示例;
```js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load static %}
</head>
<body>
<input type="file" id="f1"> <button id="b3">上传文件</button>
<script src="{% static 'jquery-3.4.1.js' %}"></script>
<script>
$('#b3').click(function () {
{##生成form键值对, FormData默认有编码方式#}
var form = new FormData();
form.append('filname','xxx')
form.append('f1',$('#f1')[0].files[0]) #f1后端获取参数
//发ajax请求
$.ajax({
url: '/upload/',
type: 'post',
data:form,
processData:false, // 不需要处理编码方式
contentType:false, // 不需要处理contentType请求头
success:function (res) {
{#console.log(res)#}
console.log(res.status)
},
})
})
</script>
</body>
</html>
#urls
urlpatterns = [
url(r'^upload/', views.upload),]
#views
def upload(request):
f1 = request.FILES.get('f1')
with open(f1.name,'wb') as f:
for i in f1.chunks():
f.write(i)
return JsonResponse({'status': '上传成功'}) # 回应请求头,变成对象
#浏览器访问信息:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarygNy1hssp07PGlGbq
Form Data
filname: xx
f1: (binary)
ajax通过django的csrf校验的方式
-
第一种方法:给post,data中加csrfmiddlewaretoken键值对,与浏览器中获取的csrfcookie对比
data:{ 'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(), 'x1':$('[name="i1"]').val(), 'x2':$('[name="i2"]').val(), }, #浏览器查询 Form Data csrfmiddlewaretoken: VAfy6g35ZvuEh30J0wmKv9wNd02jlZkOetmPJ4PxpbQyv8IgAIXR4CnzPbzXNKT6
-
第二种方法:data中加请求头 x-csrftoken键值对
headers:{ 'x-csrftoken':$('[name="csrfmiddlewaretoken"]').val(), },
-
自定义ajax全部添加:
#在status中创建文件,自己写一个getcookie方法 function getCookie(name) { #给cookie名字按照格式分隔开 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)); #如果请求为POST 返回True } $.ajaxSetup({ #给全局的ajax进行配置 beforeSend: function (xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); #为post设置请求头 } } }); #在html文件中导入引用 <script src="{% static 'ajax_setup.js' %}"></script>
sweetalert:
-
代码示例:
#引入sweetalert <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script> <button id='del' class="btn btn-primary btn-danger" url='/student_del/?pk={{ obj.id }}'>删除</button> <script> $('.btn-danger').click(function () { #获取按钮class触发 swal({ title: "你确定要删除吗?", text: "删除可就找不回来了哦!", icon: "warning", buttons: true, dangerMode: true, }) .then((willDelete) => { if (willDelete) { $.ajax({ url: $(this).attr('url'), #获取del中url信息,传到后端 success: (res) => { if (res) { $(this).parent().parent().remove(); swal("删除成功!", "你可以准备跑路了!", { icon: "success", }); } else { swal("删除失败", "你可以再尝试一下!", "error") } } }) } else { swal("你的选择很正确!"); } }); }) </script>
Form表单
-
常用的字段:
CharField #input ChoiceField #select MultipleChoiceField #多选 DecimalField #时间 ModelMultipleChoiceField #可选数据放入数据库,每次刷新页面都会重新查询 MultipleChoiceField #单选 #具体代码见视图标注
-
字段内的参数:
initial #初始值 label #中文提示 error_messages #错误提示 min_length #最小长度 max_length #最大长度 choices #可选择的值 widget #更改插件样式 PasswordInput #隐藏密码 disabled #为true不可编辑 #具体代码见视图标注
视图:
from django import forms
def check_user(value): #自定义效验,写函数
if 'haiyang' in value:
raise ValidationError('haiyang 不能注册')
from django.core.validators import validate_email,RegexValidator #内置的效验方法
class RegForm(forms.Form):
#CharField文本输入框,disabled=True不可编辑,validators填写自定义的效验和内置的效验方法
user = forms.CharField(label='用户名',initial='胡海洋',validators=[validate_email])
pwd = forms.CharField( #PasswordInput密码框
label='密码', #label 输入款前的提示
widget=forms.PasswordInput, #隐藏密码
required=True, #默认是true,为false不用填写,不校验
min_length=6,
max_length=12,
error_messages={ #根据设置属性,修改错误提示
"required":'该字段是必须要填写',
'min_length':'密码最少是6位',
'max_length':'长度过长'
})
re_pwd = forms.CharField( #PasswordInput密码框
label='确认密码', #label 输入款前的提示
widget=forms.PasswordInput, #隐藏密码
required=True, #默认是true,为false不用填写,不校验
min_length=6,
max_length=12,
error_messages={ #根据设置属性,修改错误提示
"required":'该字段是必须要填写',
'min_length':'密码最少是6位',
'max_length':'长度过长'
})
gender = forms.ChoiceField( #ChoiceField单选
label='性别',
choices=((1,'男'),(2,'女'),(3,'xx')),
widget=forms.RadioSelect #更改样式
)
# hobby = forms.MultipleChoiceField( #MultipleChoiceField多选
# label='爱好',
# initial=[1,2], #initial默认选择多个
# # choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
# widget=forms.CheckboxSelectMultiple #好用鼠标勾选
# )
hobby = forms.ModelMultipleChoiceField( #多选|forms.MultipleChoiceField单选
label='爱好',
initial=[1,2],
queryset=models.Hobby.objects.all(),
widget=forms.CheckboxSelectMultiple
)
birth = forms.DecimalField()
def reg2(request):
from_obj = RegForm() #对象实例化
if request.method == 'POST':
from_obj = RegForm(data=request.POST) #POST又实例化一次
if from_obj.is_valid(): #做校验
return HttpResponse('OK')
return render(request, 'reg2.html',{'from_obj':from_obj}) #将对象传给前端
模板:
{{ form_obj.as_p }} # 展示所有的字段
{{ form_obj.user }} # input框
{{ form_obj.user.label }} # label标签的中文提示,用户名
{{ form_obj.user.id_for_label }} # input框的id user
{{ form_obj.user.errors }} # 一个字段的错误信息
{{ form_obj.user.errors.0 }} # 一个字段的第一个错误信息
{{ form_obj.errors }} # 所有字段的错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
{% csrf_token %}
<p>
<label for="{{ from_obj.user.id_for_label }}">{{ from_obj.user.label }}:</label>
{{ from_obj.user }} <span>{{ from_obj.user.errors.0 }}</span>
</p>
<p>
<label for="{{ from_obj.pwd.id_for_label }}">{{ from_obj.pwd.label }}:</label>
{{ from_obj.pwd }} <span>{{ from_obj.pwd.errors.0 }}</span>
</p>
<p>
<label for="{{ from_obj.re_pwd.id_for_label }}">{{ from_obj.re_pwd.label }}:</label>
{{ from_obj.re_pwd }} <span>{{ from_obj.re_pwd.errors.0 }}</span>
</p>
<p>
<label for="{{ from_obj.gender.id_for_label }}">{{ from_obj.gender.label }}:</label>
{{ from_obj.gender }} <span>{{ from_obj.gender.errors.0 }}</span>
</p>
<p>
<label for="{{ from_obj.hobby.id_for_label }}">{{ from_obj.hobby.label }}:</label>
{{ from_obj.hobby }} <span>{{ from_obj.hobby.errors.0 }}</span>
</p>
<p>
<label for="{{ from_obj.phone.id_for_label }}">{{ from_obj.phone.label }}:</label>
{{ from_obj.phone }} <span>{{ from_obj.phone.errors.0 }}</span>
</p>
{# {{ from_obj.errors }}#}
<button>注册</button>
</form>
</body>
</html>
校验
required #前端输入字段是浏览器自动添加的required,post后面添加novalidate去除效验
min_length #校验字段长度
自定义的校验:
-
写函数:
from django.core.exceptions import ValidationError def check_user(value): # 通过校验 不做任何操作 # 不通过校验 抛出ValidationError异常 if 'haiyang' in value: raise ValidationError('haiyang 不能注册') #在user字段中使用validators=列表中填写效验函数 user = forms.CharField(label='用户名',initial='胡海洋',validators=[check_user])
-
使用内置的校验器:
#RegexValidator 使用内置效验器过滤 from django.core.validators import validate_email, RegexValidator phone = forms.CharField(validators=[RegexValidator(r'^1[3-9]\d{9}$','手机号格式不正确')])
-
is_valid流程:
1. is_valid()中要执行full_clean(): #第一步、第四步 1. self.is_bound data数据是否传送 and self._errors ={}#第二步重新定义一个存放错误信息的字典 2. self.cleaned_data = {} #第三步:定义一个存放有效的数据 2. 执行self._clean_fields() #第五步:清洗字段 1. 先执行内置的校验和校验器的校验 #第六步: 2. 有局部钩子,执行局部钩子 3. 执行 self.clean() 全局钩子
局部钩子和全局钩子
#局部钩子对当前字段进行效验
def clean_user(self):
v = self.cleaned_data.get('user')
# 局部钩子
# 不通过校验 抛出异常
# 通过校验 必须返回当前字段的值
if v =='haiyang':
raise ValidationError("不配")
return v
#全局部钩子对全部字段进行效验
from django.core.exceptions import ValidationError
def clean(self):
# 全局钩子
# 不通过校验 抛出异常
# 通过校验 必须返回所有字段的值 self.cleaned_data
pwd = self.cleaned_data.get('pwd')
re_pwd = self.cleaned_data.get('re_pwd')
if pwd != re_pwd:
self.add_error('re_pwd','两次密码不一致!')
raise ValidationError('两次密码不一致')
return self.cleaned_data