第十二章 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服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据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
12.1.4 HTTP状态码
- 状态代码的第一个数字代表当前响应的类型:
- 1xx消息——请求已被服务器接收,继续处理
- 2xx成功——请求已成功被服务器接收、理解、并接受
- 3xx重定向——需要后续操作才能完成这一请求
- 4xx请求错误——请求含有词法错误或者无法被执行
- 5xx服务器错误——服务器在处理某个正确请求时发生错误
- 常见的:"200 OK","404 Not Found"
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.1.6 HTTP请求格式
- 请求(request)
- 浏览器:服务器
- GET 请求没有请求数据
- 格式:
请求方式 url路径 协议版本\r\n
k1:v1\r\n
k2:v2\r\n
\r\n
数据
- 一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接
- 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成
- Web服务器解析请求,定位请求资源,服务器将资源复本写到TCP套接字,由客户端读取,一个响应由状态行、响应头部、空行和响应数据4部分组成
- 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
- 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码,然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集,客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示
- 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址
- 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接
- 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
- 服务器对浏览器请求作出响应,并把对应的html文本发送给浏览器
- 释放TCP连接
- 浏览器将该html文本并显示内容
- 1xx消息——请求已被服务器接收,继续处理
- 2xx成功——请求已成功被服务器接收、理解、并接受
- 3xx重定向——需要后续操作才能完成这一请求
- 4xx请求错误——请求含有词法错误或者无法被执行
- 5xx服务器错误——服务器在处理某个正确请求时发生错误
- 传送协议
- 层级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,是查询
- 浏览器:服务器
- GET 请求没有请求数据
请求方式 url路径 协议版本\r\n k1:v1\r\n k2:v2\r\n \r\n 数据
12.1.7 HTTP响应格式
- 响应(response)
- 服务器:浏览器
- 格式:
协议版本 状态码 状态码描述\r\n k1:v1\r\n k2:v2\r\n \r\n 响应数据(响应体)
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>
- 补充:time.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>
- 补充:index2.html
12.3 Django基本知识
12.3.1 安装及使用
- 下载安装
- 命令行:pip3 install django==1.11.21
- pycharm
- 创建项目
- 命令行:
- 找一个文件夹存放项目文件,打开终端:
- django-admin startproject 项目名称
- 项目目录
- pycahrm
- 命令行:
- 启动
- 命令行
- 切换到项目的根目录下 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
- 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"> {# 别名开头 #}
12.3.3 简单的登录实例
- form表单提交数据注意的问题:
- 提交的地址:action="",请求的方式:method="post"
- 所有的input框有name属性,如name="username"
- 有一个input框的type="submit"或者有一个button
- 提交post请求,由于Django中有一个csrf校验,所有请求会出问题
- 解决方式:把settings中MIDDLEWARE的'django.middleware.csrf.CsrfViewMiddleware'注释掉
- 重定向
- 导入方式
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 变量
- 符号:{{ }}
- 表示变量,在模板渲染的时候替换成值
- 使用方式:{{ 变量名 }}:变量名由字母数字和下划线组成
- 点(.)在模板语言中有特殊的含义,用来获取对象的相应属性值
- 注意:当模板系统遇到一个(.)时,会按照如下的顺序去查询:
- 在字典中查询
- 属性或者方法
- 数字索引
12.4.1.2 内置filter
- filter:过滤器,用来修改变量的显示结果
- 语法: {{ value|filter_name:参数 }}
- ':'左右没有空格没有空格没有空格
- 内置过滤器
- default:默认值
- 语法:{{ value|default:"nothing"}}
- 如果value值没传的话就显示nothing
- 补充:TEMPLATES的OPTIONS可以增加一个选项:string_if_invalid:'找不到',可以替代default的的作用
- filesizeformat:文件大小,将值格式化为可读的文件尺寸
- 语法:{{ value|filesizeformat }}
- 如,value=123456789,输出将会是 117.7 MB
- add:给变量加参数
- 语法:{{ first|add:second }}
- 优先看是否能转化为数字相加,其次是字符串拼接
- 如果都是列表,相当于extend,循环加入
- length:返回长度
- 语法:{{ value|length }}
- 返回value的长度,如,value=['a', 'b', 'c', 'd']的话,就显示4
- slice:切片
- 语法:{{value|slice:"2:-1"}}
- first / last:取第一个/最后一个元素
- 语法:
- 取第一个元素:{{ value|first }}
- 取最后一个元素:{{ value|last }}
- join:使用字符串拼接列表
- 语法:{{ value|join:" // " }}
- truncatechars:截断,按照字符计数
- truncatewords:按照字母计数,不能识别中文
- 如果字符串字符多于指定的字符数量,那么会被截断
- 截断的字符串将以可翻译的省略号序列(...)结尾
- 参数:截断的字符
- 语法:{{ 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>"
- default:默认值
12.4.1.3 自定义filter
- 在app下创建一个名为templatetags的python包
- 在templatetags中创建py文件,文件名自定义(my_tags.py);
- 在py文件中写:
from django import template register = template.Library() # register也不能变
- 写函数+装饰器
@register.filter def add_xx(value, arg): # 最多有两个 return '{}-{}'.format(value, arg) @register.filter(name='adds') # 相当于更改了函数名,使用时,使用新的函数名
- 在模板文件中使用,html文件
{% load my_tags %} {{ 'alex'|add_xx:'dsb' }}
- 注意:
- 为避免出错,templatetags最好是一个Python包,并且名称不能更改
- register名称也不能更改,必要时需要重启项目
- Python包下的init中可能有其他内容django不能识别,导致出错,可以直接删除内容
12.4.1.4 标签tag
- for循环
<ul> {% for user in user_list %} <li>{{ user.name }}</li> {% endfor %} </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 %}
- 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
- 模板中,不支持连续连续判断 也不支持算数运算(可使用过滤器)
- with:给变量重命名,但只在with区域内生效
{% with hobby.2 as talk %} {# 相当于 {% with talk=hobby.2 %},其中=两边不能有空格 #} {{ talk }} {% endwith %}
- csrf_token
- 该标签用于跨站请求伪造保护
- csrf:跨站请求伪造
- 使用方式:在form表单中写上{% csrf_token %}
- 这样就不用在settings中注释含csrf的中间件了
12.4.1.5 注释
- 符号:{# 要注释的内容 #}
- 快捷键:Ctrl + ?
{# 要注释的内容 #}
12.4.2 母板和继承
12.4.2.1 母板
- 母板就是一个普通的html,提取多个页面的公共部分,通过定义block块来实现
<!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 %} </head> <body> <h1>这是母板的标题</h1> {% block page-main %} {% endblock %} <h1>母板底部内容</h1> {% block page-js %} {% endblock %} </body> </html>
- block块:
{% block 块名 %} {% endblock %}
- 注意:我们通常会在母板中定义页面专用的CSS块和JS块,方便子页面替换
12.4.2.2 继承
- 子页面继承母板:{% extends '母板html' %}
{% extends 'layouts.html' %}
- 子页面通过重写block块,来替换母板中相应的内容
{% block page-main %} <p>世情薄</p> <p>人情恶</p> <p>雨送黄昏花易落</p> {% endblock %}
12.4.2.3 注意
- {% extends 'base.html' %}要写在第一行,前面不要有内容,否则内容会显示出来
- {% extends 'base.html' %}中的'base.html' 必须加上引号,不然会被当做变量去查找
- 子页面把要显示的内容写在block块中,否则不会显示出来
- 多个位置有独特内容,可以定义多个block块,特殊:定义css、js块等
12.4.3 组件
- 组件:一小段html代码段
- 可以将常用的页面内容如导航条,页尾信息等组件保存在单独的文件中,然后在需要使用的地方导入
{% 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类似,区别:接收的参数更灵活,能接收万能参数
- 定义注册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 %}
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的流程
- 项目启动加载ur.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)
- 返回响应 --> 浏览器
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对象
- 注意:之后取request对象做操作时,注意使用装饰器的方式,对应取request对象的方式也不同
- 使用@timer和使用@method_decorator(timer)的区别:
12.5.3 request对象
- 属性:
- request.methot:当前请求方式,GET/POST
- request.GET:url上携带的参数
- request.POST:POST请求提交的数据
- request.path_info:url的路径,不包含ip和端口,不包含参数
- request.body:请求体,byte类型,request.POST的数据是从boby里提取的
- request.FILES:上传的文件,类似字典
- request.META:请求头,标准的Python字典,包含左右HTTP首部
- request.COOKIES:cookie
- request.session:session
- 方法:
- request.get_host():获取主机的ip和端口
- request.get_full_path():url的路径,不包含ip和端口,包含参数
- request.is_ajax():判断是否是ajax请求,是返回Ture,否返回False
- 上传文件示例:
- urls.py
url(r'^upload/', views.upload)
- 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')
- upload.html
<form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <input type="file" name="f1"> <button>上传</button> </form>
12.5.4 response对象
- Response对象:render, redirect, HttpResponse
from django.shortcuts import render, redirect, HttpResponse HttpResponse('字符串') # 返回字符串 render(request,'模板的文件名',{k1:v1}) # 返回一个完整的HTML页面 redirect('重定向的地址') # 返回重定向,Location:地址
- 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)
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的第三个参数时一个字典,表示想要传递给视图函数的额外关键字参数
- UPLconf匹配的位置:
- 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值
- 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')), ]
12.6.4 URL的命名和反向解析
12.6.4.1 URL命名
- url的第四个参数是起一个别名,即一个可选的name参数
- 命名方式: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')) # 完整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'}) # 完整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 基本内容
- 定义:面向对象和关系型数据库的一种映射,通过操作对象的方式操作数据
- 对应关系:
- 类对应数据表
- 对象对应数据行(记录)
- 属性对应字段
ORM | DB |
---|---|
类 | 数据表 |
对象 | 数据行 |
属性 | 字段 |
- 导入:from app01 import models
- 查:
- models.Publisher.objects.all()
- 查询所有的数据,queryset:对象列表
- models.Publisher.objects.get(name='xxxx')
- 对象,获取一个对象(有且唯一),获取不到或者获取到多个对象会报错
- models.Publisher.objects.filter(name='xxxx')
- 获取满足条件的所有的对象,queryset:对象列表
- models.Publisher.objects.all()
- 增:
- models.Publisher.objects.create(name='xxx')
- 新插入数据库的对象
- obj = models.Publisher(name='xxx')
- 存在在内存中的对象
- obj.save()
- 提交到数据库中,新增
- models.Publisher.objects.create(name='xxx')
- 删:
- obj = models.Publisher.objects.get(pk=1)
- obj.delete()
- obj_list = models.Publisher.objects.filter(pk=1)
- obj_list.delete()
- obj = models.Publisher.objects.get(pk=1)
- 改:
- obj = models.Publisher.objects.get(pk=1)
- obj.name = 'new name'
- 在内存中修改对象的属性
- obj.save()
- 提交数据,保存到数据库中
12.7.2 字段
- 常用字段
- AutoField:自增的整型字段,必填参数primary_key=True
- 注意:一个model不能有两个AutoField字段
- IntegerField:整数类型,数值的范围是 -2147483648 ~ 2147483647
- CharField:字符类型,必须提供max_length参数,max_length表示字符的长度
- DateField:日期类型,日期格式为YYYY-MM-DD,相当于Python中的datetime.date的实例
- 参数:
- auto_now_add=True:新增数据的时候会自动保存当前的时间
- auto_now=True:新增、修改数据的时候会自动保存当前的时间
- DatetimeField:日期时间类型,格式为
YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
,相当于Python中的datetime.datetime的实例 - BooleanField:布尔值类型
- TextField:文本类型
- DecimalField:十进制小数
- 参数:
- max_digits:小数总长度
- decimal_places:小数位总长度
- AutoField:自增的整型字段,必填参数primary_key=True
- 自定义字段
- 自定义一个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 字段参数
- null:数据库中字段是否可以为空
- default:数据库中字段的默认值
- primary_key:数据库中字段是否为主键
- db_index:数据库中字段是否可以建立索引
- unique:数据库中字段是否可以建立唯一索引
- blank:Admin中是否允许用户输入为空
- 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"),) # 应为两个存在的字段
12.7.5 ORM操作——必知必会13条
- all():查询所有结果,返回QuerySet对象
- get():返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误,返回单个对象
- filter():返回所有符合条件的对象,返回QuerySet对象
- exclude():返回所有不符合条件的对象,返回QuerySet对象
- values('字段'):拿到对象指定的字段和字段的值,返回的是一个字典序列,返回QuerySet对象
- values_list('字段'):拿到对象指定的字段的值,返回的是一个元组序列,返回QuerySet对象
- order_by():对查询结果排序,默认升序,字段前加负号则为降序,返回QuerySet对象
- order_by('age','-pid'):先按age字段升序排列,再按pid字段降序排列
- reverse():对查询结果反向排序,只能对已经排序的QuerySet进行反转,返回QuerySet对象
- distinct():对查询结果进行去重,完全相同的内容才能去重,返回QuerySet对象
- count():计数,返回数据库中匹配查询的对象数量,返回数字
- first():返回第一条记录,即取第一个元素,没有返回None,返回单个对象
- last():返回最后一条记录,即取最后一个元素,没有返回None,返回单个对象
- exist():判断查询的数据是否存在,存在返回True,否则返回False,返回布尔值
- 总结:
# 返回的是queryset对象的 all() filter() exclude() values() values_list() order_by() reverse() distinct() # 返回的是单个对象的 get() first() last() # 返回的是数字的 count() # 返回的是布尔值的 exists()
12.7.6 单表查询的双下划线
- 条件判断,相当于SQL中的while
- __gt:大于
- ret = models.Person.objects.filter(pk__gt=1)
- 获取pk大于1的
- __lt:小于
- ret = models.Person.objects.filter(pk__lt=3)
- 获取pk小于3的
- __gte:大于等于
- ret = models.Person.objects.filter(pk__gte=1)
- 获取pk大于等于1的
- __lte:小于等于
- ret = models.Person.objects.filter(pk__lte=3)
- 获取pk小于等于3的
- __gt:大于
- __range:范围查询,相当于SQL的between...and...
- ret = models.Person.objects.filter(pk__range=[1,3])
- 获取pk范围是1到3的
- ret = models.Person.objects.filter(pk__range=[1,3])
- __in:成员判断
- 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
- ret = models.Person.objects.filter(pk__in=[1,3])
- 模糊查询:相当于SQL的 like 和正则匹配
- __contains:模糊查询
- ret = models.Person.objects.filter(name__contains='A')
- 获取name字段的值包含'A'的
- __icontains:在contains的基础上,对条件中的字母大小写不敏感
- ret = models.Person.objects.filter(name__icontains='A')
- 获取name字段的值包含'A'或'a'的,忽略大小写
- __contains:模糊查询
- 判断以...开头/结尾
- __startswith:以...开头
- ret = models.Person.objects.filter(name__startswith='A')
- 获取name字段的值以'A'开头的
- __istartswith:在startswith的基础上,对条件中的字母大小写不敏感
- ret = models.Person.objects.filter(name__istartswith='A')
- 获取name字段的值以'A'或'a'开头的,忽略大小写
- __endswith:以...结尾
- ret = models.Person.objects.filter(name__endswith='A')
- 获取name字段的值以'A'结尾的
- __iendswith:在endswith的基础上,对条件中的字母大小写不敏感
- ret = models.Person.objects.filter(name__iendswith='A')
- 获取name字段的值以'A'或'a'结尾的,忽略大小写
- __startswith:以...开头
- __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__year='2019')
- __isnull:查询与null的项
- __isnull = True:查询值是null的
- ret = models.Person.objects.filter(phone__isnull=True)
- 获取phone字段的值是null的
- __isnull = False:查询值不是null的
- __isnull = True:查询值是null的
12.7.7 ForeignKey操作
- 基于对象的查询
- 正向查询,语法:对象.关联字段.字段
book_obj = models.Book.objects.get(title='菊花怪大战MJJ') book_obj.pub book_obj.pub.name
- 反向查询,语法:对象.表名_set.all(),表名即类名小写
- 设置ForeignKey的参数:related_name,相当于替换了类名小写_set
pub_obj = models.Publisher.objects.get(pk=1) # 没有指定related_name,使用类名小写_set pub_obj.book_set.all() # 指定related_name='books' pub_obj.books.all()
- 基于字段的查询
- 正向查询,语法:关联字段__字段
# 查询老男孩出版的书 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()
- 基于字段的查询
- 正向查询,语法:多对多字段__字段
ret = models.Author.objects.filter(books__title='菊花怪大战MJJ')
- 反向查询,语法:类名小写__字段
- 设置related_query_name,相当于替换了类名小写
# 不指定related_name ret = models.Book.objects.filter(author__name='MJJ') # 指定related_name='authors' ret = models.Book.objects.filter(authors__name='MJJ') # 指定related_query_name='xxx' ret = models.Book.objects.filter(xxx__name='MJJ')
- 关系管理对象的方法
- all():所关联的所有对象
mjj = models.Author.objects.get(pk=1) mjj.books.all()
- set():设置多对多关系
# set() [id,id] [对象,对象] mjj.books.set([1,2]) mjj.books.set(models.Book.objects.filter(pk__in=[1,2,3]))
- add():添加多对多关系
# add (id,id) (对象,对象) 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 聚合和分组
- 聚合
- 内置函数:Max(最大值)、Min(最小值)、Avg(平均值)、Sum(求和)、Count(计数)
from app01 import models from django.db.models import Max, Min, Avg, Sum, Count # 为聚合值指定名称,注意关键字传参要在位置传参后面 ret = models.Book.objects.filter(pk__gt=3).aggregate(Max('price'),avg=Avg('price')) print(ret)
- 分组
- 一般和聚合一起使用
# 统计每一本书的作者个数,annotate:注释 ret = models.Book.objects.annotate(count=Count('author')) # 统计出每个出版社的最便宜的书的价格 # 方式一 ret = models.Publisher.objects.annotate(Min('book__price')).values() # 方式二:objects后面接values,就是按字段分组,相当于分组条件 ret = models.Book.objects.values('pub_id').annotate(min=Min('price'))
12.7.10 F查询和Q查询
- 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)
- 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
- 定义:保存在浏览器本地上的一组组键值对
- 特点:
- 由服务器让浏览器进行设置的
- 浏览器保存在浏览器本地
- 下次访问时自动携带
- 应用:
- 登录
- 保存浏览习惯
- 简单的投票
- 使用cookie的原因:因为HTTP是无状态的,用cookie来保存状态
- 在django中操作cookie
- 设置cookie:
# HttpResponse,render也可以 ret = redirect('/index') ret.set_cookie('key',value,...) # Set-Cookie:key=value ret.set_signed_cookie('key',value,salt='加密盐',...)
- 参数:
- key,键
- value='',值
- max_age=None,超出时间
- expires=None,超出时间(针对于IE浏览器)
- path='/',cookie生效的路径,/表示根路径
- domain=None,cookie生效的域名
- secure=False,https传输
- httponly=False,只能http协议传输,无法被JavaScript获取
- 获取cookie:
request.COOKIES.get('key') request.get_signed_cookie('key',salt='加密盐',default=RAISE_ERROR,max_age=None)
- 参数:
- default:默认值
- salt:加密盐
- max_age:后台控制过期时间
- 注意:获取时的加密盐要和设置时的加密盐相同,否则无法获取到正确的数据
- 删除cookie:
def logout(request): ret = redirect("/login/") ret.delete_cookie("key") # 删除用户浏览器上之前设置的cookie值 return rep
- 设置cookie:
- 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
- 定义:保存在服务器上的一组组键值对(必须依赖cookie来使用)
- 使用session的原因:
- cookie保存在浏览器本地,不安全
- cookie保存的大小个数受到限制(cookie本身最大支持4096字节)
- 总结:cookie弥补了HTTP无状态的不足,但是cookie以文本的形式保存在浏览器本地,自身安全性较差,所以我们通过cookie识别不同用户,对应的在session里保存私密信息以及超过4096字节的文本
- 在django中操作session
- 设置session:
request.session['key'] = value request.session.setdefault('key',value) # 设置默认值,存在则不设置
- 获取session:
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失效策略
- session流程解析
- 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
- 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 AJAX
12.9.1 基本内容
- 定义:AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”,即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)
- 作用:AJAX就是使用 js 技术发送请求和接收响应
- 优点:在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容
- 特点:
- 异步交互
- 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求
- 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求
- 局部刷新,无须刷新整个页面
- 传输的数据量少,性能高
- 异步交互
- 应用场景:
- 搜索引擎根据用户输入的关键字,自动提示检索关键字
- 注册时候的用户名的查重
- 普通示例:页面输入两个整数,通过AJAX传输到后端计算出结果并返回
# urls.py urlpatterns = [ url(r'^ajax_add/', views.ajax_add), url(r'^ajax_demo1/', views.ajax_demo1), ]
# views.py 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>
12.9.2 jQuery实现的AJAX
- 最基本的jQuery发送AJAX请求示例
# views.py def ajax_test(request): user_name = request.POST.get("username") password = request.POST.get("password") print(user_name, password) return HttpResponse("OK")
<button id="ajaxTest">AJAX 测试</button> <script> $("#ajaxTest").click(function () { $.ajax({ url: "/ajax_test/", type: "POST", data: {username: "Q1mi", password: 123456}, success: function (data) { alert(data) } }) }) </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.3 AJAX通过csrf的校验
- 前提条件:确保有csrftoken的cookie
- 在页面中使用{% csrf_token %}
- 加装饰器:ensure_csrf_cookie
- 注意:
- 如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值
- 如果你的视图渲染的HTML文件中没有包含 {% csrf_token %},Django可能不会设置CSRFtoken的cookie。
- 这个时候需要使用ensure_csrf_cookie()装饰器强制设置Cookie
- AJAX请求如何设置csrf_token
- 通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送
$.ajax({ url: "/cookie_ajax/", type: "POST", data: { "username": "Q1mi", "password": 123456, {# 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中 #} "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data); } })
- 通过获取返回的cookie中的字符串,放置在请求头中发送
- 注意:需要引入一个jquery.cookie.js插件
$.ajax({ url: "/cookie_ajax/", type: "POST", {# 从Cookie取csrftoken,并设置到请求头中 #} headers: {"X-CSRFToken": $("[name = 'csrfmiddlewaretoken']").val()}, data: {"username": "Q1mi", "password": 123456}, success: function (data) { console.log(data); } })
- 使用文件:自己写一个getCookie方法
- 粘贴在 static 下的 js 中的 一个js文件 ,比如:ajax_setup.js
function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } // 通过js获取csrftoken的值 var csrftoken = getCookie('csrftoken'); // 使用$.ajaxSetup()方法为ajax请求统一设置 function csrfSafeMethod(method) { return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ beforeSend: function (xhr, settings) { // 不是 GET|HEAD|OPTIONS|TRACE 这些请求的话,就执行if后的操作 if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); // 设置请求头,本质同方法2 } } });
12.9.4 AJAX上传文件
- 上传文件示例
<input type="file" id="f1"> <button id="b1">上传</button> <script src="/static/jquery.js"></script> <script> $('#b1').click(function () { var formobj = new FormData(); formobj.append('file',$('#f1')[0].files[0]); formobj.append('name','alex'); $.ajax({ url: '/upload/', type: 'post', data:formobj , processData:false, contentType:false, success: function (res) { $("[name='i3']").val(res) }, }) }) </script>
12.10 form组件
- form组件的功能
- 生产input标签
- 对提交的数据可以进行校验
- 提供错误提示
12.10.1 使用form组件实现注册功能
- views.py
- 先定义好一个RegForm类
from django import forms # 按照Django form组件的要求自己写一个类 class RegForm(forms.Form): name = forms.CharField(label="用户名") pwd = forms.CharField(label="密码")
- 再写一个视图函数
# 使用form组件实现注册方式 def register2(request): form_obj = RegForm() if request.method == "POST": # 实例化form对象的时候,把post提交过来的数据直接传进去 form_obj = RegForm(request.POST) # 调用form_obj校验数据的方法 if form_obj.is_valid(): # 对数据进行校验 # form_obj.cleaned_data:通过校验的数据 return HttpResponse("注册成功") return render(request, "register2.html", {"form_obj": form_bj})
- 针对form_obj.is_valid(),is_valid的流程:
- 执行full_clean()的方法:
- 定义错误字典
- 定义存放清洗数据的字典
- 执行_clean_fields方法:
- 循环所有的字段
- 获取当前的值
- 对进行校验 ( 内置校验规则 自定义校验器)
- 通过校验:self.cleaned_data[name] = value
- 如果有局部钩子,执行进行校验:
- 通过校验,self.cleaned_data[name] = value
- 不通过校验,self._errors添加当前字段的错误并且self.cleaned_data中当前字段的值删除掉
- 没有通过校验:self._errors 添加当前字段的错误
- 执行全局钩子clean方法
- login2.html
<form action="/reg2/" method="post" novalidate autocomplete="off"> {% csrf_token %} <div> <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label> {{ form_obj.name }} {{ form_obj.name.errors.0 }} </div> <div> <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label> {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }} </div> <div> <input type="submit" class="btn btn-success" value="注册"> </div> </form>
- 常用变量:
{{ form_obj.as_p }}:生成一个个P标签 input label {{ form_obj.errors }}:form表单中所有字段的错误 {{ form_obj.username }}:一个字段对应的input框 {{ form_obj.username.label }}:该字段的中文提示 {{ form_obj.username.id_for_label }}:该字段input框的id {{ form_obj.username.errors }}:该字段的所有的错误 {{ form_obj.username.errors.0 }}:该字段的第一个错误
- 看网页效果,验证了form的功能:
- 前端页面是form类的对象生成的
- 生成HTML标签功能
- 当用户名和密码输入为空或输错之后,页面都会提示
- 用户提交校验功能
- 当用户输错之后,再次输入上次的内容还保留在input框
- 保留上次输入内容
- 前端页面是form类的对象生成的
12.10.2 常用字段与插件
- 创建Form类时,主要涉及到字段和插件
- 字段用于对用户请求数据的验证
- 插件用于自动生成HTML
- 常用字段
- CharField
- ChoiceField
- MultipleChoiceField
- 常用插件
- initial:初始值,input框里面的初始值
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三" # 设置默认值 ) pwd = forms.CharField(min_length=6, label="密码")
- error_messages:重写错误信息
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ) pwd = forms.CharField(min_length=6, label="密码")
- password:密码相关
class LoginForm(forms.Form): pwd = forms.CharField( min_length=6, label="密码", widget=forms.PasswordInput )
- radioSelect:单radio值为字符串
class LoginForm(forms.Form): gender = forms.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.RadioSelect )
- 单选Select:ChoiceField,Select
class LoginForm(forms.Form): hobby = forms.ChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=3, widget=forms.Select )
- 多选Select:MultipleChoiceField,SelectMultiple
class LoginForm(forms.Form): hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=[1, 3], widget=forms.SelectMultiple )
- 单选checkbox:ChoiceField,CheckboxInput
class LoginForm(forms.Form): keep = forms.ChoiceField( label="是否记住密码", initial="checked", widget=forms.CheckboxInput )
- 多选checkbox:MultipleChoiceField,CheckboxSelectMultiple
class LoginForm(forms.Form): hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.CheckboxSelectMultiple )
- 关于choice的注意事项:
- 在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段获取的值无法实时更新,那么需要自定义构造方法从而达到此目的
- 方式一:
from django.forms import Form from django.forms import widgets from django.forms import fields class MyForm(Form): user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)或 self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
- 方式二:
from django import forms from django.forms import fields from django.forms import models as form_model class FInfo(forms.Form): # 多选 authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 单选 authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
12.10.3 Django Form常用内置字段参数
- 常用字段参数:
Field required=True, # 是否允许为空 widget=None, # HTML插件 label=None, # 用于生成Label标签或显示内容 initial=None, # 初始值 help_text='', # 帮助信息(在标签旁边显示) error_messages=None, # 错误信息 {'required': '不能为空', 'invalid': '格式错误'} validators=[], # 自定义验证规则 localize=False, # 是否支持本地化 disabled=False, # 是否可以编辑 label_suffix=None # Label内容后缀 CharField(Field) max_length=None, # 最大长度 min_length=None, # 最小长度 strip=True # 是否移除用户输入空白 IntegerField(Field) max_value=None, # 最大值 min_value=None, # 最小值 ChoiceField(Field) choices=(), # 选项,如:choices = ((0,'上海'),(1,'北京'),) required=True, # 是否必填 widget=None, # 插件,默认select插件 label=None, # Label内容 initial=None, # 初始值 help_text='', # 帮助提示 ModelChoiceField(ChoiceField) queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=None, # HTML中value的值对应的字段 limit_choices_to=None # ModelForm中对queryset二次筛选 ModelMultipleChoiceField(ModelChoiceField)
12.10.4 校验
- 内置校验
- required=True
- min_length
- max_length等
- 自定义校验
- 写函数
from django.core.exceptions import ValidationError def checkname(value): # 通过校验规则 不做任何操作 # 不通过校验规则 抛出异常 if 'alex' in value: raise ValidationError('不符合社会主义核心价值观')
- 使用内置的校验器
from django import forms from django.core.validators import RegexValidator class RegForm(forms.Form): phone = forms.CharField( validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确')] )
- 钩子函数
- 局部钩子:只针对于当前字段的校验
def clean_username(self): # 局部钩子 # 通过校验规则 必须返回当前字段的值 # 不通过校验规则 抛出异常 v = self.cleaned_data.get('username') if 'alex' in v: raise ValidationError('不符合社会主义核心价值观') else: return v # 必须返回当前字段的值
- 全局钩子:针对于所有字段的校验
def clean(self): # 全局钩子 # 通过校验规则 必须返回所有所有字段的值 # 不通过校验规则 抛出异常 '__all__' pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd == re_pwd: return self.cleaned_data # 必须返回所有所有字段的值 else: self.add_error('re_pwd','两次密码不一致!!!!!') # 在字段're_pwd'后显示错误信息 raise ValidationError('两次密码不一致') # 在'__all__'中显示错误信息
12.11 中间件
12.11.1 中间件介绍
- 定义:中间件是一个用来处理Django的请求和响应的框架级别的钩子
- 本质:就是一个类,帮助我们在视图函数执行之前和执行之后做一些额外的操作
- Django项目的Settings.py文件中的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', ]
12.11.2 csrf:跨站请求伪造
- csrf 装饰器
from django.views.decorators.csrf import csrf_exempt,csrf_protect,ensure_csrf_cookie csrf_exempt # 某个视图不需要进行csrf校验,只能加在dispatch方法上 csrf_protect # 某个视图需要进行csrf校验 ensure_csrf_cookie # 确保生成csrf的cookie
- csrf 功能
- csrf中间件中执行process_request
- 从cookie中获取到csrftoken的值
- csrftoken的值放入到 request.META
- 执行process_view
- 查询视图函数是否使用csrf_exempt装饰器,使用了就不进行csrf的校验
- 判断请求方式:
- 如果是GET', 'HEAD', 'OPTIONS', 'TRACE',不进行csrf校验
- 其他的请求方式(post,put),进行csrf校验:
- 获取cookie中csrftoken的值
- 获取csrfmiddlewaretoken的值
- 能获取到,赋值给request_csrf_token
- 获取不到,就去获取请求头中X-csrftoken的值,再赋值request_csrf_token
- 比较上述request_csrf_token和cookie中csrftoken的值
- 比较成功接收请求
- 比较不成功拒绝请求
- csrf中间件中执行process_request
12.11.3 自定义中间件
- 中间件可以定义五个方法,分别是:
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
- process_request(self,request)
- 执行时间:视图函数之前
- 参数:
- request:和视图函数中是同一个request对象
- 执行顺序:按照注册的顺序 ,顺序执行
- 返回值:
- None : 正常流程
- HttpResponse: 后面的中间件的process_request、视图函数都不执行,直接执行当前中间件中的process_response方法,倒序执行之前的中间件中的process_response方法
- process_response(self, request, response)
- 执行时间:视图函数之后
- 参数:
- request:和视图函数中是同一个request对象
- response:返回给浏览器响应对象
- 执行顺序:按照注册的顺序,倒序执行
- 返回值:
- HttpResponse:必须返回response对象
- process_view(self, request, view_func, view_args, view_kwargs)
- 执行时间:视图函数之前,process_request之后
- 参数:
- request:和视图函数中是同一个request对象
- view_func:视图函数
- view_args:视图函数的位置参数
- view_kwargs:视图函数的关键字参数
- 执行顺序:按照注册的顺序 顺序执行
- 返回值:
- None : 正常流程
- HttpResponse: 后面的中间件的process_view、视图函数都不执行,直接执行最后一个中间件中的process_response方法,倒序执行之前的中间件中的process_response方法
- process_exception(self, request, exception)
- 执行时间(触发条件):视图层面有错时才执行
- 参数:
- request:和视图函数中是同一个request对象
- exception:错误对象
- 执行顺序:按照注册的顺序,倒序执行
- 返回值:
- None : 交给下一个中间件去处理异常,如果都没有处理,就交由django处理异常
- HttpResponse: 后面的中间件的process_exception不执行,直接执行最后一个中间件中的process_response方法,倒序执行之前的中间件中的process_response方法
- process_template_response(self,request,response)
- 执行时间(触发条件):视图返回的是一个templateResponse对象
- 参数:
- request:和视图函数中是同一个request对象
- response:templateResponse对象
- 执行顺序:按照注册的顺序,倒序执行
- 返回值:
- HttpResponse:必须返回response对象
- django请求的生命周期
12.12 图书管理系统示例
12.12.1 出版社管理
12.12.1.1 数据库准备
- 创建数据库:create database bookmanager;
- 配置setting.py:
MIDDLEWARE的'django.middleware.csrf.CsrfViewMiddleware'注释掉 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bookmanager', 'HOST': '127.0.0.1', 'PORT': 3306, 'USER': 'root', 'PASSWORD': '123', } }
- 导入pymysql:与settings,py同级目录下的init文件
import pymysql pymysql.install_as_MySQLdb()
- 创建表:
from django.db import models class Publisher(models.Model): # 设置pid为主键 pid = models.AutoField(primary_key=True) name = models.CharField(max_length=32,unique=True)
- 数据库迁移:
# 检测每个注册app下的model.py,记录model的变更记录 python manage.py makemigrations # 同步变更记录到数据库中 python manage.py migrate
- 给表中添加数据
12.12.1.2 功能设计
- 设计URL:urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), # 展示功能 url(r'^publisher_list/', views.publisher_list), # 新增功能 url(r'^add_publisher/', views.add_publisher), # 删除功能 url(r'^del_publisher/', views.del_publisher), # 编辑功能 url(r'^edit_publisher/', views.edit_publisher), ]
- 展示功能:publisher_list
- 写函数:views.py
from django.shortcuts import render, redirect, HttpResponse from app01 import models # 展示出版社 def publisher_list(request): # 从数据库中查询到出版社的信息 all_publishers = models.Publisher.objects.all().order_by('pk') # 返回一个包含出版社信息的页面 return render(request, 'publisher_list.html', {'all_publishers': all_publishers})
- 写模板:publisher_list.html
<h1>出版社列表</h1> <a href="/add_publisher/">新增</a> <table border="1"> <thead> <tr> <th>序号</th> <th>ID</th> <th>名称</th> <th>操作</th> </tr> </thead> <tbody> {# 使用for循环获取数据,完善表格 #} {% for publisher in all_publishers %} <tr> <td>{{ forloop.counter }}</td> <td>{{ publisher.pk }}</td> <td>{{ publisher.name }}</td> <td> <a href="/del_publisher/?id={{ publisher.pk }}">删除</a> <a href="/edit_publisher/?id={{ publisher.pk }}">编辑</a> </td> </tr> {% endfor %} <!--for循环必须要以endfor结束--> </tbody> </table>
- 知识点:
- 使用render的第三个参数(context):字典形式,方便之后用key取数据
- {{ }}:表示变量,在模板渲染的时候替换成值,点(.)用来获取属性值
- 如上述模板中使用的publisher.pk:获取主键
- forloop.counter:当前循环的次数
- {% %}:表示逻辑相关的操作
- 如上述模板中使用的for循环,for循环必须要以endfor结束
- 新增功能:add_publisher
- 写函数:views.py
# 新增出版社 def add_publisher(request): error = '' # 对请求方式进行判断 if request.method == 'POST': # 处理POST请求 # 获取到出版社的名称 publisher_name = request.POST.get('publisher_name') # 判断出版社名称是否有重复的 if models.Publisher.objects.filter(name=publisher_name): error = '出版社名称已存在' # 判断输入的值是否为空 if not publisher_name: error = '不能输入为空' if not error: # 使用ORM将数据插入到数据库中 obj = models.Publisher.objects.create(name=publisher_name) # 跳转到展示出版社的页面 return redirect('/publisher_list/') # 返回一个包含form表单的页面 return render(request, 'add_publisher.html', {'error': error})
- 写模板:add_publisher.html
<form action="" method="post"> <p> 出版社名称: <input type="text" name="publisher_name">{{ error }} </p> <button>提交</button> </form>
- 知识点:
- 插入数据:使用ORM操作
- models.Publisher.objects.create(属性=新增值)
- 如models.Publisher.objects.create(name=publisher_name)
- 删除功能:del_publisher
- 写函数:views.py
# 删除出版社 def del_publisher(request): # 获取要删除的数据 pk = request.GET.get('id') obj_list = models.Publisher.objects.filter(pk=pk) if not obj_list: # 没有要删除的数据 return HttpResponse('要删除的数据不存在') # 删除该数据 # obj.delete() obj_list.delete() # 跳转到展示页面 return redirect('/publisher_list/')
- 知识点:
- request.GET:url上携带的参数,不是GET请求提交参数
- 所以post请求时,也可以使用该方法
- 删除数据:对象.delete() 或 对象列表.delete()
- 如obj.delete() 或 obj_list.delete()
- 编辑功能:edit_publisher
- 写函数:views.py
# 编辑出版社 def edit_publisher(request): error = '' # 查找要编辑的数据 pk = request.GET.get('id') # url上携带的参数 不是GET请求提交参数 obj_list = models.Publisher.objects.filter(pk=pk) if not obj_list: return HttpResponse('要编辑的数据不存在') obj = obj_list[0] if request.method == 'POST': # 处理POST请求 # 获取新提交的出版的名称 publisher_name = request.POST.get('publisher_name') if models.Publisher.objects.filter(name=publisher_name): # 新修改的名称已存在 error = '新修改的名称已存在' if obj.name == publisher_name: error = '名称未修改' if not publisher_name: error = '名称不能为空' if not error: # 修改数据 obj.name = publisher_name obj.save() # 保存数据到数据库中 # 跳转到出版社的展示页面 return redirect('/publisher_list/') # 返回一个包含原始数据的页面 return render(request, 'edit_publisher.html', {'obj': obj,'error':error})
- 写模板:edit_publisher.html
<form action="" method="post"> <p> 出版社名称: <input type="text" name="publisher_name" value="{{ obj.name }}"> {{ error }} </p> <button>提交</button> </form>
- 知识点:
- 修改数据:对象.属性 = 新值,如obj.name = publisher_name
- 保存数据到数据库中:对象.save(),如obj.save()
12.12.2 图书管理
12.12.2.1 数据库准备
- 在上述基础上
- 创建表:
from django.db import models class Book(models.Model): title = models.CharField(max_length=32) pub = models.ForeignKey('Publisher', on_delete=models.CASCADE)
- 数据库迁移:
# 检测每个注册app下的model.py,记录model的变更记录 python manage.py makemigrations # 同步变更记录到数据库中 python manage.py migrate
- 给表中添加数据
- 知识点:
- on_delete 在django2.0 版本之后是必填的参数,1.11之前的可以不填
- on_delete 的参数:
- models.CASCADE
- models.SET()
- models.SET_DEFAULT
- models.SET_NULL
12.12.2.2 功能设计
- 设计URL:urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^publisher_list/', views.publisher_list), url(r'^add_publisher/', views.add_publisher), url(r'^del_publisher/', views.del_publisher), url(r'^edit_publisher/', views.edit_publisher), url(r'^book_list/', views.book_list), url(r'^add_book/', views.add_book), url(r'^del_book/', views.del_book), url(r'^edit_book/', views.edit_book), ]
- 展示功能:book_list
- 写函数:views.py
from django.shortcuts import render, redirect, HttpResponse from app01 import models # 展示书籍 def book_list(request): # 查询所有的书籍 all_books = models.Book.objects.all() return render(request, 'book_list.html', {'all_books': all_books})
- 写模板:book_list.html
<a href="/add_book/" target="_blank">添加</a> <table border="1"> <thead> <tr> <th>序号</th> <th>ID</th> <th>书名</th> <th>出版社名称</th> <th>操作</th> </tr> </thead> <tbody> {% for book in all_books %} <tr> <td>{{ forloop.counter }}</td> <td>{{ book.pk }}</td> <td>{{ book.title }}</td> <td>{{ book.pub }}</td> <td> <a href="/del_book/?id={{ book.pk }}">删除</a> <a href="/edit_book/?id={{ book.pk }}">编辑</a> </td> </tr> {% endfor %} </tbody> </table>
- 知识点:
all_books = models.Book.objects.all() for book in all_books: print(book.title) print(book.pub,type(book.pub)) # ——> 所关联的出版社对象 print(book.pub.pk) # 查id 多一次查询 print(book.pub_id) # 直接在book表中查出的ID print(book.pub.name)
- 新增功能:add_book
- 写函数:views.py
# 添加书籍 def add_book(request): if request.method == 'POST': # 获取数据 book_name = request.POST.get('book_name') pub_id = request.POST.get('pub_id') # 将数据插入到数据库 # models.Book.objects.create(title=book_name,pub=models.Publisher.objects.get(pk=pub_id)) models.Book.objects.create(title=book_name, pub_id=pub_id) # 跳转到书籍的展示页面 return redirect('/book_list/') # all_books = models.Book.objects.all() # return render(request, 'book_list.html', {'all_books': all_books}) # 查询所有的出版社 all_publishers = models.Publisher.objects.all() return render(request, 'add_book.html', {'all_publishers': all_publishers})
- 写模板:add_book.html
<form action="" method="post"> <p>书名: <input type="text" name="book_name"></p> <p>出版社: <select name="pub_id" id=""> {% for publisher in all_publishers %} <option value="{{ publisher.pk }}"> {{ publisher.name }} </option> {% endfor %} </select> </p> <button>新增</button> </form>
- 知识点:
- 插入数据:使用ORM操作
- models.Book.objects.create(title=book_name,pub=出版社的对象)
- models.Book.objects.create(title=book_name,pub_id=pub_id)
- 删除功能:del_book
- 写函数:views.py
# 删除书籍 def del_book(request): # 获取要删除的对象删除 pk = request.GET.get('id') models.Book.objects.filter(pk=pk).delete() # 跳转到展示页面 return redirect('/book_list/')
- 知识点:
- pk = request.GET.get('id')
- models.Book.objects.filter(pk=pk).delete()
- 编辑功能:edit_book
- 写函数:views.py
# 编辑书籍 def edit_book(request): # 获取要编辑的书籍对象 pk = request.GET.get('id') book_obj = models.Book.objects.get(pk=pk) if request.method == 'POST': # 获取提交的数据 book_name = request.POST.get('book_name') pub_id = request.POST.get('pub_id') # 修改数据 book_obj.title = book_name # book_obj.pub_id = pub_id book_obj.pub = models.Publisher.objects.get(pk=pub_id) book_obj.save() # 重定向到展示页面 return redirect('/book_list/') # 查询所有的出版社 all_publishers = models.Publisher.objects.all() return render(request, 'edit_book.html', {'book_obj': book_obj, 'all_publishers': all_publishers})
- 写模板:edit_publisher.html
<form action="" method="post"> <p>书名: <input type="text" name="book_name" value="{{ book_obj.title }}"></p> <p>出版社: <select name="pub_id" id=""> {% for publisher in all_publishers %} {% if book_obj.pub == publisher %} <option selected value="{{ publisher.pk }}"> {{ publisher.name }} </option> {% else %} <option value="{{ publisher.pk }}"> {{ publisher.name }} </option> {% endif %} {% endfor %} </select> </p> <button>保存</button> </form>
- 知识点:
# 修改数据 book_obj.title = book_name # book_obj.pub_id = pub_id book_obj.pub = models.Publisher.objects.get(pk=pub_id) book_obj.save()
- if语句
{% if book_obj.pub == publisher %} <option selected value="{{ publisher.pk }}"> {{ publisher.name }} </option> {% else %} <option value="{{ publisher.pk }}"> {{ publisher.name }} </option> {% endif %}
12.12.3 作者管理
12.12.3.1 数据库准备
- 在上述基础上
- 创建表:
from django.db import models # 会自动生成第三张表 class Author(models.Model): name = models.CharField(max_length=32) books = models.ManyToManyField('Book') # 不在Author表中生产字段,生产第三张表
- 数据库迁移:
# 检测每个注册app下的model.py,记录model的变更记录 python manage.py makemigrations # 同步变更记录到数据库中 python manage.py migrate
- 给表中添加数据
- 注意不仅要给作者表添加数据,还有给第三张表添加数据
12.12.3.2 功能设计
- 设计URL:urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^publisher_list/', views.publisher_list), url(r'^add_publisher/', views.add_publisher), url(r'^del_publisher/', views.del_publisher), url(r'^edit_publisher/', views.edit_publisher), url(r'^book_list/', views.book_list), url(r'^add_book/', views.add_book), url(r'^del_book/', views.del_book), url(r'^edit_book/', views.edit_book), url(r'^author_list/', views.author_list), url(r'^add_author/', views.add_author), url(r'^del_author/', views.del_author), url(r'^edit_author/', views.edit_author), ]
- 展示功能:author_list
- 写函数:views.py
from django.shortcuts import render, redirect, HttpResponse from app01 import models # 展示作者 def author_list(request): # 查询所有的作者 all_authors = models.Author.objects.all() return render(request, 'author_list.html', {'all_authors': all_authors})
- 写模板:author_list.html
<a href="/add_author/" target="_blank">添加</a> <table border="1"> <thead> <tr> <th>序号</th> <th>id</th> <th>姓名</th> <th>代表作</th> <th>操作</th> </tr> </thead> <tbody> {% for author in all_authors %} <tr> <td>{{ forloop.counter }}</td> <td>{{ author.pk }}</td> <td>{{ author.name }}</td> <td> {% for book in author.books.all %} {% if forloop.last %} 《{{ book.title }}》 {% else %} 《{{ book.title }}》、 {% endif %} {% endfor %} </td> <td> <a href="/del_author/?pk={{ author.pk }}">删除</a> <a href="/edit_author/?pk={{ author.pk }}">编辑</a> </td> </tr> {% endfor %} </tbody> </table>
- 知识点:
all_authors = models.Author.objects.all() for author in all_authors: print(author.books,type(author.books)) # 关系管理对象 print(author.books.all()) # 使用all,获取对应列表
- 新增功能:add_author
- 写函数:views.py
# 增加作者 def add_author(request): if request.method == 'POST': # 获取post请求提交数据 author_name = request.POST.get('author_name') books = request.POST.getlist('books') # 存入数据库 author_obj = models.Author.objects.create(name=author_name, ) author_obj.books.set(books) # 跳转到展示页面 return redirect('/author_list/') # 查询所有的书籍 all_books = models.Book.objects.all() return render(request, 'add_author.html', {'all_books': all_books})
- 写模板:add_author.html
<form action="" method="post"> <p> 作者姓名: <input type="text" name="author_name"></p> <p> 作品: <select name="books" id="" multiple> {% for book in all_books %} <option value="{{ book.pk }}">{{ book.title }}</option> {% endfor %} </select> </p> <button>提交</button> </form>
- 知识点:
知识点:
- 获取post请求提交数据:结果是列表,就使用getlist
- author_name = request.POST.get('author_name')
- books = request.POST.getlist('books')
- 存入数据库:普通的使用create,得到一个对象,多对多再使用对象进行set把列表数据加入进去
- 新增普通的数据:author_obj = models.Author.objects.create(name=author_name, )
- 新增多对多的数据:author_obj.books.set(books)
- 删除功能:del_author
- 写函数:views.py
# 删除作者 def del_author(request): # 获取要删除对象的id pk = request.GET.get('pk') # 获取要删除的对象 删除 models.Author.objects.filter(pk=pk).delete() # 跳转到展示页面 return redirect('/author_list/')
- 编辑功能:edit_author
- 写函数:views.py
# 编辑作者 def edit_author(request): # 查询编辑的作者对象 pk = request.GET.get('pk') author_obj = models.Author.objects.get(pk=pk) if request.method == 'POST': # 获取提交的数据 name = request.POST.get('author_name') books = request.POST.getlist('books') # 修改对象的数据 author_obj.name = name author_obj.save() # 多对多的关系 author_obj.books.set(books) # 每次重新设置 # 重定向 return redirect('/author_list/') # 查询所有的书籍 all_books = models.Book.objects.all() return render(request, 'edit_author.html', {'author_obj': author_obj, 'all_books': all_books})
- 写模板:edit_author.html
<form action="" method="post"> <p> 作者姓名: <input type="text" name="author_name" value="{{ author_obj.name }}"></p> <p> 作品: <select name="books" id="" multiple> {% for book in all_books %} {% if book in author_obj.books.all %} <option value="{{ book.pk }}" selected>{{ book.title }}</option> {% else %} <option value="{{ book.pk }}">{{ book.title }}</option> {% endif %} {% endfor %} </select> </p> <button>提交</button> </form>
- 知识点:
- 修改对象的数据
- author_obj.name = name
- author_obj.save()
- 修改多对多的关系的数据:使用set,并且,不需要save
- author_obj.books.set(books)
注意:每次默认都是重新设置的
12.12.4 django设置多对多关系的三种方法
- django帮我们生成第三张表:
class Author(models.Model): name = models.CharField(max_length=32) books = models.ManyToManyField('Book') # 不在Author表中生产字段,生产第三张表
- 自己创建第三张表:
class AuthorBook(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE) date = models.DateField()
- 自建的表和 ManyToManyField 联合使用
- 使用参数through:将自建的表设置成ManyToManyField的第三张表
class Author(models.Model): name = models.CharField(max_length=32) books = models.ManyToManyField('Book',through='AuthorBook') class AuthorBook(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE) date = models.DateField()
- 特殊:自建表中有对同一个表多个外键的情况
- 使用参数through_fields:指定外键名,说明第三张表中只有这些外键
class Author(models.Model): name = models.CharField(max_length=32) books = models.ManyToManyField('Book',through='AuthorBook',through_fields=['author','book'] class AuthorBook(models.Model): author = models.ForeignKey(Author,related_name='a' ,on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE) tuijian = models.ForeignKey(Author,related_name='b',on_delete=models.CASCADE)
- 参数related_name:用来区分同一个表的外键,方便之后用来反向查询
12.13 总结
- django中所有的命令
- 下载安装
- pip install django==1.11.21 -i 源
- 创建项目
- django-admin startproject 项目名称
- 启动项目
- cd 项目根目录下
- 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
- 创建app
- python manage.py startapp app名称
- 数据库迁移
- pyhton manage.py makemigrations
- pyhton manage.py migrate
- 下载安装
- settings配置
- 静态文件夹:static
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR,‘static’), ]
- 模板文件夹:templates
- TEMPLATES下的DIRS:[os.path.join(BASE_DIR,‘templates’),]
- 连接数据库:DATABASES中,
- ENGINE:引擎
- NAME:数据库的名称
- HOST:ip
- PORT:端口,3306是mysql的端口号
- USER:用户民
- PASSWORD:密码
- app配置:
- INSTALLED_APPS =[ 'app01' 'app01.apps.App01Config' ]
- 提交POST请求
- 中间件MIDDLEWARE中,注释csrf相关的内容
- 在form表单中写上{% csrf_token %},就不用在settings中注释含csrf的中间件了
- 静态文件夹:static
- django处理请求的流程
- 浏览器的地址栏上输入地址,发送get请求
- wsgi接受到请求
- 根据url的路径找到对应的函数
- 执行函数,返回响应。响应返回给wsgi,wsgi按照HTTP响应格式返回给浏览器
- get和post的区别
- get:获取到一个资源
- 发get的途径:
- 在浏览器的地址栏种输入URL,回车
- a标签
- form表单:不指定 method或method=‘get’
- 传递参数:url路径?id=1&name=alex
- django种获取url上的参数:
- request.GET --> {'id':1,'name':'alex'}
- request.GET.get(key)或request.GET[key]
- post:提交数据
- 发post的途径:form表单,method=‘post’
- 传递参数:参数不暴露在URL,在请求体中
- django中获取post的数据:request.POST
- get:获取到一个资源
- 发请求的途径
- 地址栏中输入地址:发送get请求
- a标签:发送get请求
- form表单:发送get/post请求
- request对象的属性及方法
- 属性:
- request.method:当前请求的方式,GET/POST,字符串类型
- request.GET:url上携带的参数,不是GET请求提交参数,所以post请求时,也可以使用该方法
- request.POST:获取form表单提交post请求的数据,类似字典类型,可以仿照字典取值——[ ]或.get( )
- request.path_info:url的路径,不包含ip和端口,不包含参数
- request.body:请求体,byte类型,request.POST的数据是从boby里提取的
- request.FILES:上传的文件,一个类似于字典的对象,包含所有上传文件的信息
- request.META:请求头,标准的Python字典,包含左右HTTP首部
- 方法
- request.get_host():获取主机的ip和端口
- request.get_full_path():url的路径,不包含ip和端口,包含参数
- request.is_ajax():判断是否是ajax请求,是返回Ture,否返回False
- 属性:
- 响应方式
- Response对象
- HttpResponse('字符串'):返回字符串
- render(request,'模板文件名',{k1:v1}):返回一个完整的页面
- redirect('index'):重定向,Location:/index/
- JsonResponse对象
- Response对象
来源:https://www.cnblogs.com/yinhaiping/p/a572cc37eea509eab4a3986b8be7f20e.html