第十二章 Django框架开发
第十二章 Django框架开发
12.1 HTTP协议
12.1.1 HTTP简介
- 超文本传输协议 Hyper Text Transfer Protocol
- 是一种用于分布式、协作式和超媒体信息系统的应用层协议
- HTTP是万维网的数据通信的基础
- HTTP有很多应用,但最著名的是用于web浏览器和web服务器之间的双工通信
- HTTP是一个客户端终端和服务器端请求和响应的标准
12.1.2 HTTP 请求/响应的步骤
-
客户端连接到Web服务器
- 一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接
-
发送HTTP请求
- 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求首行(\r\n分隔)、请求头部、空行(\r\n\r\n)和请求数据4部分组成
-
服务器接受请求并返回HTTP响应
- Web服务器解析请求,定位请求资源,服务器将资源复本写到TCP套接字,由客户端读取,一个响应由状态行、响应头部、空行和响应数据4部分组成
-
释放连接TCP连接
- 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
-
客户端浏览器解析HTML内容
- 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码,然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集,客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示
-
面试题:在浏览器地址栏键入URL,按下回车之后会经历的流程:
- 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址
- 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接
- 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
- 服务器对浏览器请求作出响应,并把对应的html文本发送给浏览器
- 释放TCP连接
- 浏览器将该html文本并显示内容
12.1.3 HTTP请求方法
- GET:获取一个页面、图片(资源)
- POST:提交数据
- HEAD
- PUT
- DELETE
- TRACE
- OPTIONS
- CONNECT
请求方式: get与post请求
- GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的Body中.
- GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
- GET与POST请求在服务端获取请求数据方式不同。
- GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码.
12.1.4 HTTP状态码
1.状态代码的第一个数字代表当前响应的类型:
12.1.5 URL:统一资源定位符
- URL包含的信息:
- 传送协议
- 层级URL标记符号(为 // ,固定不变)
- 访问资源需要的凭证信息(可省略)
- 服务器(通常为域名,有时为IP地址)
- 端口号(以数字方式表示,可省略,HTTP的默认值为80,HTTPS的默认值为443)
- 路径(以 / 字符区别路径中的每一个目录名称)
- 查询(GET模式的窗体参数,以 ? 字符为起点,每个参数以 & 隔开,再以 = 分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
- 片段(以“#”字符为起点)
- 示例:
http://www.luffycity.com:80/news/index.html?id=250&page=1
- http,是传送协议
www.luffycity.com
,是服务器- 80,是服务器上的网络端口号
- /news/index.html,是路径
- ?id=250&page=1,是查询
12.2 Web框架
12.2.1 Web框架本质
- 所有的web应用本质是就是一个socket服务器,而用户的游览器就是一个socket客户端
12.2.2 Web框架功能
- socket收发消息 —— wsgiref(测试)、uwsgi(线上)
- 根据不同的路径返回不同的字符串
- 返回动态页面(字符串的替换)—— jinja2
12.2.3 Web框架种类
- django
- 根据不同的路径返回不同的字符串
- 返回动态页面(字符串的替换)
- flask
- 根据不同的路径返回不同的字符串
- tornado
- socket收发消息
- 根据不同的路径返回不同的字符串
- 返回动态页面(字符串的替换)
12.2.4 自定义web框架
-
示例一:socket服务端
import socket # 创建一个socket对象 sk = socket.socket() # 绑定IP和端口 sk.bind(('127.0.0.1', 8000)) # 监听 sk.listen(5) # 等待连接 while True: conn, addr = sk.accept() # 接收数据 data= conn.recv(1024) print(data) # 返回数据 conn.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>ok!</h1>') # 断开连接 conn.close()
-
示例二:根据不同路径返回不同的内容(普通版)
import socket # 创建一个socket对象 sk = socket.socket() # 绑定IP和端口 sk.bind(('127.0.0.1', 8000)) # 监听 sk.listen(5) # 等待连接 while True: conn, addr = sk.accept() # 接收数据 data = conn.recv(1024) data = data.decode('utf-8') url = data.split()[1] conn.send(b'HTTP/1.1 200 OK\r\n\r\n') if url == '/index/': # 返回数据 conn.send(b'<h1>index!</h1>') elif url == '/home/': conn.send(b'<h1>home!</h1>') else: conn.send(b'<h1>404 not found!</h1>') # 断开连接 conn.close()
-
示例三:根据不同路径返回不同的内容(函数版)
import socket # 创建一个socket对象 sk = socket.socket() # 绑定IP和端口 sk.bind(('127.0.0.1', 8000)) # 监听 sk.listen(5) # 函数 def index(url): ret = '<h1>index!</h1>({})'.format(url) return ret.encode('utf-8') def home(url): ret = '<h1>home!</h1>({})'.format(url) return ret.encode('utf-8') # 等待连接 while True: conn, addr = sk.accept() # 接收数据 data = conn.recv(1024) data = data.decode('utf-8') url = data.split()[1] conn.send(b'HTTP/1.1 200 OK\r\n\r\n') if url == '/index/': # 返回数据 ret = index(url) elif url == '/home/': ret = home(url) else: ret = b'<h1>404 not found!</h1>' conn.send(ret) # 断开连接 conn.close()
-
示例四:根据不同路径返回不同的内容(函数进阶版)
import socket # 创建一个socket对象 sk = socket.socket() # 绑定IP和端口 sk.bind(('127.0.0.1', 8000)) # 监听 sk.listen(5) # 函数 def index(url): ret = '<h1>index!</h1>({})'.format(url) return ret.encode('utf-8') def home(url): ret = '<h1>home!</h1>({})'.format(url) return ret.encode('utf-8') # 定义一个list1和实际要执行的函数的对应关系 list1 = [ ('/index/', index), ('/home/', home), ] # 等待连接 while True: conn, addr = sk.accept() # 接收数据 data = conn.recv(1024) data = data.decode('utf-8') url = data.split()[1] conn.send(b'HTTP/1.1 200 OK\r\n\r\n') func = None for i in list1: if url == i[0]: func = i[1] break if func: ret = func(url) else: ret = b'<h1>404 not found!</h1>' conn.send(ret) # 断开连接 conn.close()
-
示例五:返回HTML页面
import socket # 创建一个socket对象 sk = socket.socket() # 绑定IP和端口 sk.bind(('127.0.0.1', 8000)) # 监听 sk.listen(5) # 函数 def index(url): with open('index.html','rb') as f: ret = f.read() return ret def home(url): ret = '<h1>home!</h1>({})'.format(url) return ret.encode('utf-8') # 定义一个list1和实际要执行的函数的对应关系 list1 = [ ('/index/', index), ('/home/', home), ] # 等待连接 while True: conn, addr = sk.accept() # 接收数据 data = conn.recv(1024) data = data.decode('utf-8') url = data.split()[1] conn.send(b'HTTP/1.1 200 OK\r\n\r\n') func = None for i in list1: if url == i[0]: func = i[1] break if func: ret = func(url) else: ret = b'<h1>404 not found!</h1>' conn.send(ret) # 断开连接 conn.close()
-
示例六:返回动态页面
import socket import time # 创建一个socket对象 sk = socket.socket() # 绑定IP和端口 sk.bind(('127.0.0.1', 8000)) # 监听 sk.listen(5) # 函数 def index(url): with open('index.html', 'rb') as f: ret = f.read() return ret def home(url): ret = '<h1>home!</h1>({})'.format(url) return ret.encode('utf-8') def timer(url): now = time.strftime('%H:%M:%S') with open('time.html','r',encoding='utf-8') as f: data = f.read() data = data.replace('xxtimexx',now) return data.encode('utf-8') # 定义一个list1和实际要执行的函数的对应关系 list1 = [ ('/index/', index), ('/home/', home), ('/time/', timer), ] # 等待连接 while True: conn, addr = sk.accept() # 接收数据 data = conn.recv(1024) data = data.decode('utf-8') url = data.split()[1] conn.send(b'HTTP/1.1 200 OK\r\n\r\n') func = None for i in list1: if url == i[0]: func = i[1] break if func: ret = func(url) else: ret = b'<h1>404 not found!</h1>' conn.send(ret) # 断开连接 conn.close()
-
补充:time.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>当前时间是:@@time@@</h1> </body> </html>
-
12.2.5 wsgiref
-
常用的WSGI服务器有uWSGI、Gunicorn
- Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器
-
示例:
""" 根据URL中不同的路径返回不同的内容--函数进阶版 返回HTML页面 让网页动态起来 wsgiref模块版 """ from wsgiref.simple_server import make_server # 将返回不同的内容部分封装成函数 def index(url): # 读取index.html页面的内容 with open("index.html", "r", encoding="utf8") as f: s = f.read() # 返回字节数据 return bytes(s, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") def timer(url): import time with open("time.html", "r", encoding="utf8") as f: s = f.read() s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S")) return bytes(s, encoding="utf8") # 定义一个url和实际要执行的函数的对应关系 list1 = [ ("/index/", index), ("/home/", home), ("/time/", timer), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url func = None for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b"404 not found!" return [response, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("我在8090等你哦...") httpd.serve_forever()
12.2.6 jinja2
-
模板渲染现成的工具:jinja2
- 下载jinja2:pip install jinja2
-
示例:
from wsgiref.simple_server import make_server from jinja2 import Template def index(url): # 读取HTML文件内容 with open("index2.html", "r", encoding="utf8") as f: data = f.read() template = Template(data) # 生成模板文件 ret = template.render({'name': 'alex', 'hobby_list': ['抽烟', '喝酒', '烫头']}) # 把数据填充到模板中 return bytes(ret, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定义一个url和实际要执行的函数的对应关系 list1 = [ ("/index/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url func = None for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b"404 not found!" return [response, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("我在8090等你哦...") httpd.serve_forever()
-
补充:index2.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <h1>姓名:{{name}}</h1> <h1>爱好:</h1> <ul> {% for hobby in hobby_list %} <li>{{hobby}}</li> {% endfor %} </ul> </body> </html>
-
12.3 Django基本知识
12.3.1 安装及使用
-
下载安装
- 命令行:pip3 install django==1.11.21
- pycharm
-
创建项目
-
命令行:
- 找一个文件夹存放项目文件,打开终端:
- django-admin startproject 项目名称
- 项目目录
-
pycahrm
-
用虚拟环境创建django项目,在pycharm中新建一个虚拟环境,里面安装django的版本(1或者2),然后新建django项目,选择虚拟环境和指定django项目路径,创建就可以了
-
-
启动
- 命令行
- 切换到项目的根目录下 manage.py
python36 manage.py runserver
—— 127.0.0.1:80`python36 manage.py runserver 80
——127.0.0.1:80python36 manage.py runserver 0.0.0.0:80
——0.0.0.0:80- 在linux环境中 可以写一个脚本 如:
nohup /opt/mesosphere/bin/python3.5pwd
/manage.py runserver xxx:9155 >/dev/null &
- pycharm:点绿三角启动 可配置
- 命令行
-
简单使用
- 示例:返回HTML指定文件
# 在urls.py中 # 导入 from django.shortcuts import HttpResponse,render # 函数 def index(request): # return HttpResponse('index') return render(request,'index.html') # url和函数对应关系 urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', index), ]
12.3.2 静态文件
-
配置
- 在settings.py中设置
STATIC_URL = '/static/' # 别名 STATICFILES_DIRS = [ # 设置文件路径,可以设置多个 os.path.join(BASE_DIR, 'static1'), os.path.join(BASE_DIR, 'static'), os.path.join(BASE_DIR, 'static2'), ]
-
使用
- 在路径前添加别名:/static/
- 多个文件路径,也是使用同一个别名,不是文件名
- 如果别名后的路径名相同,按照STATICFILES_DIRS列表的顺序进行查找
<link rel="stylesheet" href="/static/css/login.css"> {# 别名开头 #}
-
解决找不到模块
print(os.path.split(os.path.split(os.path.realpath(__file__))[0])[0]) #顶级目录
12.3.3 简单的登录实例
-
form表单提交数据注意的问题:
- 提交的地址:action="",请求的方式:method="post"
- 所有的input框有name属性,如name="username"
- 有一个input框的type="submit"或者 有一个button id=‘btnSubmit’绑定ajax发送pos请求提交数据,这样form中就不用写post方法了。
-
提交post请求,由于Django中有一个csrf校验,所有请求会出问题
- 解决方式:把settings中MIDDLEWARE的'django.middleware.csrf.CsrfViewMiddleware'注释掉
- 或者在html 页面form表单下写
-
重定向
-
导入方式
-
from django.shortcuts import redirect
-
使用方式
-
在函数中使用: return redirect('/index/') #参数 url #注意:前面必须加/,代表从url根拼接,否则就会在当前url后面一直拼接
-
-
示例:
from django.shortcuts import HttpResponse, render, redirect def index(request): # return HttpResponse('index') return render(request, 'index.html') def login(request): if request.method == 'POST': # 获取form表单提交的书籍 username = request.POST['username'] password = request.POST['password'] # 验证用户名和密码 if models.User.objects.filter(username=username,password=password): # 验证成功跳转到index页面 # return redirect('https://www.baidu.com/') return redirect('/index/') # 不成功 重新登录 return render(request, 'login.html') urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.index), url(r'^login/', views.login), ]
12.3.4 app
-
创建app
- 命令行:python manage.py startapp app名称
- pycharm:tools --> run manage.py task --> 输入命令:startapp app名称
-
注册app
- 在settings.py中设置,例:app名为app01
INSTALLED_APPS = [ ... 'app01', 'app01.apps.App01Config', # 推荐写法 ]
-
app中的文件
- migrations:存放迁移文件的
- admin.py:Django提供的后台管理工具
- app.py:与app信息相关的
- models.py:跟ORM有关的内容
- views.py:视图,写函数的
12.3.5 使用MySQL流程
-
创建一个MySQL数据库:create database day53;
-
在settings.py中设置,Django连接MySQL数据库:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 引擎 'NAME': 'day53', # 数据库名称 'HOST': '127.0.0.1', # ip地址 'PORT':3306, # 端口 'USER':'root', # 用户 'PASSWORD':'123' # 密码 } }
-
在与settings,py同级目录下的init文件中写入:
import pymysql pymysql.install_as_MySQLdb()
-
创建表(在app下的models.py中写类):
from django.db import models class User(models.Model): username = models.CharField(max_length=32) # username varchar(32) password = models.CharField(max_length=32) # username varchar(32)
-
执行数据库迁移的命令:
- python manage.py makemigrations:检测每个注册app下的model.py,记录model的变更记录
- python manage.py migrate:同步变更记录到数据库中
12.3.6 MVC和MTV
- MVC
- M: model 模型 —— 和数据库打交道
- V:view 视图 —— HTML
- C: controller 控制器 —— 调度 传递指令 业务逻辑
- MTV:
- M: model 模型 ORM —— 和数据库打交道
- T: tempalte 模板 —— HTML
- V:view 视图 —— 函数 业务逻辑
- djando是MTV模式
12.4 Django模板系统:Template
12.4.1 模板常用语法
- 特殊符号:
- 变量:{{ }}
- 标签tag:
12.4.1.1 变量
1.符号:{{ }}
在 Django 模板中遍历复杂数据结构的关键是句点字符, 语法:{{var_name}}
-
表示变量,在模板渲染的时候替换成值
-
使用方式:{{ 变量名}} :变量名由字母数字下划线组成
-
点(.) ,在模板语言中有特殊的含义,用来获取对象的相应属性值
-
注意:当模板系统遇到一个. 时,会按照如下的顺序去查询
- 在字典中查询
- 属性或方法
- 数字索引
-
例子
views.py: def index(request): import datetime s="hello" l=[111,222,333] # 列表 dic={"name":"yuan","age":18} # 字典 date = datetime.date(1993, 5, 2) # 日期对象 class Person(object): def __init__(self,name): self.name=name person_yuan=Person("yuan") # 自定义类对象 person_egon=Person("egon") person_alex=Person("alex") person_list=[person_yuan,person_egon,person_alex] return render(request,"index.html",{"l":l,"dic":dic,"date":date,"person_list":person_list}) template: <h4>{{s}}</h4> <h4>列表:{{ l.0 }}</h4> <h4>列表:{{ l.2 }}</h4> <h4>字典:{{ dic.name }}</h4> <h4>日期:{{ date.year }}</h4> <h4>类对象列表:{{ person_list.0.name }}</h4>
注意:句点符也可以用来引用对象的方法(无参数方法):
<h4>字典:{{ dic.name.upper }}</h4>
12.4.1.2 内置filter
1.filter:过滤器,用来修改变量的显示结果
- 语法:{{ value | filter_name:参数} }
- :左右没有空格
2.内置过滤器
-
default:默认值
- 语法:{{ value|default:"nothing"}}
- 如果value值没传的话就显示nothing
- 补充:TEMPLATES的OPTIONS可以增加一个选项:string_if_invalid:'找不到',可以替代default的的作用
-
filesizeformat:文件大小,将值格式化为可读的文件尺寸
- 语法:{{ value | filesizeformat}}
- 如: value =123456789,输出是117.7MB
-
add:给变量加参数
- 语法:{{ first | add:second}}
- 优先看是否能转化为数字相加,其次是字符串拼接
- 如果都是列表,相当于extend,循环加入
-
length:返回长度
- 语法:{{ value | length}}
- 返回value的长度,如果value=['a', 'b', 'c', 'd']的话,就显示4
-
slice:切片
- 语法:{{value | slice:"2:-1"}} 顾头不顾尾
-
upper
大写
{{ value|upper}}
title
标题
{{ value|title }}
ljust
左对齐
"{{ value|ljust:"10" }}"
rjust
右对齐
"{{ value|rjust:"10" }}"
center
居中
"{{ value|center:"15" }}"
-
first/last ;取第一个、最后一个元素
- 语法:
- 取第一个元素:{{ value|first }}
- 取最后一个元素:{{ value|last }}
- 语法:
-
join:使用字符串拼接列表
-
使用字符串拼接列表。同python的str.join(list)。
{{ value|join:" // " }}
-
-
truncatechars:截断,按照字符计数
- truncatechars:按照字母计数,不能识别中文
- 如果字符串字符多于指定的字符数量,那么会被截断
- 截断的字符串将以可翻译的省略号序列(...)结尾
- 参数:截断的字符
- 语法:{{ value | truncatechars:9}}
-
date:日期格式化
-
语法{{ value | date:"Y-m-d H:i:s"}}
-
在settings,py中配置
USE_L10N = False DATETIME_FORMAT = 'Y-m-d H:i:s' # datetime类型 DATE_FORMAT = 'Y-m-d' # date类型 TIME_FORMAT = 'H:i:s' # time类型
-
-
配置后,使用{{now}}可以实现日期格式化
- 其中,'now':datetime.datetime.now()
-
safe:告诉django这段代码是安全的,不需要转义
- 语法:{{ value|safe}}
- 如,
value = "<a href='#'></a>"
12.4.1.3 自定义filter
1.app下创建一个templatetags的python包,在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的
2.在templatetags中创建py文件,文件名自定义(my_tags.py)
3.在py文件中写
from django import template
register = template.Library()
@register.filter
def add_xx(value, arg): # 最多有两个
return '{}-{}'.format(value, arg)
@register.filter(name='adds') # 相当于更改了函数名,使用时,使用新的函数名
4.在模板文件中使用,html文件
{% load my_tags %}
{{ 'alex'|add_xx:'dsb'}}
5.注意:
- 为避免出错,templatetags最好是一个python包,并且名称不能更改
- register名称也不能更改,必要时重启项目
- python包下init中可能有其他内容django不能识别,导致出错,可以直接删除内容
12.4.1.4 标签tag
1.for循环
<ul>
{% for user in user_list%}
<li>{{user.name}}</li>
{% end for %}
</ul>
- forloop:字典形式
Variable | Description |
---|---|
forloop.counter |
当前循环的索引值(从1开始) |
forloop.counter0 |
当前循环的索引值(从0开始) |
forloop.revcounter |
当前循环的倒序索引值(到1结束) |
forloop.revcounter0 |
当前循环的倒序索引值(到0结束) |
forloop.first |
当前循环是不是第一次循环(布尔值) |
forloop.last |
当前循环是不是最后一次循环(布尔值) |
forloop.parentloop |
本层循环的外层循环 |
-
for ...empty
{% for book in all_books %} <tr> ..... </tr> {% empty %} <td>没有相关的数据</td> {% endfor %}
2.if判断
- if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断
{% if 条件1 %}
xxx
{% elif 条件2 %}
xxx
{% else %}
xxxxx
{% endif %}
- 连续判断
- python中,10>5>1 --> 10>5 and 5>1 --> true
- js中,10>5>1 --> true>1 --> 1>1 --> false
- 模板中,不支持连续连续判断 也不支持算数运算(可使用过滤器)
3.with:给变量重命名,但只在with区域内生效
{% with hobby.2 as talk %}
{# 相当于 {% with talk=hobby.2 %},其中=两边不能有空格 #}
{{ talk }}
{% endwith %}
4.csrf_token
- 该标签用于跨站请求伪造保护
- csrf:跨站请求伪造
- 使用方式:在form表单中写上
- 这样就不用在settings中注释含csrf的中间件了
12.4.1.5 注释
- 符号:
- 快捷键:Ctrl + ?
12.4.2 母板和继承
12.4.2.1 母板
1.母版就是一个普通的html,提取多个页面的公共部分,通过定义block块来实现,
一般被命名base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
{% block page-css %}
{% endblock %}
<body>
<h1>这是母版的标题</h1>
{% block page-main %}
{% endblock %}
<h1>这是母版的底部内容</h1>
{% block page-js %}
{% endblock %}
</body>
2.block块
{% block 块名 %}
{% endblock %}
3.注意:我们通常会在母版中定义页面专用的css块和js块,方便用户页面替换
12.4.2.2 继承
1.子页面继承母版:{% extends '母版html' %}
{% extends 'base.html'%}
2.子页面通过重写block块,来替换母版中相应内容
{% block page-main %}
<p>哎哎哎</p>
{% endblock %}
12.4.2.3 注意
- {% extends 'base.html' %}要写在第一行,前面不要有内容,否则内容会显示出来
- {% extends 'base.html' %}中的'base.html' 必须加上引号,不然会被当做变量去查找
- 子页面把要显示的内容写在block块中,否则不会显示出来
- 多个位置有独特内容,可以定义多个block块,特殊:定义css、js块等
12.4.3 组件
1.组件:一小段html代码段
2.可以将常用的页面内容如导航条,页尾信息等组件保存在单独的文件中,然后在需要使用的地方导入
{% include 'navbar.html' %}
12.4.4 静态文件相关
-
目的:更改setting中静态文件的别名时,不影响在更改之前的静态文件的引用,即引用会跟随别名的更改而自动更改,这样就不会报错了
-
方法一:使用static,原本路径:/static/images/hi.jpg
{% load static %} <img src="{% static "images/hi.jpg" %}" alt="Hi">
- 文件多处被用到可以存为一个变量
{% load static %} {% static "images/hi.jpg" as myphoto %} <img src="{{ myphoto }}">
-
方法二:使用get_static_prefix,原本路径:/static/images/hi.jpg
{% load static %} <img src="{% get_static_prefix %}images/hi.jpg" alt="Hi"> {# 补充:获取别名 #} {% get_static_prefix %}
- 文件多处被用到可以存为一个变量
{% load static %} {% get_static_prefix as STATIC_PREFIX %} <img src="{{ STATIC_PREFIX }}images/hi.jpg" alt="Hi">
12.4.5 自定义simple_tag
-
和自定义filter类似,区别:接收的参数更灵活,能接收万能参数。参数不限,但不能放在if for语句中
-
定义注册simple_tag
@register.simple_tag def join_str(*args, **kwargs): return '{} - {} '.format('*'.join(args), '$'.join(kwargs.values())) @register.simple_tag(name='join') # 相当于更改了函数名,使用时,使用新的函数名
-
使用自定义simple_tag
{% load my_tags %} {% join_str '1' '2' k1='3' k2='4' %}
12.4.6 inclusion_tag
-
在app下的templatetags(python包)中创建py文件,文件名自定义(my_inclusion.py);
-
在py文件中写:
from django import template register = template.Library() # register也不能变
-
写函数+装饰器
@register.inclusion_tag('result.html') # result.html 是内容的html def show_results(n): n = 1 if n < 1 else int(n) data = ["第{}项".format(i) for i in range(1, n+1)] return {"data": data}
-
在result.html中写:
<ul> {% for choice in data %} <li>{{ choice }}</li> {% endfor %} </ul>
-
在模板文件中使用
{% load my_inclusion %} {% show_results 10 %}
12.4.7 总结自定义方法
-
自定义方法:filter,simple_tag,inclusion_tag
-
步骤:
-
在已注册的APP下创建templatetags的python包;
-
在包内创建python文件,如my_tags.py
-
在py文件中写固定的内容:
from django import template register = template.Library()
- 写函数 + 装饰器
@register.filter(name='add_a') def add_xx(value,arg): return 'addd_xx' @register.simple_tag def join_str(*args,**kwargs): return 'xxxxx' @register.inslusion_tag('page.html') def page(num): return {'num':range(1,num+1)}
- 写模板
{% for i in num %} {{ i }} {% endfor %}
- 使用
{% load my_tags %} {# filter#} {{ ’xxx‘|add_xx:'a' }} {{ ’xxx‘|add_a:'a' }} {# simple_tag #} {% join_str 1 2 k1=3 k2=4 %} {# inclusion_tag #} {% page 4 %}
-
-
注意点:
templatetags里要有init包、并在settings中注册了apps xxx.templatetags
12.5 Django的视图系统:View
12.5.1 CBV和FBV
-
FBV:functoin based view,基于函数的view
- 我们之前写过的都是基于函数的view
-
CBV:class based view,基于类的view
- 定义CBV:
from django.views import View class AddPublisher(View): def get(self,request): """处理get请求""" pass def post(self,request): """处理post请求""" pass
- 使用CBV:
url(r'^add_publisher/', views.AddPublisher.as_view())
-
as_view的流程
- 项目启动加载url.py时,执行类.as_view() --> view函数
- 请求到来的时候执行view函数:
- 实例化类 --> self
- self.request = request
- 执行self.dispatch(request, *args, **kwargs)
- 判断请求方式是否被允许:
- 允许:通过反射获取到对应请求方式的方法 --> handler
- 不允许:self.http_method_not_allowed --> handler
- 执行handler(request,*args,**kwargs)
- 返回响应 --> 浏览器
- 判断请求方式是否被允许:
- 实例化类 --> self
12.5.2 视图加装饰器
-
装饰器示例:
def timer(func): def inner(request, *args, **kwargs): start = time.time() ret = func(request, *args, **kwargs) print("函数执行的时间是{}".format(time.time() - start)) return ret return inner
-
FBV
- 直接在函数上加装饰器
@timer def publisher_list(request): pass
-
CBV
- 导入
from django.utils.decorators import method_decorator
- 方法一:直接在方法上加装饰器
- 作用:只作用于加了装饰器的方法
@method_decorator(timer) def get(self, request, *args, **kwargs): pass
- 方法二:在dispatch方法上加装饰器
- 作用:作用于类里面的所有方法
@method_decorator(timer) def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs)
- 方法三:在类上加装饰器,要指定name,即要加装饰器的方法名
- 作用:只作用于name指定的方法
@method_decorator(timer,name='get') class AddPublisher(View): pass
- 特殊:等效于方法二,相当于给dispatch方法上了装饰器,作用于类里面的左右方法
@method_decorator(timer,name='dispatch') class AddPublisher(View): pass
-
类的方法上也可以直接用@timer,效果等同于使用@method_decorator(timer)
- 使用@timer和使用@method_decorator(timer)的区别:
- 使用@timer:
- 使用装饰器,取参数时【def inner(*args, **kwargs)】,第一个参数默认是self,第二个参数才是request对象
- 使用@method_decorator(timer):
- 使用装饰器,取参数时【def inner(*args, **kwargs)】,第一个参数就是request对象
- 使用@timer:
- 注意:之后取request对象做操作时,注意使用装饰器的方式,对应取request对象的方式也不同
- 使用@timer和使用@method_decorator(timer)的区别:
12.5.3 request对象
1.属性:
-
request.method:当前的请求方式 get post
-
request.GET: url上携带的参数
-
request.POST:post请求提交的数据
一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。 POST 请求可以带有空的 POST 字典 —— 如果通过 HTTP POST 方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。 因此,不应该使用 if request.POST 来检查使用的是否是POST 方法;应该使用 if request.method == "POST" 另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。 注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,需要用: request.POST.getlist("hobby")
-
request.path_info:url的路径,不包括ip和端口,不包含参数
-
request.body:请求体,bytes类型,
一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。 但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。
-
request.FILES:上传的文件,类似字典
一个类似于字典的对象,包含所有的上传文件信息。 FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。 注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会 包含数据。否则,FILES 将为一个空的类似于字典的对象。
-
request.META:请求头,标准的Python字典,包含左右HTTP首部
-
request.COOKIES:cookie
-
request.seeion:session
2.方法:
- request.get_host():获取主机的ip和端口
- request.get_full_path():url的路径,不包含ip和端口,包含参数
- request.is_ajax():判断是否我饿ajax请求,是返回True,错返回Flase
3.上传文件示例:
-
urls.py
url(r'^upload/',views.upload)
-
templates --upload.html
<form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <input type="file" name="f1"> <button>上传</button> </form>
-
views.py
def upload(request): if request.method == 'POST': # 获取文件 # print(request.FILES) f1 = request.FILES.get('f1') # 保存文件 with open(f1.name, 'wb') as f: for i in f1.chunks(): f.write(i) return render(request, 'upload.html')
12.5.4 response对象
1.Response对象:render, redirect, HttpResponse
from django.shortcuts import render,redirect,HttpResponse
HttpResponse('字符串') # 返回字符串
render(request,'.html','{k1:v1,}') # 返回一个完整的HTML页面
redirect('重定向的地址')# 返回重定向,Location:地址
2.JsonResponse对象
- 普通示例:
import json
def json_data(request):
data = {'name': 'alex', 'age': 73}
return HttpResponse(json.dumps(data))
# Content-Type: text/html; charset=utf-8
- 使用JsonResponse对象示例:
from django.http.response import JsonResponse
def json_data(request):
data = {'name': 'alex', 'age': 73}
return JsonResponse(data)
# Content-Type: application/json
-
Content-Type:响应头
- Content-Type: application/json好处在于:告诉前端这是json类型,便于做相应操作
- 默认Content-Type: text/html; charset=utf-8,更改直接使用参数content_type='application/json'
import json def json_data(request): data = {'name': 'alex', 'age': 73} return HttpResponse(json.dumps(data),content_type='application/json') # 此时,Content-Type: application/json
-
JsonResponse对象默认只能传递字典,对于非字典类型,设置参数safe=False
from django.http.response import JsonResponse
def json_data(request):
data = [1, 2, 3, 4]
return JsonResponse(data,safe=False,json_dumps_params={"ensure_ascii":Flase}) #ensure_ascii=flase 支持中文
12.6 Django的路由系统:URL
12.6.1 URLconf配置
-
基本格式
from django.conf.urls import url urlpatterns = [ url(正则表达式, views视图,参数,别名), ]
- 参数说明
- 正则表达式:一个正则表达式字符串
- views视图:一个可调用对象,通常为一个视图函数
- 参数:可选的要传递给视图函数的默认参数(字典形式)
- 别名:一个可选的name参数
- 参数说明
-
django 2.0版本的路由系统
- 2.0版本中re_path和1.11版本的url是一样的用法
from django.urls import path,re_path urlpatterns = [ path('articles/2003/', views.special_case_2003), ]
12.6.2 正则表达式
-
基本配置
from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), ]
-
注意事项
- urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达
式,一旦匹配生功不再继续
- 若要从url中捕获一个值,在他周围放一个(),利用分组匹配,在视图函数中接受参数即可(按位置传参接受)
- 不需要添加一个前导的反斜杠,因为每一个url都有,例如,应该是articles,而不是/articles
- 每个正则表达式前面的'r' 是可选的但是建议加上
-
补充说明
- 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项:APPEND_SLASH=True
- Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True,其作用就是自动在网址结尾加'/'
12.6.3 分组命名匹配
-
分组:使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图函数
from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/([0-9]{4})/$', views.year_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), ]
-
命名分组:使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图函数
from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), ]
- 补充:url的第三个参数时一个字典,表示想要传递给视图函数的额外关键字参数
- 当传递额外参数的字典中的参数和URL中捕获值的命名关键字参数同名时,函数调用时,将使用的是字典中的参数,而不是URL中捕获的参数
- 即优先级:额外参数 > URL捕获参数
- 补充:url的第三个参数时一个字典,表示想要传递给视图函数的额外关键字参数
-
URLconf匹配的位置:
- URLconf 在请求的URL 上查找,将它当做一个普通的Python字符串,不包括GET和POST参数以及域名
http://www.example.com/myapp/
请求中,URLconf 将查找 /myapp/http://www.example.com/myapp/?page=3
请求中,URLconf 仍将查找 /myapp/
- URLconf 不检查请求的方法,即所有的请求方法(同一个URL的POST、GET、HEAD等),都将路由到相同的函数
- URLconf 在请求的URL 上查找,将它当做一个普通的Python字符串,不包括GET和POST参数以及域名
-
捕获的参数都是字符串:每个在URLconf中捕获的参数都作为一个普通的python字符串传递给视图函数,无论正则表达式使用什么匹配方式
-
个别情况,需要视图函数指定默认值
# urls.py中
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# 两个URL模式指向相同的view,但是第一个模式并没有从URL中捕获任何东西
# views.py中,可以为num指定默认值
def page(request, num="1"):
pass
# 如果第一个模式匹配上了,page函数将使用其默认参数num=“1”,如果第二个模式匹配,page将使用正则表达式捕获到的num值
6.include:路由分发
- 根据功能或种类的不同,可以把不同的功能的路由写在该功能的app文件下,利用include联系起来,相当于把不同功能的url放在不同的空间里
from django.conf.urls import include, url
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls')), # 可以包含其他的URLconfs文件
]
- 补充:app01.urls
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.blog),
url(r'^blog/(?P<year>[0-9]{4})/(?P<month>\d{2})/$', views.blogs),
]
- include有一个命名空间的参数:namespace
- 便于区分不同路由空间中存在的同名函数,利于指定到确切的函数
from django.conf.urls import include, url
urlpatterns = [
url(r'^app01/', include('app01.urls', namespace='app01')),#相当于有了自己的路由空间
url(r'^app02/', include('app02.urls', namespace='app02')),
]
ps:include路由解析:
1)传入字符串,后根据app01.urls去导入模块找到urlpatterns,进行解析
2) 也可以传入元组 include([url(r'^wiki/$', wiki.wiki, name='wiki'),
url(r'^wiki/add$', wiki.wiki_add, name='wiki_add')],None,None),
这种可以直接定义视图函数。
具体可看源码:
return (urlconf_module, app_name, namespace) #return([],None,None)
12.6.4 URL的命名和反向解析
12.6.4.1 URL命名
1.url的第四个参数是起一个别名,一个可选的name参数,用于反向解析路径
2.命名方式:name = '别名'
from django.conf.urls import url
urlpatterns = [
url(r'^home', views.home, name='home'), # 给我的url匹配模式起名为 home,即为匹配的路径的别名
url(r'^index/(\d*)', views.index, name='index'), # 给我的url匹配模式起名为index
]
12.6.4.2 反向解析
-
反向解析:通过别名获取完整URL路径
-
情况一:静态路由
-
命名
from django.conf.urls import url urlpatterns = [ url(r'^blog/$', views.blog, name='blog'), ]
-
在模板中使用
{% url 'blog' %} {# 完整URL路径:/blog/ #}
-
py文件中
from django.urls import reverse reverse('blog') # 完整URL路径:/blog/
-
-
情况二:分组
- 命名
from django.conf.urls import url urlpatterns = [ url(r'^blog/([0-9]{4})/(\d{2})/$', views.blogs, name='blogs'), ]
- 模板中使用
{% url 'blogs' 2222 12 %} {# 完整URL路径:/blog/2222/12/ #}
- py文件中使用
from django.urls import reverse reverse('blogs',args=('2019','06')) #args位置传参 # 完整URL路径:/blog/2019/06/
-
情况三:命名分组
- 命名
from django.conf.urls import url urlpatterns = [ url(r'^blog/(?P<year>[0-9]{4})/(?P<month>\d{2})$', views.blogs, name='blogs'), ]
- 模板中使用
{% url 'blogs' year=2222 month=12 %} {# 完整URL路径:/blog/2222/12/ #}
- py文件中使用
from django.urls import reverse reverse('blogs',kwargs={'year':'2019','month':'06'}) #kwargs键值对接受 # 完整URL路径:/blog/2019/06/
-
12.6.5 命名空间模式下的反向解析
-
项目目录下的urls.py
from django.conf.urls import url, include urlpatterns = [ url(r'^app01/', include('app01.urls', namespace='app01')), url(r'^app02/', include('app02.urls', namespace='app02')), ]
-
app01中的urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^(?P<pk>\d+)/$', views.detail, name='detail') ]
-
app02中的urls.py
from django.conf.urls import url from app02 import views urlpatterns = [ url(r'^(?P<pk>\d+)/$', views.detail, name='detail') ]
-
反向解析语法:'命名空间名称:URL名称'
- 在模板中的使用方式
{% url 'app01:detail' pk=12 %} {% url 'app02:detail' pk=12 %}
- 在py文件中的使用方式
from django.urls import reverse reverse('app01:detail', kwargs={'pk':11}) reverse('app01:detail', kwargs={'pk':11})
-
注意:如果有多层路由分发,有了多个命名空间名称,都要把命名空间名称一层一层加在别名前面
{# 简单示例:app01是第一层路由空间的namespace,xxx是第二层路由空间的namespace #} {% url 'app01:xxx:index' %}
-
12.7 ORM:对象关系映射
12.7.1 基本内容
1.定义:面向对象和关系型数据库的一种映射,通过操作对象的方式操作数据
2.对应关系:
- 类对应数据表
- 对象对应数据行(记录)
- 属性对应字段
3.导入:form app01 import models
4.查
- models.Publisher.obj.objects.all()
- 查询所有数据,queryset:对象列表
- models.Publisher.objects.get(name='xxx')
- 对象,获取一个对象(有且只有1个),获取不到或者获取到多个对象会报错
- models.Publisher.objects.filter(name ='xxx')
- 获取满足条件的所有对象 queryset:对象列表
5.增:
- models.Publisher.objects.create(name='xxx')
- 插入数据库的对象
- obj = models.Publisher(name='xxx')
- 存在内存中的对象
- obj.save() 提交到数据库中 新增
-
def create_session(self,host_obj,random_tag): session_obj=models.SessionRecord( user=self.user, bind_host=host_obj, random_tag=random_tag, ) session_obj.save() return session_obj #返回的session_obj的id,create返回的None
6.删:
- obj = models.Publisher.objects.get(pk=1)
- obj.delete()
- models.Publisher,objects.filter(pk=1).delete()
7.改(更新):
-
obj = models.Publisher.objects.get(pk=1)
-
obj.name = 'new name'
- 在内存中修改对象的属性
-
obj.save()
- 提交数据,保存到数据库中
-
models.Publisher.objects.filter(title__startswith="py").update(price=120)
12.7.2 字段
1.常用字段
-
AutoField:自增的整型字段,必填参数primary_key=True
- 注意:一个model不能有两个AutoField字段, 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model.
-
IntegerField:整数类型,数值的范围-2147483648 ~ 2147483647
-
CharField:字符类型,必须提供max_length参数,max_length表示字符的长度
-
DateField:日期类型,日期格式为YYYY-MM-DD,相当于python中的datetine.date的实例
- auto_now_add=True:新增数据的时候回自动保存当前时间
- auto_now=True:新增、修改数据的时候会自动保存当前时间
-
DatetimeField:日期时间类型,格式为
YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
,相当于Python中的datetime.datetime的实例 eg:2019-03-22 00:00:00.000000 -
Django中的DateTimeField和DateField区别 https://blog.csdn.net/winfred_hua/article/details/82223946
-
BooleanField:布尔值类型
-
TextField:
一个容量很大的文本字段. admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框).
-
DecimalField:十进制小数
- 参数:
- max_digits:小数总长度
- decimal_places:小数位总长度
- 参数:
-
<10> ImageField 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field, 如果提供这两个参数,则图片将按提供的高度和宽度规格保存. <11> FileField 一个文件上传字段. 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, 该格式将被上载文件的 date/time 替换(so that uploaded files don't fill up the given directory). admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . 注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: (1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对 WEB服务器用户帐号是可写的. (2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. <12> URLField 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且 没有返回404响应). admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> NullBooleanField 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> SlugField "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在 以前的 Django 版本,没有任何办法改变50 这个长度. 这暗示了 db_index=True. 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate the slug, via JavaScript,in the object's admin form: models.SlugField (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField 一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径. <14> FilePathField 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的. 参数 描述 path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. Example: "/home/images". match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. 注意这个正则表达式只会应用到 base filename 而不是 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. 这三个参数可以同时使用. match 仅应用于 base filename, 而不是路径全名. 那么,这个例子: FilePathField(path="/home/images", match="foo.*", recursive=True) ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). <16> CommaSeparatedIntegerField 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
2.自定义字段
- 自定义一个char类型字段
class MyCharField(models.Field):
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
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
- 使用自定义char类型字段
class Class(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=25)
# 使用自定义的char类型的字段
cname = MyCharField(max_length=25)
12.7.3 字段参数
1.null:数据库中字段是否可以为空
1.blank:Admin中是否允许用户输入为空
2.default:数据库中字段的默认值
3.primary_key:数据库中字段是否为主键
4.db_index:数据库中字段是否可以建立索引
5.unique:数据库中字段是否可以建立唯一索引
6.verbose_name:备注
7.on_delete=models.CASCADE:删除关联数据,与之关联也删除,设置在一对一(OneToOneField)、一对多(ForeignKey)关系中
8.choices:admin中是否显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
- choices=((0, '女'), (1, '男') :可填写的内容和提示
12.7.4 Model Meta参数
-
在表对于的类中写入一个类Meta
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # admin中显示的表名称 verbose_name = '个人信息' # verbose_name加s verbose_name_plural = '所有用户信息' # 联合索引 index_together = [ ("pub_date", "deadline"), # 应为两个存在的字段 ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # 应为两个存在的字段
例子:
#主机信息表
class HostInformation(models.Model):
prov_id = models.CharField(max_length=32,verbose_name='省分代码')
host_ip = models.CharField(max_length=32,unique=True) #主机ip,字段唯一
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
date = models.DateTimeField(auto_now_add=True) #时间插入时自增
reserved_1 = models.CharField(max_length=32,null=True,blank=True) #保留字段,字段可以为空
reserved_2 = models.CharField(max_length=32,null=True,blank=True) #保留字段,字段可以为空
reserved_3 = models.CharField(max_length=32,null=True,blank=True) #保留字段,字段可以为空
def __str__(self):
return self.host_ip #返回一个字符串
class Meta:
verbose_name_plural = '主机信息表'
12.7.4.1 django时区问题
# datetime.datetime.now() / datetime.datetime.utcnow() => utc时间
# TIME_ZONE = 'UTC'
# datetime.datetime.now() - 东八区时间 / datetime.datetime.utcnow() => utc时间
TIME_ZONE = 'Asia/Shanghai'
# 影响自动生成数据库时间字段;
# USE_TZ = True,创建UTC时间写入到数据库。
# USE_TZ = False,根据TIME_ZONE设置的时区进行创建时间并写入数据库
USE_TZ = False
推荐用法:
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
12.7.5 ORM操作——必知必会13条
1.all():查询所有结果,返回QuerySet对象
2.get():返回与所给筛选条件相匹配的对象,返回结果只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误,返回单个对象
3.filter():返回所有符合条件的对象,返回QuerySet对象
4.exclude(): 返回所有不符合条件的对象,返回QuerySet对象
5.values(’字段‘):拿到对象指定的字段和字段的值,返回的是一个字典序列,返回QuerySet对象 [{'dmc_host': 904}, {'dmc_host': 905}, }
6.value_list('字段'):拿到对象指定的字段的值,返回一个元组序列,返回QuerySet对象
7.order_by():对查询结果排序,默认升序,字段前加负号则为降序,返回QuerySet对象
- order_by('age','-pid'):先按age字段升序排列,再按pid字段降序排列
8.reverse():对查询结果反向排序,只能对已经排序的queryset进行反转,返回queryset对象
9.distinct():对查询结果去重,完全相同的内容才能去重,返回queryset对象
10.count():计数,返回数据库中匹配查询的对象数量,返回数字
11.first():返回第一条记录,即取第一个元素,没有,返回NONE,返回单个对象
12.last():返回最后一条记录,即取最后一个元素,没有,返回None,返回单个对象
13.exist():判断查询的数据是否存在,存在返回True,否则返回False,返回布尔值
- extra():查询时,额外的多加一个字段(自定义)
例子:
result = models.Issues.objects.filter(project_id=project_id,
create_datetime__gte=today - datetime.timedelta(days=30)).extra(
select={'ctime': "strftime('%%Y-%%m-%%d',web_issues.create_datetime)"}).values('ctime').annotate(ct=Count('id')) #web_issues表名
# select xxx,1 as ctime from xxx
# select id,name, strftime("%Y-%m-%d",create_datetime) as ctime from table; ### sqlite
# "DATE_FORMAT(web_transaction.create_datetime,'%%Y-%%m-%%d')" ### mysql
总结:
# 返回的是queryset对象的
all()
filter()
exclude()
values()
values_list()
order_by()
reverse()
distinct()
# 返回的是单个对象的
get()
first()
last()
# 返回的是数字的
count()
# 返回的是布尔值的
exists()
12.7.6 单表查询的双下划线 条件判断
1.条件判断,相当于sql中的where
-
__gt:大于 表的主键默认pk
- ret = models.Person.objects.filter(pk__gt=1) 获取主键大于1的
-
__lt:小于
-
__gte:大于等于
-
__lte:小于等于
2.__range:范围查询,相当于sql的between..and..
- ret = models.Person.objects.filter(pk__range=[1,3]) #获取pk范围是1到3的
3.__in:成员判断
- ret = ret = models.Person.objects.filter(pk__in=[1,3])
- 获取pk等于1、3的,相当于SQL的 in
- ret = models.Person.objects.exclude(pk__in=[1,3])
- 获取pk不等于1和3的,相当于SQL的 not in
4.模糊查询:相当于sql的like 和正则匹配
- __contains:模糊查询
- ret = models.Person.objects.filter(name__contains='A')
- 获取name字段的值包含'A'的
- ret = models.Person.objects.filter(name__contains='A')
- __icontains:在contains的基础上,对条件中的字母大小写不敏感
- ret = models.Person.objects.filter(name__icontains='A')
- 获取name字段的值包含'A'或'a'的,忽略大小写
- ret = models.Person.objects.filter(name__icontains='A')
5.判断以...开头结尾
-
__startswith:以...开头
- ret = models.Person.object.filter(name__startswith='A') 获取name字段的值以'A'开头的
-
__istartswith:在startswith的基础上,对条件中的字母大小写不敏感
- ret = models.Person.objects.filter(name__istartswith='A')
- 获取name字段的值以'A'或'a'开头的,忽略大小写
- ret = models.Person.objects.filter(name__istartswith='A')
-
__endswith:以...结尾
- ret = models.Person.objects.filter(name__endswith='A')
- 获取name字段的值以'A'结尾的
- ret = models.Person.objects.filter(name__endswith='A')
-
__iendswith:在endswith的基础上,对条件中的字母大小写不敏感
- ret = models.Person.objects.filter(name__iendswith='A')
- 获取name字段的值以'A'或'a'结尾的,忽略大小写
- ret = models.Person.objects.filter(name__iendswith='A')
6.__year :判断日期年份
-
ret = models.Person.objects.filter(birth__year='2019')
- 获取birth字段的值的年份是2019的
-
_year只能筛选年份,如果要筛选年月日,使用contains:模糊查询
- ret = models.Person.objects.filter(birth__contains='2018-06-24')
- 获取birth字段的值是2018-06-24的
- ret = models.Person.objects.filter(birth__contains='2018-06-24')
7.__isnull:查询与null的项
-
__isnull = True:查询值是null的
- ret =models.Person.objects.filter(phone__isnull=True)
- 获取phone字段的值是null的
- ret =models.Person.objects.filter(phone__isnull=True)
-
__isnull = False:查询值不是null的
12.7.7 ForeignKey操作 跨表查询
#models
from django.db import models
# Create your models here.
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
age=models.IntegerField()
# 与AuthorDetail建立一对一的关系
authorDetail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE)
class AuthorDetail(models.Model):
nid = models.AutoField(primary_key=True)
birthday=models.DateField()
telephone=models.BigIntegerField()
addr=models.CharField( max_length=64)
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
city=models.CharField( max_length=32)
email=models.EmailField()
class Book(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField( max_length=32)
publishDate=models.DateField()
price=models.DecimalField(max_digits=5,decimal_places=2)
# 与Publish建立一对多的关系,外键字段建立在多的一方
pub=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
# 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
authors=models.ManyToManyField(to='Author',)
#多对一 (外键在多的一方)
多本书 一个出版社
书1 1
书2 1
书3 1
1.基于对象的查询
-------以建表时的字段为正向查询基准-------
- 正向查询,语法:对象.关联字段.字段
book_obj = models.Book.objects.get('title'='zzz')
book_obj.pub #找到建表时的关联字段
book_obj.pub.name #出版社的名字
-
反向查询。语法:对象.表名_set.all(),表名即类名小写
- 设置ForeignKey的参数:related_name,相当于替换了类名小写_set
pub_obj= models.Publish.objects.get(pk=1) # 没有指定related_name,使用类名小写_set pub_obj.book_set.all() # 指定related_name='books' pub_obj.books.all()
2.基于双下划线字段的查询
-
正向查询:语法:关联字段__字段,下划线意思是告诉django,我要跨表查询了
# 查询老男孩出版的书 ret = models.Book.objects.filter(pub__name = '老男孩出版社')
-
反向查询,语法:表名__字段
- 设置ForeignKey的参数:related_query_name,相当于替换了表名
# 查询出版菊花怪大战MJJ的出版社
# 没有指定related_name,使用类名的小写
ret= models.Publisher.objects.filter(book__title='菊花怪大战MJJ')
# related_name='books'
ret= models.Publisher.objects.filter(books__title='菊花怪大战MJJ')
# related_query_name='xxx'
ret= models.Publisher.objects.filter(xxx__title='菊花怪大战MJJ')
12.7.8 多对多的操作
-
基于对象的查询
- 正向查询,语法:对象.多对多字段.all()
mjj = models.Author.objects.get(pk=1) mjj.books # 关系管理对象 mjj.books.all()
- 反向查询,语法:对象.类名小写_set.all()
- 设置related_name,相当于替换了类名小写_set
book_obj = models.Book.objects.filter(title='桃花侠大战菊花怪').first() # 不指定related_name book_obj.author_set # 关系管理对象 book_obj.author_set.all() # 指定related_name='authors' book_obj.authors # 关系管理对象 book_obj.authors.all()
-
基于字段的查询
- 正向查询,语法:多对多字段__字段
- 反向查询,语法:类名小写__字段
- 设置related_query_name,相当于替换了类名小写
询alex出过的所有书籍的名字(多对多)
# 正向查询 按字段:authors:
queryResult=Book.objects
.filter(authors__name="yuan")
.values_list("title")
# 反向查询 按表名:book
queryResult=Author.objects
.filter(name="yuan")
.values_list("book__title","book__price")
3.关系管理对象的方法
- all():所关联的所有对象
mjj = models.Author.objects.get(pk=1)
mjj.books.all()
- set():设置多对多关系,更新
# set() [id,id] [对象,对象]
mjj.books.set([1,2]) #不需要加*,set会帮你清掉这个对象之前设置的关系
mjj.books.set(models.Book.objects.filter(pk__in=[1,2,3]))
- add():添加多对多关系
# add (id,id) (对象,对象)
mjj.books.add(4,5)
mjj.books.add(*[4,5]) # *[]打散
mjj.books.add(*models.Book.objects.filter(pk__in=[4,5]))
- remove():删除多对多关系
# remove (id,id) (对象,对象)
mjj.books.remove(4,5)
mjj.books.remove(*models.Book.objects.filter(pk__in=[4,5]))
- clear():清除所有多对多关系
mjj.books.clear()
- create():创建多对多关系
obj = mjj.books.create(title='跟MJJ学前端',pub_id=1)
book__obj = models.Book.objects.get(pk=1)
obj = book__obj.authors.create(name='taibai')
12.7.9 聚合和分组
1.聚合 aggregate
- 内置函数:Max(最大值)、Min(最小值)、Avg(平均值)、Sum(求和)、Count(计数)
from app01 import models
from django.db.models import Max,Avg,Sum,Count
#为聚合值指定名称avg,注意关键字传参要在位置传参后面
ret = models.Book.objects.filter(pk__gt=3).aggregate(Max('price'),avg=Avg('price'))
#结果是{'avg': 34.35}
2.分组
- 一般和聚合一起使用
#统计每一本书的作者个数,annotate:注释
ret = models.Book.objects.annotate(count=Count('author'))
#统计出每个出版社的最便宜的书的价格
#方式一 #values后面可接要查询的字段
ret = models.Publish.objects.annotate(Min('book__price')).values()
#方式二 objects后面加values,就是按照字段分组,相当于分组条件
ret =
models.Book.objects.values('pub_id').annotate(min=Min('price'))
12.7.10 F查询和Q查询
1.F查询 字段间的计算、比较
from django.db.models import F
#比较两个字段的值
ret = models.Book.objects.filter(sale__gt=F('kucun'))
#只更新sale字段
models.Book.objects.all().update(sale=100)
#只取某个字段的值进行计算
models.Book.objects.all().update(sale=F('sale')*2+10)
2.Q查询
- 条件符号: &(与)、|(或)、~(非)
from django.db.models import Q
ret = models.Book.objects.filter(~Q(Q(pk__gt=3) | Q(pk__lt=2)) & Q(price__gt=50))
print(ret)
12.7.11 事务
-
使用try进行回滚时,必须把try写在with的外面,否则无法回滚
from django.db import transaction try: with transaction.atomic(): # 进行一系列的ORM操作 models.Publisher.objects.create(name='xxxxx') models.Publisher.objects.create(name='xxx22') except Exception as e : print(e)
12.7.12 其他操作设置、环境配置
-
Django ORM执行原生SQL
# 执行原生SQL # 更高灵活度的方式执行原生SQL语句 from django.db import connection, connections cursor = connection.cursor() cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone()
-
Django终端打印SQL语句
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
-
在Python中脚本中调用Django环境
- 在要执行的py文件中写入
import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "orm_practice.settings") # orm_practice.settings表示当前项目的settings.py,orm_practice是当前项目名称 import django django.setup() from app01 import models # 再继续写要执行的语句,例如 books = models.Book.objects.all() print(books)
12.8 cookie 和 session
12.8.1 cookie
1.定义:保存在游览器本地上的一组键值对 一般在游览器端
2.特点:
- 由服务器让游览器进行设置
- 游览器保存在游览器本地
- 下次访问时自动携带
3.应用:
- 登录
- 保存游览习惯
- 简单的投票
4.使用cookie的原因:因为HTTP是无状态的,用cookie来保存状态。http的每一次请求都是独立的
5.在django中操作cookie
-
设置cookie:
#HttpResponse,render也可以 ret = ,redirect('/index/') ret.set_cookie('key',value,...) ret.set_singed_cookie('key',value,salt='加密盐',...)
-
参数:
- key,键
- value=’‘ ,值
- max_age =None,超出时间
- expires = None
- path = '/' ,设置cookie生效的路径,/表示根路径
- domain = None,cookie生效的域名
- secure = False,https传输
- httponly=False,只能http协议传输,无法被javascript获取
-
获取cookie
request.COOKIES.get('key') request.get_singed_cookie('key',salt='加密',default=RAISE_ERROR,max_age=None)
- 参数:
- default:默认值
- salt:加密盐
- max_age:后台控制cookie过期的时间
- 注意:获取时的加密盐要和设置时的加密盐相同,否则无法获取到正确的数据
- 参数:
-
删除cookie:
def logout(request): ret = redirect('/login/') ret.delete_cookie('key') #删除用户在游览器上之前设置的cookie值 return ret
6.cookie
from django.shortcuts import render, redirect, HttpResponse
from django.views import View
class Login(View):
def get(self, request, *args, **kwargs):
return render(request, 'login.html')
def post(self, request, *args, **kwargs):
username = request.POST.get('username')
pwd = request.POST.get('pwd')
if username == 'alex' and pwd == '123':
url = request.GET.get('return_url')
if url:
ret = redirect(url)
else:
ret = redirect('/index/')
# 设置 cookie
# ret['Set-Cookie'] = 'is_login=100; Path=/'
ret.set_cookie('is_login', '1') # 不加密的 Set-Cookie: is_login=1; Path=/
ret.set_signed_cookie('is_login', '1', 's21',max_age=10000,) # 加密的
return ret
return render(request, 'login.html', {'error': '用户名或密码错误'})
# 登录验证装饰器
def login_required(func):
def inner(request, *args, **kwargs):
# 获取 cookie
is_login = request.COOKIES.get('is_login') # 不加密的
is_login = request.get_signed_cookie('is_login', salt='s21', default='') # 加密的
print(is_login)
url = request.path_info
if is_login != '1':
return redirect('/login/?return_url={}'.format(url))
# 已经登录
ret = func(request, *args, **kwargs)
return ret
return inner
# 在需要在需要登录才能访问到页面的视图上加装饰器
@login_required
def index(request):
return HttpResponse('首页')
@login_required
def home(request):
return HttpResponse('home')
def logout(request):
ret = redirect('/login/')
ret.delete_cookie('is_login')
return ret
12.8.2 session
1.定义:保存在服务器上的一组组键值对(必须依赖cookie来使用)
ps: 游览器访问服务器时,服务器会生成一个随机字符串,返回给游览器(储存在游览器本地xxxid:12asdaef),这个随机字符串后面被当做session-key和session在服务器后台保存的一组组键值对存储在django-session中
2.使用seeion的原因:
- cookie保存在游览器本地,不安全
- cookie保存的大小受限制,4096字节
3.总结:cookie弥补了HTTP无状态的不足,但是cookie以文本的形式保存在游览器本地,自身安全性较差,所以我们通过cookie识别不同的用户,对应的在seeion里保存私密信息,以及超过4096字节的文本
4.在django中操作session
-
设置seeion:
request.session['key'] = value request.session.setdefault('key',value) #设置默认值,存在则不设置
-
获取seeion
request.session['key'] request.session.get('key',None)
-
删除session
del request.session['key']
-
其他操作:
# 所有 键、值、键值对 request.session.keys() request.session.values() request.session.items() # 会话session的key request.session.session_key # 将所有Session失效日期小于当前日期的数据删除 request.session.clear_expired() # 检查会话session的key在数据库中是否存在 request.session.exists("session_key") # 删除当前会话的所有Session数据 request.session.delete() # 删除当前的会话数据并删除会话的Cookie request.session.flush() # 这用于确保前面的会话数据不可以再次被用户的浏览器访问 # 例如,django.contrib.auth.logout() 函数中就会调用它 # 设置会话Session和Cookie的超时时间 request.session.set_expiry(value) # 如果value是个整数,session会在些秒数后失效 # 如果value是个datatime或timedelta,session就会在这个时间后失效 # 如果value是0,用户关闭浏览器session就会失效 # 如果value是None,session会依赖全局session失效策略
5.session版登陆验证
from django.shortcuts import render, redirect, HttpResponse from django.views import View class Login(View): def get(self, request, *args, **kwargs): return render(request, 'login.html') def post(self, request, *args, **kwargs): username = request.POST.get('username') pwd = request.POST.get('pwd') if username == 'alex' and pwd == '123': url = request.GET.get('return_url') if url: ret = redirect(url) else: ret = redirect('/index/') # 设置 session request.session['is_login'] = 1 # value可以设置为数字 # 设置会话Session和Cookie的超时时间,0表示用户关闭浏览器session就会失效 # request.session.set_expiry(0) return ret return render(request, 'login.html', {'error': '用户名或密码错误'}) # 登录验证装饰器 def login_required(func): def inner(request, *args, **kwargs): # 获取 session is_login = request.session.get('is_login') print(is_login) url = request.path_info if is_login != 1: return redirect('/login/?return_url={}'.format(url)) # 已经登录 ret = func(request, *args, **kwargs) return ret return inner # 在需要在需要登录才能访问到页面的视图上加装饰器 @login_required def index(request): # request.session.session_key:会话session的key request.session.clear_expired() # 将失效的数据删除 return HttpResponse('首页') @login_required def home(request): return HttpResponse('home') def logout(request): ret = redirect('/login/') request.session.delete() # 删除session数据 不删除cookie request.session.flush() # 删除session数据 并删除cookie return ret
6.django中的session配置
-
全局配置:from django.conf import global_settings
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 SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎 其他公用设置项: # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存的路径(默认) SESSION_COOKIE_PATH = "/" # Session的cookie保存的域名(默认) SESSION_COOKIE_DOMAIN = None # 是否Https传输cookie(默认) SESSION_COOKIE_SECURE = False # 是否Session的cookie只支持http传输(默认) SESSION_COOKIE_HTTPONLY = True # Session的cookie失效日期(2周)(默认) SESSION_COOKIE_AGE = 1209600 # 是否关闭浏览器使得Session过期(默认) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否每次请求都保存Session,默认修改之后才保存(默认) SESSION_SAVE_EVERY_REQUEST = False
12.9 文件上传 之ContentType
一、请求头ContentType
ContentType指的是请求体的编码类型,常见的类型共有3种:
1 application/x-www-form-urlencoded
这应该是最常见的 POST 提交数据的方式了。浏览器的原生
表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了):
POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
user=yuan&age=22
2 multipart/form-data
这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让
表单的 enctype 等于 multipart/form-data。直接来看一个请求示例:
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="user"
yuan
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
这个例子稍微复杂点。首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。关于 multipart/form-data 的详细定义,请前往 rfc1867 查看。
这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。
上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段标准中原生
表单也只支持这两种方式(通过 元素的 enctype 属性指定,默认为 application/x-www-form-urlencoded。其实 enctype 还支持 text/plain,不过用得非常少)。
随着越来越多的 Web 站点,尤其是 WebApp,全部使用 Ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,给开发带来更多便利。
3 application/json
application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。
JSON 格式支持比键值对复杂得多的结构化数据,这一点也很有用。记得我几年前做一个项目时,需要提交的数据层次非常深,我就是把数据 JSON 序列化之后来提交的。不过当时我是把 JSON 字符串作为 val,仍然放在键值对里,以 x-www-form-urlencoded 方式提交。
# 1.问题由来
在传递json数据的时候。通常都是使用`application/json`,并通过JSON.stringify(data)传换为json字符串来传递。
但是最近发现,如果把Content-Type改成`text/plain`,也可以传递json字符串,达到的效果是一样的。
# 2.问题
然后看了一下http请求体,发现两者传递数据都是 `Request Playload` 格式,两者并没有什么区别,为什么不使用常规的`text/plain`来传递json字符串呢?
求大神讲解。
答案:
传递数据设置为`application/json`,就是告诉请求的接收者,body体的数据格式是符合json格式的,接受者拿到这些数据后可以直接使用相应的格式化方法转换成处理语言识别的数据对象或者框架拦截器自动进行转换,能更早发现数据传递上的错误
如果直接通过`text/plain`传递,那么接收者需要自己执行判断怎么处理这个数据。
这个更多是前后台API接口的规范性的要求
12.9 Django与AJAX
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。
-
同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
-
异步请求:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
AJAX除了异步的特点外,还有一个就是:游览器局部刷新,(这一特点给用户的感受是在不知不觉中完成请求和响应的过程)
优点:
- AJAX使用Javascript技术向服务器发送异步请求
- AJAX无须刷新整个页面
12.9.1 基于jquery的ajax实现
# urls.py
urlpatterns = [
url(r'^ajax_add/', views.ajax_add),
url(r'^ajax_demo1/', views.ajax_demo1),
]
# views.py
from django.shortcuts import render,redirect,HttpResponse
from django.http import JsonResponse
def ajax_demo1(request):
return render(request, "ajax_demo1.html")
def ajax_add(request):
i1 = int(request.GET.get("i1"))
i2 = int(request.GET.get("i2"))
ret = i1 + i2
return JsonResponse(ret, safe=False)
{# ajax_demo1.html #}
<input type="text" id="i1">+
<input type="text" id="i2">=
<input type="text" id="i3">
<input type="button" value="AJAX提交" id="b1">
<script src="/static/jquery-3.2.1.min.js"></script>
<script>
$("#b1").on("click", function () {
$.ajax({
url:"/ajax_add/",
type:"GET",
data:{"i1":$("#i1").val(),"i2":$("#i2").val()},
success:function (data) {
$("#i3").val(data);
},
error:function (error) {
console.log(error)
},
})
})
</script>
-
$.ajax参数
-
data参数中的键值对,如果值不为字符串,需要将其转换成字符串类型
<script> $("#b1").on("click", function () { $.ajax({ url:"/ajax_add/", type:"GET", data:{"i1":$("#i1").val(),"i2":$("#i2").val(),"hehe": JSON.stringify([1, 2, 3])}, success:function (data) { $("#i3").val(data); } }) }) </script>
-
12.9.2 案例
1 用户名是否已被注册
在注册表单中,当用户填写了用户名后,把光标移开后,会自动向服务器发送异步请求。服务器返回true或false,返回true表示这个用户名已经被注册过,返回false表示没有注册过。客户端得到服务器返回的结果后,确定是否在用户名文本框后显示“用户名已被注册”的错误信息!
2 基于Ajax进行登录验证
用户在表单输入用户名与密码,通过Ajax提交给服务器,服务器验证后返回响应信息,客户端通过响应信息确定是否登录成功,成功,则跳转到首页,否则,在页面上显示相应的错误信息。
12.9.3 基于form表单的文件上传
模板部分
<form action="",method="post",enctype="mutlipart/form-data">
用户名<input type="text" name ="user">
头像 <input type="file" name="avatar">
<input type="submit">
</form>
视图部分
def index(request):
print(request.body) # 原始的请求体数据
print(request.GET) # GET请求数据
print(request.POST) # POST请求数据
print(request.FILES) # 上传的文件数据
return render(request,"index.html")
12.9.4 基于Ajax的文件上传
模板
<form>
用户名 <input type="text" id="user">
头像 <input type="file" id="avatar">
<input type="button" id="ajax-submit" value="ajax-submit">
</form>
<script>
$("#ajax-submit").click(function(){
var formdata=new FormData();
formdata.append("user",$("#user").val());
formdata.append("avatar_img",$("#avatar")[0].files[0]); ##取文件
$.ajax({
url:"",#(默认为当前页地址)发送请求的地址
type:"post", #(默认为get)请求方式post或get、注意:其他http请求方法,例如update和delete也可以使用,但仅部分浏览器支持。
data:formdata,
processData: false , // 不处理数据
contentType: false, // 不设置内容类型
success:function(data){
console.log(data)
}
})
})
</script>
视图
def index(request):
if request.is_ajax():
print(request.body) # 原始的请求体数据
print(request.GET) # GET请求数据
print(request.POST) # POST请求数据
print(request.FILES) # 上传的文件数据
return HttpResponse("ok")
return render(request,"index.html")
检查浏览器的请求头:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaWl9k5ZMiTAzx3FT
12.10 Django组件-forms组件
12.10.1 forms组件
校验字段功能
针对一个实例:注册用户讲解。
模型:models.py
class UserInfo(models.Model):
name=models.CharField(max_length=32)
pwd=models.CharField(max_length=32)
email=models.EmailField()
tel=models.CharField(max_length=32)
模板: register.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<div>
<label for="user">用户名</label>
<p><input type="text" name="name" id="name"></p>
</div>
<div>
<label for="pwd">密码</label>
<p><input type="password" name="pwd" id="pwd"></p>
</div>
<div>
<label for="r_pwd">确认密码</label>
<p><input type="password" name="r_pwd" id="r_pwd"></p>
</div>
<div>
<label for="email">邮箱</label>
<p><input type="text" name="email" id="email"></p>
</div>
<input type="submit">
</form>
</body>
</html>
视图函数:register
# forms组件
from django.forms import widgets
wid_01=widgets.TextInput(attrs={"class":"form-control"})
wid_02=widgets.PasswordInput(attrs={"class":"form-control"})
class UserForm(forms.Form):
name=forms.CharField(max_length=32,
widget=wid_01
)
pwd=forms.CharField(max_length=32,widget=wid_02)
r_pwd=forms.CharField(max_length=32,widget=wid_02)
email=forms.EmailField(widget=wid_01)
tel=forms.CharField(max_length=32,widget=wid_01)
def register(request):
if request.method=="POST":
form=UserForm(request.POST)
if form.is_valid():
#form.instance即为这个表的用户对象 form.instance.字段 = xxx,可在form.save()前为字段赋值xform.save()相当于create()操作,返回新增对象。
print(form.cleaned_data) # 所有干净的字段以及对应的值
else:
print(form.cleaned_data) #
print(form.errors) # ErrorDict : {"校验错误的字段":["错误信息",]}
print(form.errors.get("name")) # ErrorList ["错误信息",]
return HttpResponse("OK")
form=UserForm()
return render(request,"register.html",locals())
12.10.2 渲染标签功能
渲染方式1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<h3>注册页面</h3>
<div class="container">
<div class="row">
<div class="col-md-6 col-lg-offset-3">
<form action="" method="post">
{% csrf_token %}
<div>
<label for="">用户名</label>
{{ form.name }}
</div>
<div>
<label for="">密码</label>
{{ form.pwd }}
</div>
<div>
<label for="">确认密码</label>
{{ form.r_pwd }}
</div>
<div>
<label for=""> 邮箱</label>
{{ form.email }}
</div>
<input type="submit" class="btn btn-default pull-right">
</form>
</div>
</div>
</div>
</body>
</html>
渲染方式2
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
<div>
<label for="{{ field.auto_id }}">{{ field.label }}</label>
##<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>
{% endfor %}
<input type="submit" class="btn btn-default pull-right">
</form>
渲染方式3
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn btn-default pull-right">
</form>
12.10.3 显示错误与重置输入信息功能
视图
def register(request):
if request.method=="POST":
form=UserForm(request.POST)
if form.is_valid():
print(form.cleaned_data) # 所有干净的字段以及对应的值
else:
print(form.cleaned_data) #
print(form.errors) # ErrorDict : {"校验错误的字段":["错误信息",]}
print(form.errors.get("name")) # ErrorList ["错误信息",]
return render(request,"register.html",locals())
form=UserForm()
return render(request,"register.html",locals())
模板
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div>
<label for="">{{ field.label }}</label>
{{ field }} <span class="pull-right" style="color: red">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<input type="submit" class="btn btn-default">
</form>
12.10.4 局部钩子与全局钩子
模板
# forms组件
from django.forms import widgets
wid_01=widgets.TextInput(attrs={"class":"form-control"})
wid_02=widgets.PasswordInput(attrs={"class":"form-control"})
from django.core.exceptions import ValidationError
#先定义一个类UserForm
class UserForm(forms.Form):
name=forms.CharField(max_length=32,
widget=wid_01
)
pwd=forms.CharField(max_length=32,widget=wid_02)
r_pwd=forms.CharField(max_length=32,widget=wid_02)
email=forms.EmailField(widget=wid_01)
tel=forms.CharField(max_length=32,widget=wid_01)
# 局部钩子
def clean_name(self):
val=self.cleaned_data.get("name")
if not val.isdigit():
return val
else:
raise ValidationError("用户名不能是纯数字!")
# 全局钩子
def clean(self):
pwd=self.cleaned_data.get("pwd")
r_pwd=self.cleaned_data.get("r_pwd")
if pwd==r_pwd:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致!')
def register(request):
if request.method=="POST":
form=UserForm(request.POST)
if form.is_valid():
print(form.cleaned_data) # 所有干净的字段以及对应的值
else:
clean_error=form.errors.get("__all__")
return render(request,"register.html",locals())
form=UserForm()
return render(request,"register.html",locals())
视图
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div>
<label for="">{{ field.label }}</label>
{{ field }}
<span class="pull-right" style="color: red">
{% if field.label == 'R pwd' %}
<span>{{ clean_error.0 }}</span>
{% endif %}
{{ field.errors.0 }}
</span>
</div>
{% endfor %}
<input type="submit" class="btn btn-default">
</form>
示例:
from django.forms import ModelForm, Form
from rbac import models
from django import forms
from django.core.exceptions import ValidationError
class UserModelForm(forms.ModelForm):
"""
角色UserModelForm
"""
confirm_password = forms.CharField(max_length=32,label='确认密码')
class Meta:
model=models.Userinfo
# exclude =['roles']
fields=['name','email','password','confirm_password']
def __init__(self, *args, **kwargs):
super(UserModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = field.label
def clean_name(self):
name = self.cleaned_data.get('name')
name_obj = models.Userinfo.objects.filter(name=name).first()
if not name_obj:
return self.cleaned_data['name']
else:
raise ValidationError('用户名已存在')
#全局钩子,全局的错误信息
def clean(self):
pwd = self.cleaned_data.get("password")
re_pwd = self.cleaned_data.get("confirm_password")
print(pwd,re_pwd)
if pwd and re_pwd:
if pwd != re_pwd: #如果一致还把原来的cleaned_data返回
raise ValidationError('两次密码不一致!')
else:return self.cleaned_data
views.py
def user_add(request):
"""
添加用户
:param request:
:return:
"""
if request.method == 'POST':
form = UserModelForm(request.POST) #
if form.is_valid():
form.save() #如果验证通过,直接加到数据库了
return redirect(reverse('rbac:user_list')) #反向解析
else:
clean_error = form.errors.get("__all__") #全局错误
return render(request, 'rbac/role_change.html', {'form': form,'clean_error':clean_error})
#get
form=UserModelForm()
# print(form)
return render(request, 'rbac/role_change.html', {'form':form})
django modelform 参考资料
需要request参数,可以加上,在初始化init中接受request参数
form = UserModelForm() 生成表单
form = UserModelForm(data=request.POST) #验证
form = UserModelForm(instance=obj) #编辑的时候 保留原有数据
ModelForm使用:
https://www.cnblogs.com/hanfe1/p/12335319.html
https://www.cnblogs.com/zhufanyu/p/11953623.html
理解ModelForm/Form如何清洗表单里的数据的 源码分析:
https://blog.csdn.net/qq_38327772/article/details/105728086
12.11 Django组件-用户认证 auth
一、auth模块
from django.contrib import auth
django.contrib.auth中提供了许多方法,这里主要介绍其中的三个:
1.1 、authenticate()
提供了用户认证,即验证用户名以及密码是否正确,一般需要username password两个关键字参数
如果认证信息有效,会返回一个 User 对象。authenticate()会在User 对象上设置一个属性标识那种认证后端认证了该用户,且该信息在后面的登录过程中是需要的。当我们试图登陆一个从数据库中直接取出来不经过authenticate()的User对象会报错的!!
user = authenticate(username='someone',password='somepassword')
1.2 、login(HttpRequest, user)
该函数接受一个HttpRequest对象,以及一个认证了的User对象
此函数使用django的session框架给某个已认证的用户附加上session id等信息。
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...
1.3 、logout(request) 注销用户
from django.contrib.auth import logout
def logout_view(request):
logout(request)
# Redirect to a success page.
该函数接受一个HttpRequest对象,无返回值。当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
二、User对象
User 对象属性:username, password(必填项)password用哈希算法保存到数据库
2.1 、user对象的 is_authenticated()
如果是真正的 User 对象,返回值恒为 True 。 用于检查用户是否已经通过了认证。
通过认证并不意味着用户拥有任何权限,甚至也不检查该用户是否处于激活状态,这只是表明用户成功的通过了认证。 这个方法很重要, 在后台用request.user.is_authenticated()判断用户是否已经登录,如果true则可以向前台展示request.user.name
要求:
1 用户登陆后才能访问某些页面,
2 如果用户没有登录就访问该页面的话直接跳到登录页面
3 用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址
方法1:
def my_view(request):
if not request.user.is_authenticated():
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
方法2:
django已经为我们设计好了一个用于此种情况的装饰器:login_requierd()
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
...
若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' (这个值可以在settings文件中通过LOGIN_URL进行修改)。并传递 当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
2.2 、创建用户
使用 create_user 辅助函数创建用户:
python manage.py createsuperuser
from django.contrib.auth.models import User
user = User.objects.create_user(username='',password='',email='')
2.3 、check_password(passwd)
用户需要修改密码的时候 首先要让他输入原来的密码 ,如果给定的字符串通过了密码检查,返回 True
2.4 、修改密码
使用 set_password() 来修改密码
user = User.objects.get(username='')
user.set_password(password='')
user.save
2.5 、简单示例
注册:
def sign_up(request):
state = None
if request.method == 'POST':
password = request.POST.get('password', '')
repeat_password = request.POST.get('repeat_password', '')
email=request.POST.get('email', '')
username = request.POST.get('username', '')
if User.objects.filter(username=username):
state = 'user_exist'
else:
new_user = User.objects.create_user(username=username, password=password,email=email)
new_user.save()
return redirect('/book/')
content = {
'state': state,
'user': None,
}
return render(request, 'sign_up.html', content)
修改密码:
@login_required
def set_password(request):
user = request.user
state = None
if request.method == 'POST':
old_password = request.POST.get('old_password', '')
new_password = request.POST.get('new_password', '')
repeat_password = request.POST.get('repeat_password', '')
if user.check_password(old_password):
if not new_password:
state = 'empty'
elif new_password != repeat_password:
state = 'repeat_error'
else:
user.set_password(new_password)
user.save()
return redirect("/log_in/")
else:
state = 'password_error'
content = {
'user': user,
'state': state,
}
return render(request, 'set_password.html', content)
12.12 Django组件-分页器(paginator)
view
from django.shortcuts import render,HttpResponse
# Create your views here.
from app01.models import *
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def index(request):
'''
批量导入数据:
Booklist=[]
for i in range(100):
Booklist.append(Book(title="book"+str(i),price=30+i*i))
Book.objects.bulk_create(Booklist)
'''
'''
分页器的使用:
book_list=Book.objects.all()
paginator = Paginator(book_list, 10)
print("count:",paginator.count) #数据总数
print("num_pages",paginator.num_pages) #总页数
print("page_range",paginator.page_range) #页码的列表
page1=paginator.page(1) #第1页的page对象
for i in page1: #遍历第1页的所有数据对象
print(i)
print(page1.object_list) #第1页的所有数据
page2=paginator.page(2)
print(page2.has_next()) #是否有下一页
print(page2.next_page_number()) #下一页的页码
print(page2.has_previous()) #是否有上一页
print(page2.previous_page_number()) #上一页的页码
# 抛错
#page=paginator.page(12) # error:EmptyPage
#page=paginator.page("z") # error:PageNotAnInteger
'''
book_list=Book.objects.all()
paginator = Paginator(book_list, 10)
page = request.GET.get('page',1)
currentPage=int(page)
try:
print(page)
book_list = paginator.page(page)
except PageNotAnInteger:
book_list = paginator.page(1)
except EmptyPage:
book_list = paginator.page(paginator.num_pages)
return render(request,"index.html",{"book_list":book_list,"paginator":paginator,"currentPage":currentPage})
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h4>分页器</h4>
<ul>
{% for book in book_list %}
<li>{{ book.title }} -----{{ book.price }}</li>
{% endfor %}
</ul>
<ul class="pagination" id="pager">
{% if book_list.has_previous %}
<li class="previous"><a href="/index/?page={{ book_list.previous_page_number }}">上一页</a></li>
{% else %}
<li class="previous disabled"><a href="#">上一页</a></li>
{% endif %}
{% for num in paginator.page_range %}
{% if num == currentPage %}
<li class="item active"><a href="/index/?page={{ num }}">{{ num }}</a></li>
{% else %}
<li class="item"><a href="/index/?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if book_list.has_next %}
<li class="next"><a href="/index/?page={{ book_list.next_page_number }}">下一页</a></li>
{% else %}
<li class="next disabled"><a href="#">下一页</a></li>
{% endif %}
</ul>
</div>
</body>
</html>
扩展
def index(request):
book_list=Book.objects.all()
paginator = Paginator(book_list, 15)
page = request.GET.get('page',1)
currentPage=int(page)
# 如果页数十分多时,换另外一种显示方式
if paginator.num_pages>11:
if currentPage-5<1:
pageRange=range(1,11)
elif currentPage+5>paginator.num_pages:
pageRange=range(currentPage-5,paginator.num_pages+1)
else:
pageRange=range(currentPage-5,currentPage+5)
else:
pageRange=paginator.page_range
try:
print(page)
book_list = paginator.page(page)
except PageNotAnInteger:
book_list = paginator.page(1)
except EmptyPage:
book_list = paginator.page(paginator.num_pages)
return render(request,"index.html",locals())
12.13 Django组件-中间件
一、中间件的概念
中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。
Django的中间件的定义:
Middleware is a framework of hooks into Django’s request/response processing. <br>It’s a light, low-level “plugin” system for globally altering Django’s input or output.
如果你想修改请求,例如被传送到view中的HttpRequest对象。 或者你想修改view返回的HttpResponse对象,这些都可以通过中间件来实现。
可能你还想在view执行之前做一些操作,这种情况就可以用 middleware来实现。
Django默认的Middleware:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
每一个中间件都有具体的功能。
二、自定义中间件
中间件中一共有四个方法:
process_request
process_view
process_exception
process_response
process_request,process_response
如果return None,表示中间件通过。
当用户发起请求的时候会依次经过所有的的中间件,这个时候的请求时process_request,最后到达views的函数中,views函数处理后,在依次穿过中间件,这个时候是process_response,最后返回给请求者。
上述截图中的中间件都是django中的,我们也可以自己定义一个中间件,我们可以自己写一个类,但是必须继承MiddlewareMixin
需要导入
from django.utils.deprecation import MiddlewareMixin
in views:
def index(request):
print("view函数...")
return HttpResponse("OK")
in Mymiddlewares.py:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class Md1(MiddlewareMixin):
def process_request(self,request):
print("Md1请求")
def process_response(self,request,response):
print("Md1返回")
return response
class Md2(MiddlewareMixin):
def process_request(self,request):
print("Md2请求")
#return HttpResponse("Md2中断")
def process_response(self,request,response):
print("Md2返回")
return response
结果:
Md1请求
Md2请求
view函数...
Md2返回
Md1返回
注意:如果当请求到达请求2的时候直接不符合条件返回,即return HttpResponse("Md2中断"),程序将把请求直接发给中间件2返回,然后依次返回到请求者,结果如下:
返回Md2中断的页面,后台打印如下:
Md1请求
Md2请求
Md2返回
Md1返回
流程图如下:
process_view
process_view(self, request, callback, callback_args, callback_kwargs)
Mymiddlewares.py修改如下
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class Md1(MiddlewareMixin):
def process_request(self,request):
print("Md1请求")
#return HttpResponse("Md1中断")
def process_response(self,request,response):
print("Md1返回")
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print("Md1view")
class Md2(MiddlewareMixin):
def process_request(self,request):
print("Md2请求")
return HttpResponse("Md2中断")
def process_response(self,request,response):
print("Md2返回")
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print("Md2view")
结果如下:
Md1请求
Md2请求
Md1view
Md2view
view函数...
Md2返回
Md1返回
下图进行分析上面的过程:
当最后一个中间的process_request到达路由关系映射之后,返回到中间件1的process_view,然后依次往下,到达views函数,最后通过process_response依次返回到达用户。
process_view可以用来调用视图函数:
class Md1(MiddlewareMixin):
def process_request(self,request):
print("Md1请求")
#return HttpResponse("Md1中断")
def process_response(self,request,response):
print("Md1返回")
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
# return HttpResponse("hello")
response=callback(request,*callback_args,**callback_kwargs)
return response
结果如下:
Md1请求
Md2请求
view函数...
Md2返回
Md1返回
注意:process_view如果有返回值,会越过其他的process_view以及视图函数,但是所有的process_response都还会执行。
process_exception
process_exception(self, request, exception)
示例修改如下:
class Md1(MiddlewareMixin):
def process_request(self,request):
print("Md1请求")
#return HttpResponse("Md1中断")
def process_response(self,request,response):
print("Md1返回")
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
# return HttpResponse("hello")
# response=callback(request,*callback_args,**callback_kwargs)
# return response
print("md1 process_view...")
def process_exception(self):
print("md1 process_exception...")
class Md2(MiddlewareMixin):
def process_request(self,request):
print("Md2请求")
# return HttpResponse("Md2中断")
def process_response(self,request,response):
print("Md2返回")
return response
def process_view(self, request, callback, callback_args, callback_kwargs):
print("md2 process_view...")
def process_exception(self):
print("md1 process_exception...")
结果如下:
Md1请求
Md2请求
md1 process_view...
md2 process_view...
view函数...
Md2返回
Md1返回
流程图如下:
当views出现错误时:
将md2的process_exception修改如下:
def process_exception(self,request,exception):
print("md2 process_exception...")
return HttpResponse("error")
结果如下:
Md1请求
Md2请求
md1 process_view...
md2 process_view...
view函数...
md2 process_exception...
Md2返回
Md1返回
5个方法4个特点
process_request(self,request):
执行时间:
路由匹配之前,process_view方法前
参数:
request 请求的对象 ,和后续的request都是同一个
执行顺序:
按照注册的顺序 顺序 执行
返回值:
None 正常流程,中间件不处理跳过
HttpResponse对象 当前中间件之后的中间件中的process_request、路由匹配、process_view、视图都不执行,直接去执行当前中间件的process_response方法
process_response(self,request,response):
执行时间:
视图之后执行
参数:
request 请求的对象 ,和后续的request都是同一个
response 返回给浏览器的响应对象
执行顺序:
按照注册的顺序 倒序 执行
返回值:
HttpResponse对象 必须返回
process_view(self,request,view_func,view_args,view_kwargs):
执行时间:
路由之后,视图之前执行
参数:
request 请求的对象 ,和后续的request都是同一个
view_func 视图函数
view_args 视图函数所需的位置参数
view_kwargs 视图函数所需的关键字参数
执行顺序:
按照注册的顺序 顺序 执行
返回值:
None 正常流程
HttpResponse对象 当前中间件之后的中间件中的process_view、视图都不执行,直接去执行最后一个中间件的process_response方法
process_exception(self,request,exception):
执行时间(触发条件):
视图中有异常时才执行
参数:
request 请求的对象 ,和后续的request都是同一个
exception 异常的对象
执行顺序:
按照注册的顺序 倒序 执行
返回值:
None 当前的中间件没有处理异常,交于下一个中间处理,如果所有的都没有处理,django处理异常
HttpResponse对象 当前的中间件处理了异常,后面要执行的process_exception方法就不执行了,执行最后一个中间件的process_response方法
process_template_response(self,request,response):
执行时间(触发条件):
视图返回TemplateResponse对象
参数:
request 请求的对象 ,和后续的request都是同一个
response TemplateResponse对象
执行顺序:
按照注册的顺序 倒序 执行
返回值:
HttpResponse对象 必须返回
处理对象
response.template_name 模板的名字
response.context_data 模板渲染的变量 {}