- Django
- 1 Django简介
- 2 python三大主流web框架
- 3 注意事项
- 4 django基本操作
- 5 应用
- 6 主要文件介绍
- 7 命令行与pycharm创建的区别
- 8 django小白必会三板斧
- 9 静态文件配置
- 10 request对象方法
- 11 数据库(MySQL)相关操作
- 12 django请求生命周期流程图(**************)
- 13 路由层
- 14 视图层
- 15 模板层
- 16 测试脚本
- 17 模型层
- 18 MTV与MVC模型
- 19 Ajax
- 20 批量插入
- 21 分页器
- 22 Forms组件
- 23 cookie与session
- 24 CBV添加装饰器
- 25 django中间件
- 26 Auth模块
- 27 DTL模板语言和static
Django
1 Django简介
Django是一个开放源代码的Web应用框架,由Python写成。
采用了MTV的框架模式,即模型M,视图V和模版T。
2 python三大主流web框架
"""
django
特点:
大而全
自带的功能特别的多 类似于航空母舰
不足之处:
有时候过于笨重
flask
特点:
小而精
自带的功能特别的少 类似于游骑兵
第三方的模块特别的多,
如果将flask第三方的模块加起来完全可以盖过django,且也越来越像django
不足之处:
比较依赖于第三方的开发者
tornado
特点:
异步非阻塞 支持高并发
甚至可以开发游戏服务器
"""
如果将框架都分为三部分:
socket部分
路由与视图函数对应关系(路由匹配)
模版语法
django
socket部分用的是别人的 wsgiref模块
路由匹配是自己写的
模版语法是自己写的(没有jinja2好用 但是也很方便)
flask
socket部分用的是别人的werkzeug(内部还是wsgiref模块)
路由匹配自己写的
模版语法用的别人的(jinja2)
tornado
socket部分,路由匹配,模版语法都是自己写的
3 注意事项
3.1 django版本问题
1.X 2.X 3.X(不成熟,直接忽略)
1.X和2.X差距不大
讲解主要以1.X为例 会讲解2.X区别
公司之前用的1.8 慢慢过渡到了1.11版本 有一些项目用的2.0
3.2 django安装
pip3 install django==1.11.11
如果已经安装了其他版本 无需自己卸载
直接重新装 会自动卸载安装新的
验证是否安装成功的方式:
终端输入django-admin看看有没有反应
3.3 启动项目注意点
1.计算机的名称不能有中文
2.一个pycharm窗口只开一个项目
3.项目里面所有的文件也尽量不要出现中文
4.python解释器尽量使用3.4~3.6之间的版本
(如果项目报错 点击最后一条报错信息,去源码中把一个逗号删掉)
4 django基本操作
4.1 命令行操作
4.1.1 创建django项目
"""
可以先切换到对应的D盘 然后再创建
"""
cmd终端中输入:
django - admin startproject 项目名
创建完成后的结构:
-mysite项目文件夹
--mysite文件夹
---__init__.py
---settings.py 配置文件
---urls.py 路由与视图函数对应关系(路由层)
---wsgi.py web服务网关接口模块
--manage.py django的入口文件
--db.sqlite3 django自带的sqlite3数据库(小型数据库 功能不多,有bug)
4.1.2 启动django项目
"""
先切换到项目目录下
cd /mysite
"""
cmd终端中输入:
python3 manage.py runserver
tips: CTRL-BREAK就是CTRL+C
# 通过http://127.0.0.1:8000/即可访问
4.1.3 创建应用
"""
Next, start your first app by running python manage.py startapp [app_label].
"""
cmd终端中输入:
python manage.py startapp app01
应用名应该做到见名知意
user
order
web
......
4.2 pycharm操作
# 1 创建django项目
左上角file
new project 选择左侧第二个django,创建
# 2 启动
第一种方式:在控制台处的TERMINAL终端用命令行启动
第二种方式:点击右上角绿色小箭头即可
tips: CTRL-BREAK就是CTRL+C
# 3 创建应用
1.pycharm提供的终端直接输入完整命令
2.点击上方工具栏tools
选择run manage.py task
在窗口中直接输入startapp app01(前期不要用)
# 4 修改端口号以及创建server
点击启动服务端绿色小箭头旁边的项目名区域
选择edit configurations 进行更改
5 应用
5.1 应用的定义
django是一款专门用来开发app的web框架
django框架就类似于是一所大学(空壳子)
app就类似于大学里面各个学院(具体功能的app)
例如开发淘宝
订单相关
用户相关
投诉相关
选课系统
学生功能
老师功能
创建不同的app对应不同的功能
一个app就是一个独立的功能模块
5.2 应用的注意点
创建的应用一定要去配置文件中注册 !!!!!!!!!!!!!!!!
***********************创建的应用一定要去配置文件中注册**********************
在settings文件中:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config', # 全写
'app01', # 简写
]
# 创建出来的的应用第一步先去配置文件中注册
ps:在用pycharm创建项目的时候 pycharm可以创建一个app并且自动注册(仅能携带一个app)
***********************************************************************
6 主要文件介绍
-mysite项目文件夹
--mysite文件夹
---settings.py 配置文件
---urls.py 路由与视图函数对应关系(路由层)
---wsgi.py wsgiref模块(不考虑)
--manage.py django的入口文件
--db.sqlite3 django自带的sqlite3数据库(小型数据库 功能不是很多还有bug)
--app01文件夹
---admin.py django后台管理
---apps.py 注册使用
---migrations文件夹 数据库迁移记录
---models.py 数据库相关的 模型类(orm对象关系映射)
---tests.py 测试文件
---views.py 视图函数(视图层)
7 命令行与pycharm创建的区别
# 区别:
# 命令行创建不会自动创建template文件夹 需要自己手动创建,配置文件也没配置路径
# pycharm创建会自动创建template文件夹,并且还会自动在配置文件中配置对应的路径
# pycharm创建后 配置文件中
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
}
# 命令行创建后 配置文件中
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
}
"""
这也就意味着在用命令创建django项目的时候不单单需要创建templates文件夹还需要去配置文件中配置路径
'DIRS': [os.path.join(BASE_DIR, 'templates')]
"""
8 django小白必会三板斧
"""
HttpResponse
返回字符串类型的数据
render
返回html文件
redirect
重定向(同url可以只写后缀)
return redirect('https://www.mzitu.com/')
return redirect('/home/')
"""
注意点:
1.from app01 import views
2.url(r'^wow/', views.wow)
3.from django.shortcuts import render,HttpResponse,redirect
4.INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
]
9 静态文件配置
9.1 简略版
html文件--->统一放入template文件夹
css文件,js文件,图片文件,第三方前端框架---->统一放入static文件夹
-static文件夹
--js文件夹
--css文件夹
--img文件夹
--其他第三方文件文件夹
在返回含有bootstrap框架的HTML页面时会发现该页面的bootstrap样式没生效
尝试访问http://127.0.0.1:8000/static/bootstrap-3.3.7-dist/css/bootstrap.min.css
发现返回404错误
这是因为没开设static资源(无url对应关系和视图函数)
Django提供了开设static资源的方法: 静态文件配置
在配置文件中有STATIC_URL变量,它类似于访问静态文件的令牌
STATIC_URL = '/static/'
在html文件中进行文件的引用:
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
必须要以该令牌为开头 !!!
# 静态文件配置 (在settings中添加该列表变量)
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
]
# STATICFILES_DIRS列表是令牌通过后才会遍历的路径 !
/static/令牌通过后会取列表,
从上往下依次查找bootstrap-3.3.7-dist/js/bootstrap.min.js
不存在就报错
# 静态文件动态解析(通过模版语法使引用的路径 随着STATIC_URL自动改变)
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
# 在前期我们使用django提交post请求的时候 需要取配置文件中注释掉一行代码
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',
]
9.2 详细版
"""
我们将html文件默认都放在templates文件夹下
将网站所使用的静态文件默认都放在static文件夹下
静态文件
前端已经写好了的 能够直接调用使用的文件
网站写好的js文件
网站写好的css文件
网站用到的图片文件
第三方前端框架
...
拿来就可以直接使用的
"""
# django默认是不会自动创建static文件夹 需要手动创建
一般情况下在static文件夹内还会做进一步的划分处理
-static
--js
--css
--img
--其他第三方文件
"""
在浏览器中输入url能够看到对应的资源,是因为后端提前开设了该资源的接口
如果访问不到资源 说明后端没有开设该资源的接口
http://127.0.0.1:8000/static/bootstrap-3.3.7-dist/css/bootstrap.min.css
"""
# 静态文件配置
"""
****************************************************************
当你在写django项目的时候 可能会出现后端代码修改了但是前端页面没有变化的情况
1.你在同一个端口开了好几个django项目
一直在跑的其实是第一个django项目
2.浏览器缓存的问题
右键检查,右上角选settings,勾选network类下的disable cache
开发时自动禁用缓存
*****************************************************************
"""
STATIC_URL = '/ooo/' # 类似于访问静态文件的令牌
"""如果你想要访问静态文件 你就必须以static开头"""
"""
/static/bootstrap-3.3.7-dist/js/bootstrap.min.js
/static/令牌
取列表里面从上往下依次查找
bootstrap-3.3.7-dist/js/bootstrap.min.js
都没有才会报错
"""
# 静态文件配置
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
os.path.join(BASE_DIR,'static2'),
]
# 静态文件动态解析
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
# form表单默认是get请求数据
http://127.0.0.1:8000/login/?username=jason&password=123
"""
form表单action参数的三种使用方式
1.不写 默认朝当前所在的url提交数据
2.全写 指名道姓
3.只写后缀 /login/
"""
# 在前期我们使用django提交post请求的时候 需要取配置文件中注释掉一行代码
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',
]
10 request对象方法
request.method # 返回请求方式 并且是全大写的字符串形式 <class 'str'>
request.POST # 获取用户post请求提交的普通数据不包含文件
request.POST.get() # 只获取列表最后一个元素
request.POST.getlist() # 直接将列表取出
request.GET # 获取用户提交的get请求数据
request.GET.get() # 只获取列表最后一个元素
request.GET.getlist() # 直接将列表取出
"""
get请求携带的数据是有大小限制的 大概好像只有2KB左右
而post请求则没有限制
"""
def login(request):
# 返回一个登陆界面
"""
get请求和post请求应该有不同的处理机制
:param request: 请求相关的数据对象 里面有很多简易的方法
:return:
"""
# print(type(request.method)) # 返回请求方式 并且是全大写的字符串形式 <class 'str'>
if request.method == 'POST':
return HttpResponse("收到了 宝贝")
return render(request, 'login.html')
11 数据库(MySQL)相关操作
11.1 链接数据库
11.1.1 pycharm链接数据库
"""
三个位置查找数据库相关
1.右侧上方database
2.左下角的方块,悬浮之后选择database
3.在pycharm的settings配置中的plugins插件搜索安装
查找到之后点击加号,选择MySQL
然后输入IP,端口,用户名,密码,要链接的数据库
就可以正确链接MySQL数据库
pycharm可以充当很多款数据库软件的客户端
使用pycharm链接数据库需要提前创建好库
此方法无法创建库
"""
11.1.2 django链接数据库
# Django默认用的数据库是sqlite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# django链接MySQL
1.第一步在配置文件settings中,修改DATABASES
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'day60',
'USER':'root',
'PASSWORD':'admin123',
'HOST':'127.0.0.1',
'PORT':3306,
'CHARSET':'utf8'
}
}
2.代码声明
django默认用的是mysqldb模块链接MySQL
但是该模块的兼容性不好
要让django不使用默认的mysqldb,而是使用pymysql
需要手动改为用pymysql链接
# 在项目名下的__init__.py文件或者任意的应用名下的__init__.py文件中书写以下代码
import pymysql
pymysql.install_as_MySQLdb()
# 使用pycharm链接数据库需要提前创建好库
# 此方法无法创建库
11.2 Django ORM
"""
ORM 对象关系映射Object Relational Mapping
作用:
能够让一个不会sql语句的程序员也能够通过python 面向对象的代码简单快捷的操作数据库
不足之处:
封装程度太高 有时候sql语句的效率偏低 需要自己写SQL语句
它将数据库中的表,记录,数据 对应成python中的类,对象,对象属性
类 表
对象 记录
对象属性 记录某个字段对应的值
"""
要使用ORM要进入应用下的models.py文件
# 1 在models.py中书写一个类
class User(models.Model): # 必须继承models.Model
# 创建三个字段
# id int primary_key auto_increment
id = models.AutoField(primary_key=True)
# username varchar(32)
username = models.CharField(max_length=32)
# password int
password = models.IntegerField()
# 2 数据库迁移命令*************非常重要***********************************
python3 manage.py makemigrations
# 将操作记录记录到小本本上(migrations文件夹)
python3 manage.py migrate
# 将操作真正的同步到数据库中
# 只要修改了models.py中跟数据库相关的代码 就必须重新执行上述的两条命令
*****************************非常重要*******************************************
class User(models.Model):
# 类似于sql语句的id int primary_key auto_increment
id = models.AutoField(primary_key=True,verbose_name='主键')
# 类似于sql语句的username varchar(32)
username = models.CharField(max_length=32,verbose_name='用户名')
"""
CharField必须要指定max_length参数 不指定会直接报错
verbose_name该参数是所有字段都可以有的属性, 用来对字段解释
"""
# 类似于sql语句的password int
password = models.IntegerField(verbose_name='密码')
# 由于一张表中必须要有一个主键字段 并且一般情况下都叫id字段
# 所以orm会在不定义主键字段的时候 自动在表中创建一个名为id主键字段
# 也就意味着 后续我们在创建模型表的时候如果主键字段名没有特殊的名称 那么主键字段可以省略不写
class Author(models.Model):
# 类似于sql语句的username varchar(32)
username = models.CharField(max_length=32)
# 类似于sql语句的password int
password = models.IntegerField()
11.3 数据操作
11.3.1 字段的增删改查
# 字段的增加
由于在表中有数据的时候增加字段会导致该字段下没有数据
所以需要进行额外的设置
直接在models.py的表中添加字段后 info = models.CharField(max_length=32)
执行makemigrations 与migrate后,终端会提示无法操作,有以下三个方法:
1.可以在终端提示后 直接在终端内提交默认值
2.使该字段可以为空
info = models.CharField(max_length=32,verbose_name='个人简介',null=True)
3.给字段设置默认值
hobby = models.CharField(max_length=32,verbose_name='兴趣爱好',default='study')
# 字段的修改
直接修改代码然后执行数据库迁移的两条命令即可!
# 字段的删
直接注释对应的字段然后执行数据库迁移的两条命令即可!
执行完毕之后字段对应的数据也都没有了
"""
在操作models.py的时候一定要细心
千万不要随意注释一些字段
执行迁移命令之前最好先检查一下自己写的代码
"""
11.3.2 数据的增删改查
11.3.2.1 精华版
# ================查
res = models.User.objects.filter(username=username)
# 该结果能够支持索引取值,切片操作,但是不支持负数索引,官方不推荐使用索引的方式取值
user_obj = models.User.objects.filter(username=username).first()
# 可以使用first方法取值
# ================增
# 增第一种方法
user_obj = models.User(username=username,password=password)
user_obj.save()
# 增第二种方法
res = models.User.objects.create(username=username,password=password)
# ================删
models.User.objects.filter(id=delete_id).delete()
# ================改
# 改第一种方法
edit_obj = models.User.objects.filter(username=username)
edit_obj.username = username
edit_obj.password= password
edit_obj.save()
# 改第二种方法
models.User.objects.filter(id=edit_id).update(username=username,password=password)
11.3.2.2 详细版
# 查
res = models.User.objects.filter(username=username)
"""
返回值可以暂时看成是列表套数据对象的格式
它也支持索引取值 切片操作 但是不支持负数索引
不推荐使用索引的方式取值
user_obj = models.User.objects.filter(username=username).first()
"""
filter括号内可以携带多个参数(条件) 条件与条件之间默认是and关系
可以把filter联想成where记忆
# 第一种增加
from app01 import models
res = models.User.objects.create(username=username,password=password)
# 返回值就是当前被创建的对象本身
# 第二种增加
user_obj = models.User(username=username,password=password)
user_obj.save() # 保存数据
# 通过一个例子学习数据的改,删操作
# 将数据库中的数据全部展示到前端 然后给每一个数据两个按钮 一个编辑一个删除
# 查看
def userlist(request):
# 查询出用户表里面所有的数据
# 方式1
# data = models.User.objects.filter()
# print(data)
# 方式2
user_queryset = models.User.objects.all()
# return render(request,'userlist.html',{'user_queryset':user_queryset})
return render(request,'userlist.html',locals())
# 编辑功能
# 点击编辑按钮朝后端发送编辑数据的请求
"""
如何告诉后端,用户想要编辑哪条数据?
将编辑按钮所在的那一行数据的主键值发送给后端
利用url问号后面携带参数的方式
{% for user_obj in user_queryset %}
<tr>
<td>{{ user_obj.id }}</td>
<td>{{ user_obj.username }}</td>
<td>{{ user_obj.password }}</td>
<td>
<a href="/edit_user/?user_id={{ user_obj.id }}" class="btn btn-primary btn-xs"> 编辑</a>
<a href="" class="btn btn-danger btn-xs">删除</a>
</td>
</tr>
{% endfor %}
"""
# 后端查询出用户想要编辑的数据对象 展示到前端页面供用户查看和编辑
ef edit_user(request):
# 获取url问号后面的参数
edit_id = request.GET.get('user_id')
# 查询当前用户想要编辑的数据对象
edit_obj = models.User.objects.filter(id=edit_id).first()
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
# 去数据库中修改对应的数据内容
# 修改数据方式1
models.User.objects.filter(id=edit_id).update(username=username,password=password)
"""
将filter查询出来的列表中所有的对象全部更新 批量更新操作
只修改被修改的字段
"""
# 修改数据方式2
edit_obj.username = username
edit_obj.password= password
edit_obj.save()
"""
上述方法当字段特别多的时候效率会非常的低
从头到尾将数据的所有字段全部更新一边 无论该字段是否被修改
"""
# 跳转到数据的展示页面
return redirect('/userlist/')
# 将数据对象展示到页面上
return render(request,'edit_user.html',locals())
# 删除功能
# 跟编辑功能逻辑类似
def delete_user(request):
# 获取用户想要删除的数据id值
delete_id = request.GET.get('user_id')
# 直接去数据库中找到对应的数据删除即可
models.User.objects.filter(id=delete_id).delete()
"""
批量删除
"""
# 跳转到展示页面
return redirect('/userlist/')
# 真正的删除功能应该需要二次确认 我们这里先不做后面会讲
# 删除数据内部其实并不是真正的删除 我们会给数据添加一个标识字段用来表示当前数据是否被删除了,如果数据被删了仅仅只是修改一个表示状态的字段
username password is_delete
jason 123 0
egon 123 1
11.4 django orm中创建表关系
11.4.1 精华版
# 不写tofield参数 默认是与对方的主键字段做外键关联
# to参数是传入 键所关联的表的表名
# 在django1.X版本中外键默认都是级联更新删除的
# 在写入数据库时,一对多与一对一 会将该字段名拼接一个_id,如publish_id
# 多对多会新建一张表存放id的对应关系
# 出版社与书籍为一对多的关系,外键创建在多的一方,即书籍方
publish = models.ForeignKey(to='Publish')
# 作者与书是多对多的关系,MySQL中需要重新创建一张表,models中不用,直接在任意一方建立关联即可
authors = models.ManyToManyField(to='Author')
# 作者与作者详情是一对一的关系,键创建在使用频率高的一方,即作者
author_detail = models.OneToOneField(to='AuthorDetail')
11.4.2 详细版
"""
表与表之间的关系
一对多
多对多
一对一
没有关系
判断表关系的方法:换位思考
"""
图书表
出版社表
作者表
作者详情表
"""
图书和出版社是一对多的关系 外键字段建在多的那一方 book
图书和作者是多对多的关系 需要创建第三张表来专门存储
作者与作者详情表是一对一
"""
from django.db import models
# Create your models here.
# 创建表关系 先将基表创建出来 然后再添加外键字段
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
# 总共八位 小数点后面占两位
"""
图书和出版社是一对多 并且书是多的一方 所以外键字段放在书表里面
"""
publish = models.ForeignKey(to='Publish') # 默认就是与出版社表的主键字段做外键关联
"""
如果字段对应的是ForeignKey 那么会orm会自动在字段的后面加_id
如果字段名手动加了_id那么orm还是会在后面继续加_id
后面在定义ForeignKey的时候就不要自己加_id
"""
"""
图书和作者是多对多的关系 外键字段建在任意一方均可 但是推荐建在查询频率较高的一方
"""
authors = models.ManyToManyField(to='Author')
"""
authors是一个虚拟字段 主要是用来告诉orm 书籍表和作者表是多对多关系
让orm自动帮你创建第三张关系表
"""
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
"""
作者与作者详情是一对一的关系 外键字段建在任意一方都可以 但是推荐建在查询频率较高的表中
"""
author_detail = models.OneToOneField(to='AuthorDetail')
"""
OneToOneField也会自动给字段加_id后缀
所以也不要字段名手动的自己加_id
"""
class AuthorDetail(models.Model):
phone = models.BigIntegerField() # 或者直接字符类型
addr = models.CharField(max_length=32)
"""
orm中如何定义三种关系
publish = models.ForeignKey(to='Publish') # 默认就是与出版社表的主键字段做外键关联
authors = models.ManyToManyField(to='Author')
author_detail = models.OneToOneField(to='AuthorDetail')
ForeignKey
OneToOneField
会自动在字段后面加_id后缀
"""
# 在django1.X版本中外键默认都是级联更新删除的
# 多对多的表关系可以有好几种创建方式 这里暂且先介绍一种
# 针对外键字段里面的其他参数 暂时不要考虑 如果感兴趣自己可以百度试试看
12 django请求生命周期流程图(**************)
# 该图可以将Django的知识点全部串联起来,用于复习
# 扩展知识点
"""
缓存数据库
提前已经将你想要的数据准备好了 你来直接拿就可以
提高效率,降低响应时间
当在修改数据的时候 数据并不是立刻修改完成的
而是需要经过一段时间才会修改,这是因为一般的修改操作并不是直接修改真正的数据库
博客园
"""
13 路由层
13.1 路由匹配
# 路由匹配
url(r'test',views.test),
url(r'testadd',views.testadd)
"""
url方法第一个参数是正则表达式
只要第一个参数正则表达式能够匹配到内容 那么就会立刻停止往下匹配
直接执行对应的视图函数
在输入url的时候会默认加斜杠的原因:
django内部做的重定向
路由匹配的第一次匹配不成功,就会在url后面加斜杠再来一次
"""
# 取消自动加斜杠
APPEND_SLASH = False/True # 默认是自动加斜杠的
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 首页
url(r'^$',views.home),
# 路由匹配
url(r'^test/$',views.test),
url(r'^testadd/$',views.testadd),
# 尾页(了解)
url(r'',views.error),
]
13.2 无名分组
"""
分组就是将某一段正则表达式用小括号扩起来
"""
url(r'^test/(\d+)/',views.test)
def test(request,xx):
print(xx)
return HttpResponse('test')
# 无名分组会将括号内正则表达式匹配到的内容当作 位置参数 传递给后面的视图函数
13.3 有名分组
"""
可以给正则表达式起一个别名
"""
url(r'^testadd/(?P<year>\d+)',views.testadd)
def testadd(request,year):
print(year)
return HttpResponse('testadd')
# 有名分组会将括号内正则表达式匹配到的内容当作 关键字参数 传递给后面的视图函数
13.4 无名有名是否可以混合使用
"""
不能混用
但是同一个分组可以使用多次
"""
# 同类的分组可以使用多次
url(r'^index/(\d+)/(\d+)/(\d+)/',views.index),
url(r'^index/(?P<year>\d+)/(?P<age>\d+)/(?P<month>\d+)/',views.index),
13.5 反向解析
# 反向解析就是通过一些方法得到一个结果 该结果可以直接访问对应的url触发视图函数
# 先给路由与视图函数起一个别名
url(r'^func_kkk/',views.func,name='ooo')
# 反向解析
# 后端反向解析
from django.shortcuts import render,HttpResponse,redirect,reverse
reverse('ooo')
# 可以得到name='ooo'的路由匹配关系的网址的进入方法:
# '/func_kkk/'
# 前端反向解析
<a href="{% url 'ooo' %}">111</a>
# 点击该a标签即可跳转到ooo对应的url地址
13.6 无名有名分组与反向解析组合
# 无名分组的反向解析
url(r'^index/(\d+)/',views.index,name='xxx')
# 前端的解析方法(需要传入一个符合分组内正则表达式的参数)
{% url 'xxx' 123 %}
# 得到结果为 index/123/
# 后端的解析方法(需要在args=()中写入一个符合分组内正则表达式的参数)
reverse('xxx', args=(1,))
# 得到结果为 index/1/
# 有名分组反向解析
url(r'^func/(?P<year>\d+)/',views.func,name='ooo')
# 前端
# 有名分组反向解析 写法1
<a href="{% url 'ooo' year=123 %}">111</a>
# 有名分组反向解析 写法2 与无名的操作一模一样
<a href="{% url 'ooo' 123 %}">222</a>
# 后端
# 有名分组反向解析 写法1
print(reverse('ooo',kwargs={'year':123}))
# 有名分组反向解析 写法2 与无名的操作一模一样
print(reverse('ooo',args=(111,)))
"""
这个数字一般情况下放的是数据的主键值
例如可以用于数据的编辑和删除
url(r'^edit/(\d+)/',views.edit,name='xxx')
def edit(request,edit_id):
reverse('xxx',args=(edit_id,))
{%for user_obj in user_queryset%}
<a href="{% url 'xxx' user_obj.id %}">编辑</a>
{%endfor%}
"""
13.7 路由分发
路由分发:
用于减轻总路由的压力,使结构更清晰
`
注意事项:
总路由中的url正则表达式不能加$结尾
"""
django的每一个应用都可以有自己的templates文件夹,urls.py,static文件夹
正是基于上述的特点 django能够非常好的做到分组开发(每个人只写自己的app)
作为组长 只需要将手下书写的app全部拷贝到一个新的django项目中
然后在配置文件里面注册所有的app再利用路由分发的特点将所有的app整合起来
当一个django项目中的url特别多的时候 总路由urls.py代码会非常冗余不好维护
这个时候也可以利用路由分发来减轻总路由的压力
利用路由分发之后 总路由不再直接表示 路由与视图函数的直接对应关系
而是做一个分发处理
识别当前url是属于哪个应用下, 直接分发给对应的应用去处理
"""
# 总路由
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 1.路由分发
url(r'^app01/',include(app01_urls)), # 只要url前缀是app01开头 全部交给app01处理
url(r'^app02/',include(app02_urls)) # 只要url前缀是app02开头 全部交给app02处理
# 2.简便写法,不需要导入 推荐使用
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls'))
# 注意事项:总路由里面的url千万不能加$结尾
]
# 子路由
# app01 urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [url(r'^reg/',views.reg)]
# app02 urls.py
from django.conf.urls import url
from app02 import views
urlpatterns = [url(r'^reg/',views.reg)]
13.8 名称空间(了解)
# 当多个应用出现了相同的别名 我们研究反向解析会不会自动识别应用前缀
"""
正常情况下的反向解析是没有办法自动识别前缀的
"""
# 名称空间
# 总路由
url(r'^app01/',include('app01.urls',namespace='app01')),
url(r'^app02/',include('app02.urls',namespace='app02'))
# 解析的时候
# app01
urlpatterns = [
url(r'^reg/',views.reg,name='reg')
]
# app02
urlpatterns = [
url(r'^reg/',views.reg,name='reg')
]
reverse('app01:reg')
reverse('app02:reg')
{% url 'app01:reg' %}
{% url 'app02:reg' %}
# 其实只要保证名字不冲突 就没有必要使用名称空间
"""
一般情况下 有多个app的时候我们在起别名的时候会加上app的前缀
这样的话就能够确保多个app之间名字不冲突的问题
"""
urlpatterns = [
url(r'^reg/',views.reg,name='app01_reg')
]
urlpatterns = [
url(r'^reg/',views.reg,name='app02_reg')
]
13.9 伪静态(了解)
"""
静态网页
数据是固定的 万年不变
伪静态
将一个动态网页伪装成静态网页
例如:https://www.cnblogs.com/Dominic-Ji/p/9234099.html
为什么要伪装呢?
伪装的目的在于增大本网站的seo查询力度
Search Engine Optimization搜索引擎优化
利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名
但是无论怎么优化 怎么处理,始终还是比不过RMB玩家(交广告费)
"""
# 进行伪静态的方法: 后缀添加.html
urlpatterns = [
url(r'^reg.html',views.reg,name='app02_reg')
]
13.10 虚拟环境(了解)
"""
在正常开发中 我们会给每一个项目配备一个该项目独有的解释器环境
该环境内只有该项目用到的模块 用不到一概不装
linux:缺什么才装什么
虚拟环境
每创建一个虚拟环境就类似于重新下载了一个纯净的python解释器(无任何其他模块,工具)
但是虚拟环境不要创建太多,因为它是需要消耗硬盘空间的
扩展:
每一个项目都需要用到很多模块 并且每个模块版本可能还不一样
那我该如何安装呢?
开发当中我们会给每一个项目配备一个requirements.txt文件
里面书写了该项目所有的模块即版本
只需要直接输入一条命令即可一键安装所有模块即版本
"""
13.11 django版本在路由层的区别
13.11.1 路由层使用的是匹配方法
django1.X路由层使用的是url方法,而在django2.X和3.X版本中路由层使用的是path方法
url()第一个参数支持正则
path()第一个参数不支持正则, 写什么就匹配什么
如果你习惯使用正则匹配.那么2.X也为你提供了另外一个方法
from django.urls import path, re_path
re_path(r'^index/',index)
from django.conf.urls import url
url(r'^login/',login)
2.X和3.X中的re_path方法就等价于1.X里面的url方法
13.11.2 转换器
虽然2.X和3.X的path不支持正则 但是它的内部支持五种转换器
str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
# 使用方法:
path('index/<int:id>/',index)
# 它会将匹配/<int:id>/的内容 先转成整型 然后以关键字的形式传递给后面的视图函数
def index(request,id):
print(id,type(id))
return HttpResponse('index')
# 除了有默认的五个转换器之外 还支持自定义转换器(了解)
class MonthConverter:
regex = '\d{2}' # 属性名必须为regex
def to_python(self, value):
return int(value)
def to_url(self, value):
return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
from django.urls import path, register_converter
from app01.path_converts import MonthConverter
# 先注册转换器
register_converter(MonthConverter, 'mon')
from app01 import views
urlpatterns = [
path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa')]
13.11.3 级联更新 级联删除
1.X模型层中的外键默认都是级联更新,级联删除的
而2.X和3.X中外键的级联更新,级联删除需要手动配置参数,才能产生
models.ForeignKey(to='Publish')
models.ForeignKey(to='Publish',on_delete=models.CASCADE,on_update=models.CASCADE)
14 视图层
14.1 小白必会三板斧补充
HttpResponse 用于返回字符串类型
render 用于返回html页面 并且在返回给浏览器之前还可以给html文件传值
redirect 用于重定向
研究三者的源码可以发现他们实例化产生的对象,本质上都是HttpResponse对象
# 如果视图函数不返回结果,页面会报错:
The view app01.views.index didn't return an HttpResponse object.
It returned None instead.
# 结论:
# 视图函数必须要返回一个HttpResponse对象
# render简单内部原理
from django.template import Template,Context
res = Template('<h1>{{ user }}</h1>')
con = Context({'user':{'username':'jason','password':123}})
ret = res.render(con)
print(ret)
return HttpResponse(ret)
14.2 JsonResponse对象
json格式的数据有什么用?
前后端数据交互需要使用到json作为过渡 实现跨语言传输数据
前端序列化
JSON.stringify() json.dumps()
JSON.parse() json.loads()
# 使用json模块序列化
import json
from django.http import JsonResponse
def ab_json(request):
user_dict = {'username':'jason好帅哦,我好喜欢!','password':'123','hobby':'girl'}
l = [111,222,333,444,555]
# 先转成json格式字符串,使用ensure_ascii=False让页面能够展示中文
json_str = json.dumps(user_dict,ensure_ascii=False)
# 将该字符串返回
return HttpResponse(json_str)
# 使用JsonResponse序列化字典
from django.http import JsonResponse
def ab_json(request):
user_dict = {'username':'jason好帅哦,我好喜欢!','password':'123','hobby':'girl'}
return JsonResponse(user_dict,json_dumps_params={'ensure_ascii':False})
# 研究源码,使用JsonResponse序列化其他数据类型
from django.http import JsonResponse
def ab_json(request):
l = [111,222,333,444,555]
# 执行return JsonResponse(l)会报错
# In order to allow non-dict objects to be serialized set the safe parameter to False.
# 读报错信息可知: 设置safe参数为False才可以传字典以外的对象
return JsonResponse(l,safe=False)
# 默认只能序列化字典 序列化其他需要加safe=False
14.3 form表单上传文件及后端操作
form表单上传文件类型的数据的前提:
1.method必须指定成post
2.enctype必须换成formdata
def ab_file(request):
if request.method == 'POST':
# print(request.POST) # 只能获取普通的键值对数据,文件无法获取
print(request.FILES) # 获取文件数据
# <MultiValueDict: {'file': [<InMemoryUploadedFile: u=1288812541,1979816195&fm=26&gp=0.jpg (image/jpeg)>]}>
file_obj = request.FILES.get('file') # 文件对象
print(file_obj.name)
with open(file_obj.name,'wb') as f:
for line in file_obj.chunks():
# 推荐加上.chunks()方法
f.write(line)
return render(request,'form.html')
14.4 request对象的方法
request.method # 'POST' 'GET'
request.POST # 类似于{'username'=['wu'],'hobby'=['read','study']}
request.POST.get('username') # 得到'wu'
request.POST.getlist('hobby') # 得到['read','study']
request.GET
request.GET.get('username') # 得到'wu'
request.GET.getlist('hobby') # 得到['read','study']
request.FILES # 类似于{'file'=[<文件对象>,<文件对象>]}
request.body # 原生的浏览器发过来的二进制数据 后面详细的讲
request.path # 能获取url /app01/ab_file/
request.path_info # 能获取url /app01/ab_file/
request.get_full_path() # 能获取完整的url以及问号后面的参数 /app01/ab_file/?username=jason
print(request.path) # /app01/ab_file/
print(request.path_info) # /app01/ab_file/
print(request.get_full_path()) # /app01/ab_file/?username=jason
14.5 FBV与CBV
14.5.1 FBV与CBV的使用
# 视图函数既可以是函数也可以是类
# FBV路由
url(r'^index/',views.index)
def index(request):
return HttpResponse('index')
# CBV路由
url(r'^login/',views.MyLogin.as_view())
from django.views import View
class MyLogin(View):
def get(self,request):
return render(request,'form.html')
def post(self,request):
return HttpResponse('post方法')
"""
FBV和CBV各有千秋
CBV特点
能够直接根据请求方式的不同直接匹配到对应的方法执行
"""
14.5.2 CBV源码剖析
# 突破口在urls.py
url(r'^login/',views.login)
url(r'^login/',views.MyLogin.as_view())
# 两种写法的差距在于login与MyLogin.as_view()
# 推测MyLogin.as_view()的结果是一个函数的地址
# 且由于MyLogin是自己写的类,继承View类,
# 推测as_view()是被@staicmethod修饰的静态方法或是被@classmethod修饰的类方法
"""
在看python源码的时候 一定要时刻提醒自己面向对象属性方法查找顺序
先从对象自己找
再去产生对象的类里面找
之后再去父类找
...
总结:看源码只要看到了self 一定要清楚当前这个self到底是什么
"""
# 进入as_view方法的源码查看,可以看到是一个View下的类方法
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
...
return view
# 在该类方法内定义了一个view函数,并且将该函数对象作为返回值
# 也就是说views.MyLogin.as_view()得到的结果本质上是views.view == views.MyLogin.as_view()
# url(r'^login/',views.view) 与FBV一模一样
# 即CBV与FBV在路由匹配上本质是一样的 都是路由 对应 函数内存地址
# 接下来看view函数被执行时发生了什么
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs) # cls是我们自己写的类 MyLogin
# self = MyLogin(**initkwargs) 实例化产生一个MyLogin类的对象
return self.dispatch(request, *args, **kwargs)
return view
# 可以看到执行了view执行,最终会执行self.dispatch(request, *args, **kwargs)并返回结果
# 其中self是MyLogin类的对象
# 接下来继续看dispatch方法的源码
# CBV的精髓
def dispatch(self, request, *args, **kwargs):
# request.method.lower()获取当前请求的小写格式
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# if条件是在比对当前的请求方式是否合法,在此列表中,
# 如果请求不在该列表中,则执行else代码块,http_method_not_allowed,最终会报错 ...
# 接下来以get请求为例,会执行
# handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
# getattr是反射-->即通过字符串转换成真正的函数
# request.method.lower() == 'get'
# handler == self.get (MyLogin类的对象中的get方法)
# 且设置了默认值:self无get方法时报错,执行self.http_method_not_allowed
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
"""
反射:通过字符串来操作对象的属性或者方法
handler = getattr(自己写的类产生的对象,'get',当找不到get属性或者方法的时候就会用第三个参数)
handler = 我们自己写的类里面的get方法
"""
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
"""
自动调用get方法
"""
15 模板层
15.1 模版语法传值
{{ }}:变量相关
{% %}:逻辑相关
{{ }}模版语法 内部会判断内部的对象是否callable
如果可调用就会调用,获取它的返回值,
如果不可调用就会直接获取该对象
验证方法:
def test(request):
class A:
def a(self):
print(self)
# def __call__(self, *args, **kwargs):
# return 1
obj = A()
print(locals())
return render(request,'test.html',locals())
不添加__call__方法,{{ obj }}会展示对象:
<app01.views.test.<locals>.A object at 0x000001CC87AD9160>
添加__call__方法,{{ obj }}会展示__call__方法的返回值:1
def index(request):
# 模版语法可以传递的后端python数据类型
n = 123
f = 11.11
s = '我也想奔现'
b = True
l = ['小红','姗姗','花花','茹茹']
t = (111,222,333,444)
d = {'username':'jason','age':18,'info':'这个人有点意思'}
se = {'晶晶','洋洋','嘤嘤'}
def func():
print('我被执行了')
return '你的另一半在等你'
class MyClass(object):
def get_self(self):
return 'self'
@staticmethod
def get_func():
return 'func'
@classmethod
def get_class(cls):
return 'cls'
# 对象被展示到html页面上 就类似于执行了打印操作也会触发__str__方法
def __str__(self):
return '到底会不会?'
obj = MyClass()
# return render(request,'index.html',{}) # 一个个传
return render(request,'index.html',locals())
<p>{{ n }}</p>
<p>{{ f }}</p>
<p>{{ s }}</p>
<p>{{ b }}</p>
<p>{{ l }}</p>
<p>{{ d }}</p>
<p>{{ t }}</p>
<p>{{ se }}</p>
<p>传递函数名会自动加括号调用 但是模版语法不支持给函数传额外的参数:{{ func }}</p>
<p>传类名的时候也会自动加括号调用(实例化){{ MyClass }}</p>
<p>内部会判断对象是否callable 如果可调用就会调用,取返回值</p>
<p>{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_func }}</p>
<p>{{ obj.get_class }}</p>
# django模版语法的取值 是固定的格式 只能采用“句点符” .
<p>{{ d.username }}</p>
<p>{{ l.0 }}</p>
<p>{{ d.hobby.3.info }}</p>
# 即可以点键也可以点索引 还可以两者混用
15.2 过滤器
过滤器主要是用来对数据内容做简单的处理
过滤器只能最多有两个参数
{{数据|过滤器:参数}}
# 过滤器类似于模版语法 内置的 内置方法
# 主要是用来对数据内容做简单的处理
# django内置有60多个过滤器 这里只介绍部分
# 基本语法
{{数据|过滤器:参数}}
# 转义()
# 前端转义方法
{{数据|safe}}
# 后端转义方法
from django.utils.safestring import mark_safe
res = mark_safe('<h1>新新</h1>')
"""
以后你在全栈项目的时候 前端代码不一定非要在前端页面书写
也可以现在先在后端写好 然后传递给前端页面
"""
<p>统计长度:{{ s|length }}</p>
<p>默认值:第一个参数的布尔值是True就展示第一个参数的值,否则展示冒号后面的值:</p>
<p>默认值:内部使用的是 b or '啥也不是' {{ b|default:'啥也不是' }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>日期格式化:{{ current_time|date:'Y-m-d H:i:s' }}</p>
<p>切片操作(支持步长):{{ l|slice:'0:4:2' }}</p>
<p>切取字符(数字包含三个点):{{ info|truncatechars:9 }}</p>
<p>切取单词(数字不包含三个点... 按照空格切):{{ info|truncatewords:9 }}</p>
<p>移除所有特定的字符(只能输入1个符号):{{ msg|cut:' ' }}</p>
<p>拼接操作:{{ l|join:'$' }}</p>
<p>拼接操作(加法):{{ n|add:10 }}</p>
<p>拼接操作(加法):{{ s|add:msg }}</p>
<p>转义:{{ hhh|safe }}</p>
<p>转义:{{ sss|safe }}</p>
<p>转义:{{ res }}</p>
15.3 标签
标签支持逻辑相关的语法
如:
for 循环
if 判断
with ... as ... 起别名
# for循环
{% for foo in l %}
<p>{{ forloop }}</p>
<p>{{ foo }}</p>
{% endfor %}
# forloop的结果:{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 6, 'revcounter0': 5, 'first': True, 'last': False}
# 其中counter0是从0开始的计数,即索引,counter为从1开始的计数,
# revcounter与revcounter0是对应的倒序计数
# 只有第一次for循环的forloop的first参数才是True,其他都为False
# 只有最后一次for循环的forloop的last参数才是True,其他都为False
# if判断
{% if b %}
<p>baby</p>
{% elif s%}
<p>都来把</p>
{% else %}
<p>老baby</p>
{% endif %}
# for与if混合使用
{% for foo in lll %}
{% if forloop.first %}
<p>这是我的第一次</p>
{% elif forloop.last %}
<p>这是最后一次啊</p>
{% else %}
<p>{{ foo }}</p>
{% endif %}
{% empty %}
<p>循环的可迭代对象内没有元素时触发</p>
{% endfor %}
# 支持字典的keys,values,items方法
{% for foo in d.keys %}
<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.values %}
<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.items %}
<p>{{ foo }}</p>
{% endfor %}
# with as起别名
# 起别名后在其子代码块中即可使用该别名进行代替
# 注意:别名只有在with与endwith范围内才有效
{% with d.hobby.3.info as nb %}
<p>{{ nb }}</p>
在with语法内就可以通过as后面的别名快速的使用到前面非常复杂获取数据的方式
<p>{{ d.hobby.3.info }}</p>
{% endwith %}
15.4 自定义过滤器
过滤器只能最多有两个参数
{{数据|过滤器:参数}}
前提条件:
1.在应用下创建一个名为templatetags的文件夹
2.在该文件夹内创建任意名称的py文件 如:mytag.py
3.在该py文件内"必须"先书写下列两行代码
from django import template
register = template.Library()
# 自定义过滤器 类似于无逻辑语法的简单自定义函数(必须为1或2个参数)
@register.filter(name='baby')
def my_sum(v1, v2):
return v1 + v2
# 使用
{% load mytag %}
<p>{{ n|baby:666 }}</p>
15.5 自定义标签与inclusion_tag
前提条件:
1.在应用下创建一个名为templatetags的文件夹
2.在该文件夹内创建任意名称的py文件 如:mytag.py
3.在该py文件内"必须"先书写下列两行代码
from django import template
register = template.Library()
# 自定义标签(参数可以有多个) 类似于自定义函数
@register.simple_tag(name='plus')
def index(a,b,c,d):
return '%s-%s-%s-%s'%(a,b,c,d)
# 使用
标签多个参数彼此之间空格隔开
<p>{% plus 'jason' 123 123 123 %}</p>
# 自定义inclusion_tag
"""
内部原理
先定义一个方法
在页面上调用该方法 并且可以传值
该方法会生成一些数据然后传递给一个html页面
之后将渲染好的结果放到调用的位置
"""
@register.inclusion_tag('left_menu.html')
def left(n):
data = ['第{}项'.format(i) for i in range(n)]
# 第一种
# return {'data':data} # 将data传递给left_menu.html
# 第二种
return locals() # 将data传递给left_menu.html
{% left 5 %}
# 总结:当html页面某一个地方的页面需要传参数才能够动态的渲染出来,并且在多个页面上都需要使用到该局部 那么就考虑将该局部页面做成inclusion_tag形式
(在讲bbs的时候会使用到)
15.6 模版的继承
"""
你们有没有见过一些网站
这些网站页面整体都大差不差 只是某一些局部在做变化
"""
# 模版的继承 你自己先选好一个你要想继承的模版页面
{% extends 'home.html' %}
# 继承了之后子页面跟模版页面长的是一模一样的 你需要在模版页面上提前划定可以被修改的区域
{% block content %}
模版内容
{% endblock %}
# 子页面就可以声明想要修改哪块划定了的区域
{% block content %}
子页面内容
{% endblock %}
# 一般情况下模版页面上应该至少有三块可以被修改的区域
1.css区域
2.html区域
3.js区域
{% block css %}
{% endblock %}
{% block content %}
{% endblock %}
{% block js %}
{% endblock %}
# 这样每一个子页面就都可以有自己独有的css代码 html代码 js代码
一般情况下 模版的页面上划定的区域越多 那么该模版的扩展性就越高
但是如果划定的区域太多 就没必要划分了,直接重写页面
15.7 模版的导入
"""
将页面的某一个局部当成模块的形式
哪个地方需要就可以直接导入使用即可
"""
{% include 'wasai.html' %}
16 测试脚本
测试脚本可以让你不用书写前后端交互的形式,直接进行django里单个py文件的测试
# 测试环境的准备 去manage.py中拷贝前四行代码 然后添加import django与django.setup()
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
import django
django.setup()
# 在这个代码块中就可以测试django里的单个py文件了
# 补充:
# pycharm中也有一个测试环境
# 在控制台的python console
17 模型层
django自带的sqlite3数据库对日期格式不是很敏感 处理的时候容易出错
需要将默认数据库更改为mysql
详情见 11.1.2 django链接数据库
17.1 单表操作
class User(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
register_time = models.DateField() # 年月日
"""
DateField
DateTimeField
两个重要参数
auto_now:每次操作数据的时候 该字段会自动将当前时间更新
auto_now_add:在创建数据的时候会自动将当前创建时间记录下来 之后只要不认为的修改 那么就一直不变
"""
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
publish_date = models.DateField(auto_now_add=True)
# 一对多
publish = models.ForeignKey(to='Publish')
# 多对多
authors = models.ManyToManyField(to='Author')
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
email = models.EmailField()
# email 数据库中显示varchar(254)
# 这是因为EmailField字段类型不是给models看的 而是给校验性组件看的
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
# 一对一
author_detail = models.OneToOneField(to='AuthorDetail')
class AuthorDetail(models.Model):
phone = models.BigIntegerField()
# 电话号码用BigIntegerField或者直接用CharField
addr = models.CharField(max_length=64)
17.1.1 记录的查
# 第一种查找方法 models.User.objects.filter(筛选条件).first()
# 获取QuerySet对象中的第一条记录对象
user_obj = models.User.objects.filter(pk=6).first() # pk会自动对应表中的主键字段
# 第二种查找方法 obj = models.User.objects.get(pk=4)
# 通过get直接获取记录对象,无符合条件的记录报错,不推荐使用
user_obj = models.User.objects.get(pk=4) # pk会自动对应表中的主键字段
# 两种方法的区别
# get方法返回的是当前记录对象, 一旦数据不存在该方法会直接报错,不推荐使用
# filter方法返回的是QuerySet对象,数据不存在会返回一个空QuerySet对象,不报错
17.1.2 记录的增
import datetime
# 第一种增加方法 models.表名.objects.create(字段=数据值)
res = models.User.objects.create(name='jason',age=18,register_time='2002-1-21')
print(res) # res为刚刚创建的记录对象
# 第二种增加方法 obj=models.表名(字段=数据值) + obj.save()
ctime = datetime.datetime.now()
user_obj = models.User(name='egon',age=84,register_time=ctime)
user_obj.save()
17.1.3 记录的删
# 第一种删除方法 models.表名.objects.filter(筛选条件).delete()
# 获取QuerySet对象直接删除(批量删除)
res = models.User.objects.filter(pk=2).delete() # pk会自动对应表中的主键字段
print(res) # res为删除的数据
# 第二种删除方法 obj=models.表名.objects.filter(筛选条件).first() + obj.delete()
# 获取单条记录对象 再通过obj.delete()方法删除
user_obj = models.User.objects.filter(pk=1).first() # pk会自动对应表中的主键字段
user_obj.delete()
17.1.4 记录的改
# 第一种修改方法 models.User.objects.filter(筛选条件).update(字段=新值)
# 获取QuerySet对象直接修改(批量修改)
models.User.objects.filter(pk=4).update(name='egonDSB') # pk会自动对应表中的主键字段
# 第二种修改方法
# obj=models.表名.objects.filter(筛选条件).first() + obj.字段=新值 + obj.save()
# 获取单条的记录对象,进行属性修改,再用 obj.save()方法保存
user_obj = models.User.objects.filter(pk=6).first() # pk会自动对应表中的主键字段
# user_obj = models.User.objects.get(pk=4)
user_obj.name = 'egonPPP'
user_obj.save()
17.2 必知必会13条
1.all() 获取所有记录对象
res = models.User.objects.all()
2.filter() 带有过滤条件的查询
res = models.User.objects.filter(pk=2)
3.get() 直接取记录对象 符合条件的记录不存在时直接报错
res = models.User.objects.get(pk=2)
4.first() 取QuerySet中的第一个元素
res = models.User.objects.all().first()
5.last() 取QuerySet中的最后一个元素
res = models.User.objects.all().last()
6.values() 获取指定的记录字段 select name,age from User 列表套字典形式
res = models.User.objects.values('name','age')
# <QuerySet [{'name': 'jason', 'age': 18}, {'name': 'egonPPP', 'age': 84}]>
7.values_list() 获取指定的记录字段 select name,age from User 列表套元祖形式
res = models.User.objects.values_list('name','age')
# <QuerySet [('jason', 18), ('egonPPP', 84)]>
8.distinct() 去重
res = models.User.objects.values('name','age').distinct()
# 去重数据一定要是一模一样的
# 如果数据带有主键,那么肯定不一样,就无法去重 !
9.order_by() 排序
res = models.User.objects.order_by('age') # 默认升序
res = models.User.objects.order_by('-age') # 降序
10.reverse() 反转的前提是:数据已排序,否则反转无效 order_by()
res = models.User.objects.order_by('age').reverse()
11.count() 统计当前记录的条数
res = models.User.objects.count()
12.exclude() 将符合条件的记录排除在外
res = models.User.objects.exclude(name='jason')
13.exists() 查看是否存在 返回布尔值 由于数据本身自带布尔值,使用情急较少
res = models.User.objects.filter(pk=10).exists()
17.3 查看 方法对应sql语句的方式
# 方式1(QuerySet对象专用)
res = models.User.objects.values_list('name','age')
# <QuerySet [('jason', 18), ('egonPPP', 84)]>
print(res.query)
QuerySet对象才能够使用.query 查看内部的sql语句
# 方式2(通用方法)
# 配置文件中配置(settings.py)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
17.4 双下划线查询
1 年龄大于35岁的用户数据
res = models.User.objects.filter(age__gt=35)
print(res)
2 年龄小于35岁的用户数据
res = models.User.objects.filter(age__lt=35)
print(res)
3 大于等于32岁的用户数据
res = models.User.objects.filter(age__gte=32)
print(res)
4 小于等于32岁的用户数据
res = models.User.objects.filter(age__lte=32)
print(res)
5 年龄是18或者32或者40岁的用户数据
res = models.User.objects.filter(age__in=[18,32,40])
print(res)
6 年龄在18到40岁之间的用户数据(18,40都包含)
res = models.User.objects.filter(age__range=[18,40])
print(res)
7 查询出名字里面含有s的用户数据 (模糊查询) 区分大小写
res = models.User.objects.filter(name__contains='s')
print(res)
8 查询出名字里面含有p的用户数据 忽略大小写
res = models.User.objects.filter(name__icontains='p')
print(res)
9 查询出名字以j为开头的用户数据 忽略大小写
res = models.User.objects.filter(name__startswith='j')
10 查询出名字以j为结尾的用户数据 忽略大小写
res = models.User.objects.filter(name__endswith='j')
11 查询注册时间是1月的用户数据
res = models.User.objects.filter(register_time__month='1')
12 查询注册时间是2020年的用户数据
res = models.User.objects.filter(register_time__year='2020')
17.5 一对多外键相关的增删改
# 含有一对多外键的数据的增加
# 1 将关联记录的id值 传入数据库中的真实字段
models.Book.objects.create(title='论语',price=899.23,publish_id=1)
models.Book.objects.create(title='聊斋',price=444.23,publish_id=2)
models.Book.objects.create(title='老子',price=333.66,publish_id=1)
# 2 将被关联的记录对象 传入ORM中的虚拟字段
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='红楼梦',price=666.23,publish=publish_obj)
# 一对多关系中一的一方的数据 删除是级联删除
models.Publish.objects.filter(pk=1).delete() # 级联删除
# 含有一对多外键的数据修改外键绑定
# 1 将关联记录的id值 传入数据库中的真实字段
models.Book.objects.filter(pk=1).update(publish_id=2)
# 2 将被关联的记录对象 传入ORM中的虚拟字段
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)
17.6 多对多外键相关的增删改
17.6.1 多对多外键相关方法
# 我们知道多对多在数据库中其实是有第三张表的,而在ORM中我们只有两张表
# 如何选择到这第三张表,并修改外键关系呢?
# 增加多对多外键
# 1. add中传入绑定对象的主键值
建立外键方对象.虚拟字段.add(1,2,3)
# 2. add中传入绑定对象
建立外键方对象.虚拟字段.add(被关联对象1,被关联对象2)
# 删除多对多外键
# 1. remove中传入绑定对象的主键值
建立外键方对象.虚拟字段.remove(1)
# 2. remove中传入绑定对象
建立外键方对象.虚拟字段.remove(被关联对象)
# 修改多对多外键
# 1. set中传入绑定对象的主键值组成的可迭代对象
建立外键方对象.虚拟字段.set([1,2,3])
# 2. set中传入绑定对象
建立外键方对象.虚拟字段.set([被关联对象1,被关联对象2])
# 清空多对多外键
# 建立外键方对象.虚拟字段.clear()
# 删除该对象绑定的所有多对多外键关系
# 括号内不要加任何参数
17.6.2 多对多外键增删改实例
# 给书籍添加作者
# 1. add中传入绑定对象的主键值
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.authors) # 就类似于选择到第三张关系表
book_obj.authors.add(1) # 为该书籍绑定主键为1的作者
book_obj.authors.add(2,3) # 为该书籍绑定主键为2,3的作者
# 2. add中传入绑定对象
author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj2 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj)
book_obj.authors.add(author_obj1,author_obj2)
# 删除绑定关系
# 1. remove中传入绑定对象的主键值
book_obj.authors.remove(2)
book_obj.authors.remove(1,3)
# 2. remove中传入绑定对象
author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj,author_obj1)
# 修改绑定关系(覆盖)
book_obj.authors.set([1,2]) # 括号内必须为一个可迭代对象
book_obj.authors.set([3]) # 括号内必须为一个可迭代对象
author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj,author_obj1]) # 括号内必须为一个可迭代对象
# set括号内必须传一个可迭代对象,该对象内既可以数字也可以对象 并且都支持多个
# 清空绑定关系
# 在第三张关系表中清空某个书籍与作者的绑定关系
book_obj.authors.clear()
# clear括号内不要加任何参数
17.7 多表查询
17.7.1 正反向的概念
通过外键字段所在的表查询不含外键字段的表就是正向
通过不含外键字段的表查询含外键字段的表就是反向
book >>> 外键字段在书表中(正向) >>> publish
publish >>> 外键字段在书表中(反向) >>> book
一对一和多对多正反向的判断也是如此
正向查询 用类中外键字段
反向查询 用类名小写
# 子查询中正向什么时候需要加.all():
当查询目标为 多的一方 时需要加.all()
当查询目标为 一的一方则直接拿到数据对象
book_obj.publish 一对多 一的一方
book_obj.authors.all() 多对多 多的一方(双方都是多)
author_obj.author_detail 一对一 一的一方(双方都是一)
# 子查询中反向查询什么时候需要加_set.all():
当查询目标为 多的一方 时需要加_set.all()
当查询目标为 一的一方 则直接拿到数据对象 不需要加_set.all()
17.7.2 子查询(基于对象的跨表查询)
1.查询书籍主键为1的出版社
book_obj = models.Book.objects.filter(pk=1).first()
# 通过书查出版社 正向 按类中外键字段
res = book_obj.publish
print(res)
print(res.name)
print(res.addr)
2.查询书籍主键为2的作者
book_obj = models.Book.objects.filter(pk=2).first()
# 通过书查作者 正向 按类中外键字段
# res = book_obj.authors
# app01.Author.None 遇到该结果就添加.all()
res = book_obj.authors.all()
# <QuerySet [<Author: Author object>, <Author: Author object>]>
print(res)
3.查询作者jason的电话号码
author_obj = models.Author.objects.filter(name='jason').first()
res = author_obj.author_detail
print(res)
print(res.phone)
print(res.addr)
4.查询出版社是东方出版社出版的书
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
出版社查书 反向
res = publish_obj.book_set # app01.Book.None
res = publish_obj.book_set.all()
print(res)
5.查询作者是jason写过的书
author_obj = models.Author.objects.filter(name='jason').first()
作者查书 反向
res = author_obj.book_set # app01.Book.None
res = author_obj.book_set.all()
print(res)
6.查询手机号是110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
res = author_detail_obj.author
print(res.name)
17.7.3 联表查询(基于双下划线的跨表查询)
1.1 查询jason的手机号和作者姓名
res = models.Author.objects.filter(name='jason').values('author_detail__phone','name')
print(res)
1.2 反向(不用author表 查询jason的手机号和作者姓名)
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','author__name')
print(res)
2.1 查询书籍主键为1的出版社名称和书的名称
res = models.Book.objects.filter(pk=1).values('title','publish__name')
print(res)
2.2 反向(不使用书籍表)
res = models.Publish.objects.filter(book__id=1).values('name','book__title')
print(res)
3.1 查询书籍主键为1的作者姓名
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)
3.2 反向(不用书籍表)
res = models.Author.objects.filter(book__id=1).values('name')
print(res)
4.1 查询书籍主键是1的作者的手机号
book author authordetail
res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)
4.2 反向(不用书籍表)
models.Author.objects.filter(book__id=1).values('author_detail__phone')
# 只要掌握了正反向的概念以及双下划线方法
# 那么就可以无限制的跨表查询
17.8 聚合查询
聚合函数: max,min,avg,sum,count
聚合查询通常情况下都是配合分组一起使用的
不分组的情况下需要使用聚合函数 要使用关键字:aggregate
`
tips:
与数据库相关的模块大部分在django.db.models中
少部分在django.db中
# 聚合查询 关键字:aggregate
from app01 import models
from django.db.models import Max, Min, Sum, Count, Avg
# 1 求所有书的平均价格
res = models.Book.objects.aggregate(Avg('price'))
print(res)
# 2.聚合查询的使用
res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Count('pk'), Avg('price'))
print(res)
17.9 分组查询
分组关键字: annotate
MySQL中分组查询的特点:
分组之后默认只能获取到分组的依据 组内其他字段都无法直接获取了
这是设置了严格模式实现的 sql_mode=ONLY_FULL_GROUP_BY
ORM中如果出现分组查询报错的情况,可能需要修改数据库严格模式
models.Book.objects.annotate() # 按照book主键分组
models.Book.objects.values('price').annotate() # 按照price字段分组
# 分组查询 annotate
from django.db.models import Max, Min, Sum, Count, Avg
# res = models.Book.objects.annotate()
# 按照book主键分组
# 1.统计每一本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors')).values('title','author_num')
# author_num是自定义的字段 用来存储统计出来的每本书对应的作者个数 Count('authors') 的值
# Count('authors') 传入外键虚拟字段名authors等同为传入'authors__pk'
res1 = models.Book.objects.annotate(author_num=Count('authors__id')).values('title','author_num')
print(res,res1)
# 2.统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price')
print(res)
# 3.统计不止一个作者的图书
# 思路: 先按图书分组,获取各本图书的作者数量,再筛选作者数量大于1的图书数据
res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=1).values('title','author_num')
print(res)
# 类似于链式操作,上面的每个操作的结果都是QuerySet对象,可以继续操作
# 4.查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name','sum_price')
print(res)
17.10 F与Q查询
17.10.1 F查询
F查询能够直接获取到表中某个字段对应的数据
但是在操作字符类型的数据的时候 F不能直接做到字符串的拼接
# 1.查询卖出数大于库存数的书籍
from django.db.models import F
res = models.Book.objects.filter(maichu__gt=F('kucun'))
print(res)
# 2.将所有书籍的价格提升500块
from django.db.models import F
models.Book.objects.update(price=F('price') + 500)
# 3.将所有书的名称后面加上爆款两个字
# 在操作字符类型的数据的时候 F不能够直接做到字符串的拼接
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
# 如果执行models.Book.objects.update(title=F('title') + '爆款')
# 会导致所有的title会全部变成空白!!!
17.10.2 Q查询
使用Q查询可以使条件之间不再为and的关系,还可以使用or与not
如:
~Q(条件)|Q(条件)
其中~为not |为or
# 查询卖出数大于100或者价格小于600的书籍
# res = models.Book.objects.filter(maichu__gt=100,price__lt=600)
# 该语句的filter内的多个条件是and关系,无法实现
from django.db.models import Q
res = models.Book.objects.filter(Q(maichu__gt=100),Q(price__lt=600))
# 如果直接使用Q方法,用逗号分割 还是and关系
res = models.Book.objects.filter(Q(maichu__gt=100)|Q(price__lt=600))
# | 是or关系
res = models.Book.objects.filter(~Q(maichu__gt=100)|Q(price__lt=600))
# ~ 是not关系
print(res) # <QuerySet []>
# Q的高阶用法
# 实现通过用户输入的字符串类型数据,组成查询条件
# 首先通过字符串的拼接得到'price__lt',600两个变量,再将其变成条件
q = Q()
q.connector = 'or' # 更改q对象中条件的连接关系,默认为and
q.children.append(('maichu__gt', 100)) # 添加条件
q.children.append(('price__lt', 600)) # 添加条件
res = models.Book.objects.filter(q) # 不修改connector默认是and关系
print(res)
17.11 事务
17.11.1 事务简介
事务的特性: ACID
原子性 A atomicity
事务是一个整体,不可分割
一致性 C consistency
与原子性相辅相成,同一事务中的结果(数据的操作)是一致的,要么全成功,要么全失败
隔离性 I isolation
不同事务之间互相不干扰
持久性 D durability
事务一旦确认,数据的操作会永久生效
事务的回滚:
将数据回滚到事务执行前的状态
rollback
事务的确认
确认事务执行成功,保存数据,一旦确认无法回滚
commit
17.11.2 事务的使用
from django.db import transaction
try:
with transaction.atomic():
# ORM操作1
# ORM操作2
...
# 在with代码块中书写的所有orm操作都属于同一个事务
except Exception as e:
print(e)
print('执行其他操作')
17.12 orm中的字段及参数
17.12.1 orm中的常用字段及参数
AutoField
一般用于主键字段 primary_key=True
CharField varchar
verbose_name 字段的注释
max_length 长度
IntegerField int
BigIntegerField bigint
DecimalField decimal
max_digits=8 对应decimal的两个参数
decimal_places=2
EmailFiled varchar(254)
DateField date
DateTimeField datetime
auto_now:每次修改数据时都会自动更新该数据到当前时间
auto_now_add:在创建数据时会记录创建时间,后续不会自动修改
BooleanField(Field) - 布尔值类型
该字段传布尔值(False/True) 数据库中实际保存0/1
TextField(Field) - 文本类型
该字段可以用来存大段内容(文章、博客...) 没有字数限制
FileField(Field) - 字符类型
upload_to = "/data"
给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中如:/data/a.txt
# 外键字段及参数
unique=True
ForeignKey(unique=True) === OneToOneField()
# 用ForeignKey(unique=True)创建一对一外键时
# orm会有提示信息:推荐使用OneToOneField,但是使用前者也能创建
db_index
如果db_index=True 则代表着为此字段设置索引
to_field
设置要关联的表的字段 默认不写关联的就是另外一张的主键字段
on_delete
on_update
当删除或更新关联表中的数据时,当前表与其关联的行的行为。
# django2.X及以上版本 需要手动设置外键字段的级联更新,级联删除
# 其他字段
参考博客:https://www.cnblogs.com/Dominic-Ji/p/9203990.html
17.12.2 自定义字段
# django除了提供默认字段类型之外 还支持自定义字段
class MyCharField(models.Field):
def __init__(self,max_length,*args,**kwargs):
self.max_length = max_length
# 调用父类的init方法
super().__init__(max_length=max_length,*args,**kwargs)
# 一定要以关键字的形式传入max_length,否则会传给位置形参
def db_type(self, connection):
"""
:return:返回字符串,字符串内为SQL语句的数据类型及各种约束条件 如:char(32)
"""
return 'char(%s)'%self.max_length
# 自定义字段使用
myfield = MyCharField(max_length=16,null=True)
17.13 数据库查询优化
orm语句的特点:
惰性查询
如果你仅仅只是书写了orm语句 在后面根本没有用到该语句所查询出来的参数
那么orm会自动识别 直接不执行
res = models.Book.objects.all() # 不print就不会执行对应的sql语句
print(res) # 要用数据了才会走数据库
17.13.1 only与defer
only是只获取括号内的字段对应的数据,而defer相反
only与defer的选择,看字段个数,单个字段的数据多少
# only与defer
# 获取书籍表中所有书的名字
res = models.Book.objects.values('title')
for d in res:
print(d.get('title'))
# 实现获取到一个数据对象 通过.title就能够取到书名 并且不含其他字段
res = models.Book.objects.only('title')
print(res) # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>, <Book: 论语爆款>, <Book: 聊斋爆款>, <Book: 老子爆款>]>
for i in res:
print(i.title)
# .only括号内的字段 不会走数据库
print(i.price)
# .only括号内没有的字段 会重新走数据库查询
# 如果是res=models.Book.objects.all()获取后就不需要再通过数据库查询price,直接从对象中获取
res = models.Book.objects.defer('title')
# 对象除了title属性之外其他的属性都有
for i in res:
print(i.price)
"""
defer与only刚好相反
defer括号内放的字段不在查询出来的对象里面 查询该字段需要重新查询数据库
而如果查询的是非括号内的字段 则不需要查询数据库
"""
17.13.2 select_related与prefetch_related
select_related 是联表查询
prefetch_related是子查询
select_related与prefetch_related选择,看连表时间长还是额外访问数据库时间长
# select_related与prefetch_related 与跨表操作有关
res = models.Book.objects.all()
for i in res:
print(i.publish.name)
# 获取关联表中的数据时,每循环一次就要查询一次数据库
res = models.Book.objects.select_related('authors')
# 查看SQL语句发现是INNER JOIN,联表操作
"""
select_related内部直接先将book与publish连起来
然后一次性将大表里面的所有数据全部封装给查询出来的对象
此时对象无论是.book表的数据还是publish的数据都无需再走数据库查询了
select_related括号内只能放一对多 一对一的外键字段
多对多不行
"""
for i in res:
print(i.publish.name) # 每循环一次就要走一次数据库查询
res = models.Book.objects.prefetch_related('publish') # 子查询
"""
prefetch_related该方法内部其实就是子查询,
将上一次的查询结果作为下一次查询的范围
最终将子查询 查询出来的所有结果封装到对象中
"""
for i in res:
print(i.publish.name)
17.14 查询练习之图书管理系统
from django.shortcuts import render,redirect,HttpResponse
from app01 import models
# Create your views here.
def home(request):
return render(request,'home.html')
def book_list(request):
# 先查询出所有的书籍信息 传递给html页面
book_queryset = models.Book.objects.all()
return render(request,'book_list.html',locals())
def book_add(request):
if request.method == 'POST':
# 获取前端提交过来的所有数据
title = request.POST.get("title")
price = request.POST.get("price")
publish_date = request.POST.get("publish_date")
publish_id = request.POST.get("publish")
authors_list = request.POST.getlist("authors") # [1,2,3,4,]
# 操作数据库存储数据
# 书籍表
book_obj = models.Book.objects.create(title=title,price=price,publish_date=publish_date,publish_id=publish_id)
# 书籍与作者的关系表
book_obj.authors.add(*authors_list)
# 跳转到书籍的展示页面
"""
redirect括号内可以直接写url
其实也可以直接写别名
但是如果你的别名需要额外给参数的话,那么就必须使用reverse解析了
"""
return redirect('book_list')
# 先获取当前系统中所有的出版社信息和作者信息
publish_queryset = models.Publish.objects.all()
author_queryset = models.Author.objects.all()
return render(request,'book_add.html',locals())
def book_edit(request,edit_id):
# 获取当前用户想要编辑的书籍对象 展示给用户看
edit_obj = models.Book.objects.filter(pk=edit_id).first()
if request.method == 'POST':
title = request.POST.get("title")
price = request.POST.get("price")
publish_date = request.POST.get("publish_date")
publish_id = request.POST.get("publish")
authors_list = request.POST.getlist("authors") # [1,2,3,4,]
models.Book.objects.filter(pk=edit_id).update(title=title,
price=price,
publish_date=publish_date,
publish_id=publish_id
)
# 改第三张关系表
edit_obj.authors.set(authors_list)
return redirect('book_list')
publish_queryset = models.Publish.objects.all()
author_queryset = models.Author.objects.all()
return render(request,'book_edit.html',locals())
def book_delete(request,delete_id):
# 简单粗暴 直接删除
models.Book.objects.filter(pk=delete_id).delete()
# 直接跳转到展示页
return redirect('book_list')
17.15 choices参数(数据库字段设计常见)
17.15.1 choices参数的使用
gender_choices = (
(1,'男'),
(2,'女'),
(3,'其他'),
)
gender = models.IntegerField(choices=gender_choices)
models.User.objects.create(username='jason',age=18,gender=1)
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender) # 1
# 固定写法: obj.get_字段名_display()
print(user_obj.get_gender_display()) # 男
例如用户表中的性别,学历,工作经验,是否结婚,是否生子,客户来源等等
只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数
class User(models.Model):
username = models.CharField(max_length=32)
age = models.IntegerField()
# 性别
gender_choices = (
(1,'男'),
(2,'女'),
(3,'其他'),
)
gender = models.IntegerField(choices=gender_choices)
# 成绩
score_choices = (
('A','优秀'),
('B','良好'),
('C','及格'),
('D','不合格'),
)
# 保证字段类型跟列举出来的元祖第一个数据类型一致即可
score = models.CharField(choices=score_choices,null=True)
gender字段在数据库中存的是int类型,可以存int范围内的所有数字
但是如果存的数字是在gender_choices元祖列举的范围之内时
可以通过这个字段 获取到数字对应的真正的内容
from app01 import models
models.User.objects.create(username='jason',age=18,gender=1)
models.User.objects.create(username='egon',age=85,gender=2)
models.User.objects.create(username='tank',age=40,gender=3)
# 存的时候 没有列举出来的数字也能存(范围还是按照字段类型决定)
models.User.objects.create(username='tony',age=45,gender=4)
# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender)
# 只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()
print(user_obj.get_gender_display())
user_obj = models.User.objects.filter(pk=4).first()
# 如果没有对应关系 那么字段是什么还是展示什么
print(user_obj.get_gender_display()) # 4
17.15.2 实际应用场景
# 实际项目案例
# CRM相关内部表
class School(models.Model):
"""
校区表
如:
北京沙河校区
上海校区
"""
title = models.CharField(verbose_name='校区名称', max_length=32)
def __str__(self):
return self.title
class Course(models.Model):
"""
课程表
如:
Linux基础
Linux架构师
Python自动化开发精英班
Python自动化开发架构师班
Python基础班
go基础班
"""
name = models.CharField(verbose_name='课程名称', max_length=32)
def __str__(self):
return self.name
class Department(models.Model):
"""
部门表
市场部 1000
销售 1001
"""
title = models.CharField(verbose_name='部门名称', max_length=16)
code = models.IntegerField(verbose_name='部门编号', unique=True, null=False)
def __str__(self):
return self.title
class UserInfo(models.Model):
"""
员工表
"""
name = models.CharField(verbose_name='员工姓名', max_length=16)
email = models.EmailField(verbose_name='邮箱', max_length=64)
depart = models.ForeignKey(verbose_name='部门', to="Department",to_field="code")
user=models.OneToOneField("User",default=1)
def __str__(self):
return self.name
class ClassList(models.Model):
"""
班级表
如:
Python全栈 面授班 5期 10000 2017-11-11 2018-5-11
"""
school = models.ForeignKey(verbose_name='校区', to='School')
course = models.ForeignKey(verbose_name='课程名称', to='Course')
semester = models.IntegerField(verbose_name="班级(期)")
price = models.IntegerField(verbose_name="学费")
start_date = models.DateField(verbose_name="开班日期")
graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)
memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True, )
teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo',limit_choices_to={'depart':1002})
tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo',related_name="class_list",limit_choices_to={'depart':1006})
def __str__(self):
return "{0}({1}期)".format(self.course.name, self.semester)
class Customer(models.Model):
"""
客户表
"""
qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ号必须唯一')
name = models.CharField(verbose_name='学生姓名', max_length=16)
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
education_choices = (
(1, '重点大学'),
(2, '普通本科'),
(3, '独立院校'),
(4, '民办本科'),
(5, '大专'),
(6, '民办专科'),
(7, '高中'),
(8, '其他')
)
education = models.IntegerField(verbose_name='学历', choices=education_choices, blank=True, null=True, )
graduation_school = models.CharField(verbose_name='毕业学校', max_length=64, blank=True, null=True)
major = models.CharField(verbose_name='所学专业', max_length=64, blank=True, null=True)
experience_choices = [
(1, '在校生'),
(2, '应届毕业'),
(3, '半年以内'),
(4, '半年至一年'),
(5, '一年至三年'),
(6, '三年至五年'),
(7, '五年以上'),
]
experience = models.IntegerField(verbose_name='工作经验', blank=True, null=True, choices=experience_choices)
work_status_choices = [
(1, '在职'),
(2, '无业')
]
work_status = models.IntegerField(verbose_name="职业状态", choices=work_status_choices, default=1, blank=True,
null=True)
company = models.CharField(verbose_name="目前就职公司", max_length=64, blank=True, null=True)
salary = models.CharField(verbose_name="当前薪资", max_length=64, blank=True, null=True)
source_choices = [
(1, "qq群"),
(2, "内部转介绍"),
(3, "官方网站"),
(4, "百度推广"),
(5, "360推广"),
(6, "搜狗推广"),
(7, "腾讯课堂"),
(8, "广点通"),
(9, "高校宣讲"),
(10, "渠道代理"),
(11, "51cto"),
(12, "智汇推"),
(13, "网盟"),
(14, "DSP"),
(15, "SEO"),
(16, "其它"),
]
source = models.SmallIntegerField('客户来源', choices=source_choices, default=1)
referral_from = models.ForeignKey(
'self',
blank=True,
null=True,
verbose_name="转介绍自学员",
help_text="若此客户是转介绍自内部学员,请在此处选择内部学员姓名",
related_name="internal_referral"
)
course = models.ManyToManyField(verbose_name="咨询课程", to="Course")
status_choices = [
(1, "已报名"),
(2, "未报名")
]
status = models.IntegerField(
verbose_name="状态",
choices=status_choices,
default=2,
help_text=u"选择客户此时的状态"
)
consultant = models.ForeignKey(verbose_name="课程顾问", to='UserInfo', related_name='consultanter',limit_choices_to={'depart':1001})
date = models.DateField(verbose_name="咨询日期", auto_now_add=True)
recv_date = models.DateField(verbose_name="当前课程顾问的接单日期", null=True)
last_consult_date = models.DateField(verbose_name="最后跟进日期", )
def __str__(self):
return self.name
class ConsultRecord(models.Model):
"""
客户跟进记录
"""
customer = models.ForeignKey(verbose_name="所咨询客户", to='Customer')
consultant = models.ForeignKey(verbose_name="跟踪人", to='UserInfo',limit_choices_to={'depart':1001})
date = models.DateField(verbose_name="跟进日期", auto_now_add=True)
note = models.TextField(verbose_name="跟进内容...")
def __str__(self):
return self.customer.name + ":" + self.consultant.name
class Student(models.Model):
"""
学生表(已报名)
"""
customer = models.OneToOneField(verbose_name='客户信息', to='Customer')
class_list = models.ManyToManyField(verbose_name="已报班级", to='ClassList', blank=True)
emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='紧急联系人')
company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True)
location = models.CharField(max_length=64, verbose_name='所在区域', blank=True, null=True)
position = models.CharField(verbose_name='岗位', max_length=64, blank=True, null=True)
salary = models.IntegerField(verbose_name='薪资', blank=True, null=True)
welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True)
date = models.DateField(verbose_name='入职时间', help_text='格式yyyy-mm-dd', blank=True, null=True)
memo = models.CharField(verbose_name='备注', max_length=256, blank=True, null=True)
def __str__(self):
return self.customer.name
class ClassStudyRecord(models.Model):
"""
上课记录表 (班级记录)
"""
class_obj = models.ForeignKey(verbose_name="班级", to="ClassList")
day_num = models.IntegerField(verbose_name="节次", help_text=u"此处填写第几节课或第几天课程...,必须为数字")
teacher = models.ForeignKey(verbose_name="讲师", to='UserInfo',limit_choices_to={'depart':1002})
date = models.DateField(verbose_name="上课日期", auto_now_add=True)
course_title = models.CharField(verbose_name='本节课程标题', max_length=64, blank=True, null=True)
course_memo = models.TextField(verbose_name='本节课程内容概要', blank=True, null=True)
has_homework = models.BooleanField(default=True, verbose_name="本节有作业")
homework_title = models.CharField(verbose_name='本节作业标题', max_length=64, blank=True, null=True)
homework_memo = models.TextField(verbose_name='作业描述', max_length=500, blank=True, null=True)
exam = models.TextField(verbose_name='踩分点', max_length=300, blank=True, null=True)
def __str__(self):
return "{0} day{1}".format(self.class_obj, self.day_num)
class StudentStudyRecord(models.Model):
'''
学生学习记录
'''
classstudyrecord = models.ForeignKey(verbose_name="第几天课程", to="ClassStudyRecord")
student = models.ForeignKey(verbose_name="学员", to='Student')
record_choices = (('checked', "已签到"),
('vacate', "请假"),
('late', "迟到"),
('noshow', "缺勤"),
('leave_early', "早退"),
)
record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
score_choices = ((100, 'A+'),
(90, 'A'),
(85, 'B+'),
(80, 'B'),
(70, 'B-'),
(60, 'C+'),
(50, 'C'),
(40, 'C-'),
(0, ' D'),
(-1, 'N/A'),
(-100, 'COPY'),
(-1000, 'FAIL'),
)
score = models.IntegerField("本节成绩", choices=score_choices, default=-1)
homework_note = models.CharField(verbose_name='作业评语', max_length=255, blank=True, null=True)
note = models.CharField(verbose_name="备注", max_length=255, blank=True, null=True)
homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)
stu_memo = models.TextField(verbose_name='学员备注', blank=True, null=True)
date = models.DateTimeField(verbose_name='提交作业日期', auto_now_add=True)
def __str__(self):
return "{0}-{1}".format(self.classstudyrecord, self.student)
17.16 多对多三种创建方式
# 全自动: 利用orm自动创建第三张关系表
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
优点:
不需要写第三张表,
支持orm提供操作第三张关系表的方法,包括跨表查询和多对多外键修改添加删除...
不足之处:
第三张关系表的扩展性差(无法额外添加字段...)
# 纯手动: 手动创建第三张关系表,只用外键连接
class Book(models.Model):
name = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')
优点:
第三张表拓展性好
不足之处:
不能使用orm提供的跨表查询和多对多外键修改添加删除的方法
不建议用该方式
# 半自动: 手动创建第三张关系表,并告知django使用该关系表
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',
through='Book2Author',
through_fields=('book','author')
)
class Author(models.Model):
name = models.CharField(max_length=32)
# books = models.ManyToManyField(to='Book',
# through='Book2Author',
# through_fields=('author','book')
# )
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
through_fields字段先后顺序
判断的本质:
通过第三张表查询对应的表 需要用到哪个字段就把哪个字段放前面
可以简化判断
当前表是谁 就把对应的关联字段放前面
优点:
第三张表拓展性好,可以使用orm的正反向查询 (跨表查询)
不足之处:
无法使用add,set,remove,clear这四个方法
# 总结:
# 需要掌握的是全自动和半自动 为了扩展性更高 一般我们都会采用半自动
18 MTV与MVC模型
# MTV:Django号称是MTV模型
M:models
T:templates ---对应MVC中的V:views
V:views ---对应MVC中的C:controller
# MVC:其实django本质也是MVC
M:models
V:views
C:controller
MVC: 用户发送请求给controller,controller通过models查找数据,交给views,展示给用户
# vue框架:MVVM模型
19 Ajax
19.1 Ajax简介
Ajax特点:
异步提交
局部刷新
例子:github注册网页
可以动态获取用户名,实时跟后端确认并实时展示结果到前端(局部刷新)
发送请求的方式
1.浏览器地址栏直接输入url回车 GET请求
2.a标签href属性 GET请求
3.form表单 GET请求/POST请求
4.ajax GET请求/POST请求
AJAX 不是编程语言,而是一种使用现有标准的新方法(类似于python中的装饰器)
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(无感交互)
此处Ajax只学习jQuery封装之后的版本(原生的Ajax较为复杂并且在实际项目中使用较少)
在前端页面使用ajax的时候需要确保导入了jQuery
ps:并不只有jQuery能够实现ajax,其他的框架也可以 但是换汤不换药 原理是一样的
19.2 Ajax实例
页面上有三个input框
在前两个框中输入数字 点击按钮 朝后端发送ajax请求
通过后端计算出结果 再返回给前端动态展示的到第三个input框中
(整个过程页面无刷新,也不能在前端计算)
$('#btn').click(function () {
// 朝后端发送ajax请求
$.ajax({
// 1.指定朝哪个后端发送ajax请求
url:'', // 不写就是朝当前地址提交
// 2.请求方式
type:'post', // 不指定默认就是get 都是小写
// 3.数据
//data:{'username':'jason','password':123},
data:{'i1':$('#d1').val(),'i2':$('#d2').val()},
// 4.回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果
success:function (args) {
// alert(args)
// 通过DOM操作动态渲染到第三个input里面
// $('#d3').val(args)
console.log(typeof args)
}
})
})
"""
针对后端如果是用HttpResponse返回的数据 回调函数不会自动帮你反序列化
如果后端直接用的是JsonResponse返回的数据 回调函数会自动帮你反序列化
HttpResponse解决方式
1.在前端使用JSON.parse()反序列化
2.在ajax里面配置一个参数
dataType:'JSON'
"""
19.3 前后端传输数据的编码格式(contentType)
get请求数据就是在url地址后面的部分
http://127.0.0.1:8000/index/?username=jason&password=123
这里主要研究post请求数据的编码格式
朝后端发送post请求的方式:
1.form表单
2.ajax请求
前后端传输数据的编码格式(contentType)有三种:
1.urlencoded
# form表单和ajax传输数据的默认格式,无法传输文件数据,无法传输json格式数据,只能传输username=jason&password=123格式的数据
2.formdata
# 可以传输文件数据与username=jason&password=123格式数据,无法传输json格式数据
3.json
# json格式数据
19.3.1 不同发送方式的区别
19.3.1.1 form表单
# form表单
默认的数据编码格式是urlencoded
urlencoded数据格式:username=wu&password=123
django后端会对符合urlencoded编码格式的数据自动解析,封装成QueryDict,保存到request.POST中
将form表单的编码格式(enctype)修改为formdata
那么urlencoded数据格式的数据还是会解析,保存到request.POST中
但是会将文件数据解析,保存到request.FILES中
form表单无法发送json格式数据
19.3.1.2 ajax请求
# ajax请求 不写contentType时的默认编码
默认的编码格式是urlencoded
# 可以从浏览器-检查-network-请求-Request Headers中查看Content-Type
数据格式:username=wu&age=20
django后端会对符合urlencoded编码格式的数据自动解析,封装成QueryDict,保存到request.POST中
# ajax请求可以发送json编码格式的数据以及formdata编码格式的数据
19.4 ajax发送json格式数据
19.4.1 ajax发送json格式数据的方法
前后端传输数据时一定要确保编码格式与数据真正的格式保持一致
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:JSON.stringify({'username':'jason','age':25}),
contentType:'application/json', // 指定编码格式
success:function () {
}
})
})
</script>
ajax发送json格式数据需要注意点
1.contentType参数指定成:application/json
2.数据是json格式的数据
3.django后端不会封装,处理json格式数据,需要从request.body获取并处理
19.4.2 后端接收json格式数据的方法
前端发送了json格式数据:{"username":"jason","age":25}
在request.POST中没有该数据,因为request.POST中只封装了urlencoded数据格式
在后端json格式的数据需要从request.body中获取
django不会对json格式的数据做任何的处理,
可以在request.body中看到数据依旧为bytes类型
我们需要自己对数据进行处理
json_bytes = request.body # 获取二进制的json格式数据
json_str = json_bytes.decode('utf-8') # 解码成字符串类型
json_dict = json.loads(json_str) # 反序列化成字典
# 上述方法可以有更简便的方法:
json_bytes = request.body
json_dict = json.loads(json_bytes)
# json.loads括号内如果传入了一个二进制格式的数据那么会自动解码再反序列化
request对象方法:
request.is_ajax()
判断当前请求是否是ajax请求 返回布尔值
19.5 ajax发送文件数据
19.5.1 ajax发送文件数据的方法
ajax发送文件需要借助于js的内置对象FormData
<script>
// 点击按钮朝后端发送普通键值对和文件数据
$('#d4').on('click',function () {
// 1 首先创建FormData对象(内置)
let formDateObj = new FormData();
// 2 添加普通的键值对数据
formDateObj.append('username',$('#d1').val());
formDateObj.append('password',$('#d2').val());
// 3 添加文件对象
formDateObj.append('myfile',$('#d3')[0].files[0])
// 4 将对象基于ajax发送给后端
$.ajax({
url:'',
type:'post',
data:formDateObj, // 直接将对象作为data传输
// ajax发送文件必须要指定的两个参数
contentType:false, // 1.不使用任何编码(django后端能够自动识别formdata对象)
processData:false, // 2.不对数据进行任何处理
success:function (args) {
}
})
})
</script>
19.5.2 后端接收文件数据的方法
def ab_file(request):
if request.is_ajax():
if request.method == 'POST':
print(request.POST)
print(request.FILES)
return render(request,'ab_file.html')
# 普通键值对数据在request.POST中
# 文件对象在request.FILES中
总结:
1.创建FormData对象
①添加普通的键值对
formDateObj.append('username',$('#d1').val());
formDateObj.append('password',$('#d2').val());
②添加文件对象
formDateObj.append('myfile',$('#d3')[0].files[0]);
2.指定两个关键性的参数
contentType:false, // 不使用任何编码 (django后端能够自动识别formdata对象)
processData:false, // 不对数据进行任何处理
3.django后端能够直接识别到formdata对象
将内部的普通键值对自动解析并封装到request.POST中
文件数据自动解析并封装到request.FILES中
19.6 django内置的序列化组件serializers
# 获取到后端用户表内的所有的数据,返回到前端,格式为列表套字典
# 第一种方法:手动获取数据,存入字典,列表
import json
from django.http import JsonResponse
from django.core import serializers
def ab_ser(request):
user_queryset = models.User.objects.all()
# 要返回[{},{},{},{},{}]的形式
user_list = []
for user_obj in user_queryset:
tmp = {
'pk':user_obj.pk,
'username':user_obj.username,
'age':user_obj.age,
'gender':user_obj.get_gender_display()
}
user_list.append(tmp)
return JsonResponse(user_list,safe=False)
# 第二种方法:使用django内置的序列化组件serializers进行序列化
import json
from django.http import JsonResponse
from django.core import serializers
def ab_ser(request):
user_queryset = models.User.objects.all()
res = serializers.serialize('json',user_queryset)
"""serializer会自动将数据变成json格式的字符串 并且数据非常全面"""
return HttpResponse(res)
[
{"pk": 1, "username": "jason", "age": 25, "gender": "male"},
{"pk": 2, "username": "egon", "age": 31, "gender": "female"},
{"pk": 3, "username": "kevin", "age": 32, "gender": "others"},
{"pk": 4, "username": "tank", "age": 40, "gender": 4}
]
前后端分离的项目
后端开发只需要写代码将数据处理好,序列化返回给前端即可
再写一个接口文档 告诉前端每个字段代表的意思即可
[
{ "model": "app01.user",
"pk": 1,
"fields": {"username": "jason", "age": 25, "gender": 1}},
{ "model": "app01.user",
"pk": 2,
"fields": {"username": "egon", "age": 31, "gender": 2}},
{ "model": "app01.user",
"pk": 3,
"fields": {"username": "kevin", "age": 32, "gender": 3}},
{ "model": "app01.user",
"pk": 4,
"fields": {"username": "tank", "age": 40, "gender": 4}}
]
19.7 ajax结合sweetalert实例
<script>
$('.del').on('click',function () {
// 先将当前标签对象存储起来
let currentBtn = $(this);
// 二次确认弹框
swal({
title: "你确定要删吗?",
text: "你可要考虑清除哦,可能需要拎包跑路哦!",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "是的,老子就要删!",
cancelButtonText: "算了,算了!",
closeOnConfirm: false,
closeOnCancel: false,
showLoaderOnConfirm: true
},
function(isConfirm) {
if (isConfirm) {
// 朝后端发送ajax请求删除数据之后 再弹下面的提示框
$.ajax({
{#url:'/delete/user/' + currentBtn.attr('delete_id'), // 1 传递主键值方式1#}
url:'/delete/user/', // 2 放在请求体里面
type:'post',
data:{'delete_id':currentBtn.attr('delete_id')},
success:function (args) { // args = {'code':'','msg':''}
// 判断响应状态码 然后做不同的处理
if(args.code === 1000){
swal("删了!", args.msg, "success");
// 1.lowb版本 直接刷新当前页面
{#window.location.reload()#}
// 2.利用DOM操作 动态刷新
currentBtn.parent().parent().remove()
}else{
swal('完了','出现了位置的错误','info')
}
}
})
} else {
swal("怂逼", "不要说我认识你", "error");
}
});
})
</script>
20 批量插入
多次models.Book.objects.create()插入数据的速度远比批量插入慢
models.Book.objects.bulk_create(book_list)
# 给Book插入一万条数据
def ab_pl(request):
# 循环单次插入
for i in range(10000):
models.Book.objects.create(title='第%s本书'%i)
# 再将所有的数据查询并展示到前端页面
book_queryset = models.Book.objects.all()
return render(request,'ab_pl.html',locals())
def ab_pl(request):
# 批量插入
book_list = []
for i in range(100000):
book_obj = models.Book(title='第%s本书'%i)
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list)
return render(request,'ab_pl.html',locals())
当批量插入数据的时候 使用orm提供的bulk_create能够大大的减少操作时间
21 分页器
21.1 自定义分页器思路
django中有自带的分页器模块
但是书写起来很麻烦并且功能太简单
所以我们自己使用自己写的自定义分页器
实现思路:
1.queryset对象支持切片操作
# 前提条件
2.用户访问的页数如何确定?
# 通过不同的get请求page参数对应不同的数据
# url?page=1
current_page = request.GET.get('page',1)
# 获取的数据是字符串类型 要注意类型转换
3.规定每页展示的数据量
per_page_num = 10
4.通过current_page与per_page_num计算出切片的起始位置和终止位置
start_page = (current_page - 1)* per_page_num
end_page = current_page * per_page_num
# 找规律
5.获取数据的总量
book_queryset.count()
6.通过数据总量与per_page_num,确定需要的总页数
# 可以使用python内置函数divmod()
page_count, more = divmod(all_count,per_page_num)
if more:
page_count += 1
7.由于前端模版语法是没有range功能的,在后端写html代码
# 前端代码不一定非要在前端书写 也可以在后端生成传递给页面,注意转义!(safe)
8.规定网页展示多少个页码按钮
# 一般情况下页码的个数设计都是奇数(符合审美标准) 11个页码
当前页减5
当前页加6 (range顾头不顾尾)
可以给标签加样式,让选中的页码高亮显示
9.针对页码小于6的情况 特殊处理 不显示负数
# 分页
book_list = models.Book.objects.all()
# 想访问哪一页
current_page = request.GET.get('page', 1) # 如果获取不到当前页码 就展示第一页
# 数据类型转换
try:
current_page = int(current_page)
except Exception:
current_page = 1
# 每页展示多少条
per_page_num = 10
# 起始位置
start_page = (current_page - 1) * per_page_num
# 终止位置
end_page = current_page * per_page_num
# 计算出需要多少页
all_count = book_list.count()
page_count, more = divmod(all_count, per_page_num)
if more:
page_count += 1
# 拼接前端代码并传输
page_html = ''
xxx = current_page
if current_page < 6:
current_page = 6
for i in range(current_page - 5, current_page + 6):
if xxx == i:
page_html += '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i)
else:
page_html += '<li><a href="?page=%s">%s</a></li>' % (i, i)
book_queryset = book_list[start_page:end_page]
21.2 自定义分页器的拷贝及使用
当需要使用到第三方功能模块或者组件的时候
一般情况下需要创建一个名为utils文件夹,将第三方功能模块放入
并在该文件夹内对模块进行功能性划分
utils可以在项目下创建,也能在应用下创建 具体结合实际情况
封装代码时尽量用面向对象封装
该自定义的分页器是基于bootstrap样式来的 所以需要提前导入bootstrap
bootstrap 版本 v3
jQuery 版本 v3
======================================================================================
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=10, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
# 后端
book_queryset = models.Book.objects.all() # 获取所有数据
current_page = request.GET.get('page',1) # 获取用户选择的页数
all_count = book_queryset.count() # 获取数据总数
# 1 传入用户选择页数与数据总数,实例化生成分页器对象
page_obj = Pagination(current_page=current_page,all_count=all_count)
# 2 对总数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
# 3 将page_queryset传递到页面
return render(request,'xxx.html',locals())
# 前端
# 展示page_queryset中的数据
{% for book_obj in page_queryset %}
<p>{{ book_obj.title }}</p>
<nav aria-label="Page navigation">
</nav>
{% endfor %}
利用自定义分页器显示分页器样式
{{ page_obj.page_html|safe }}
22 Forms组件
22.1 前戏
实现注册功能:
获取用户名和密码 利用form表单提交数据
在后端判断用户名和密码是否符合条件:
1.用户名中不能含有jpm
2.密码不能少于三位
如果不符合条件,需要将提示信息展示到前端页面
难点: form表单提交数据后会刷新页面,如何保留提示信息和输入的数据
def ab_form(request):
back_dic = {'username':'','password':''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if 'jpm' in username:
back_dic['username'] = '不符合社会主义核心价值观'
if len(password) < 3:
back_dic['password'] = '不能太短 不好!'
"""
无论是post请求还是get请求
页面都能够获取到字典 只不过get请求来的时候 字典值都是空的
而post请求来之后 字典可能有值
"""
return render(request,'ab_form.html',locals())
<form action="" method="post">
<p>username:
<input type="text" name="username">
<span style="color: red">{{ back_dic.username }}</span>
</p>
<p>password:
<input type="text" name="password">
<span style="color: red">{{ back_dic.password }}</span>
</p>
<input type="submit" class="btn btn-info">
</form>
实现思路:
1. span是行内标签,标签占用长度依赖于内部的字符长度
如果没有字符,则大小为0
2.使用模版语法及字典,当请求为post时进行校验,
如果不符合条件就为该字典传值,长度不为0,显示span标签
不足:
代码复杂,校验简单
操作:
1.手动书写前端获取用户数据的html代码 渲染html代码
2.后端对用户数据进行校验 校验数据
3.对不符合要求的数据进行前端提示 展示提示信息
22.2 forms组件的作用
forms组件同样能完成这三个操作
1.渲染html代码
2.校验数据
3.展示提示信息
疑问:
为什么数据校验非要去后端 不能在前端利用js直接完成呢?
因为前端的校验是弱不禁风的 可以直接使用开发工具在浏览器上修改
或者利用爬虫程序绕过前端页面直接朝后端提交数据
所以前端的数据校验可有可无
但是后端必须要有!!!
例如:
购物网站
在选取了货物之后 会计算一个价格发送给后端
如果有人在前端修改提交的数据,而后端不做价格的校验,会出现严重的问题
实际情况是获取到用户选择的所有商品的主键值
然后在后端查询出所有商品的价格 再次计算一遍
如果跟前端一致 那么完成支付如果不一致直接拒绝
22.3 forms组件的基本使用
from django import forms
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,max_length=8)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8)
# email字段必须符合邮箱格式 xxx@xx.com
email = forms.EmailField()
# forms代码与models很相似
22.4 校验数据
from app01 import views
# 1 将待校验的数据组织成字典的形式,实例化传入即可
form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})
# 2 判断数据是否合法 注意该方法只有在所有的数据全部合法的情况下才会返回True
form_obj.is_valid()
# False
# 3 查看所有校验通过的数据
form_obj.cleaned_data
# {'username': 'wu', 'password': '123'}
# 4 查看所有不符合校验规则以及不符合的原因
form_obj.errors
# {'email': ['Enter a valid email address.']}
# 5 校验数据只校验类中的字段 多传的字段直接忽略
form_obj = views.MyForm({'username':'jason','password':'123','email':'123@qq.com','hobby':'study'})
form_obj.is_valid()
# True
# 6 校验数据 类中所有的字段默认都是required=True,少传值会使校验不通过
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid()
# False
22.5 渲染标签
forms组件只能渲染 获取用户输入的标签(如:input select radio checkbox)
不包括按钮
# 渲染方法
def index(request):
# 1 先产生一个空对象
form_obj = MyForm()
# 2 直接将该空对象传递给html页面
return render(request,'index.html',locals())
# 前端使用form_obj空对象进行操作
<p>第一种渲染方式:代码书写极少,但封装程度太高 不便于后续的扩展 一般只在本地测试使用</p>
{{ form_obj.as_p }}
{{ form_obj.as_ul }}
{{ form_obj.as_table }}
<p>第二种渲染方式:可扩展性强 但是要写的代码太多 一般情况下不用</p>
<p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
<p>{{ form_obj.password.label }}:{{ form_obj.password }}</p>
<p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>
<p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高</p>
{% for form in form_obj %}
<p>{{ form.label }}:{{ form }}</p>
{% endfor %}
# label属性默认展示的是类中定义的字段名首字母大写的形式
# 可以修改展示内容: 直接给对应的字段加label属性即可
# username = forms.CharField(min_length=3,max_length=8,label='用户名')
22.6 展示提示信息
浏览器会自带数据的校验
让浏览器不做校验的方法: form标签中加novalidate
<form action="" method="post" novalidate>
# 后端
def index(request):
# 1 先产生一个空对象
form_obj = MyForm()
if request.method == 'POST':
# 2.获取用户数据并校验
# 校验数据需要构造成字典的格式传入MyForm()才行
# 而request.POST的结果为QueryDict对象,可以看成是一个字典
form_obj = MyForm(request.POST)
# 3.判断数据是否合法
if form_obj.is_valid():
# 4.如果合法 操作数据库存储数据
return HttpResponse('OK')
# 5.不合法 有错误,需要给前端form_obj.errors,
# 可以直接返回form_obj再获取,与get请求写在一起
# get请求会直接将该空对象传递给html页面,前端页面span标签内的form.errors是空,不显示该标签
# post请求会得到一个含有form_obj.errors的form_obj,返回给前端,显示span标签
return render(request,'index.html',locals())
# 前端
{% for form in form_obj %}
<p>
{{ form.label }}:{{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</p>
{% endfor %}
"""
注意点:
1.get请求和post传给html页面对象变量名必须一样
2.forms组件会在数据不合法的情况下,保存上次的数据 让用户能基于之前的结果进行修改,更加的人性化
"""
22.7 定制错误提示信息
# 针对错误的提示信息还可以自己定制(error_messages,根据出错的条件定制提示的信息)
class MyForm(forms.Form):
# username字符串类型最小3位最大8位
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={
'min_length':'用户名最少3位',
'max_length':'用户名最大8位',
'required':"用户名不能为空"
}
)
# password字符串类型最小3位最大8位
password = forms.CharField(min_length=3,max_length=8,label='密码',
error_messages={
'min_length': '密码最少3位',
'max_length': '密码最大8位',
'required': "密码不能为空"
}
)
# email字段必须符合邮箱格式 xxx@xx.com
email = forms.EmailField(label='邮箱',
error_messages={
'invalid':'邮箱格式不正确',
'required': "邮箱不能为空"
}
)
22.8 钩子函数(HOOK)
钩子函数就是在特定的节点会自动触发,完成相应操作的函数
钩子函数在forms组件中就类似于第二道关卡,能够自定义校验规则
在forms组件中有两类钩子
1.局部钩子
给单个字段增加校验规则的时候可以使用
2.全局钩子
给多个字段增加校验规则的时候可以使用
# 实际案例
# 1.校验用户名中不能含有666 只校验username字段 用局部钩子
# 2.校验密码和确认密码是否一致 password与confirm_password两个字段 用全局钩子
# 钩子函数 在类中写方法即可
class MyForm(forms.Form):
# 局部钩子
def clean_username(self):
# 获取到用户名
username = self.cleaned_data.get('username')
if '666' in username:
# 提示前端展示错误信息
self.add_error('username','光喊666是不行滴~')
# 将钩子函数钩去出来数据再放回去
return username
# 全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not confirm_password == password:
self.add_error('confirm_password','两次密码不一致')
# 将钩子函数钩出来数据再放回去
return self.cleaned_data
22.9 forms组件其他参数及补充知识点
label 前端展示的字段名
error_messages 自定义的报错信息
initial 设置input框内的默认值
required 控制字段是否必填(True,False)
问题:
1.input标签没有样式
2.input的type类型如何选择,默认为text
如text,password,date,radio,checkbox,...
widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})
# 1的解决方法:
widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})
# 在attr属性内写字典,可以写该标签内的属性,包括自定义属性和内置属性
# 2的解决方法:
# forms.widgets.PasswordInput
# forms.widgets.TextInput
# forms.widgets.DateInput
# forms.widgets.RadioSelect
# forms.widgets.CheckboxInput
# forms.widgets.FileInput
# 第一道关卡中还可以使用正则校验
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
]
)
22.10 forms组件源码
切入点:
form_obj.is_valid()
def is_valid(self):
return self.is_bound and not self.errors
# 如果is_valid返回True 那么self.is_bound要为True 且 self.errors要为空
# self.is_bound 部分
self.is_bound = data is not None or files is not None
# 只要给MyForm实例化时传了值 就为True
# self.errors 部分
@property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
# 默认为None
self.full_clean()
return self._errors
# forms组件所有的功能基本都出自于该方法
def full_clean(self):
self._clean_fields() # 校验字段 + 局部钩子
self._clean_form() # 全局钩子
self._post_clean()
22.11 其他标签类型渲染
# radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
# 单选select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选select
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
23 cookie与session
23.1 cookie与session简介
web发展:
1.最初网站都没有保存用户信息的需求 所有用户访问网站返回的结果都是一样的
eg:新闻、博客、文章...
2.后来出现了一些需要保存用户信息的网站
(由于http协议是无状态的,无法保存用户信息,于是产生了cookie)
eg:淘宝、支付宝、京东...
以登陆功能为例:
如果不保存用户登陆状态 也就意味着用户每次访问网站都需要重复的输入用户名和密码
最初各类网站会在用户第一次登陆成功之后,将用户的用户名密码返回给用户浏览器,并且让浏览器将数据保存在本地,之后访问网站的时候浏览器会自动将保存在浏览器上的用户名和密码发送给服务端,服务端在获取之后自动验证
早期的这种方式具有非常大的安全隐患
优化:
当用户登陆成功之后,服务端会产生一个随机字符串(在服务端用kv键值对的形式保存用户的数据)
将该随机字符串交由客户端浏览器进行保存
随机字符串1:用户1相关信息
随机字符串2:用户2相关信息
随机字符串3:用户3相关信息
之后浏览器在访问服务端的时候,都会将该随机字符串发送到服务端,
服务端到数据库中比对是否有对应的随机字符串,从而获取到对应的用户信息,校验
但是如果该随机字符串被截获到了,那么就可以冒充当前用户 其实还是有安全隐患
在web领域没有绝对的安全也没有绝对的不安全
cookie
服务端保存在客户端浏览器上的信息都可以称为cookie
它的表现形式一般都是k:v键值对(可以有多个)
session
数据是保存在服务端的并且它的表现形式一般也是k:v键值对(可以有多个)
补充:
token
session的数据虽然能保存在服务端, 但是数据量太大也会产生问题
token服务端不再保存数据
登陆成功之后 将一段用户信息进行加密处理(加密算法只有该公司开发知道)
将加密之后的结果拼接在信息后面 整体返回给浏览器保存
浏览器下次访问的时候带着该信息 服务端自动切去前面一段信息再次使用自己的加密算法
跟浏览器尾部的密文进行比对
jwt认证
三段信息
总结:
1.cookie是保存在客户端浏览器上的信息
2.session是保存在服务端上的信息
3.session是基于cookie工作的(session也需要将随机字符串保存在浏览器上)
(大部分的保存用户状态的操作都需要使用到cookie)
23.2 Cookie操作
23.2.1 cookie基本操作
设置cookie
obj.set_cookie(key,value)
获取cookie
request.COOKIES.get(key)
删除cookie(注销功能)
obj.delete_cookie('cookie_key')
在设置cookie的时候可以添加一个cookie过期时间
obj.set_cookie('cookie_key', 'cookie_value',max_age=300,expires=300)
max_age,expires两者都可以设置cookie过期时间, 都是以秒为单位
针对IE浏览器需要使用expires
设置加盐的cookie
obj.set_signed_cookie(key,value,salt='盐')
获取加盐的cookie
request.get_signed_cookie(key,salt='盐')
# cookie是服务端告知客户端浏览器需要保存内容
# 客户端浏览器也可以禁止cookie保存数据
# 如果禁止了cookie, 那么只要是需要记录用户状态的网站,登陆功能都无法使用
# 之前视图函数的返回值
return HttpResponse()
return render()
return redirect()
# 要设置或删除cookie就必须分步操作
obj = HttpResponse()
# 操作cookie
obj.set_cookie(key,value)
return obj
设置cookie
obj.set_cookie(key,value)
获取cookie
request.COOKIES.get(key)
在设置cookie的时候可以添加一个超时时间
obj.set_cookie('username', 'jason666',max_age=3,expires=3)
max_age,expires两者都是设置超时时间的 并且都是以秒为单位
针对IE浏览器需要使用expires
在设置cookie的时候可以加盐
obj.set_signed_cookie('username', 'jason666',salt='盐')
request.get_signed_cookie('username',salt='盐')
主动删除cookie(注销功能)
obj.delete_cookie('username')
23.2.2 cookie实现登陆验证
# 登录功能,注销功能
如果在没有登陆的情况下想访问一个需要登陆的页面,那么应该先跳转到登陆页面
在用户输入正确的用户名和密码之后,跳转到用户之前想要访问的页面去
# 登录认证装饰器
def login_auth(func):
def inner(request,*args,**kwargs):
# print(request.path_info)
# print(request.get_full_path()) # 能够获取到用户上一次想要访问的url
target_url = request.get_full_path()
if request.COOKIES.get('username'):
return func(request,*args,**kwargs)
else:
return redirect('/login/?next=%s'%target_url)
return inner
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
# 获取用户上一次想要访问的url
target_url = request.GET.get('next') # 这个结果可能是None
if target_url:
obj = redirect(target_url)
else:
# 保存用户登陆状态
obj = redirect('/home/')
# 让浏览器记录cookie数据
obj.set_cookie('username', 'jason666')
"""
浏览器不单单会帮你存
而且后面每次访问你的时候还会带着它过来
"""
# 跳转到一个需要用户登陆之后才能看的页面
return obj
return render(request,'login.html')
@login_auth
def home(request):
# 获取cookie信息 判断你有没有
# if request.COOKIES.get('username') == 'jason666':
# return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")
# # 没有登陆应该跳转到登陆页面
# return redirect('/login/')
return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")
@login_auth
def logout(request):
obj = redirect('/login/')
obj.delete_cookie('username')
return obj
23.3 session操作
23.3.1 session基本操作
session数据是保存在服务端的(一般存在数据库中),给客户端返回的是一个随机字符串
形式: sessionid:随机字符串
在默认情况下操作session时,需要一张django_session表
而数据库迁移命令中
django会创建很多表 django_session就是其中的一张
django默认session的过期时间是14天,也可以另行设置
设置session
request.session['key'] = value
获取session
request.session.get('key')
设置过期时间
request.session.set_expiry()
括号内可以放四种类型的参数
1.整数 秒
2.日期对象 到指定日期就失效
3.0 浏览器窗口关闭失效
4.不写 失效时间就取决于django内部全局session默认的失效时间
清除session
request.session.delete() # 只删服务端的 客户端的不删
request.session.flush() # 浏览器和服务端都清空(推荐使用)
session是保存在服务端的 但是session的保存位置可以有多种选择
1.MySQL
2.文件
3.redis
4.memcache
...
django_session表中的数据条数是取决于浏览器的
同一个计算机上(IP地址)同一个浏览器只会有一条数据生效
(当session过期的时候可能会同时出现多条数据,但是该现象不会持续很久,内部会自动识别过期的数据清除 也可以通过代码人工清除)
设置为一条数据主要是为了节省服务端数据库资源
request.session['hobby'] = 'girl'
内部发生了哪些事
1.django内部会自动生成一个随机字符串
2.django内部自动将随机字符串和对应的数据存储到django_session表中
2.1先在内存中产生操作数据的缓存
2.2在响应经过django中间件的时候才真正的操作数据库
3.将产生的随机字符串返回给客户端浏览器保存
request.session.get('hobby')
request.session['hobby'] = 'girl'
request.session.get('hobby')
内部发生了哪些事
1.自动从浏览器请求中获取sessionid对应的随机字符串
2.拿着该随机字符串去django_session表中查找对应的数据
3.如果比对上了 则将对应的数据取出并以字典的形式封装到request.session中
如果比对不上 则request.session.get()返回的是None
补充:
如果多个视图函数要使用到同一份数据,可以考虑将数据保存到django_session表中,实现数据共享
23.3.2 session实现登陆验证
详见作业36
# app01 views.py
from django.shortcuts import render, redirect
from django import forms
# Create your views here.
class MyForm(forms.Form):
username = forms.CharField(max_length=5, required=True)
password = forms.CharField(max_length=10)
def home(request):
return render(request, 'home.html')
def auth(func):
def wrapper(request, *args, **kwargs):
if request.session.get('username'):
username = request.session.get('username')
password = request.session.get('password')
if username == 'wu' and password == '123':
res = func(request, *args, **kwargs)
return res
path = request.get_full_path()
return redirect(f'/login/?next={path}')
return wrapper
def login(request):
form_obj = MyForm()
if request.method == 'POST':
form_obj = MyForm(request.POST)
if form_obj.is_valid():
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'wu' and password == '123':
request.session['username'] = username
request.session['password'] = password
url = request.GET.get("next")
if url:
obj = redirect(url)
else:
obj = redirect('/home/')
return obj
else:
form_obj.add_error('password','密码错误')
return render(request, 'login.html', locals())
@auth
def func1(request):
return render(request, 'func1.html')
@auth
def func2(request):
return render(request, 'func2.html')
@auth
def func3(request):
return render(request, 'func3.html')
@auth
def logout(request):
request.session.flush()
return redirect('/login/')
# 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'^home/',views.home),
url(r'^login/',views.login),
url(r'^func1/',views.func1),
url(r'^func2/',views.func2),
url(r'^func3/',views.func3),
url(r'^logout',views.logout)
]
24 CBV添加装饰器
from django.views import View
from django.utils.decorators import method_decorator
"""
CBV中django不建议直接给类的方法加装饰器
无论该装饰器能否正常运行 都不建议直接加
推荐使用@method_decorator添加装饰器
"""
@method_decorator(login_auth,name='get') # 方式2 加在类上并指定加装饰器的方法
@method_decorator(login_auth,name='post') # 可以添加多个 针对不同的方法加不同的装饰器
class MyLogin(View):
# 方式3: 加在dispatch上(所有方法的必经之路,源码里的方法) 直接作用于当前类里面的所有的方法
@method_decorator(login_auth)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)
@method_decorator(login_auth) # 方式1 单个加 加在要装饰的方法上
def get(self,request):
return HttpResponse("get请求")
def post(self,request):
return HttpResponse('post请求')
25 django中间件
django中间件是在web服务网关接口(wsgi)与路由分发(urls)之间的一个部分
django自带七个中间件,也可以支持自定义中间件
·
wsgi接收到请求之后必须通过django中间件才能到达路由分发,进而到视图函数
视图函数返回的响应也必须通过django中间件才能到达wsgi,进而进行网络传输,响应浏览器
25.1 django中间件初步了解
"""
django中间件是django的门户
1.请求来的时候需要先经过中间件才能到达真正的django后端
2.响应走的时候最后也需要经过中间件才能发送出去
django自带七个中间件
"""
# 研究django中间件代码规律
# 在settings.py中可以看到django的七个自带的中间件
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',
]
# 我们通过from django.middleware.security import SecurityMiddleware的形式
# 可以查看这七个中间件的源码
class SessionMiddleware(MiddlewareMixin):
def process_request(self, request):
def process_response(self, request, response):
class CsrfViewMiddleware(MiddlewareMixin):
def process_request(self, request):
def process_view(self, request, callback, callback_args, callback_kwargs):
def process_response(self, request, response):
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
# 可以看到django自带的中间件都有固定的一些方法
django支持程序员自定义中间件并且暴露给程序员五个可以自定义的方法
1.重要
process_request
process_response
2.了解
process_view
process_template_response
process_exception
25.2 django中间件的五个自定义方法
重要的两个方法
process_request
1.请求来的时候需要经过每一个中间件里面的process_request方法才能到达路由分发
经过的顺序是按照配置文件settings.py中注册的中间件 从上往下的顺序依次执行
2.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
而是直接原路返回(校验失败不允许访问...)
process_request方法就是用来做全局相关的所有限制功能
process_response
1.响应走的时候需要经过每一个中间件里面的process_response方法才能到wsgi
顺序是按照配置文件中注册了的中间件从下往上依次经过
2.该方法该方法有两个额外的参数request,response,
request为请求对象,
response为上一个中间件的process_response或视图函数返回的HttpResponse对象
该方法需要返回一个HttpResponse对象
如果通过该中间件的校验 就直接返回形参response,
如果未通过该中间件的校验,可以返回提示信息(HttpResponse对象)
注意:如果在某一个process_request方法中返回了HttpResponse对象,
那么响应走的时候会直接开始执行同级别的process_reponse,
再层层往上返回(process_reponse)
注意:flask框架中的中间件与django不同
如果在某一个process_request方法中返回了HttpResponse对象,
必须经过所有中间件里的process_reponse方法才能进入wsgi
了解的三个方法
process_view
路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该方法
顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
process_template_response
返回的HttpResponse对象有render属性的时候才会触发
顺序是按照配置文件中注册了的中间件从下往上依次经过
process_exception
当视图函数中出现异常的情况下触发
顺序是按照配置文件中注册了的中间件从下往上依次经过
# process_view方法在视图函数执行之前触发,触发顺序按注册顺序自上而下
# process_template_response方法在视图函数为下面的格式才会触发,触发顺序按注册顺序自下而上
def aaa(request):
obj = HttpResponse('aaa')
def render():
print('内部的render')
return HttpResponse("O98K")
obj.render = render
return obj
# process_exception方法在视图函数出现异常的情况下才会触发,触发顺序按注册顺序自下而上
25.3 自定义中间件
自定义中间件步骤:
1.在项目名或者应用名下创建一个任意名称的文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内书写中间件的类(这个类必须继承MiddlewareMixin)
from django.utils.deprecation import MiddlewareMixin
在这个类中可以自由使用五个可以自定义的方法
process_request,process_response,
process_view,process_template_response,process_exception
4.需要将类的路径以字符串的形式注册到配置文件中才能生效
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',
'自定义的中间件的路径.自定义中间件类名1',
'自定义的中间件的路径.自定义中间件类名1',
'自定义的中间件的路径.自定义中间件类名1',
]
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class MyMiddleware1(MiddlewareMixin):
def process_request(self,request):
print('process_request方法')
# return HttpResponse('baby!')
def process_response(self,request,response):
print('process_response方法')
return response
def process_view(self,request,view_name,*args,**kwargs):
print(view_name,args,kwargs)
print('process_view')
def process_template_response(self,request,response):
print('process_template_response')
return response
def process_exception(self,request,exception):
print('process_exception')
print(exception)
25.4 csrf跨站请求伪造校验
django自带的七大中间件之一:
django.middleware.csrf.CsrfViewMiddleware
25.4.1 csrf跨站请求伪造校验简介
Cross-site request forgery
钓鱼网站:
搭建一个跟正规网站一模一样的网站页面
用户进入到了钓鱼网站,给其他人转账
钓鱼网站将转账的操作提交给中国银行的系统,
但转账的目标账户不是用户输入的转账账户,而是钓鱼网站的账户
内部原理
在钓鱼网站的页面 针对转账的目标账户 只提供一个没有name属性的普通input框
在页面上隐藏一个含有name与value的input框
最终通过钓鱼网站给真正的网站发送post请求,冒充用户进行操作
这就是csrf跨站请求伪造
如何规避上述问题:
使用csrf跨站请求伪造校验中间件(django自带的七大中间件之一)
django.middleware.csrf.CsrfViewMiddleware
网站在给用户返回一个具有提交数据功能页面的时候给这个页面加一个名为csrfmiddlewaretoken的标识
(每次访问,该标识的值都不同)
当这个页面朝后端发送post请求的时候 后端会校验唯一标识的值
如果唯一标识的值不正确,就直接拒绝(403 forbbiden)
如果正确则正常执行
25.4.2 通过token校验的请求方式
# form表单的请求方式: 添加{% csrf_token %}
<form action="" method="post">
{% csrf_token %}
<p>username:<input type="text" name="username"></p>
<p>target_user:<input type="text" name="target_user"></p>
<p>money:<input type="text" name="money"></p>
<input type="submit">
</form>
# ajax的请求方式: 3种方法
// 第一种方法 利用标签的查找,获取页面上的随机字符串
// 如果用forms组件 无法用该方法
data:{"username":'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()}
// 第二种方法 利用模版语法提供的快捷书写
data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'}
// 第三种方法 通用方式直接拷贝下面的js代码并引用到html页面上即可
data:{"username":'jason'}
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
25.4.3 csrf相关的装饰器
两个需求:
1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验
@csrf_protect ===> 指定该视图函数需要csrf校验
FBV直接添加装饰器即可
CBV需要用method_decorator,三种装饰器使用方法都能用
@csrf_exempt ===> 指定该视图函数忽视csrf校验
FBV直接添加装饰器即可
CBV只能整体忽视,即只有用method_decorator给dispatch加装饰器的方法才有效
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
@csrf_exempt
@csrf_protect
def transfer(request):
if request.method == 'POST':
username = request.POST.get('username')
target_user = request.POST.get('target_user')
money = request.POST.get('money')
print('%s给%s转了%s元'%(username,target_user,money))
return render(request,'transfer.html')
from django.views import View
@method_decorator(csrf_protect,name='post') # 针对csrf_protect 第二种方式可以
@method_decorator(csrf_exempt,name='post') # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch') # 等同的第三种方式,也可以
class MyCsrfToken(View):
@method_decorator(csrf_protect) # 针对csrf_protect 第三种方式可以
@method_decorator(csrf_exempt) # 针对csrf_exempt 第三种方式可以
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)
def get(self,request):
return HttpResponse('get')
@method_decorator(csrf_protect) # 针对csrf_protect 第一种方式可以
@method_decorator(csrf_exempt) # 针对csrf_exempt 第一种方式不可以
def post(self,request):
return HttpResponse('post')
25.5 django中间件配置方式的模拟
25.5.1 模拟思路
# 借鉴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',
]
# 模块:importlib
# 可以实现 用字符串格式的模块名 导入模块
import importlib
res = 'myfile.b'
module_obj = importlib.import_module(res)
# 相当于 from myfile import b
# 该方法只能精确到py文件名
print(module_obj)
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware']
1.将类名与模块路径分隔开
2.通过importlib模块与模块路径导入模块
3.通过反射,类名获取类
4.使用类内的方法
25.5.2 实现代码
import settings
import importlib
def send_all(content):
for path_str in settings.NOTIFY_LIST: #'notify.email.Email'
module_path,class_name = path_str.rsplit('.',maxsplit=1)
# module_path = 'notify.email' class_name = 'Email'
# 1 利用字符串导入模块
module = importlib.import_module(module_path) # from notify import email
# 2 利用反射获取类名
cls = getattr(module,class_name) # Email、QQ、Wechat
# 3 生成类的对象
obj = cls()
# 4 利用鸭子类型直接调用send方法
obj.send(content)
26 Auth模块
在创建django项目之后执行数据库迁移命令会自动生成很多表
django_session
auth_user
...
django项目启动后可以直接访问admin路由,
网站可以输入用户名和密码,该网站的用户名和密码的数据就来自于auth_user表,
并且必须是管理员用户(superuser)才能进入
创建超级用户(管理员)
python3 manage.py createsuperuser
依赖于auth_user表完成用户相关的所有功能
26.1 Auth模块中的方法
# 1.比对用户名和密码是否正确(验证命令创建的超级用户)
user_obj = auth.authenticate(request,username=username,password=password)
print(user_obj.password) # 密文
# 2.保存用户状态
auth.login(request,user_obj)
# 类似于request.session[key] = user_obj
# 执行了该方法后 就可以在任何地方通过request.user获取到当前登陆的用户对象
# 3.判断当前用户是否登陆,返回布尔值
# request.user.is_authenticated不用加括号!
request.user.is_authenticated()
# 4.获取当前登陆用户
request.user
# 5.校验用户是否登陆的装饰器
from django.contrib.auth.decorators import login_required
# 局部配置
@login_required(login_url='/login/')
# 全局配置(settings.py)
LOGIN_URL = '/login/'
@login_required
优先级:
局部配置 > 全局配置
局部配置和全局配置的优缺点:
全局配置无需重复写代码 但是跳转的页面却很单一
局部配置可以让不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面
# 6.比对原密码,返回布尔值
request.user.check_password(old_password)
# 7.修改密码
request.user.set_password(new_password) # 修改对象中的password属性
request.user.save() # 操作数据库保存修改后的对象
# 8.注销
auth.logout(request)
# 9.注册
# 操作auth_user表写入数据
from django.contrib.auth.models import User
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用这种方法创建超级用户时邮箱是必填的 而用命令创建超级用户可以不填
User.objects.create_superuser(username=username,email='123@qq.com',password=password)
# 不能用create方法: create写入的密码是明文,没有加密处理
# User.objects.create(username=username,password=password)
26.2 扩展auth_user表的字段的方法
from django.db import models
from django.contrib.auth.models import User,AbstractUser
# Create your models here.
# 第一种: 使用一对一关系 不推荐
class UserDetail(models.Model):
phone = models.BigIntegerField()
user = models.OneToOneField(to='User')
# 第二种:面向对象的继承
class UserInfo(AbstractUser):
phone = models.BigIntegerField()
如果继承了AbstractUser,写新的表替代auth_user
那么在执行数据库迁移命令的时候,就不会再创建auth_user表
而UserInfo表中会出现 auth_user中所有的字段 以及 UserInfo内所有的字段
auth模块的功能还是可以照常使用,
只是参考的表页由原来的auth_user变成了UserInfo
此方法的注意点:
1.在换表之前没有执行过数据库迁移命令
即auth_user没有被创建,
如果当前的库已经创建了,那么需要重新换一个库
2.新的表中不要出现与AbstractUser里面的字段重名的字段名
会覆盖,可能导致出错
3.需要在配置文件中告知django用UserInfo替代auth_user
AUTH_USER_MODEL = 'app01.UserInfo'
'应用名.表名'
27 DTL模板语言和static
# static文件夹放在app目录下
# 如果想在html中导入静态文件需要先导入static
# {% load static %}
# {% static 'css/js/xxx.js' %}
# 在视图层return locals()可以将名称空间内所有的名字传到html页面通过DTL调用