django

web框架

Web框架(Web framework)或者叫做Web应用框架(Web application framework),是用于进行Web开发的一套软件架构。大多数的Web框架都封装了一些通用的功能,为Web的行为提供支持,开发人员只需要写入项目对应的逻辑代码即可。

python的三大主流web框架

django 
特点: 大而全,自带的功能非常非常多
缺点: 过于笨重

flask
特点: 小而精 自动的功能特别少,支持插件扩展,第三方模块很多,所有第三方模块加起来可以盖过django,也越来越像django。
缺点: 依赖第三方开发者

tornado
特点:异步非阻塞,支持高并发,牛逼到可以开发游戏服务
缺点:会的人少

web框架提供的通用功能主要包括:

A. socket部分 (数据发送、接收与传输相关);
B. 路由与视图函数的对应关系 (路由匹配)
C. 模板语法 (动态页面引入外部变量的语法)

django:
A 用的是wsgiref模块
B 用的自己的
C 用的自己的(没有jinjia2好用,但也很方便)

flask
A 用的是werkzeug(内部也是wsgiref模块)
B 用的自己的
C 用的jinjia2模块

tornado
A B C 都是自己写的

django安装与创建项目

注意事项:

# 如何让你的计算机能够正常启动django项目
1. 计算机的名称不能有中文
2. 一个pycharm窗口只开一个项目(不要打开多余的文件夹)
3. 项目里的文件也尽量不要出现中文
4. python解释器尽量使用 3.4-3.6之间的版本
  (如果你的项目报错 点击最后一个报错信息,去源码中把逗号删掉)

# django的版本问题
1.x  2.x  3.x(太新)
目前用的最多的还是1.x与2.x

安装与简单使用

命令行

  1. 安装django
# 如果超时报错,再次执行安装命令即可
pip install django==1.11.11

# 安装好后输入django-admin可看到命令介绍
  1. 创建一个名为mysite1 的django项目
django-admin startproject mysite1
  1. 进入项目目录,启动Django项目(默认127.0.0.1:8000)
cd mysite1
python manage.py runserver

#或指定ip端口
#python manage.py runserver 127.0.0.1:8888

浏览器访问 http://127.0.0.1:8000/,效果为
image

  1. 创建应用
#这里的应用也叫做app,可以理解为这个项目的各个大的功能模块,就类似于大学中的各个学院,一个学院就为一个应用。

python manage.py startapp app01
  1. templates配置
# 项目根目录下手动创建templates文件夹
# 配置文件 mysite1/mysite1/settings.py 中加入templates路径
'DIRS': [os.path.join(BASE_DIR, 'templates')],

image

  1. 应用注册

新建的应用app需要到配置文件 mysite1/mysite1/settings.py 中注册

image

pycharm

注: 社区版pycharm不支持django

  1. 安装django
    image

  2. 创建新项目
    点击file--> new project --> 选择django
    image

  3. 启动服务

命令行的方式启动python manage.py runserver

点击绿色小箭头。

  1. 创建应用

pycharm终端命令行输入 python manage.py startapp appname

  1. templates配置
    pycharm会自动创建templates目录与添加路径

  2. 应用注册与命令行的步骤一样

vscode

  1. 安装django

我没找到哪里能选择版本,就随便装了一个相近的版本
image

  1. 创建项目我直接使用命令行的方式,不知道vscode控制台还有没有其他方式

  2. 启动服务

需要先修改配置,把此段代码加到configurations中(别的教程能选择django,自动添加,我没有实现)
image

      {
         "name": "Python: Django",
         "type": "python",
         "request": "launch",
         "program": "${workspaceFolder}\\manage.py",
         "args": [
            "runserver",
            "--noreload"
         ],
         "django": true,
         "justMyCode": false,
      },

注意这里需要只打开项目根目录,上层不能有其他目录
image

选择调试模式django,点击绿色箭头即可启动服务
image

后续其他操作都与命令行一样。

项目文件介绍

image

mysite1/
├── manage.py            # 管理文件,django的入口文件
└── app01                # 应用app01文件夹
|   ├── migrations       # 文件夹,数据库迁移记录
|   |   └── __init__.py
|   ├── __init__.py
|   ├── admin.py         # django 后台管理相关
|   ├── apps.py          # 注册使用
|   ├── models.py        # 数据库相关的 模型类(orm)
|   ├── tests.py         # 测试文件
|   └── views.py         # 视图函数(视图层)
|
└── mysite1              # 项目目录
|   ├── __init__.py
|   ├── settings.py      # 项目配置
|   ├── urls.py          # 路由,URL和函数的对应关系
|   └── wsgi.py          # wsgiref模块相关,runserver命令就使用此模块做简单的web server
|
└──  templates           # 存放html页面的文件夹

django初识

这里会简单过一下django的常用功能,后续会再次详细记录每一块的知识点。

django小白必会三板斧

HttpResponse    # 返回字符串类型的数据
    return HttpResponse('字符串')
render          # 返回html文件
    1. 返回静态页面
	   return render(request,'hello.html')
    2. 返回动态页面(需要给html页面传入变量),两种传值方式:
	  1) 以字典的方式传入变量名与值
	     return render(request,'hello.html',{'变量名1':值1,'变量名2',值2})
	  2) 将当前名称空间的全部变量传递给html页面,适用于需传递数据很多的情况
	     return render(request,'hello.html',locals())
redirect        # 重定向
    return redirect('https://www.baidu.com') # 重定向到某个网页
    return redirect('/home/')                # 重定向到本地的一个页面
    return redirect('login')                 # 重定向别名为login对应的url 
    return redirect(reverse('login'))        # 等价于return redirect('login'),但reverse()可以带参数传参,上一种写法不行。

1.简单使用:

#创建3个路由:
    /index      返回字符串"Hi,it's django~~~"
    /hello      返回页面 hello.html
    /oldindex   跳转到本地/index
    /baidu      跳转到https://www.baidu.com

image

image

image

重启django服务后,访问效果为:
image

  1. 动态页面简单演示
    就复用上面的/hello路由 与 hello.html

hello视图函数
image

html页面,这里面带入变量的语法后边会详细介绍。
image

动态页面效果展示,时间会变化为当前时间:
image
image

静态文件配置

对于一些内容固定的文件,即静态文件,存放在static目录下,且在配置文件中有相关配置。

这里以登陆页面为例:
image

image

1. 创建静态资源目录
mysite1/                 # 项目根目录
└── static               # 静态文件目录
    ├── css              # 存放 css 文件
    ├── js               # 存放 js 文件
    ├── image            # 存放 图片 文件
    └── others           # 存放第三方文件,也可以不用others,直接将第三方文件的目录放在这里

2. 添加静态资源配置: 确定令牌与添加静态资源路径
   mysite1\mysite1\settings.py

    # 确定静态资源令牌字段,放在静态目录下的资源将使用此字段:http://127.0.0.1:8000/static/others/xxx
    STATIC_URL = '/static/'

    # 添加静态资源路径,这里的static为创建的存放静态资源的目录名字,和上面的令牌要区分开。
    # 当有多个静态文件目录时,会从上到下依次匹配所需资源,匹配到即停止。
    STATICFILES_DIRS = [
        os.path.join(BASE_DIR,'static'),
        # 如果有其他项目的静态文件目录,可以接着写
        # os.path.join(BASE_DIR,'static2'),
    ]

3. templates目录下添加页面文件login.html

4. 将静态资源放到static目录下
   此登录页面只用到一个第三包 bootstrap放到了others下

5. 确认静态文件加载路径
   这里的路径有两种写入方式:
   1) 直接写入静态资源令牌
      <link rel="stylesheet" href="/static/others/bootstrap-3.4.1/css/bootstrap.css">
   2) 引入令牌变量,这样的好处是在令牌变更后,无需再更改这里的路径代码
      {% load static %}
      <link rel="stylesheet" href="{% static '/others/bootstrap-3.4.1/css/bootstrap.css' %}">
login.html页面代码 点击展开
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- <link rel="stylesheet" href="/static/others/bootstrap-3.4.1/css/bootstrap.css">
    <script src="/static/others/bootstrap-3.4.1/js/bootstrap.js"></script> -->
    {% load static %}
    <link rel="stylesheet" href="{% static '/others/bootstrap-3.4.1/css/bootstrap.css' %}">
    <script src="{% static '/others/bootstrap-3.4.1/js/bootstrap.js' %}"></script>


</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <form class="form-horizontal" action="" method="">
                    <h2 class="text-center">请登录</h2>
                    <div class="form-group">
                      <label for="inputUsername3" class="col-sm-3 control-label">用户名</label>
                      <div class="col-sm-9">
                        <input type="text" class="form-control" id="inputUsername3" placeholder="Username" name="username">
                      </div>
                    </div>
                    <div class="form-group">
                      <label for="inputPassword3" class="col-sm-3 control-label">密码</label>
                      <div class="col-sm-9">
                        <input type="password" class="form-control" id="inputPassword3" placeholder="Password" name="password">
                      </div>
                    </div>
                    <div class="form-group">
                      <div class="col-sm-offset-2 col-sm-10">
                        <button type="submit" class="btn btn-block btn-success">Sign in</button>
                      </div>
                    </div>
                  </form>
            </div>
        </div>
    </div>
</body>
</html>

**效果展示:**

image

当需要更新静态资源令牌w为static_test时,由于静态资源路径加载使用了令牌变量,故只需更改settings.py的配置 STATIC_URL = '/static_test/'

image

request对象方法初识

先介绍html的form表单的两个属性:

<form action="" method=""></form>

action属性:
	1. 不写,即默认为向当前所在url提交数据;
	2. 写入完整url,即向该url提交数据
	3. 只写后缀 eg:/login ,即向当前网页的此路由提交数据

method属性:
    浏览器使用 method 属性设置的方法将表单中的数据传送给服务器进行处理。
	1. 默认为GET方式
	2. 可以设置为POST
	浏览器/服务器对GET请求的size会有限制,POST没有。

request方法:

request.method      # 返回请求方法GET/POST

request.GET         # 获取url ?后携带的参数,返回请求参数组成的字典
    .get("key")     # 返回key对应的value,一个key有多个值时只能取到最后一个值。返回值为字符串
    .getlist("key") # 返回key对于的一个/多个值,类型为列表

request.POST        # 获取post请求提交过来的普通键值对 (不包括文件)
    .get("key")     # 与GET类似
    .getlist("key")

注意: 前期在向django后端提交post请求时,需要去配置文件注释一行代码:
MIDDLEWARE = [
     # ...csrf...
]

当访问一个路由例如登录界面,时使用get获取到页面,再使用post提交数据,视图函数可以这样写:

def login(request):
	if request.method == 'POST':
		...#逻辑代码
		return HttpResponse('xxx')
	# get请求返回的页面
	return render(request,'login.html')

在django中使用POST方式提交数据需要注释一个csrf配置,否则启动会报403
image

演示:提交登录信息

更新login视图函数
image

GET方法提交数据:

登录界面提交数据
image

输出为:
image

POST方法提交数据:
先到html页面更新form method="POST"。
登录界面填写信息,点击sign in
image

链接数据库

pycharm链接数据库

三个位置查找数据库相关操作页面
pycharm右边竖栏
左下角两个正方形重叠的标识点开
配置里搜索插件安装 Database Tools and SQL

pycharm操作的表需要提前在库中建好

image

image

image

image

image

vscode链接数据库

安装两个插件
image
官方使用文档: https://marketplace.visualstudio.com/items?itemName=cweijan.vscode-mysql-client2
image

image

django链接数据库

1.将配置文件中的DATABASE从默认的sqlite3修改为mysql

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'school',   # 库名
        'USER': 'root',
        'PASSWORD': '123',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'CHARSET': 'utf-8',
    }
}

image

  1. 代码声明链接模块类型

django默认用mysqldb模块链接mysql,但此模块兼容性不好,需要改为pymysql。
在应用或者项目名同名目录下的init文件中加上以下两行代码

import pymysql
pymysql.install_as_MySQLdb()

image

django orm简介

什么是orm

orm (bject Relational Mapping) 对象关系映射
好处:能够让不会sql语句的小白也能通过python面向对象的代码快捷的操作数据库。
缺点: 封装程度太高 执行效率会偏低

python 类     ---- mysql 表
python 对象   ---- mysql 数据记录
python 对象属性 ---- mysql记录的某个字段的值

orm建表以及对表字段结构的一些操作放到 models.py 文件中

数据库迁移命令

对models.py中的数据库相关的代码有所改动时,都要执行以下两条命令

python manage.py makemigrations  将操作记录写到migrations文件夹中,类似于日志
python manage.py migrate  将操作同步到数据库中

建表

当你未设置主键时,orm会自动帮你创建id主键,所以id字段可以省略不写。

class User(models.Model):
    # 相当于 id int primary key auto_increment
    id = models.AutoField(primary_key=True,verbose_name='主键')
    # 相当于username varchar(32)
    username = models.CharField(max_length=32,verbose_name='用户名')
    '''
    AutoField即一个自增列 
    verbose_name相当于对字段的解释
    '''

image

执行两个数据库迁移命令

image
image

查看创建的表
image

orm之表字段的增删改

直接操作models.py文件即可

新增字段

对于表中已有数据的情况:
1. 添加了新的字段,执行数据库迁移命令时会让输入默认值;
2. 将新增字段设置为允许为空
   info = models.CharField(max_length=32,verbose_name='个人简介',null=True)
3. 直接给字段设置默认值
   hobby = models.CharField(max_length=32,verbose_name='爱好',default='study')

修改字段

直接修改代码,然后执行两条数据迁移命令即可。
例如修改hobby字段为hobbies,长度改为30:
#hobby = models.CharField(max_length=32,verbose_name='爱好',default='study')
hobbies = models.CharField(max_length=30,verbose_name='爱好',default='study')

删除字段

直接注释掉对应的字段代码,然后执行两条数据迁移命令即可,删掉后该字段的数据也会丢失
例如删除hobby字段:
#hobby = models.CharField(max_length=32,verbose_name='爱好',default='study')

注意: 不要轻易注释 models.py 中的代码 !!!

添加字段演示:

  1. 添加默认值

image
查看字段与默认值已经加进去了
image

  1. 添加可为空与有默认值的字段

image

orm之表数据的增删改查

# 查询数据

#方式一:filter
res = models.User_info.objects.filter()   # 没有条件相当于查询所有数据,返回对象集
res = models.User_info.objects.filter(username=xxx)  # 单个条件的查询,如果没有符合的则返回空对象集 <QuerySet []>。
res = models.User_info.objects.filter(username=xxx,password=xxx) # 多个条件查询,用逗号分割,相当于and
    # 获取表对象的两种方式:索引与.first()
    # obj = res[0]
    obj = res.first()
    # 获取到表对象中的字段值
    value = obj.字段名

#方式二:all 查询所有数据

#返回对象集,可以for循环拿到对象,再 对象.字段名 拿到字段对应的值
user_queryset = models.User_info.objects.all() 

orm查询语句与获取值
image

使用演示: 用户登录校对

1.创建存储用户名密码的表,并插入数据
image

2.login视图函数:查询数据库用户名密码校验
image

  1. 效果演示
    image
    image
# 添加数据

# 方式一:
res = models.User_info.objects.create(username=xxx,password=xxx)
    #返回值就是创建的对象本身

# 方式二:
User_info_obj = models.User_info(username=xxx,password=xxx)
User_info_obj.save()

使用演示: 用户注册

  1. 添加注册页面register.html
注册页面,点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- <link rel="stylesheet" href="/static/others/bootstrap-3.4.1/css/bootstrap.css">
    <script src="/static/others/bootstrap-3.4.1/js/bootstrap.js"></script> -->
    {% load static %}
    <link rel="stylesheet" href="{% static '/others/bootstrap-3.4.1/css/bootstrap.css' %}">
    <script src="{% static '/others/bootstrap-3.4.1/js/bootstrap.js' %}"></script>


</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <form class="form-horizontal" action="" method="POST">
                    <h2 class="text-center">请注册</h2>
                    <div class="form-group">
                      <label for="inputUsername3" class="col-sm-3 control-label">用户名</label>
                      <div class="col-sm-9">
                        <input type="text" class="form-control" id="inputUsername3" placeholder="Username" name="username">
                      </div>
                    </div>
                    <div class="form-group">
                      <label for="inputPassword3" class="col-sm-3 control-label">密码</label>
                      <div class="col-sm-9">
                        <input type="password" class="form-control" id="inputPassword3" placeholder="Password" name="password">
                      </div>
                    </div>

                    <div class="form-group">
                      <div class="col-sm-offset-2 col-sm-10">
                        <button type="submit" class="btn btn-block btn-danger">Sign in</button>
                      </div>
                    </div>

                  </form>
            </div>
        </div>
    </div>
</body>
</html>
  1. 添加路由与视图函数对应关系
    image

  2. 添加视图函数

点击查看代码
def register(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        ######### 添加数据方式一:#########
        #返回值就是被创建的对象本身
        # usr_info_obj = models.User_info.objects.create(username=username,password=password)
        # print(usr_info_obj,usr_info_obj.username,usr_info_obj.password,ret.id)
        # #输出 User_info object 杨刷刷 123 3

        ######### 添加数据方式二:#########
        usr_info_obj = models.User_info(username=username,password=password)
        usr_info_obj.save()

        return HttpResponse('注册成功')
    return render(request,'register.html')

image

  1. 注册效果
    image
    image
    image
修改数据

方式一:update
models.User_info.objects.filter(id=1).update(username=xxx,password=xxx)
  # 将filter查询出来的所有对象全部更新,批量操作,所以过滤条件最好用主键。
  # 只会更新需要更新的字段

方式二:
edit_obj.username = xxx
edit_obj.password = xxx
edit_obj.save()
  # edit_obj 为一个对象
  # 此种方式当字段很多的时候效率会很低,因为它会把所有字段都重写一遍,包括不需要更新的。
删除数据

# 此种方式也是批量操作,将filter条件匹配到的数据对象全部删除
models.User_info.objects.filter(id=1).delete()

使用演示: 将数据库中的 user_info 表全部展示在前端页面,给每一行数据添加编辑与删除按钮。

功能拆分:(这里只做简单演示,所以功能实现不是很细致,例如删除还需二次确认,此处就不搞了)

  • 整张表数据展示页面 /userlist;
  • 编辑单行数据,展示一个编辑页面 /edit_user,编辑提交后同步到数据库,返回到整张表数据展示页面;
  • 点击删除按钮,调用 /delete_user,删除单行数据,同步到数据库,返回到整张表数据展示页面;

1.首先添加路由与三个视图函数

image

image

2.实现整张表的数据展示

编辑视图函数userlist

def userlist(request):
    # 拿到整张表的数据对象集合
    user_info_queryset = models.User_info.objects.all()
    return render(request,'userlist.html',locals())

添加页面userlist.html

userlist.html 点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>userlist</title>
    {% load static %}
    <link rel="stylesheet" href="{% static '/others/bootstrap-3.4.1/css/bootstrap.css' %}">
    <script src="{% static '/others/bootstrap-3.4.1/js/bootstrap.js' %}"></script>
</head>
<body>
    <div class='container'>
        <div class='row'>
            <div class='col-md-8 col-md-offset-2'>
                <h2 class="text-center">用户数据展示</h2>
                <hr>
                <table class="table table-striped table-hover">
                    <thead>
                        <tr>
                            <th>id</th>
                            <th>username</th>
                            <th>password</th>
                            <th>active</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for user_obj in user_info_queryset %}
                            <tr>
                                <td>{{ user_obj.id }}</td>
                                <td>{{ user_obj.username}}</td>
                                <td>{{ user_obj.password }}</td>
                                <td>
                                    <a href="" class='btn btn-xs btn-success'>编辑</a>
                                    <a href="" class='btn btn-xs btn-danger'>删除</a>
                                </td>
                            </tr>

                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>

页面效果
image

3.实现单行编辑

在userlist.html设置编辑按钮跳转的链接
image

编辑视图函数 edit_user

def edit_user(request):
    # 拿到要修改的行的id
    edit_id=request.GET.get('user_id')
    edit_obj = models.User_info.objects.filter(id=edit_id).first()

    # 将用户在编辑页面用post请求提交的数据更新到数据库
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        models.User_info.objects.filter(id=edit_id).update(username=username,password=password)
        #返回到展示页面
        return redirect('/userlist')
    #跳转到编辑页面,展示默认值
    return render(request,'edit_user.html',{'edit_obj':edit_obj})

添加编辑页面 edit_user.html

edit_user.html 点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- <link rel="stylesheet" href="/static/others/bootstrap-3.4.1/css/bootstrap.css">
    <script src="/static/others/bootstrap-3.4.1/js/bootstrap.js"></script> -->
    {% load static %}
    <link rel="stylesheet" href="{% static '/others/bootstrap-3.4.1/css/bootstrap.css' %}">
    <script src="{% static '/others/bootstrap-3.4.1/js/bootstrap.js' %}"></script>


</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <form class="form-horizontal" action="" method="POST">
                    <h2 class="text-center">编辑</h2>
                    <div class="form-group">
                      <label for="inputUsername3" class="col-sm-3 control-label">用户名</label>
                      <div class="col-sm-9">
                        <input type="text" value="{{ edit_obj.username }}" class="form-control" id="inputUsername3" placeholder="Username" name="username">
                      </div>
                    </div>
                    <div class="form-group">
                      <label for="inputPassword3" class="col-sm-3 control-label">密码</label>
                      <div class="col-sm-9">
                        <input type="text" value="{{ edit_obj.password }}" class="form-control" id="inputPassword3" placeholder="Password" name="password">
                      </div>
                    </div>

                    <div class="form-group">
                      <div class="col-sm-offset-2 col-sm-10">
                        <button type="submit" class="btn btn-block btn-danger">提交</button>
                      </div>
                    </div>

                  </form>
            </div>
        </div>
    </div>
</body>
</html>

4.实现单行删除

在userlist.html设置删除按钮跳转的链接
image

编辑视图函数 delete_user

def delete_user(request):
    # 拿到要删除的id,从数据库中删除该行
    delete_id=request.GET.get('user_id')
    models.User_info.objects.filter(id=delete_id).delete()
    # 返回到展示页面
    return redirect('/userlist')

5.演示效果
image
image
image
image

orm之表关系的创建

表与表之间的关系

一对多

多对多

一对一

现创建四张表来演示三种关系的创建
"""
图书表
出版社表
作者表
作者详情表
"""
由于版权限制,一本图书只能一个出版社出版,一个出版社可以出版多本书

多对一: 图书与出版社,外键字段建在多的那方 book
publish = models.ForeignKey(to='Publish')

多对多: 图书与作者,需要创建第三张关系表。
orm语句随便写在哪方模型类下面,提交后会自动创建第三张关系表
authors = models.ManyToManyField(to='Author')

一对一: 作者与作者详情,外键字段建在查询频率多的那张表
author_detail = models.OneToOneField(to='AuthorDetail')
"""
多对一与一对一创建的外键字段会自动加上_id,不要认为去加(你加了后它还会再加一个)
django 1.x 版本orm创建的外键都是级联更新删除的

四张表的orm语句

点击查看代码
class Book(models.Model):
    name = models.CharField(max_length=32,verbose_name="书名")
    price = models.DecimalField(max_digits=8,decimal_places=2,verbose_name='价格')
    # 小数类型,总共8为,精确到小数点后两位

    # 多对一
    publish = models.ForeignKey(to='Publish')  # to_field关联字段不写,默认为关联表的主键
    
    # 多对多,会自行创建一张关系表
    authors = models.ManyToManyField(to='Author')


class Publish(models.Model):
    name = models.CharField(max_length=32,verbose_name="出版社名")
    addr = models.CharField(max_length=32,verbose_name='出版社地址')

class Author(models.Model):
    name = models.CharField(max_length=32,verbose_name='作者名字')
    age = models.IntegerField(verbose_name='年龄')

    # 一对一
    author_detail = models.OneToOneField(to='AuthorDetail')

class AuthorDetail(models.Model):
    phone = models.CharField(max_length=20)
    history_books = models.TextField()

image

django生命周期流程图

image

路由层

路由匹配

路由与视图函数的关系在urls.py中定义,格式为:
url(r'index/',views.index),
url(r'hello/',views.hello)

url的第一个参数是正则表达式,访问的路由会从上往下依次匹配,只要匹配到,就会停止匹配,然后执行对应的视图函数。
当路由很多时,会出现路由顶替(应该后面匹配的路由被前面路由的正则表达式匹配上了)的情况,右两种解决方式:
    1. 修改正则表达式,使其匹配更严格 eg: 加/$
    2. 调整url的位置

当类似于/xxx 的请求路由没有找到匹配项时,django会让浏览器在末尾带个/,再次尝试访问,也可以在配置中取消自动加斜杠的特性.
APPEND_SLASH = False  #取消自动加/

如果你要严格只能127.0.0.1:8000/test/ 匹配上,,需要配置^ $为:
url(r'^test/$',views.test)

设置首页 127.0.0.1:8000/访问的页面
url(r'^$',views.home)

image

无名分组与有名分组

分组: 就是在正则表达式中用小括号括起来

无名分组:

# 正则表单式括号内没有定义分组的名字就是无名分组
# 无名分组匹配到的内容会当作位置参数传给后面的视图函数

例如:访问 127.0.0.1:8000/index/666

url(r'index/(\d+)',views.index)

def index(request,args):
    print(args)             # 输出666
    return HttpResponse("Hi,it's django~~~")

有名分组:

# 正则表单式括号内定义了分组的名字就是有名分组
# 有名分组匹配到的内容会当作关键字参数传给后面的视图函数
例如:访问 127.0.0.1:8000/index/2022

url(r'index/(?P<year>\d+)',views.index)

def index(request,year):
    print(year)             # 输出2022
    return HttpResponse("Hi,it's django~~~")

# 有名分组和无名分组不能混合使用
# 可以有多个分组
url(r'index/(\d+)/(\d+)/(\d+)',views.index)
url(r'index/(?P<year>\d+)/(?P<year2>\d+)/(?P<year3>\d+)',views.index)

反向解析

当你在一个页面定义了要跳转到/aaa路由,或者后端重定向到/aaa路由,当你的产品经理告诉你,这个/aaa路由要改成/bbb,你得去其他定义跳转的页面,也一一改过来,有没有一种简单的方法我们只用改/aaa的路由,其他地方就能自动识别呢?

反向解析: 通过一些方法得到一个结果 该结果可以直接访问对应的url触发的视图函数。

# 先给路由与视图函数起一个别名
url(r'aaa/',views.aaa,name='ooo')

# 反向解析 直接调用别名。
    # 前端反向解析
	<a href="{% url 'ooo' %}">456</a>
	# 后端反向解析
	from django.shortcuts import redirect, render,reverse
	reverse('ooo')

例如:

前端: /home路由的html页面定义了到/aaa页面的链接
image

后端: /home2路由重定向到/aaa页面
image

改成反向解析的方式:
image

image

image

无名有名分组反向解析

当后端以反向解析的方式 跳转 无名与有名分组的路由,需要加上分组对于的参数。

有名分组路由:
url(r'index/(?P<year>\d+)',views.index,name='iii'),

前端:
{% url 'iii' year=2021 %}  #有名分组写法
{% url 'iii' 2021 %}      #无名/有名分组 都能用的写法

后端:
reverse('iii',kwargs={'year':2021})  #有名分组写法
reverse('iii',args=('2022',))   #无名/有名分组 都能用的写法

一般分组的内容用来放置主键值,用于数据的更新与删除

例如以上面的表数据的删除与更新使用这个方式可以改为

路由:
    url(r'userlist2/',views.userlist2),
    url(r'edit_user2/(\d+)',views.edit_user2,name='editUser'),
    url(r'delete_user2/(\d+)',views.delete_user2,name='deleteUser'),

userlist2.html:
    # 这里主要展示使用无名分组的方式接收参数的使用场景
    <a href="{% url 'editUser' user_obj.id %}" class='btn btn-xs btn-success'>编辑</a>
    <a href="{% url 'deleteUser' user_obj.id %}" class='btn btn-xs btn-danger'>删除</a>

其他的视图函数也稍作更改,添加一个接收id的形参

路由分发

使用场景:

django的每一个应用都可以有自己的templates文件夹, url.py 与 static文件夹。
基于这个特点django能够很好的做到分组开发,即每个人只写他负责的app。
作为组长,只需要将手下写的app全部拷到一个新的django项目中,然后利用路由分发的特点将所有app整合起来。

当一个django项目中url特别多,总路由文件urls.py非常冗余不好维护,也可以利用路由分发来减轻总路由的压力。

实现原理:
使用路由分发后,总路由不再用于记录路由与视图函数的对应关系,
而是做分发处理,识别当前url是属于哪个应用下的,直接分发给对应的应用去处理。

子路由1:
from django.conf.urls import url
from app01 import views
urlpatterns = [
    url(r'app/',views.app),
]

子路由2:
from django.conf.urls import url
from app02 import views
urlpatterns = [
    url(r'app/',views.app),
]

总路由:设置不同的前缀对应不同的app
有两种写法
from django.conf.urls import include, url
from django.contrib import admin
# from app01 import urls as app01_urls  #由于urls重名所以要重命名
# from app02 import urls as app02_urls
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    #方式一:
    # url(r'^app01/',include(app01_urls)),
    # url(r'^app02/',include(app02_urls)),

    #方式二:
    url(r'^app01/',include('app01.urls')),
    url(r'^app02/',include('app02.urls')),

## 路由分发的本质:
查看include源码:
返回一个有三个元素的元组,其中后两个元素默认是None,第一个元素是由多个子路由组成的列表
def include(arg, namespace=None, app_name=None):
    ...
    return (urlconf_module, app_name, namespace)

所以路由分发还可以写成以下形式:

    # url(r'^index1/',([url1,url2],None,None)
    # 定义的url为: index1/index1_1/
    #             index1/index1_2/
    url(r'^index1/',([
                    url(r'^index1_1/',views.index),
                    url(r'^index1_2/',views.index),
                    ],None,None)),
    # 还可以无限套娃
    # 定义的url为: index2/index2_1/index2_1_1/
    #             index2/index2_1/index2_1_2/
    #             index2/index2_2/
    #             index2/index2_3/
    url(r'^index2/',([
                    url(r'^index2_1/',([
                        url(r'^index2_1_1/',views.index),
                        url(r'^index2_1_2/',views.index),
                        ],None,None)),
                    url(r'^index2_2/',views.index),
                    url(r'^index2_3/',views.index),
                    ],None,None))

使用举例:

项目结构如下,在两个app中创建同名路由app,并创建对应的视图函数。
image

总路由配置,不同的路由前缀下发到不同app
image

访问效果
image

名称空间

当多个应用的url出现了相同的别名,反向解析就不知道走哪个app了

例如app01 与 app02都添加以下路由,取了相同的别名app
url(r'app/',views.app,name='app'),

在两个app的后端都打印别名对应的路由
def app(requests):
    print(reverse('app'))  #添加此行,这里就不确定打印哪个应用的app了

此种情况有两种解决方式:
1. 给url别名加上应用名前缀
url(r'app/',views.app,name='app01_app')

url(r'app/',views.app,name='app02_app')

2. 使用名称空间来解决,不同app对应不同的namespace,引用对应的名称空间下的别名

总路由:设置名称空间
    url(r'^app01/',include('app01.urls',namespace='app01')),
    url(r'^app02/',include('app02.urls',namespace='app02')),

后端反向解析:使用名称空间下的别名
    reverse('app01:app')
    reverse('app02:app')

前端反向解析:
    {% url 'app01:app' %}
    {% url 'app02:app' %}

伪静态

将动态网页伪装成静态的好处:
1. 增大本网站seo查询力度
2. 增加搜索引擎收藏该网站的概率

SEO(Search Engine Optimization):汉译为搜索引擎优化。是一种方式:利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。

怎么实现?
直接将动态页面的路由改为.html结尾
例如:
url(r'test.html',views.test)

虚拟环境

正常开发中,我们会给每一个项目配备一个该项目独有的解释器环境,改环境内只用该项目用到的模块,用不到的不装。

模块名及版本都放到requirements.txt文件中,只需输入一个命令即可安装所有模块。

虚拟环境相当于重新下载了一个纯净的python解释器,会消耗硬盘空间。

pycharm创建虚拟环境

image
image

vscode创建虚拟环境

(还有点问题:pip运行是使用的系统的,怎么使用虚拟环境的pip安装虚拟环境的模块?有待解决)

  1. 创建虚拟环境,直接在vscode中执行
python -m venv .venv
  1. 激活虚拟环境
.venv/Scripts/activate

这里如果报错,Restricted,需要更改执行策略等级,本地编写脚本可运行
右下角开始,搜索powershell,以管理员身份运行,执行

set-ExecutionPolicy RemoteSigned
  1. 退出虚拟环境
deactivate

django 1.x与2.x区别

1. 路由层方法不同
   1.x : 使用url方法,第一个参数支持正则
   2.x 与 3.x :
       使用path方法,第一个参数不支持正则
	   提供re_path方法,支持正则
  
2. path支持5种转换器,例如
   path('index/<int:id>/',index)
   直接将路由的第二个斜杠后的内容转换为整数型,传给后端。而url默认是str类型。

   5种转换器分别为:
   str: 匹配处理路径分隔符(/)之外的非空字符串,这是默认的形式;
   int: 匹配正整数,包括0;
   slug: 匹配字母、数字、横杠、下划线组成的字符串;
   uuid: 匹配格式化的uuid;
   path: 匹配任何非空字符串,包括分隔符斜杠。
   还支持自定义转换器。

3. 模型层里1.x外键默认级联更新删除
  2.x与3.x 需要手动配置参数
  1.x: models.ForeignKey(to='Publish')
  2.x/3.x: models.ForeignKey(to='Publish',on_delete=models.CASCADE...)

视图层

三板斧

三板斧:
HttpResponse  返回字符串类型
render        返回html页面,可以对html文件动态传值
redirect      重定向

查看源代码发现三种返回类型都要返回HttpResponse对象。

render原理:
    from django.template import Template,Context
    res = Template('<h1>{{ user }}</h1>')
    con = Context({'user':{'username':'yy','password':'12'}})
    ret = res.render(con)
    print(ret)    #输出: <h1>{&#39;username&#39;: &#39;yy&#39;, &#39;password&#39;: &#39;12&#39;}</h1>
    return HttpResponse(ret)

JsonResponse

用来返回json格式的数据,一些场景前后端交互需要用到json格式进行过度,实现跨语言传输。

实现json格式内容传输这里展示两种方式:
1. 使用json模块转换
import json
def Detail(requests):
    con={'username':'小华','password':'12'}
    ret=json.dumps(con,ensure_ascii=False)  #ensure_ascii=False显示汉字,ensure_ascii=True显示ascii编码
    return HttpResponse(ret)

2. 使用JsonResponse
from django.http.response import HttpResponse,JsonResponse

def Detail(requests):
    con={'username':'小华','password':'12'}  #序列化字典
    return JsonResponse(con,safe=True,json_dumps_params={'ensure_ascii':False})

def Detail2(requests):
    con=[2,5,'a']   #序列化字典外其他 safe=False
    return JsonResponse(con,safe=False,json_dumps_params={'ensure_ascii':False})

safe模式:
默认 safe=True,这种模式下只能序列化字典。
其他类型对象序列化需要设置 safe=False 。

from表单上传文件

from 表单上传文件类型的数据:
    1. method 必须指定为POST
    2. enctype必须设置为multipart/form-data

文件相关request方法
request.FILES    #{前端定义的上传文件的name属性:文件名及内容}
file_obj=request.FILES.get('head_pic')  #通过name属性拿到文件对象
file_obj.name     #文件名

with open(file_obj.name,'wb') as f:   #存储文件
    for line in file_obj.chunks():    #.chunks()方法表示一行一行的读,不加也一样。
        f.write(line)

使用举例:

添加路由
image

添加视图函数
image

添加页面
image

点击查看代码
#视图函数
def test(request):
    if request.method == 'POST':
        print(request.FILES)     # 输出 {'head_pic': [<InMemoryUploadedFile: backpi.jpg (image/jpeg)>]}>
        file_obj=request.FILES.get('head_pic')   # .get() 方法拿到文件对象
        print(file_obj.name)                     # 输出 backpi.jpg

        with open(file_obj.name,'wb') as f:      # 存储文件
            for line in file_obj.chunks():       # .chunks()表示一行一行读,这里也可不加,一样的
                f.write(line)
    return render(request,'test.html')


#html页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form method="POST" enctype="multipart/form-data">
        <label>name: <input type="text" name="username"></label>
        <label>上传头像: <input type="file" name='head_pic'></label>
        <input type="submit">

    </form>
</body>
</html>

效果展示:
image

上传的文件保存下来了
image

request属性_方法总结

request.method
request.POST
request.GET
request.FILES
request.body   # 原生的浏览器发过来的二进制数据,后续会详细讲
request.path             #获取路由
request.path_info        #获取路由
request.get_full_path()  #获取路由,包括?后的参数
request.is_ajax()      # 判断是不是来自ajax的请求,返回布尔值

例如: 访问 http://127.0.0.1:8000/app01/test.html/8888?sdggv=73646&sbdb=888
    print(request.path)              # /app01/test.html/8888
    print(request.path_info)         # /app01/test.html/8888
    print(request.get_full_path())   # /app01/test.html/8888?sdggv=73646&sbdb=888

FBV与CBV

FBV(function base views)基于函数的视图,就是在视图里使用函数处理请求。 

    #FBV路由
    url(r'index/',views.index)
    #视图函数
    def index(request):
        return HttpResponse('index page')

CBV(class base views)基于类的视图,就是在视图里使用类处理请求。
它能直接根据请求方式的不同直接匹配到对应的方法。

    #CBV路由
    url(r'test2/',views.MyLogin.as_view())  #MyLogin是视图类的类名

    #视图类: get的请求会自动走到get(),post请求会自动走到post()
	from django.views import View
    class MyLogin(View):
        def get(self,request):
            return render(request,'test.html')
        def post(self,request):
            return HttpResponse('IN POST')

CBV源码剖析

以url(r'test2/',views.MyLogin.as_view())的as_view()为突破口

as_view是一个类方法,调用时会将类本身作为cls参数传入,view中的self = cls(**initkwargs),self即为我们所定义的类MyLogin的对象。

image

dispatch是CBV的核心

image

返回Http响应头

def test(request):
    ret = HttpResponse('ok')
    ret['Access-Control-Allow-Origin']='*'
    return ret

模板层

模板语法传值

{{}} 变量相关
{%%} 逻辑相关

传入变量

在django的html模板语言中,引入变量使用 {{ 变量名 }}
变量名可以由字母数字以及下划线组成,不能有空格或标点符号。

针对函数名与类名,模板语法会自动加括号调用,但不支持传参。

点的使用

句点符(.)在模板语言中有特殊用法,它以以下顺序查询:

  • 字典查询
  • 属性或方法查询
  • 数字索引查询
def template(request):
    l=[11,22,33]
    dic={'name':'tom','age':'11'}[]
    st='你好,新年快乐'

    class Person:

        def __init__(self,name,age): # __init__ 初始化函数,实力化一个对象时,会自动执行此函数,给对象封装属性
            self.name = name    #self为实例化的对象本身(对象空间)
            self.age = age

    p1 = Person("大白",18)
    p2 = Person("小红",18)
    person_list=[p1,p2]

    return render(request,'template.html',locals())

模板中点的使用

    <!-- 获取 l 列表中的索引1对应的值 -->
    <p>{{ l.1 }}</p>
    <!-- 获取字符串st索引3对应的字符 -->
    <p>{{ st.3 }}</p>
    <!-- 获取字典key对应的值 -->
    <p>{{ dic.name }} 今年 {{ dic.age}} 岁了</p>
    <!-- 获取p1对象对应的age属性值 -->
    <p>{{ p1.age }}</p>
	<!-- 点的联合使用 -->
	<p>{{ person_list.1.name }}</p>

效果
image

过滤器 Filter

模板语法中支持Filter(过滤器)来改变变量的显示。

过滤器语法: {{ 变量名|过滤器名:参数}}

需要注意:
1. 使用管道符应用过滤器,管道符前后没有空格;
2. 过滤器支持链式操作,即一个过滤器的输出作为另一个过滤器的输入;
3. 过滤器可以接收参数,如果参数包含空格,必须用引号引起来。eg:用逗号与空格链接l变量中的元素 {{ l|join:', ' }}
4. 参数一般是数字或者字符串,不能是列表等。自定义过滤器的函数支持最多两个参数,即 变量名与参数。

Django的模板语言中提供了大约六十个内置过滤器,这里只展示一些常用的。

视图函数中定义变量

def template(request):
    l = [11,22,33]
    dic = {'name':'tom','age':'11'}
    i = 12
	sti="66"
    st = '你好,新年快乐'
    tu = ('qq','wechat','飞信','message')
    se = {'qq','wechat','飞信','message','飞鸽传书'}
    bo = False
    nu = ''
    l2=['aa','bb','cc','dd',1,2,3,4,5,6]
    l3=['aa','bb','cc','dd']

    import datetime
    2222221=datetime.datetime.now()

    class Person:   

        def __init__(self,name,age): # __init__ 初始化函数,实力化一个对象时,会自动执行此函数,给对象封装属性
            self.name = name    #self为实例化的对象本身(对象空间)
            self.age = age

    p1 = Person("大白",18)
    p2 = Person("小红",18)
    person_list=[p1,p2]
    user_list=[]

    return render(request,'template.html',locals())

length

返回值的长度,作用于字符串、字典、列表、元组、集合。不能计算长度的变量返回0。

{{ 变量名|length }}

    <p>{{ dic|length }}</p>
    <p>{{ l|length }}</p>
    <p>{{ st|length }}</p>
    <p>{{ tu|length }}</p>
    <p>{{ se|length }}</p>
	<!-- 布尔和整数不能计算长度,返回0 -->
    <p>{{ bo|length }}</p>
    <p>{{ i|length }}</p>

default

如果一个变量是false或者为空或者不存在,则使用给定的默认值。 否则,使用变量的值。

{{ 变量名|default:参数 }}

    <p>{{ i|default:"nothing" }}</p>  <!-- i有值,则输出i对应的值12 -->
    <p>{{ bo|default:"nothing" }}</p> <!-- bo为False,输出默认值nothing -->
    <p>{{ aaa|default:"nothing" }}</p> <!-- aaa变量未定义,输出默认值nothing -->
    <p>{{ nu|default:"nothing" }}</p> <!-- nu为空,输出默认值nothing -->

filesizeformat

将值格式化为一个 “可读性强的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。
前面的数字的地方不能加入计算符,例如不能写成 1024*1024。
显示的最小单位为bytes,不足1bytes的显示为0bytes。

    <p>{{ 2|filesizeformat }}</p>

    <p>{{ 1024|filesizeformat }}</p>

    <p>{{ 1048576|filesizeformat }}</p>

输出:
image

slice

切片,切片规则与python语法的一样

    <p>{{ l2|slice:'1:6:2' }}</p>    <!--正切,步长为2 -->
    <p>{{ l2|slice:'-2:1:-2' }}</p>  <!--倒切,步长为2 -->

image

date

{{ current_time|date:"Y-m-d H:i:s" }}
格式化字符 描述 示例输出
a 'a.m.'或'p.m.'(请注意,这与PHP的输出略有不同,因为这包括符合Associated Press风格的期间) 'a.m.'
b 月,文字,3个字母,小写。 'jan'
B 未实现。
c ISO 8601格式。 (注意:与其他格式化程序不同,例如“Z”,“O”或“r”,如果值为naive datetime,则“c”格式化程序不会添加时区偏移量(请参阅datetime.tzinfo) 。 2008-01-02T10:30:00.000123+02:00或2008-01-02T10:30:00.000123
d 月的日子,带前导零的2位数字。 '01'到'31'
D 一周中的文字,3个字母。 “星期五”
e 时区名称 可能是任何格式,或者可能返回一个空字符串,具体取决于datetime。 ''、'GMT'、'-500'、'US/Eastern'等
E 月份,特定地区的替代表示通常用于长日期表示。 'listopada'(对于波兰语区域,而不是'Listopad')
f 时间,在12小时的小时和分钟内,如果它们为零,则分钟停留。 专有扩展。 '1','1:30'
F 月,文,长。 '一月'
g 小时,12小时格式,无前导零。 '1'到'12'
G 小时,24小时格式,无前导零。 '0'到'23'
h 小时,12小时格式。 '01'到'12'
H 小时,24小时格式。 '00'到'23'
i 分钟。 '00'到'59'
I 夏令时间,无论是否生效。 '1'或'0'
j 没有前导零的月份的日子。 '1'到'31'
l 星期几,文字长。 '星期五'
L 布尔值是否是一个闰年。 True或False
m 月,2位数字带前导零。 '01'到'12'
M 月,文字,3个字母。 “Feb”
n 月无前导零。 '1'到'12'
N 月份缩写。 专有扩展。 'Jan.','Feb.','March','May'
o ISO-8601周编号,对应于使用闰年的ISO-8601周数(W)。 对于更常见的年份格式,请参见Y。 '1999年'
O 与格林威治时间的差异在几小时内。 '+0200'
P 时间为12小时,分钟和'a.m。'/'p.m。',如果为零,分钟停留,特殊情况下的字符串“午夜”和“中午”。 专有扩展。 '1 am','1:30 pm' / t3>,'midnight','noon','12:30 pm' / T10>
r RFC 5322格式化日期。 'Thu, 21 Dec 2000 16:01:07 +0200'
s 秒,带前导零的2位数字。 '00'到'59'
S 一个月的英文序数后缀,2个字符。 'st','nd','rd'或'th'
t 给定月份的天数。 28 to 31
T 本机的时区。 'EST','MDT'
u 微秒。 000000 to 999999
U 自Unix Epoch以来的二分之一(1970年1月1日00:00:00 UTC)。
w 星期几,数字无前导零。 '0'(星期日)至'6'(星期六)
W ISO-8601周数,周数从星期一开始。 1,53
y 年份,2位数字。 '99'
Y 年,4位数。 '1999年'
z 一年中的日子 0到365
Z 时区偏移量,单位为秒。 UTC以西时区的偏移量总是为负数,对于UTC以东时,它们总是为正。 -43200到43200

safe
浏览器会对HTML标签和JS等语法标签进行自动转义,例如:<p>海燕</p>转译后只会显示海燕两个字,前后的标签都看不见。但我们传入的变量中如果有一些html标签或者js语法,django认为其内容不明,不能保证其安全性,所以不会自动转译,需要加上一个安全标识告诉django这个变量可以安全转译。

而这个安全标识加在前端就是使用过滤器 "|safe";
加在后端视图函数中,就是使用mark_safe()。

思路:前端代码不一定非要在html页面书写,也可以在后端生成,例如 变量=mark_safe('前端代码'),再将变量传递给html页面。

例如:
视图函数中定义变量

def template(request):
	
    p='<p>海燕</p>'
    js='<script>alert("aaa")</script>'
    
	# js_backend 在后端被标识为安全
    from django.utils.safestring import mark_safe
    js_backend=mark_safe('<script>alert("123")</script>')
	
    return render(request,'template.html',locals())
    <p>{{ p }}</p>          # 这里没有安全标识,显示为 <p>海燕</p>
    <p>{{ p|safe }}</p>     # 这里网页显示为海燕
    <p>{{ js }}</p>         # 这里没有安全标识,显示为 <script>alert("aaa")</script>
    <p>{{ js|safe }}</p>    # 这句前端标识js变量为安全,网页先弹出aaa
    <p>{{ js_backend}}</p>  # 这句在后端已被标识为安全,网页再弹出123

truncatechars
设定显示前面显示固定个字符数(这个字符数中还包含...这三个字符),多余的会被截断,显示为"...",普遍用于批量显示博文开头。

以下例子中设定字符数为6,减去固定的3个点的字符,则st变量中只会显示前三个字符。

<p>{{ st|truncatechars:6 }}</p>  # 显示为 你好,...

truncatewords
word数不包含3个点,word以空格为分割。

    words='i am a girl from china,I am 18 years old'
   <p>{{ words|truncatewords:6 }}</p>  #显示为 i am a girl from china,I ...

cut
移除变量值中所有的与给出的cut字符串相同的内容

    <p>{{ words|cut:'am' }}</p>  #删除words变量中所有的"am"字符串

join
以什么连接,相当于 str.join(list)

    <p>{{ l3|join:'__' }}</p>   #输出 aa__bb__cc__dd

add
将传进来的参数添加到原来的值上面,这个过滤器会尝试将“值”和“参数”转换成整型,然后进行相加,再进行字符串的拼接,但整数不会自动转换为字符串。

    <p>{{ i|add:10 }}</p>     # 12 + 10,显示为 22
    <p>{{ i|add:"10" }}</p>   # 先将"10"转换为整数,再相加
	<p>{{ sti|add:"10" }}</p> # 先将sti的"66"与"10"转换为整数,再相加
    <p>{{ st|add:"10" }}</p>  # st是一串中文,不能转为整数,直接字符串相加
	<p>{{ st|add:10 }}</p>    # st是一串中文,不能转为整数,字符串不能与整数相加,不显示
    <p>{{ st|add:st }}</p>    # 字符串拼接

标签 Tags

for循环

    {% for i in l %}
        <p>{{ forloop }}</p>
	    <!-- <p>{{ i }}</p> -->
    {% endfor %}

image

Variable Description
forloop.parentloop 本层循环的外层循环
forloop.counter0 当前循环的索引值(从0开始)
forloop.counter 当前循环的索引值(从1开始)
forloop.revcounter 当前循环的倒序索引值(从1开始)
forloop.revcounter0 当前循环的倒序索引值(从0开始)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)

for循环字典

<p>------ dic.keys ------</p>
    {% for key in dic.keys %}
        {{ key }}
    {% endfor %}
<p>------ dic.values ------</p>
    {% for value in dic.values %}
        {{ value }}
    {% endfor %}
<p>------ dic.items ------</p>
    {% for item in dic.items %}
        {{ item }}
    {% endfor %}

image

for empty
当遍历的可迭代对象数为0时,走empty

视图函数中定义变量

    class Person:   

        def __init__(self,name,age): # __init__ 初始化函数,实力化一个对象时,会自动执行此函数,给对象封装属性
            self.name = name    #self为实例化的对象本身(对象空间)
            self.age = age

    p1 = Person("大白",18)
    p2 = Person("小红",18)
    person_list=[p1,p2]
    user_list=[]

前端用法

    <!-- user_list为空,走empty -->
    <ul>
    {% for user in user_list %}
      <li>{{ user.name }}</li>
    {% empty %}
      <li>空空如也</li>
    {% endfor %}
    </ul>

    <!-- person_list不为空,直接打印person.name -->
    <ul>
    {% for person in person_list %}
        <li>{{ person.name }}</li>
    {% empty %}
        <li>空空如也</li>
    {% endfor %}
    </ul>

if判断
if...else... 与 if...elif...else...

	# user_list为空则走else,显示没有人
    {% if user_list %}
        人数:{{user_list|length}}
    {% else %}
        没有人
    {% endif %}
	
	# person_list不为空,显示为 人数:2
	{% if person_list %}
        人数:{{person_list|length}}
    {% else %}
        没有人
    {% endif %}
    {% if person_list|length > 5 %}
        7座车
    {% elif person_list|length > 0 %}
        5座车
    {% else %}
        不要车
    {% endif %}

if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。
Django的模板语言不支持连续判断,即 {% if a > b > c %} 这种写法不支持。

	# and使用
    {% if person_list|length > 0 and person_list|length < 5 %}
        5座车
    {% elif person_list|length > 6 %}
        7座车
    {% else %}
        不要车
    {% endif %}

for与if混用
视图层定义变量

    class Person:

        def __init__(self,name,age): # __init__ 初始化函数,实力化一个对象时,会自动执行此函数,给对象封装属性
            self.name = name    #self为实例化的对象本身(对象空间)
            self.age = age

    p1 = Person("大白",18)
    p2 = Person("小红",17)
    p3 = Person("dahei",20)
    person_list=[p1,p2,p3]
    <ul>
    {% for person in person_list %}
        <li>
        {% if forloop.first %}
            这是第一个
        {% elif forloop.last %}
            这是最后一个
        {% else  %}
            这是中间的
        {% endif %}

        {{ person.name }}
        </li>
    {% endfor %}
    </ul>

image

with

定义一个中间变量,多用于给一个复杂的变量起别名。
注意等号左右不要加空格。

<p>第一种写法 with...as newname</p>

{% with dic2.hobbys.sport as sport  %}
    {{sport}}
{% endwith %}

<p>第二种写法 newname=xxx  等号前后不能有空格 </p>
	
{% with book=dic2.hobbys.book  %}
    {{book}}
{% endwith %}

自定义 过滤器、标签、inclusion_tag

先三步操作:
	1. 在应用下创建一个名为 templatetags 的文件夹;
	2. 在该文件夹下创建任意名称的py文件,eg:mytag.py;
	3. 在该文件内必须先添加以下两行
	   from django import template
       register = template.Library()
	然后直接在下面定义自己的过滤器(过滤器最多有两个参数) 、标签与inclusion_tag

例如:

mytag.py文件中

from django import template

register = template.Library()

# 自定义求和过滤器,过滤器最多有两个参数
@register.filter(name='mysum')
def my_sum(v1,v2):
    return v1 + v2
	
# 自定义标签,标签可以有多个参数
@register.simple_tag(name='plus')
def my_tag(a,b,c,d):
    return '%s-%s-%s-%s' %(a,b,c,d)

自定义过滤器/标签的使用

<!-- 加载自定义过滤器/标签 文件 -->
{% load mytag %}

<!-- 自定义过滤器的使用 -->
{{i|mysum:60}}

<!-- 自定义标签的使用,标签名后面是参数,多个参数用空格隔开 -->
{% plus 'aa' 'bb' 123 'cc' %}

inclusion_tag

inclusion_tag的内部原理:
    先定义一个方法
	在页面上调用该方法,并且可以传值
	该方法会生成一些数据传递给一个html页面
	之后会将渲染好的结果,即那个html页面放到调用该方法的位置

这里举一个简单的例子

http://127.0.0.1:8000/app01/template/ 这个url对应template.html

1. 先在mytag.py中定义inclusion_tag,创建一个left方法,并定义好left_menu.html页面;
2. 在template.html中调用该方法,并传值;
3. 之后left方法会将其中定义的数据传入left_menu.html;
4. 之后将left_menu.html页面插入到template.html中调用left方法的地方。

mytag.py 文件中定义inclusion_tag

from django import template

register = template.Library()
@register.inclusion_tag("left_menu.html")
def left(n):
    data = ['第{}项'.format(i) for i in range(n)]

    #两种传值方式
    # return {"data":data}
    return locals()

创建left_menu.html

<ul>
{% for i in data %}
    <li>{{ i }}</li>
{% endfor %}
</ul>

template.html中调用left方法

<!-- 加载自定义过滤器/标签 文件 -->
{% load mytag %}

<!-- 调用inclusion_tag定义的方法 -->
{% left 5 %}

重启django服务,访问template.html对应的url,效果为:
image

通过这个简单的例子,inclusion_tag的使用场景总结为:

当html页面某一个地方的局部页面需要传参才能动态地渲染出来,并且在多个页面都需要用到该局部,那么可以考虑将改局部做成inclusion_tag形式。

模板的继承

使用场景:对于一些网站整体都大差不差,只有某些局部在变化的场景,可以使用模板的继承
	
语法:
# 在子页面继承某个模板
{% extends '模板名.html' %}

# 继承了模板的子页面与模板页面一摸一样,需要在模板页上提前划定可以被修改的区域
{% block content %}
    模板上此区域的内容
{% endblock %}

# 子页面上就可以自定制可以修改的区域
{% block content %}
    子页面内容
{% endblock %}

此外子页面也可以通过block.super变量使用模板的内容
{% block content %}
    {{ block.super }}
{% endblock %}
# 一般情况下模板页面上应该至少有三块可以被修改的区域,每个子页面就都可以有自己独有的 css/js/html 代码
  1. css区域
  2. html区域
  3. js区域

{% block css %}
    模板上此区域的内容
{% endblock %}

{% block html %}
    模板上此区域的内容
{% endblock %}

{% block js %}
    模板上此区域的内容
{% endblock %}

# 一般情况下 模板的页面划定的可供子页面修改的区域越多,灵活性越高,但划定的区域太多,还不如不用模板,直接复制粘贴修改各个页面。

简单使用举例:

页面效果:
image
image
image

代码:

urls.py 添加三个路由

    url(r'^home/',views.home),
    url(r'^login/',views.login),
    url(r'^reg/',views.reg),

views.py 添加三个视图函数

def home(request):
    return render(request,'home.html')

def login(request):
    return render(request,'login.html')

def reg(request):
    return render(request,'reg.html')

templates下添加三个html页面

home.html 是主页面,login.html与reg.html都继承它。
主页面包括三个部分:顶部不变的横栏,左侧不变的导航栏,右侧可变的面板。
home.html需要注意的点:
image

home.html 点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    {% load static %}
    <link rel="stylesheet" href="{% static '/others/bootstrap-3.4.1/css/bootstrap.css' %}">

    {% block css %} 
    <!-- css 可以被子页面修改的区域  -->
    {% endblock css %}
</head>
<body>
    <!-- 上栏导航条 -->
    <nav class="navbar navbar-default">
        <div class="container-fluid">
          <!-- Brand and toggle get grouped for better mobile display -->
          <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Brand</a>
          </div>
      
          <!-- Collect the nav links, forms, and other content for toggling -->
          <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
              <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
              <li><a href="#">Link</a></li>
              <li class="dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                <ul class="dropdown-menu">
                  <li><a href="#">Action</a></li>
                  <li><a href="#">Another action</a></li>
                  <li><a href="#">Something else here</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="#">Separated link</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="#">One more separated link</a></li>
                </ul>
              </li>
            </ul>
            <form class="navbar-form navbar-left">
              <div class="form-group">
                <input type="text" class="form-control" placeholder="Search">
              </div>
              <button type="submit" class="btn btn-default">Submit</button>
            </form>
            <ul class="nav navbar-nav navbar-right">
              <li><a href="#">Link</a></li>
              <li class="dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                <ul class="dropdown-menu">
                  <li><a href="#">Action</a></li>
                  <li><a href="#">Another action</a></li>
                  <li><a href="#">Something else here</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="#">Separated link</a></li>
                </ul>
              </li>
            </ul>
          </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
      </nav>
      <div class="container">
            <div class="row">
                <!-- 侧边导航栏 -->
                <div class="col-md-3">
                    <div class="list-group">
                        <a href="/home" class="list-group-item active">
                          Home
                        </a>
                        <a href="/login" class="list-group-item">Login</a>
                        <a href="/reg" class="list-group-item">Register</a>
                    </div>
                </div>
                <!-- 左边内容栏 是一个面板  -->
                <div class="col-md-9">
                    <div class="panel panel-primary">
                        <div class="panel-body">
                        </div>
                        <div class="panel-footer">

                          {% block content %}
                          <!-- html 可以被子页面修改的区域  -->
                          <!-- Home页面展示为巨幕 -->
                          <div class="jumbotron">
                            <h1>Hello, world!</h1>
                            <p>...</p>
                            <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
                          </div>                            
                          {% endblock content %}

                        </div>
                    </div>
                </div>
            </div>
      </div>
  {% block js %}   
  <!-- js 可以被子页面修改的区域  -->
  {% endblock js %}
</body>
</html>

子页面需要注意的点
image

login.html 点击查看代码
{% extends "home.html" %}

{% block css %}
<style>
    h1 {
        color: dodgerblue;
    }
</style>
{% endblock css %}

{% block content %}
<h1 class="text-center">登录页面</h1>
<form method="POST">
    <p>username: <input type="text" name="username" class="form-control"></p>
    <p>password: <input type="password" name="password" class="form-control"></p>
    <button type="submit" class="btn btn-block btn-success">login in</button>
</form>
{% endblock content %}

{% block js %}
<script>
    alert("跳转登录页面")
</script>
{% endblock js %}
reg.html 点击查看代码
{% extends "home.html" %}

{% block css %}
<style>
    h1 {
        color: red;
    }
</style>
{% endblock css %}

{% block content %}
<h1 class="text-center">注册页面</h1>
<form method="POST">
    <p>username: <input type="text" name="username" class="form-control"></p>
    <p>password: <input type="password" name="password" class="form-control"></p>
    <button type="submit" class="btn btn-block btn-danger">Sign in</button>
</form>
{% endblock content %}

{% block js %}
<script>
    alert("跳转注册页面")
</script>
{% endblock js %}

模板的导入

将某个页面作为一个模块的形式导入到其他页面的局部。哪里需要就可以直接导入使用

语法: 将wasai.html页面作为模块导入
{% include 'wasai.html' %}

例如:
创建一个wasai.html页面,在login.html页面中导入wasai.html

image

image

image

模型层

Django ORM 常用字段和参数

常用字段

# AutoField
int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则会自动创建一个列名为id的列。

# BigAutoField(AutoField)
bigint自增列,必须填入参数 primary_key=True

# IntegerField
一个整数类型,范围在 -2147483648 to 2147483647,最多到10位,不支持手机号。

# PositiveIntegerField
正整数 0 ~ 2147483647

# BigIntegerField
长整型(有符号的) -9223372036854775808 ~ 9223372036854775807,最多到19位

# DecimalField
10进制小数类型,有两个参数:
            max_digits,小数总长度
            decimal_places,小数位长度
# SmallIntegerField(IntegerField):
小整数 -32768 ~ 32767

# CharField
对于varchar类型,必须提供字符长度max_length参数。django中不提供现成的char对应的字段,但可通过自定义的方式自己设置。

# TextField
文本类型,此字段可以用来存大量内容(文章、博客),没有字数限制

# FileField
参数 upload_to='/xxx'
字符类型,给此字段传一个文件对象a.txt,会将文件自动保存到/xxx目录下,将文件路径保存到数据库中 /xxx/a.txt

#BooleanField(Field)
布尔值类型,此字段传布尔值 False/True,数据库中存0/1

#NullBooleanField(Field):
可以为空的布尔值

# EmailField
本质是varchar(254),本字段可为Django Admin以及ModelForm中提供验证机制

#DateField与DataTimeField

DateField:日期字段,日期格式  YYYY-MM-DD,相当于Python中的datetime.date()实例。
DataTimeField: 日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。

以上两个字段常用参数:
auto_now: 更新时间,每次操作数据的时候,该字段会自动更新为当前时间;
auto_now_add: 创建时间,在创建数据的时候会自动将当前创建时间记录下来,之后只要不人为的修改,就一直不变。

# TimeField(DateTimeCheckMixin, Field)
时间格式      HH:MM[:ss[.uuuuuu]]

# IPAddressField(Field)
字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

# GenericIPAddressField(Field)
字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
参数:
    protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
    unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"

# URLField(CharField)
字符串类型,Django Admin以及ModelForm中提供验证 URL

# SlugField(CharField)
字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

# CommaSeparatedIntegerField(CharField)
字符串类型,格式必须为逗号分割的数字

# UUIDField(Field)
字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

# FilePathField(Field)
字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
参数:
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹

# ImageField(FileField)
字符串,路径保存在数据库,文件上传到指定目录
参数:
    upload_to = ""      上传文件的保存路径
    storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
    width_field=None,   上传图片的高度保存的数据库字段名(字符串)
    height_field=None   上传图片的宽度保存的数据库字段名(字符串)

# DurationField(Field)
长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

# FloatField(Field)
浮点型

# BinaryField(Field)
二进制类型

各个字段与数据库的字段类型对应关系

点击查看
对应关系:
    'AutoField': 'integer AUTO_INCREMENT',
    'BigAutoField': 'bigint AUTO_INCREMENT',
    'BinaryField': 'longblob',
    'BooleanField': 'bool',
    'CharField': 'varchar(%(max_length)s)',
    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
    'DateField': 'date',
    'DateTimeField': 'datetime',
    'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
    'DurationField': 'bigint',
    'FileField': 'varchar(%(max_length)s)',
    'FilePathField': 'varchar(%(max_length)s)',
    'FloatField': 'double precision',
    'IntegerField': 'integer',
    'BigIntegerField': 'bigint',
    'IPAddressField': 'char(15)',
    'GenericIPAddressField': 'char(39)',
    'NullBooleanField': 'bool',
    'OneToOneField': 'integer',
    'PositiveIntegerField': 'integer UNSIGNED',
    'PositiveSmallIntegerField': 'smallint UNSIGNED',
    'SlugField': 'varchar(%(max_length)s)',
    'SmallIntegerField': 'smallint',
    'TextField': 'longtext',
    'TimeField': 'time',
    'UUIDField': 'char(32)',

自定义字段

# models.py
# 定义字段MyCharField
class MyCharField(models.Field):
    def __init__(self,max_length,*args,**kwargs):
        self.max_length=max_length
        super().__init__(max_length=max_length,*args,**kwargs)
        # 这里调用父类方法时,max_length一定要以关键字参数的形式传入
    def db_type(self,connection):
        # 返回真正的数据类型及约束的参数
        return 'char(%s)' %self.max_length

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

常用参数

null 
用于表示某个字段是否可以为空 null=True/False

unique
如果设置为unique=True 则该字段的值必须是唯一的

db_index
如果db_index=True 则代表着为此字段设置索引

default
为该字段设置默认值,default="default_value"

choices参数

对于可能性可以完全列举出来的字段,一般采用chices参数。

例如: 性别 成绩等级

# 定义字段使用choices参数

class User(models.Model):
    name = models.CharField(max_length=32)
    gender_choices = (
        (1,'男'),
        (2,'女'),
        (3,'其他'),
    )
    gender = models.IntegerField(choices=gender_choices)
    '''
    gender_choices 的第一个元素(上面的1,2,3)是什么类型,就定义为什么类型的字段
    '''
    score_choices = (
        ('A','优秀'),
        ('B','良好'),
        ('C','及格'),
        ('D','不及格'),
    )
    score = models.CharField(max_length=1,choices=score_choices,null=True,)
    '''
    score_choices第一个值(ABCD)是字符类型,该字段就为字符类型 CharField
    '''

# choices参数的字段存取值

    # 写入值直接使用choices选项中的第一个元素,如果存的值不在选项中也不会报错。
    models.User.objects.create(name='yxf',gender=1,score='B')
    models.User.objects.create(name='tmh',gender=5,score='E')  # 这里的5与E在choices的选项中没有

    user_obj = models.User.objects.filter(name='yxf').first()

    # 查看chioces属性字段对应的真实信息,用 get_字段名_display()
    print(user_obj.gender)         # 1
    print(user_obj.get_gender_display()) #男
    print(user_obj.get_score_display())  #良好

    # 对于有choices属性的字段,插入的值如果不在定义的选择中,get_字段名_display()还是显示原来的值
    user_obj = models.User.objects.filter(name='tmh').first()
    print(user_obj.get_gender_display()) #5
    print(user_obj.get_score_display())  #E

关系字段及参数

# ForeignKey  
外键关联关系,一般把ForeignKey字段设置在 '一对多'中'多'的一方,可以和其他表关联,也能和自身关联

# OneToOneField   相当于 ForeignKey(unique=True)
通常一对一字段用来扩展已有字段。(通俗的说就是一个人的所有信息不是放在一张表里面的,简单的信息一张表,隐私的信息另一张表,之间通过一对一外键关联)

# ManyToManyField  
多对多
 
字段参数:
    to: 设置要关联的表
    to_field:设置要关联的表的字段
    on_delete: 表示删除关联数据时的一些选项,比如 on_delete=models.SET_NULL 删除关联数据,与之关联的值设置为null
        on_delete=models.CASCADE,   # 删除关联数据,与之关联也删除
        on_delete=models.DO_NOTHING, # 删除关联数据时,删不掉,会直接报错
        on_delete=models.PROTECT,   # 删除关联数据,引发错误ProtectedError
        # models.ForeignKey('关联表', on_delete=models.SET_NULL, blank=True, null=True)
        on_delete=models.SET_NULL,  # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理)
        # models.ForeignKey('关联表', on_delete=models.SET_DEFAULT, default='默认值')
        on_delete=models.SET_DEFAULT, # 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值,一对一同理)
        on_delete=models.SET,     # 删除关联数据,
            a. 与之关联的值设置为指定值,设置:models.SET(值)
            b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

单表增删改查

# 增
1. .create() 
2. 对象.save()

# 查
1. .all()     查所有
2. .filter(xx=xx,xx=xx)  筛选条件,括号内多个条件用逗号隔开,默认and关系,返回queryset
3. .get(xx=xx,xx=xx)     筛选条件,返回数据对象,如果条件对应的数据不存在,会报错

# 改
1. .update()   操作queryset对象,批量更新
2. 对象.save() 

# 删
1. .delete()      操作queryset对象,批量删除
2. 对象.delete()

后续在之前 orm之表数据的增删改查 的基础上作补充

操作演示:

MYSQL中新建库django_orm

将数据库从sqlite3变更为mysql,具体配置可参照django链接数据库

models.py中定义user表,并创建(执行两条migrate命令)

class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    register_time = models.DateField()

(测试脚本)

在之前我们操作表数据是前后端交互,在视图函数中进行的,这里我们只是进行简单的测试,就不用前后端交互的形式,只需要准备一个测试环境。
两种测试方式:

  • 直接使用它准备好的测试环境,在pycharm的左下角有个 python console,点开,就能直接在里面写代码
  • 准备测试脚本,详细如下:
# 应用app01下的test.py文件
# 测试环境的准备 去manage.py中拷贝前四行代码,删除第二行,然后自己添加两行
import os

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite3.settings")
    import django
    django.setup()

    # 从这里开始就可以添加测试代码了,这里随便写两行
    from app01 import models
    print(models.User_info.objects.all())

当以上代码运行时如果报错: ModuleNotFoundError: No module named 'mysite3'
可以尝试打印sys.path看一下里面是否有项目根目录,如果没有则加上
image

新增数据

# 新增数据
    #方式一: 返回值就是创建的对象本身
    res = models.User.objects.create(name='yxf',age=3,register_time='2020-4-1')

    # 这里DateField类型的register_time不仅可以是固定格式的字符串,还可以是日期对象,会自动保存年月日,丢弃多余的时分秒。
    import datetime
    ctime = datetime.datetime.now()

    # 方式二: 先定义一个对象,然后保存
    user_obj = models.User(name='yh',age=99,register_time=ctime)
    user_obj.save()

删除数据

# 删除数据
    # 方式一: 删除查询到的所有内容,对对象集进行删除
    res=models.User.objects.filter().delete()
    print(res)  # 返回影响行数和删除的对象个数 (2, {'app01.User': 2})
    # pk表示主键,如果需要根据主键来删除,不需要知道主键具体字段叫什么,直接使用pk就行

    # 方式二: 删除单个对象
    user_obj=models.User.objects.filter(pk=5).first()
    res=user_obj.delete()
    print(res)    # 返回影响行数和删除的对象个数 (1, {'app01.User': 1})

修改数据

# 修改数据
    # 方式一 update() 只作用于对象集
    models.User.objects.filter(pk=7).update(age=99)

    # 方式二  先获取数据对象再修改字段内容,再保存
    # user_obj=models.User.objects.filter(pk=7).first()
    user_obj=models.User.objects.get(pk=7)
    # get()方法能直接返回数据对象,但当对象不存在会报错,所以不推荐使用
    user_obj.name='huandada'
    user_obj.age=88
    user_obj.save()

(必知必会13条)

1. .all()       查询所有数据,返回对象集
2. .filter()    带有过滤条件的查询,返回对象集
3. .get()       带有条件的查询,直接拿到数据对象,但如果条件不存在会报错
    models.User.objects.get(pk=7)

4. .first()     拿queryset 对象集里的第一个元素
    models.User.objects.all().first()

5. .last()      拿queryset 对象集里的最后一个元素
    models.User.objects.get(pk=7)all().last()

6. .values()    可以知道获取的字段 相当于  select name,age from ... ,返回列表套字典。.values() 就是获取全部字段
    res=models.User.objects.filter(pk=7).values('name','age')  
    print(res)  #返回 <QuerySet [{'name': 'huandada', 'age': 99}]>

    res=models.User.objects.values('name','age')
    print(res)   #返回 <QuerySet [{'name': 'yxf', 'age': 66}, {'name': 'huandada', 'age': 99}, {'name': 'huandada', 'age': 88}]>

7. .values_list() 可以直到获取的字段,返回列表套元组
    res=models.User.objects.filter(pk=7).values_list('name','age')
    print(res)   # <QuerySet [('huandada', 99)]>

    res=models.User.objects.values_list('name','age')
    print(res)  # <QuerySet [('yxf', 66), ('huandada', 99), ('huandada', 88)]>
	
8. .distinct() 去重,数据对象一模一样才能去重,如果有主键,那一定去不了重
    res=models.User.objects.values('name')
    print(res)    # <QuerySet [{'name': 'yxf'}, {'name': 'huandada'}, {'name': 'huandada'}]>
    res=models.User.objects.values('name').distinct()
    print(res)    # <QuerySet [{'name': 'yxf'}, {'name': 'huandada'}]>

9. .order_by() 排序
    res=models.User.objects.order_by('age')
    print([i.age for i in res])      # 默认正序 [66, 88, 99]
    res=models.User.objects.order_by('-age')
    print([i.age for i in res])      # 倒序 [99, 88, 66]

10. .reverse()  反转,前提是数据已经排过序了
    res=models.User.objects.order_by('age').reverse()
    print([i.age for i in res])      # order_by默认正序,反转之后倒序 [99, 88, 66]
	
11. .count() 统计查询出来的数据个数
    res=models.User.objects.count()
    print(res)      # 3
    res=models.User.objects.order_by('age').reverse().count()
    print(res)      # 3
	
12. .exclude() 排除在外
    res = models.User.objects.exclude(age="99")
    print([i.age for i in res])  # [66, 88]
	
13. .exists() 判断数据集是否有值,返回为布尔值,基本用不到,因为数据本身就带有布尔值的属性
    res = models.User.objects.filter(age="0").exists()
    print(res)  # False

(查看orm操作数据对应的sql语句)

方式一: 对象集.query

res = models.User.objects.filter(age="88").values('name','age')
print(res.query)   # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user` WHERE `app01_user`.`age` = 88

方式二: 在Django项目的settings.py文件最后添加以下代码,后续使用orm操作数据库时就会在终端打印出对应的sql语句

点击查看代码
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}
![image](https://img2022.cnblogs.com/blog/1314872/202202/1314872-20220215125513437-59874143.png)

双下划线查询

如果与常量比较,=右边可以直接写常量
如果与其他字段比较,=右边不能直接写字段名,需要借助F查询(详见本篇F查询)

# 大于 小于 大于等于 小于等于
__gt=num
__lt=num
__gte=num
__lte=num

# 成员查询(类似于sql的in)
__in=(num1,num2,num3)

# 范围查询 都是闭区间
__range=[num1,num2] 或 __range=(num1,num2) 

# 模糊查询
__contains='xx'    # 区分大小写
__icontains='xx'   # 不区分大小写

# 以什么开始/结尾 区分/不区分大小写
__startswith
__istartswith
__endswith
__iendswith

# 日期相关的查询,安装年份/月份/日/周
__year
__month
__day
__week_day  #1-7从周日开始算,即周日到周六

简单使用:

先准备数据
image

    # 大于 小于 年龄大于22,小于88
    res=models.User.objects.filter(age__gt=22,age__lt=88)
    # print([i.age for i in res]) 
    # 大于等于 小于等于 年龄大于等于22,小于等于88
    res=models.User.objects.filter(age__gte=22,age__lte=88)
    # in  年龄等于22,33,55,66的数据
    res=models.User.objects.filter(age__in=(22,33,55,66))
    # not in 年龄不等于22,33,55,66的数据
    res=models.User.objects.exclude(age__in=(22,33,55,66))
    # like "%h%" name字段中带有h的数据
    res=models.User.objects.filter(name__contains='h') # __contains区分大小写
    res=models.User.objects.filter(name__icontains='H') # __icontains不区分大小写
    # between 22 and 88  age字段在22与88之间的数据,闭区间
    res=models.User.objects.filter(age__range=[22,88])

    # like "y%" 以y开始
    res=models.User.objects.filter(name__startswith='y')  # 区分大小写
    res=models.User.objects.filter(name__istartswith='Y') # 不区分大小写

    # like "%y" 以y结尾
    res=models.User.objects.filter(name__endswith='y')  # 区分大小写
    res=models.User.objects.filter(name__iendswith='Y') # 不区分大小写

    # date字段相关:
    # date字段可以通过在其后加__year,__month,__day,__week_day等来获取date的特点部分数据
    # __week_day的1-7从周日开始算,即周日到周六
    res=models.User.objects.filter(register_time__week_day=2) #找出为周一的register_time
    res=models.User.objects.filter(register_time__year=2019)
    res=models.User.objects.filter(register_time__year=2019,register_time__month=7)
    res=models.User.objects.filter(register_time__day=1)

    # print([i.register_time for i in res]) 

多表查询

前期准备:多张表的创建 models.py文件

# 创建一套关系表 book publish author author_detail 
# publish 与 book       一对多  (版权限制,一本书只一个出版社出版)
# book 与 author        多对多 
# author 与 auth_detail 一对一

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)  # 出版时间设置为该条数据的添加时间
    stock = models.IntegerField()  #库存数量
    sales = models.IntegerField()  #卖出数量

    # 添加外键关系 与 多对多
    publish = models.ForeignKey(to='Publish')
    author = models.ManyToManyField(to='Author')

class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()  
    # EmailField本质是varchar(254) EmailFiel这个字段类型不是给models看的,是给校验性组件看的

class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 添加一对一关系
    author_detail=models.OneToOneField(to='Author_detail')

class Author_detail(models.Model):
    phone = models.CharField(max_length=20)
    addr = models.CharField(max_length=64)

再执行migrate两条命令,创建表。再添加一些数据。

外键字段的增删改

两种方式:

  • 基于外键字段对应的主键;
  • 基于外键字段对应的数据对象;

一对多的增删改(一对一也一样)

    # 一对多外键增删改
    # 增 .create()
    # 方式一   外键字段直接写实际字段名   
    # models.Book.objects.create(title='红楼梦',price=666.66,publish_id=1)

    # 方式二    外键字段使用虚拟字段(即 models.py中定义外键的那个变量名),添加对象
    # publish_obj = models.Publish.objects.filter(pk=2).first()
    # models.Book.objects.create(title='水浒传',price=888.88,publish=publish_obj)

    # 删 .delete()
    # models.Book.objects.filter(pk=1).delete()    # 删除book中的单行就正常删
    # models.Publish.objects.filter(pk=1).delete() # 级联删除,publish中pk=1的行删除,book中publish_id=1的行也删了

    # 改 .update() 给书籍修改出版社
    # 方式一 实际字段 
    # models.Book.objects.filter(pk=4).update(publish_id=1)
    # 方式二 虚拟字段 添加对象
    # publish_obj = models.Publish.objects.filter(pk=2).first()
    # models.Book.objects.filter(pk=6).update(publish=publish_obj)

多对多的增删改

    # 多对多外键增删改 就是在操作第三张关系表

    # 增 .add() 给书籍添加作者
    # 方式一: 直接添加作者的主键id
    # book_obj = models.Book.objects.filter(pk=4).first()
    # book_obj.author.add(2)   #这里的author是models.py中定义多对多的那个变量名
    # book_obj.author.add(1,3)
    # 方式二: 添加作者对象
    # book_obj = models.Book.objects.filter(pk=5).first()
    # author_obj1 = models.Author.objects.filter(pk=1).first()
    # author_obj2 = models.Author.objects.filter(pk=2).first()
    # book_obj.author.add(author_obj1,author_obj2)     #这里的author是models.py中定义多对多的那个变量名
    """
    .add() 给第三张关系表添加数据
        括号内既可以传数字也可以传对象,并支持多个
    """

    # 删 .remove() 删除pk=xx的书的pk=xx的作者
    # 方式一: 直接删除作者的主键id  
    # book_obj = models.Book.objects.filter(pk=4).first()
    # book_obj.author.remove(1)    
    # book_obj.author.remove(2,3)  #删除pk=4的书的多个作者
    # 方式二: 删除作者对象
    # book_obj = models.Book.objects.filter(pk=5).first()
    # author_obj1 = models.Author.objects.filter(pk=1).first()
    # author_obj2 = models.Author.objects.filter(pk=2).first()
    # book_obj.author.remove(author_obj1,author_obj2) 

    """
    .remove() 删除第三张关系表的数据
        括号内既可以传数字也可以传对象,并支持多个
    """

    #改 set()
    # 方式一: 直接修改作者的主键id     
    # book_obj = models.Book.objects.filter(pk=4).first()
    # book_obj.author.set([1,2])    # 将pk=4的书改为pk=1,2 的作者

    # book_obj = models.Book.objects.filter(pk=5).first()
    # book_obj.author.set([3])     # # 将pk=5的书改为pk=3 的作者

    # 方式二: 修改作者对象
    # book_obj = models.Book.objects.filter(pk=6).first()
    # author_obj1 = models.Author.objects.filter(pk=1).first()
    # author_obj2 = models.Author.objects.filter(pk=2).first() 
    # book_obj.author.set([author_obj1,author_obj2])    
    """
    .set() 修改第三张关系表的数据
        括号内只能传一个可迭代对象,该对象内既可以传数字也可以传对象,并支持多个
        其机制为: 先删除,后增加

        eg: 表中已有book2 与 author1,author2对应关系,修改 book2.author.set([author2,author3])
        它会先删除 book2 与 author1的关系行,再增加 book2 与 author3的行,book2 与 author2的行不动
    """

    # 清空  在第三张关系表中清空某个书籍与作者的绑定关系
    # book_obj = models.Book.objects.filter(pk=6).first()
    # book_obj.author.clear()
    """
    .clear()  括号内不用加任何参数
    """

(正反方向概念)

例如: book表里有个到publish表的外键

由书查出版社 就是正向查询
由出版社查书 就是反向查询

外键在我这里,由我查你,就是正向
外键在我这里,由你查我,就是反向

多表查询中,记得两句话:
# 正向查询按字段  (这里的字段指models.py中定义字段的变量名)
# 反向查询按表名小写

子查询(基于对象的跨表查询)

# 子查询
1. 先拿到一个数据对象
2. 对象.字段(或表名)  # 正向查询按字段,反向查询按表名小写
   对象.字段.all()          # 正向,得到的数据可能为多个则加.all(),一对一关系不加.all()
   对象.表名小写_set.all()   # 反向,得到的数据可能为多个则加_set.all(),一对一关系不加_set.all()

简单使用:

基于对象 正向

    #基于对象  正向跨表

    # 1. 查询书籍主键为5的出版社  
    # # 书查出版社,正向; 书对出版社 一对一
    book_obj = models.Book.objects.filter(pk=5).first()
    res = book_obj.publish     # 正向查询按字段 
    print(res)         # Publish object
    print(res.name)    # 西方出版社
    """
    book_obj.publish
        表示从book表切换到publish字段对应的表,所以返回是Publish object
    """

    # 2. 查询书籍主键为4的作者 
    # # 书查作者,正向; 书对作者 一对多
    book_obj = models.Book.objects.filter(pk=4).first()
    # res = book_obj.author
    # print(res)       # app01.Author.None这个报错表示需要加上 all()
    res = book_obj.author.all() 
    print(res)    # <QuerySet [<Author: Author object>, <Author: Author object>]>

    # 3. 查询作者gjm的电话号码
    # # 作者查作者详情表 正向, 一对一
    author_obj = models.Author.objects.filter(name='gjm').first()
    res = author_obj.author_detail
    print(res.phone)

    """
    正向,什么时候加 .all() ?
        当表a 查 表b为正向:
        表a 对 表b 为 一对多,即返回结果有可能是一个到多个需要加 .all(),返回对象集;
        表a 对 表b 为 一对一,即返回结果只能是一个,不用加.all(),直接返回数据对象。
            book_obj.author.all()
            author_obj.author_detail
    """

基于对象 反向

    #基于对象  反向跨表

    # 4. 查询名为 “东方出版社” 的出版社出版的书籍
    # # 出版社查书,反向; 出版社对书 一对多
    publish_obj = models.Publish.objects.filter(name='东方出版社').first()
    res = publish_obj.book_set.all()
    print(res)  # <QuerySet [<Book: Book object>, <Book: Book object>]>

    # 5. 查询作者是"huandada"写过的书名
    # # 作者查书,反向;作者对书 一个作者能写多本书 一对多
    author_obj = models.Author.objects.filter(name='huandada').first()
    res = author_obj.book_set.all()
    print([i.title for i in res])  # ['水浒传', '红楼梦']

    # 6. 查询手机号是110的作者姓名
    # # 作者详情查作者 反向,一对一
    author_detail_obj = models.Author_detail.objects.filter(phone='110').first()
    res = author_detail_obj.author    # 因为一对一,所以这里没有用_set.all()
    print(res.name)  # huandada

    """
    基于对象反向查询时,
        表b 查 表a为反向:
        表b 对 表a为一对多, 即查询结果为一到多个时,需要加上_set.all();
        表b 对 表a为一对一, 即查询结果只为一个,不加_set.all()。
            author_obj.book_set.all()
            author_detail_obj.author
    """

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

# 联表查询
基于双下线进行联表操作,同样遵循 正向查询按字段,反向查询按表名小写
  #一番练习下来发现,以左边的表为准,左边有的,右边没有,会以null代替(不确定这个观点是否一定正确)

简单使用:

    # 基于双下划线的跨表查询  两种方式:正向 与 反向

    # 1. 查询gjm的手机号和年龄  

    #方式一: 正向 author表 --> author_detail
    res = models.Author.objects.filter(name='gjm').values('author_detail__phone','age')
    print(res)     # <QuerySet [{'author_detail__phone': '999', 'age': 66}]>

    '''
    .values('author_detail__phone')
    正向查询按字段 -- models.py的Author表中 作者详情字段的变量名为 author_detail
    author_detail__phone -- 切换到 author_detail外键字段对应的作者详情表,并获取phone字段
    '''
   
    #方式二: 反向  author_detail --> author表
    res = models.Author_detail.objects.filter(author__name='gjm').values('phone','author__age')
    print(res)   # <QuerySet [{'phone': '999', 'author__age': 66}]>

    '''
    .filter(author__name='gjm').values('phone','author__age')
    反向按表名小写 -- author就为Author表的表名小写
    '''

    # 2. 查询书籍主键为5的出版社名称和书名
    # 方式一: 正向  Book --> Publish
    res = models.Book.objects.filter(pk=5).values('publish__name','title')
    print(res)    # <QuerySet [{'publish__name': '西方出版社', 'title': '红楼梦'}]>

    # 方式二: 反向  Publish --> Book
    res = models.Publish.objects.filter(book__pk=5).values('name','book__title')
    print(res)    # <QuerySet [{'name': '西方出版社', 'book__title': '红楼梦'}]>

    # 3. 查询书籍主键为6的作者姓名与作者电话  
    # 方式一: 正向 Book --> author --> author_detail
    res = models.Book.objects.filter(pk=6).values('author__name','author__author_detail__phone')
    print(res)    # <QuerySet [{'author__name': 'gjm', 'author__author_detail__phone': '999'}]>

    # 方式二: 反向 author_detail --> author --> Book
    res = models.Author_detail.objects.filter(author__book__pk=6).values('author__name','phone')
    print(res)    # <QuerySet [{'author__name': 'gjm', 'phone': '999'}]>
    
    '''
    连续跨表查询: 只要有外键关系,可以一直跨不停
    author__author_detail__phone 正向 用字段  
    author__book__pk=6 与  author__name  反向 用表名小写
    '''

聚合查询和分组查询

聚合查询

使用聚合函数进行查询

mysql中常用聚合函数:
max()、 min()、 sum()、 count()、avg()

一般聚合查询和分组查询一起使用

# 加载聚合函数
    from django.db.models import Max,Min,Sum,Count,Avg

1. 只有聚合查询,不涉及分组(或者说所有数据看成一个组),用aggregate()
    res = models.Book.objects.aggregate(avg_price=Avg('price'),min_price=Min('price'))
    print(res)  # {'avg_price': 722.025, 'min_price': Decimal('555.06')}

2. 分组查询中使用聚合查询,直接在分组查询的方法 annotate()中使用聚合函数
    res = models.Publish.objects.annotate(avg_price=Avg('book__price'),max_price=Max('book__price')).values('name','avg_price','max_price')
    print(res)  # <QuerySet [{'name': '北方出版社', 'avg_price': 888.88, 'max_price': Decimal('888.88')},...]>

分组查询

1. 直接根据某张表的主键值分组
 # .annotate()会根据前面.values()中的字段进行分组,当没有.values()字段时,会默认根据表的主键字段分组

 # res = models.Book.objects.values('id').annotate(author_num=Count('author')).values('title','author_num')  
 res = models.Book.objects.annotate(author_num=Count('author')).values('title','author_num')  #当根据主键分组时,.values('id')可以省略
 print(res)  # <QuerySet [{'title': '水浒传', 'author_num': 2}, {'title': '红楼梦', 'author_num': 2}, ...]>

2. 根据除主键外的其他字段分组
 res = models.Book.objects.values('publish_date').annotate(book_num=Count('pk')).values('publish_date','book_num')

 """
 如果你的分组查询一直报错,可以查看是否数据库为严格模式 ONLY_FULL_GROUP_BY
 严格模式下: group by 分组查询的select字段 ,除了group by 的字段之外其他的字段都要有聚合函数包裹
 """

聚合查询与分组查询使用举例

点击查看代码
    # # 聚合查询 aggregate
    from django.db.models import Max,Min,Sum,Count,Avg
    # 1. 求所有书的平均价格  没有分组用.aggregate(),返回字典对象
    # res = models.Book.objects.aggregate(avg_price=Avg('price'),min_price=Min('price'))
    # print(res)  # {'avg_price': 722.025, 'min_price': Decimal('555.06')} 
  
    
    # # 分组查询+聚合查询

    # 2. 求单个出版社出版书的平均价格
    # res = models.Publish.objects.annotate(avg_price=Avg('book__price'),max_price=Max('book__price')).values('name','avg_price','max_price')
    # print(res)  # <QuerySet [{'name': '北方出版社', 'avg_price': 888.88, 'max_price': Decimal('888.88')},...]>

    # 3. 统计每一本书的作者个数
    # # res = models.Book.objects.values('id').annotate(author_num=Count('author')).values('title','author_num')
    # res = models.Book.objects.annotate(author_num=Count('author')).values('title','author_num')
    # print(res)  # <QuerySet [{'title': '水浒传', 'author_num': 2}, {'title': '红楼梦', 'author_num': 2}, ...]>

    # 4. 统计出版时间统计同一天出版的书的数量
    # res = models.Book.objects.values('publish_date').annotate(book_num=Count('pk')).values('publish_date','book_num')
    # print(res)  # <QuerySet [{'publish_date': datetime.date(2022, 2, 16), 'book_num': 2}, {'publish_date': datetime.date(2022, 2, 17), 'book_num': 1}]>

    # 5. 统计每个出版社卖出的最便宜的书
    # res = models.Publish.objects.annotate(min_price=Min("book__price")).values('name','min_price')
    # print(res)

    # 6. 统计不止一个作者的图书
    # ret = models.Book.objects.annotate(author_num=Count('author')).filter(author_num__gt=1).values('title','author_num')
    # print(ret)  # <QuerySet [{'title': '水浒传', 'author_num': 2}, {'title': '红楼梦', 'author_num': 2}]>

    """
    只要orm语句得出的结果时queryset对象
    就可以无限使用 queryset对象封装的方法
    """

    # 7. 查询每个作者出的书的总价格
    # 反向 
    # ret = models.Author.objects.annotate(totle_price=Sum('book__price')).values('name','totle_price')
    # print(ret)

    # # 正向 
    # ret = models.Book.objects.values('author__pk').annotate(totle_price=Sum('price')).values('author__name','totle_price')
    # print(ret)

F与Q查询

F查询

我们查询的字段名有可能是用户从页面输入的,那么输入的内容就是字符串类型数据。
对与查询条件左边的内容,F查询能直接使用字符类型获取对应字段的数据。
对于查询条件右边的内容,需要使用Q查询才能直接使用字符类型获取对应字段的数据。

1. 双下划线查询=右边是字段的情况
sales__gt=F('stock')  #sales字段大于stock字段

2. 数字类型的字段运算
price=F('price')+1000  #price字段+1000

3. 字符串类型字段拼接

# 不能直接title=F('TITLE')+'爆款',这样会报错,并清空该字段!!!!!

# from django.db.models.functions import Concat
# from django.db.models import Value
# title=Concat(Value('爆款'),F('title'),Value('爆款')) #给title字段前后加上'爆款'两个字

F查询的使用举例

点击查看代码
    # F查询
    from django.db.models import F
    # 1. 查询卖出数大于库存数的书籍
    res = models.Book.objects.filter(sales__gt=F('stock')).values('title')
    print(res)

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

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

Q查询

filter() 等方法中逗号隔开的条件是与的关系。那么or与 not对应的或非关系怎么操作呢?可以使用Q查询。

1. 使用Q()实现filter()中多个条件的与或非关系
    from django.db.models import Q
    
    # 与 逗号分割
    res=models.Book.objects.filter(Q(sales__gt=100),Q(price__lt=600)).values('title')  # 卖出数大于100 and 价格小于600
    print(res) 
    # 或 |分割
    res=models.Book.objects.filter(Q(sales__gt=100)|Q(price__lt=600)).values('title')  # 卖出数大于100 or 价格小于600
    print(res)
    # 非 前面加~
    res=models.Book.objects.filter(~Q(sales__gt=100),Q(price__lt=600)).values('title')  # not 卖出数大于100 and not价格小于600
    print(res) 

2. 使用Q()实现将查询条件左边也变成字符串形式(sales__gt=xxx 左边sales也为字符串时的情况)
    q = Q()
    q.connector = 'or'  #不设置此行,默认为and
    q.children.append(('sales__gt',100))
    q.children.append(('price__lt',600))
    res=models.Book.objects.filter(q).values('title')
    print(res)

事务

事务的定义:将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性。
四大特性(ACID): 原子性,一致性,隔离性,持久性

django中的事务操作

from django.db import transaction
with transaction.atomic(): 
    # 数据库操作一
    # 数据库操作二
    '''
    在with代码块内书写的所有orm操作都属于同一个事务
    '''

也可以加上异常处理代码完善一下:
try:
    with transaction.atomic():
        ...
except Exception as e:
    print(e)

数据库查询优化

only与defer

    '''
    '''
    orm语句的惰性查询:
        如果你只是写了orm语句,在后面没有用到该语句查询的结果,那么orm会自动识别不执行
        例如:
            res = models.Book.objects.filter(pk=5).values('publish__name','title')
            # print(res)   #这里注释掉,没用res变量,上面的orm语句就不会执行。
    '''

    # only 与 defer
    '''
    only('字段1','字段2'): 
    当你明确只需要一张表的某个或几个字段时,可以用only(),它在查询时就只会查询主键与指定的字段。
      当你for循环only orm返回的数据对象获取only()中没有的字段时,每一次循环又会发起一次数据库的查询。
      所以用only对象获取字段值时,最好只获取only()中有的字段。

    all(): 会将全部字段都查出来,当表字段很多时就会占用更多资源。

    defer('字段1','字段2'):
    与only()相反,除了defer()中的字段,其他字段都会查询出来,获取defer中的字段需要重新走数据库连接
    '''

    res = models.Book.objects.only('title')
    print([(i.title) for i in res])
    # print([(i.title,i.price) for i in res])  #不推荐获取only()中没有的字段

    res = models.Book.objects.all()
    print([(i.title,i.price) for i in res])

    res = models.Book.objects.defer('title')
    print([(i.price) for i in res])
    # print([(i.title) for i in res])  ##不推荐获取defer()中有的字段

only解析:
image

defer解析:
image

    # select_related 与 prefetch_related

    # res = models.Book.objects.all()
    # print([(i.title,i.publish.name) for i in res]) 
    # 用all()返回的book表的数据对象获取publish表的字段,每循环一次就要查询一次数据库

    # res = models.Book.objects.select_related('publish')
    # print([(i.title,i.publish.name) for i in res])

    # res = models.Book.objects.select_related('author') #作者与书表多对多,不能用select_related
    # print([(i.title,i.author.name) for i in res])
    '''
    select_related() 括号内只能放外键字段  一对多 与 一对一可以用,多对多不能用!!!
    只能正向使用,反向的表里没有外键字段。

    相当于联表查询 INNER JOIN,先将两张表连接起来,再将大表的数据封装给查询到的对象。
    所以获取book或者publish表的字段只用走一次查询,后续for循环都不需要再走数据库。
    '''

    # 获取主键为1的书名与出版社名
    # res = models.Book.objects.prefetch_related('publish').filter(pk=7)
    # print([(i.title,i.publish.name) for i in res])

    '''
    prefetch_related() 括号内只能是外键字段,内部是子查询,同样不支持多对多查询与反向查询

    将原表book与子查询对应的表publish查询到的所有字段的结果都封装到返回的对象中,
    所以感觉是一次就搞定。
    '''

image

image

MTV 与 MVC模型

# MTV: django号称是MTV模型
M:models
T:templates
V:views

MVC: django本质也是MVC模型
M:models
V:views
C:controller

多对多关系表的三种创建方式

# 推荐使用半自动,方便关系表的字段扩展,同时也能使用orm的正反向查询

# 全自动(自己测试的时候用,方便)
class Books(models.Model):
    title = models.CharField(max_length=32)
    author = models.ManyToManyField(to='Authors')
class Authors(models.Model):
    name = models.CharField(max_length=32)
'''
多对多 ManyToManyField 放在哪边都可以
优点: 不用写第三张关系表,很方便,还提供orm操作第三张关系表的方法
缺点: 第三张关系表的扩展性极差,不能添加额外字段
'''

# 纯手动(不推荐)
class Books(models.Model):
    title = models.CharField(max_length=32)
class Authors(models.Model):
    name = models.CharField(max_length=32)
class BooksAuthors(models.Model):
    book = models.ForeignKey(to='Books')
    author = models.ForeignKey(to='Author')
'''
优点: 第三张表可以进行额外的字段扩展
缺点: 不能用orm提供的简单的操作方法
'''

# 半自动(实际项目中使用)
class Books(models.Model):
    title = models.CharField(max_length=32)
    author = models.ManyToManyField(to='Author',
                                    through='BooksAuthors',
                                    through_fields=('book','author'))

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

class BooksAuthors(models.Model):
    book = models.ForeignKey(to='Books')
    author = models.ForeignKey(to='Author')

'''
through='BooksAuthors',          # 指明此字段对应的已创建的第三张关系表 
through_fields=('book','author') # 指明关系表中的字段。

through_fields 字段的先后顺序:
    当前表是谁,就把对应的第三张表中的字段放在前面

半自动: 第三张关系表可以进行字段扩展,可以使用orm正反向查询,不能使用add,set,remove,clear四个方法
'''

批量插入数据

bulk_create() 批量插入

#需求: 调用/user时,在后端批量插入1000条数据到数据库的User表,并展示到前端页面

#models.py 创建User表
class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

操作代码:

# 视图函数:
def user(request):
    
    # 方式一: 这样每一次for循环都要请求一次数据库,比较慢,用户加载页面时,由于创建时间过长就会卡一会儿
    # for i in range(1000):
    #     models.User.objects.create(name="user%s" %i ,age=18)
    
    # 方式二: 先创建对象,在统一插入,只用连接一次数据库,时间大大缩短
    book_list=[]
    for i in range(1000,2000):
        user_obj = models.User(name="user%s" %i ,age=18) 
        book_list.append(user_obj)
    models.User.objects.bulk_create(book_list)
    '''
    bulk_create([数据对象,数据对象,...]) 是orm提供的批量插入的方法,能大大缩短操作时间
    '''

    user_set = models.User.objects.all()
    return render(request,'user.html',locals())

# urls.py
url(r'^user/',views.user),

# user.html
    {% for user in user_set  %}
      <p>{{ user.name }}  {{ user.age }}</p>
    {% endfor %}

图书管理系统项目

页面展示

首页

image

图书列表展示

image

新增图书

image

编辑图书

image

先将首页,图书列表,以及新增、编辑功能实现

实现步骤:

  • 准备工作

创建BMS app并注册,数据库配置为mysql,配置好templates,static目录,使用post要注释crsf那一行,bootstrap包放到 templates/others/下。

定义表字段

BMS\models.py 点击查看代码
from django.db import models

# 创建一套关系表 book publish author author_detail 
# publish 与 book       一对多  (版权限制,一本书只一个出版社出版)
# book 与 author        多对多 
# author 与 auth_detail 一对一

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')
    author = models.ManyToManyField(to='Author')

class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()  
    # EmailField本质是varchar(254) EmailFiel这个字段类型不是给models看的,是给校验性组件看的

class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 添加一对一关系
    author_detail=models.OneToOneField(to='Author_detail')

class Author_detail(models.Model):
    phone = models.CharField(max_length=20)
    addr = models.CharField(max_length=64)

执行两条命令,创建表。然后添加一些数据进去。

python .\manage.py makemigrations
python .\manage.py migrate
  • 添加路由
mysite4\urls.py 点击查看代码
from BMS import views
from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),

    url(r'^$',views.home,name='home'),
    url(r'book/list/',views.book_list,name='book_list'),
    url(r'book/add/',views.book_add,name='book_add'),

    # 有名分组与无名分组
    url(r'book/edit/(?P<edit_id>\d+)',views.book_edit,name='book_edit'),
    url(r'book/delete/(\d+)',views.book_delete,name='book_delete'),

    url(r'publish/list/',views.publish_list,name='publish_list'),
    url(r'author/list/',views.author_list,name='author_list'),
    url(r'more/',views.more,name='more'),
]
  • 添加视图函数
BMS\views.py 点击查看代码
from django.shortcuts import redirect, render
from BMS import models
# Create your views here.
def home(request):
    return render(request,'home.html')

def book_list(request):
    res = 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_id')
        author_idlist = request.POST.getlist('author_idlist')
        book_obj = models.Book.objects.create(title=title,price=price,publish_date=publish_date,publish_id=publish_id)
        book_obj.author.add(*author_idlist)

        return redirect('book_list')

    publish_set = models.Publish.objects.all()
    author_set = models.Author.objects.all()
    return render(request,'book_add.html',locals())

def book_edit(request,edit_id):
    book_set = models.Book.objects.filter(pk=edit_id)
    book_obj = book_set.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_id')
        author_idlist = request.POST.getlist('author_idlist')

        book_set.update(title=title,price=price,publish_date=publish_date,publish_id=publish_id)
        book_obj.author.set(author_idlist)
        return redirect('book_list')

    publish_set = models.Publish.objects.all()
    author_set = 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')

def publish_list(request):
    pass

def author_list(request):
    pass

def more(request):
    pass
  • 添加前端页面

templates下的页面: home.html 作为主页面,后续其他页面都继承主页面。

image


主页面 home.html 点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    {% load static %}
    <link rel="stylesheet" href='{% static "/others/bootstrap-3.4.1/css/bootstrap.css" %}'>
    
    {% block css %}
      
    {% endblock css %}
</head>
<body>
    <!-- 导航条 -->
    <nav class="navbar navbar-inverse">
        <div class="container-fluid">
          <!-- Brand and toggle get grouped for better mobile display -->
          <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">图书管理系统</a>
          </div>
      
          <!-- Collect the nav links, forms, and other content for toggling -->
          <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
              <li class="active"><a href="#">图书 <span class="sr-only">(current)</span></a></li>
              <li><a href="#">作者</a></li>
              <li class="dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
                <ul class="dropdown-menu">
                  <li><a href="#">Action</a></li>
                  <li><a href="#">Another action</a></li>
                  <li><a href="#">Something else here</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="#">Separated link</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="#">One more separated link</a></li>
                </ul>
              </li>
            </ul>
            <form class="navbar-form navbar-left">
              <div class="form-group">
                <input type="text" class="form-control" placeholder="Search">
              </div>
              <button type="submit" class="btn btn-default">Submit</button>
            </form>
            <ul class="nav navbar-nav navbar-right">
              <li><a href="#">我的主页</a></li>
              <li class="dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">设置 <span class="caret"></span></a>
                <ul class="dropdown-menu">
                  <li><a href="#">Action</a></li>
                  <li><a href="#">Another action</a></li>
                  <li><a href="#">Something else here</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="#">Separated link</a></li>
                </ul>
              </li>
            </ul>
          </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>
    <div class="container">
        <div class="row">
            <!-- 导航条 -->
            <div class="col-md-4">
                <div class="list-group">
                    <a href="{% url 'home' %}" class="list-group-item active">
                      首页
                    </a>
                    <a href="{% url 'book_list' %}" class="list-group-item">图书列表</a>
                    <a href="{% url 'publish_list' %}" class="list-group-item">出版社列表</a>
                    <a href="{% url 'author_list' %}" class="list-group-item">作者列表</a>
                    <a href="{% url 'more' %}" class="list-group-item">更多</a>
                </div>
            </div>

            <!-- 面板 -->
            <div class="col-md-8">
                <div class="panel panel-primary">
                    <div class="panel-heading">
                      <h3 class="panel-title">BMS</h3>
                    </div>
                    <div class="panel-body">

                      {% block content %}
                      <div class="jumbotron">
                        <h1>欢迎来到图书管理系统!</h1>
                        <p>这里是全世界最全的书库</p>
                        <p><a class="btn btn-primary btn-lg" href="#" role="button">了解我们</a></p>
                      </div>
                      {% endblock content %}
                    </div>
                  </div>
            </div>

        </div>
    </div>
    {% block js %}
      
    {% endblock js %}
</body>
</html>

图书列表页面 book_list.html 点击查看代码
{% extends "home.html" %}

{% block content %}
<a href="{% url 'book_add' %}" class="btn btn-success btn-sm">添加</a>
<br>
<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>书名</th>
            <th>价格</th>
            <th>出版日期</th>
            <th>出版社</th>
            <th>作者列表</th>
            <th>操作</th>
        </tr>
    </thead> 
    <tbody>

        {% for book_obj in res %}
        <tr>
            <td>{{ book_obj.pk }}</td>
            <td>&laquo;{{ book_obj.title }}&raquo;</td>
            <td>{{ book_obj.price }}</td>
            <td>{{ book_obj.publish_date|date:"Y-m-d" }}</td>
            <td>{{ book_obj.publish.name }}</td>
            <td>
                {% for author_obj in book_obj.author.all %}
                    {% if forloop.last %}
                        {{ author_obj.name }}
                    {% else %}
                        {{ author_obj.name }},
                    {% endif %}
                {% endfor %}
            </td>
            <td>
                <a href="{% url 'book_edit' book_obj.pk %}" class="btn btn-primary btn-xs">编辑</a>
                <a href="{% url 'book_delete' book_obj.pk %}" class="btn btn-danger btn-xs">删除</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
  
{% endblock content %}

添加书籍页面 book_add.html 点击查看代码
{% extends "home.html" %}

{% block content %}
<h2 class="text-center">添加书籍</h2>
<form action="" method='post'>
    <p>书名:
        <input type="text" name="title" class="form-control">
    </p>
    <p>价格:
        <input type="text" name="price" class="form-control">
    </p>
    <p>出版日期:
        <input type="date" name="publish_date" class="form-control">
    </p>
    <p>出版社:
        <select name="publish_id" class="form-control">
            {% for publish_obj in publish_set %}
            <option value="{{ publish_obj.pk }}">{{ publish_obj.name }}</option>
            {% endfor %}
        </select>
    </p>
    <p>作者:
        <select name="author_idlist" class="form-control" multiple>
            {% for author_obj in author_set %}
                <option value="{{ author_obj.pk }}">{{ author_obj.name }}</option>
            {% endfor %}
        </select>
    </p>
    <input type="submit" class="btn btn-primary btn-block">
</form>

{% endblock content %}

编辑书籍页面 book_edit.html 点击查看代码
{% extends "home.html" %}

{% block content %}
<h2 class="text-center">编辑书籍</h2>
<form action="" method='post'>
    <p>书名:
        <input type="text" name="title" class="form-control" value="{{ book_obj.title }}">
    </p>
    <p>价格:
        <input type="text" name="price" class="form-control" value="{{ book_obj.price }}">
    </p>
    <p>出版日期:
        <input type="date" name="publish_date" class="form-control" value="{{ book_obj.publish_date|date:'Y-m-d' }}">
    </p>
    <p>出版社:
        <select name="publish_id" class="form-control">
            {% for publish_obj in publish_set %}
                {% if edit_obj.publish.pk == publish_obj.pk %}
                    <option value="{{ publish_obj.pk }}" selected>{{ publish_obj.name }}</option>
                {% else %}
                    <option value="{{ publish_obj.pk }}">{{ publish_obj.name }}</option>
                {% endif %}
            {% endfor %}
        </select>
    </p>
    <p>作者:
        <select name="author_idlist" class="form-control" multiple>
            {% for author_obj in author_set %}
                {% if author_obj in book_obj.author.all %}
                    <option value="{{ author_obj.pk }}"selected>{{ author_obj.name }}</option>
                {% else %}
                    <option value="{{ author_obj.pk }}">{{ author_obj.name }}</option>
                {% endif %}
            {% endfor %}
        </select>
    </p>
    <input type="submit" class="btn btn-primary btn-block">
</form>

{% endblock content %}

Ajax

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(不只是XML)。

AJAX 不是新的编程语言,而是一种使用现有标准的实现的新方法/功能。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)

两个概念:

  • 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
  • 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求

所以ajax的特点可以概括为: 异步提交,局部更新

这里只介绍jquery封装的Ajax(原生的与其他版本的功能类似)。


# ajax语法
$.ajax({
    // 1.指定向哪个后端url发送ajax请求
    url:'',  // 不写就是当前地址
    // 2. 请求方式
    type:'post', // 不写默认为get
    // 3. 向后端发送的数据,字典类型
    data:{'v1':$('#v1').val(),'v2':$('#v2').val()},
    // 4. 回调函数,当后端返回结果时会自动触发此函数,args接收后端返回的结果
    success:function (args){
        $('#v3').val(args)
    }
})

# 前端接收后端返回字典的几种处理方式
1. HttpResponse
   1.1 序列化与反序列化
   后端return HttpResponse(json.dumps(dic)) 序列化字典返回;
   前端ajax 反序列化 dic=JSON.parse(args),然后就可以拿到key对应的值 dic.key
   1.2 序列化与特殊参数配置
   后端return HttpResponse(json.dumps(dic)) 序列化字典返回;
   前端在ajax中设置dataType:"json",就能直接接收到字典对象。(后边会详细介绍)
   $.ajax({
    url:'',
    type:'post', // 不写默认为get
    data:{'v1':$('#v1').val(),'v2':$('#v2').val()},
	dataType:"json",             #添加此行
    success:function (args){
        $('#v3').val(args)
      }
    })
2. JsonResponse
   后端 return JsonResponse(dic);
   前端拿到的就是字典对象 console.log(typeof args)  // object

# 目前学到的朝后端发请求的4种方式:
1. 浏览器地址栏输入url回车访问;   GET请求
2. a标签href属性;               GET请求
3. form表单提交;                GET/POST请求
4. ajax                         GET/POST请求

小栗子:

需求:
页面有三个input框和一个提交按钮:
[] + [] = []
[commit]

前两个框输入数字,点击提交按钮 向后端发送ajax请求,再将结果返回到第三个框中。
整个过程不能有页面的刷新,也不能在前端计算。

代码:
#urls.py 新增一个路由
    url(r'^ajax_sum/',views.ajax_sum),
	
#views.py 新增视图函数:
def ajax_sum(request):
    if request.method == 'POST':
        v1 = request.POST.get('v1')
        v2 = request.POST.get('v2')
        v3 = int(v1) + int(v2)
        print(v1,v2)
        return HttpResponse(v3)
    return render(request,'ajax_sum.html')

#templates下新增ajax_sum.html页面,主要代码为
<body>
    <input type="text" id="v1">+
    <input type="text" id="v2">=
    <input type="text" id="v3">
    <p>
        <button id='m1'>求和</button>
    </p>
    <script>
        // 给按钮绑定一个点击事件
        $('#m1').click(function(){
            $.ajax({
                // 1.指定向哪个后端url发送ajax请求
                url:'',  // 不写就是当前地址
                // 2. 请求方式
                type:'post', // 不写默认为get
                // 3. 向后端发送的数据
                data:{'v1':$('#v1').val(),'v2':$('#v2').val()},
                // 4. 回调函数,当后端返回结果时会自动触发此函数,args接收后端返回的结果
                success:function (args){
                    $('#v3').val(args)
                }
            })
        })
    </script>
</body>

ajax_sum.html 点击查看完整代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
    <input type="text" id="v1">+
    <input type="text" id="v2">=
    <input type="text" id="v3">
    <p>
        <button id='m1'>求和</button>
    </p>
    <script>
        // 给按钮绑定一个点击事件
        $('#m1').click(function(){
            $.ajax({
                // 1.指定向哪个后端url发送ajax请求
                url:'',  // 不写就是当前地址
                // 2. 请求方式
                type:'post', // 不写默认为get
                // 3. 向后端发送的数据
                data:{'v1':$('#v1').val(),'v2':$('#v2').val()},
                // 4. 回调函数,当后端返回结果时会自动触发此函数,args接收后端返回的结果
                success:function (args){
                    $('#v3').val(args)
                }
            })
        })
    </script>
</body>
</html>

image

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

# 主要研究post请求数据的编码格式
'''
get请求数据就是直接放在url后面的
http://127.0.0.1:8000/test/?username=yy&password=111
'''

可以向后端发送post请求的方式:
1. form 表单
2. Ajax

前后端传输数据的编码格式:
1. urlencoded
2. formdata
3. json

# 研究form表单:

默认数据编码格式为 urlencoded (查看请求头 Content-Type: application/x-www-form-urlencoded;)
数据格式为: username=yxf&password=123
- django针对符合urlencoded数据编码格式的数据都会自动帮你解析封装到 request.POST中

当提交内容中有文件时,需要把编码格式改为formdata (form表单设置 enctype="multipart/form-data",查看请求头 Content-Type: multipart/form-data;)
- django 会将普通键值对解析到request.POST中,将文件解析到 request.FILES中。

form表单不能发送json格式的代码

# 研究Ajax:

默认编码格式也是 urlencoded
数据格式为: username=yxf&password=123
- django针对符合urlencoded数据编码格式的数据都会自动帮你解析封装到 request.POST中

Ajax提交文件与json格式数据会在接下来单独拿出来讲

ajax发送json格式数据

$.ajax({
    url:'',
    type:'post',
    // 向后端发送json格式的数据 JSON.stringify()转为json格式
    data:JSON.stringify({'name':'yxf','age':3}),
    // 设置内容格式为json 必须contentType与内容实际格式要一致
    contentType:'application/json',
    success:function (){
    }
})
后端接收json数据使用request.body

        json_b = request.body  # b'{"name":"yxf","age":3}' bytes类型的数据

        # json.loads()中可以传入字符串或二进制格式数据,它会识别是否需要解码,再反序列化
        json_str=json_b.decode('utf-8')
        dic=json.loads(json_str)    # 字符串反序列化
        # dic=json.loads(json_b)    # 二进制反序列化

'''
ajax发送json格式数据需要注意:
  1. contentType参数指定成 application/json;
  2. 数据需要是真正的json格式
  3. django后端不会帮你处理json格式数据,需要自己去request.body 获取并处理
'''

ajax发送文件

ajax发送文件需要借助于js内置对象FormData


            $('#m1').click(function(){
                // 先生成FormData内置对象
                let formDataobj = new FormData();
                // 添加普通的键值对
                formDataobj.append('username',$('#d1').val());
                formDataobj.append('password',$('#d2').val());
                formDataobj.append('myfile',$('#f1')[0].files[0]);  //获取文件对象
                console.log($('#f1')[0].files[0]);
                $.ajax({
                    url:'',
                    type:'post',
                    // 传送的数据直接放formDataobj对象
                    data:formDataobj,
                    // 设置内容格式为json 必须contentType与内容实际格式要一致
                    contentType:false,  // 不需要使用任何编码 django后端能自动识别formdata对象
                    processData: false,  // 告诉浏览器不要对我的数据进行任何处理
                    success:function (args){
                    }
                })
        })

后端接收:
    if request.is_ajax(): 
        if request.method == 'POST':
            print(request.POST)   # <QueryDict: {'username': ['yy'], 'password': ['123']}>
            print(request.FILES)  # <MultiValueDict: {'myfile': [<InMemoryUploadedFile: backpi.jpg (image/jpeg)>]}>
            
            # 存储myfile内容到myfile1文件:
            file1= request.FILES.get('myfile')

            with open('myfile1','wb') as f:
                # 读取file1 的内容
                data=file1.read()                    
                f.write(data)
'''
ajax发送文件,总结:
1. 需利用内置对象Formdata
    添加普通键值对:formDataobj.append('username',$('#d1').val());
    添加文件对象:  formDataobj.append('myfile',$('#f1')[0].files[0]);
2. 需要指定两个关键性参数
    contentType:false,  // 不需要使用任何编码 django后端能自动识别formdata对象
    processData: false,  // 告诉浏览器不要对我的数据进行任何处理
3. django后端能自动识别到Formdata对象,并将其普通键值对封装到 request.POST中,文件封装到 request.FILES中
'''

django自带的序列化组件

#序列化组件 serializers
#需求:在前端以列表套字典的形式获取到后端user表中的所有数据。
def user(request):
    user_set = models.User.objects.all()

    # lis = []
    # for user_obj in user_set:
    #     dic = {"name":user_obj.name,"age":user_obj.age}
    #     lis.append(dic)
    # return JsonResponse(lis,safe=False)
    #页面展示为 [{"name": "yxf", "age": 3}, {"name": "huandada", "age": 18}, {"name": "tmh", "age": 4}]

    # django自带的序列化组件 
    from django.core import serializers
    # 会自动将数据转成第一个字段的格式,并且内容很全面
    lis = serializers.serialize('json',user_set)
    return HttpResponse(lis)

    # 页面展示为:
    # [{"model": "app01.user", "pk": 2, "fields":
    #  {"name": "yxf", "age": 3}}, 
    #  {"model": "app01.user", "pk": 3, "fields": 
    #  {"name": "huandada", "age": 18}}, 
    #  {"model": "app01.user", "pk": 4, "fields": 
    #  {"name": "tmh", "age": 4}}]

'''
对应前后端分离的项目 :
    后端开发只需要将数据处理好,序列化返回给前端
    再写一个接口文档,告诉前端每个字段代表的意思
'''

https://www.bejson.com/ 可以实现json的格式化校验(规范json的格式)。

ajax与sweetalert实现删除二次确认

删除二次确认的两种方式:
1. ajax + DOM的confirm()
DOM的confirm("提示消息:你确定吗"),弹出框有确定与取消两个按钮,分别返回true与false,可以判断如果为true则执行ajax

2. ajax + sweetalert
以下详细介绍此方法的实现。

sweetalert能很方便的提供好看的确认框。
sweetalert使用教程: https://lipis.github.io/bootstrap-sweetalert/
sweetalert资源地址: https://github.com/lipis/bootstrap-sweetalert#sweetalert-for-bootstrap

sweetalert的导入:

- 方式一: 本地导入:
  下载github的zip包,解压后只需要用到dist目录,将其重命名为bootstrap-sweetalert并放到项目中
  加载(这里我使用static动态加载,你也可以直接写路径): 

  {% load static %}
  <link rel="stylesheet" href="{% static '/others/bootstrap-sweetalert/sweetalert.css' %}">
  <script src="{% static '/others/bootstrap-sweetalert/sweetalert.js' %}"></script>

- 方式二: 使用cdn.bootcdn.net的cdn服务
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.css" integrity="sha512-f8gN/IhfI+0E9Fc/LKtjVq4ywfhYAVeMGKsECzDUHcFJ5teVwvKTqizm+5a84FINhfrgdvjX8hEJbem2io1iTA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.js" integrity="sha512-XVz1P4Cymt04puwm5OITPm5gylyyj5vkahvf64T8xlt/ybeTpz4oHqJVIeDtDoF5kSrXMOUmdYewE4JS/4RWAA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

sweetalert图标遮挡问题(title为中文,很容易出现此问题):

可以调节标题位置,加上以下样式代码

    div.sweet-alert h2 {
        padding-top: 10px;
    }

小栗子:

直接使用图书管理系统项目,点击图书列表页面的删除时,触发二次确认。

点击取消后,弹出框消失;点击删除后,显示已删除,点击ok,弹出框消失,页面刷新

代码:

#book_list.html(这里只列出关键代码)
<!-- 删除按钮 -->
<button type="button" class="btn btn-danger btn-xs delete"  data_id="{{ book_obj.pk }}" > 删除 </button>

<script>
    $(".delete").click(function(){
        // 在按钮的属性中定义该条数据的主键,再拿到主键值,传递给后端
        let delete_pk =  $(this).attr('data_id') 
        swal({
            title: "请再次确认?",               // 弹出框标题
            text: "删除后将无法找回!!!",      // 弹出框正文
            type: "warning",                   // 弹出框类型,图标显示不同,有四种:warning,error,success,info
            showCancelButton: true,            // 显示取消按钮(确认按钮默认显示)
            cancelButtonClass: "btn-success",  // 取消按钮为btn-success样式,蓝色
            cancelButtonText: "取消",          // 取消按钮显示内容
            confirmButtonClass: "btn-danger",  // 确认按钮样式,红色
            confirmButtonText: "删除",         // 确认按钮显示内容
            closeOnConfirm: false,             // false表示点击确认按钮不关闭弹框
            },
            function(){                        // 点击确认后会触发这个function,点击取消后弹窗会关闭,不触发。
                $.ajax({
                    url: 'http://127.0.0.1:8000/book/delete/',
                    type: 'post',
                    data: {'delete_pk':delete_pk},
                    success: function(args){
                        if (args.code === 0){
                            // 弹窗显示已删除,点击确认后刷新页面
                            swal({title:"已删除!!!",},function(){window.location.reload()})
                            }
                        else {
                            swal("未知错误!!!")}
                    }
                })
            });
    })
</script>

#urls.py定义删除路由
url(r'book/delete/',views.book_delete,name='book_delete'),

#后端代码:
def book_delete(request):
    if request.is_ajax():
        delete_pk = int(request.POST.get('delete_pk'))
        print(delete_pk)
        models.Book.objects.filter(pk=delete_pk).delete()
        return JsonResponse({'code':0})
book_list.html,点击查看完整代码
{% extends "home.html" %}
{% block css %}
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.css" integrity="sha512-f8gN/IhfI+0E9Fc/LKtjVq4ywfhYAVeMGKsECzDUHcFJ5teVwvKTqizm+5a84FINhfrgdvjX8hEJbem2io1iTA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.js" integrity="sha512-XVz1P4Cymt04puwm5OITPm5gylyyj5vkahvf64T8xlt/ybeTpz4oHqJVIeDtDoF5kSrXMOUmdYewE4JS/4RWAA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- {% load static %}
<link rel="stylesheet" href="{% static '/others/bootstrap-sweetalert/sweetalert.css' %}">
<script src="{% static '/others/bootstrap-sweetalert/sweetalert.js' %}"></script> -->
{% endblock css %}

{% block content %}
<a href="{% url 'book_add' %}" class="btn btn-success btn-sm">添加</a>
<br>
<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>书名</th>
            <th>价格</th>
            <th>出版日期</th>
            <th>出版社</th>
            <th>作者列表</th>
            <th>操作</th>
        </tr>
    </thead> 
    <tbody>

        {% for book_obj in res %}
        <tr>
            <td>{{ book_obj.pk }}</td>
            <td>&laquo;{{ book_obj.title }}&raquo;</td>
            <td>{{ book_obj.price }}</td>
            <td>{{ book_obj.publish_date|date:"Y-m-d" }}</td>
            <td>{{ book_obj.publish.name }}</td>
            <td>
                {% for author_obj in book_obj.author.all %}
                    {% if forloop.last %}
                        {{ author_obj.name }}
                    {% else %}
                        {{ author_obj.name }},
                    {% endif %}
                {% endfor %}
            </td>
            <td>
                <a href="{% url 'book_edit' book_obj.pk %}" class="btn btn-primary btn-xs">编辑</a>
                <!-- <a href="" class="btn btn-danger btn-xs" id="delete" data_id="{{ book_obj.pk }}">删除</a> -->
                <!-- 删除按钮 -->
                <button type="button" class="btn btn-danger btn-xs delete"  data_id="{{ book_obj.pk }}" > 删除 </button>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
  
{% endblock content %}

{% block js %}
<script>
    $(".delete").click(function(){
        // 在按钮的属性中定义该条数据的主键,再拿到主键值,传递给后端
        let delete_pk =  $(this).attr('data_id') 
        swal({
            title: "请再次确认?",               // 弹出框标题
            text: "删除后将无法找回!!!",      // 弹出框正文
            type: "warning",                   // 弹出框类型,图标显示不同,有四种:warning,error,success,info
            showCancelButton: true,            // 显示取消按钮(确认按钮默认显示)
            cancelButtonClass: "btn-success",  // 取消按钮为btn-success样式,蓝色
            cancelButtonText: "取消",          // 取消按钮显示内容
            confirmButtonClass: "btn-danger",  // 确认按钮样式,红色
            confirmButtonText: "删除",         // 确认按钮显示内容
            closeOnConfirm: false,             // false表示点击确认按钮不关闭弹框
            },
            function(){
                $.ajax({
                    url: 'http://127.0.0.1:8000/book/delete/',
                    type: 'post',
                    data: {'delete_pk':delete_pk},
                    success: function(args){
                        if (args.code === 0){
                            // 弹窗显示已删除,点击确认后刷新页面
                            swal({title:"已删除!!!",},function(){window.location.reload()})
                            }
                        else {
                            swal("未知错误!!!")}
                    }
                })
            });
    })
</script>
  
{% endblock js %}

分页器

理论推导

1. 用户要访问哪一页可以用个体请求携带参数来确定 url?page=6

2. queryset对象支持索引取值和切片操作,不支持负数索引

3.推到过程
current_page = request.GET.get("page",1)  # 获取用户想访问的页码  如果没有 默认展示第一页
try:  # 由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
  current_page = int(current_page)
except Exception as e:
  current_page = 1
# 还需要定义页面到底展示几条数据
per_page_num = 10  # 一页展示10条数据

# 需要对总数据进行切片操作 需要确定切片起始位置和终止位置
start_page = ?
end_page = ?
"""
下面需要研究current_page、per_page_num、start_page、end_page四个参数之间的数据关系
per_page_num = 10
current_page                start_page                  end_page
    1                           0 (因为索引从0开始)          10
    2                           10                          20
    3                           20                          30
    4                           30                          40

per_page_num = 5
current_page                start_page                  end_page
    1                           0                           5
    2                           5                           10
    3                           10                          15
    4                           15                          20
可以很明显的看出规律
start_page = (current_page - 1) * per_page_num
end_page =  current_page* per_page_num
"""

# 数据总页面数获取
  # divmod(数据总条数,每页展示条数)  ==> (商,余数)
  # 余数只要不是0,页面数就为 商+1
book_queryset = models.Book.objects.all()
all_count = book_queryset.count()  # 数据总条数
all_pager, more = divmod(all_count, per_page_num)
if more:  # 有余数则总页数加一
  all_pager += 1

# 利用start_page和end_page对总数据进行切片取值再传入前端页面就能够实现分页展示
book_list = models.Book.objects.all()[start_page:end_page]
return render(request,'booklist.html',locals())

# 前端页面的代码编写
{% for book in book_list %}
	<p>{{ book.title }}</p>
{% endfor %}

#现在我们实现了最简单的分页,但是前端没有按钮去让用户点击需要看第几页,所以我们需要渲染分页器相关代码,直接去bootstrap框架拷贝代码即可

分页器代码

现成代码,粘贴即用。

一般在django中要用到第三方功能或者代码的时候,会创建一个utils文件夹。utils文件夹可以创建在应用下或项目根目录下,看实际情况。再在utils下放现成的代码文件,或者目录。

class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=2, 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)

自定义分页器的使用

将上面的代码放到split_page.py文件中,再将该文件放在utils下。

后端:

from utils.split_pages import Pagination
def user(request):
    user_queryset = models.User.objects.all()
    current_page = request.GET.get('page',1)
    all_count = user_queryset.count()

    # 1. 传值生成对象
    page_obj = Pagination(current_page=current_page,all_count=all_count)
    # 2. 直接对数据集进行切片
    page_queryset = user_queryset[page_obj.start:page_obj.end]

    return render(request,'user.html',locals())

前端:
需要加载bootstrap 3.x

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 加载bootstrap -->
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
    {% for user_obj in page_queryset %}
        <p>{{ user_obj.name }} {{ user_obj.age }}</p>
        <nav aria-label="Page navigation">
        </nav>
    {% endfor %}

    {{ page_obj.page_html|safe }}
</body>
</html>

效果
image

forms组件

引子

写一个注册功能:
获取用户名和密码 利用form表单提交数据
在后端判断用户名和密码是否符合以下条件:
    用户名中不能有 "一般般" 这个词汇,否则返回提示信息
	密码3-8位,超出范围返回提示信息

实现代码:

#视图函数:
def register(request):
    # 定义一个空字典,当get方式加载时字典为value为空,post方式加载时,value为key字段对应的提示信息

    dic = {'name':'','password':''}
    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        if not name:
            dic['name'] = 'name 不能为空'
        elif "一般般" in name:
            dic['name'] = '不能一般般'
        if len(password) < 3:
            dic['password'] = '密码不能小于三位'
        elif len(password) > 8:
            dic['password'] = '密码不能大于8位'
    return render(request,'register.html',locals())

# 前端:
    <form action="" method="post">
        <p>name:
            <!-- span标签占位提示信息,为空则不显示,有提示信息时显示 -->
            <input type="text" name="name"> <span style="color: red;">{{ dic.name }}</span>
        </p>
        <p>password:
            <input type="password" name="password"> <span style="color: red;">{{ dic.password }}</span>
        </p>
        <input type="submit">
    </form>

效果:

以上代码实现了三件事:

1. 手动书写前端获取用户数据的html代码   - 渲染html代码
2. 后端对用户数据进行校验              - 校验数据
3. 对不符合要求的数据进行前端提示       - 展示提示信息

django的forms组件也能够完成:
- 渲染html代码
- 校验数据
- 展示提示信息

为什么数据校验要在后端完成 不能直接在前端利用js完成呢?
因为前端校验是弱不禁风的,前端代码可以直接在浏览器上修改,或者通过爬虫程序绕过前端页面直接向后端提交数据。
所以:
数据校验前端可有可无,后端必须要有!!!

例如 购物网站:
选取了一些货物后,前端会计算价格发给后端,如果后端不做价格校验,用户篡改了价格,那就...

实际上: 后端获取到用户选择商品的主键值,在后端查出所有商品价格,再计算一遍,
        如果和前端价格一致,则正常交易,如果不一致,则终止交易。

Forms组件使用

基本使用

定义一个继承forms.Form的子类,其中定义需要校验的字段及条件

from django import forms

class MyForms(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字段必须符合邮箱格式 xx@xxx.com
    email = forms.EmailField()

校验数据

#可以在测试脚本中进行测试
    #测试代码
    from app01.views import MyForms
    forms_obj = MyForms({'username':'yxf','password':'1232','email':'666'})
    print(forms_obj.is_valid())   #False
    print(forms_obj.cleaned_data) #{'username': 'yxf', 'password': '1232'}
    print(forms_obj.errors)       #<ul class="errorlist"><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li></ul>
    '''
    forms_obj = MyForm() 将需要校验的数据以字典的方式传入 {"字段":"数据",...}
    forms_obj.is_valid()  传入数据都符合规则返回True,否则返回False
    forms_obj.cleaned_data 返回传入字典中合规的数据,
       注: 调用 is_valid() 后,才能调用 cleaned_data 。否则报错 'MyForms' object has no attribute 'cleaned_data'
    forms_obj.errors      返回传入字典中不合规的字段及原因
    '''

    forms_obj = MyForms({'username':'yxf','password':'1232','hobby':'打篮球','email':'666@123.com'})
    print(forms_obj.is_valid())   #True
    print(forms_obj.cleaned_data) #{'username': 'yxf', 'password': '1232', 'email': '666@123.com'}
    print(forms_obj.errors)       #为空
    '''
    传入MyForm类中没有的字段:
        django 会自动舍弃多余的字段,就如同那个字段没有一样。
    '''
    forms_obj = MyForms({'username':'yxf','password':'1232'})
    # form_obj = MyForm({'username':'','password':'1232','email':'123@12.com'})
    print(forms_obj.is_valid())   #False
    print(forms_obj.cleaned_data) #{'username': 'yxf', 'password': '1232', 'email': '666@123.com'}
    print(forms_obj.errors)       #<ul class="errorlist"><li>email<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
    '''
    默认条件下,字典中需要传入MyForm定义的所有字段对应的数据,且不能为空,
    否则,没有传入的字段相当于不合规
    '''
    #校验数据时,默认情况下可以多传,但不能少传。

渲染标签

# forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)

# 视图函数
def is_forms(request):
    # 定义一个空对象
    forms_obj=MyForms()
    # 将空对象传给页面
    return render(request,"is_forms.html",locals())

# html页面 is_form.html
    # 第一种渲染方式: 代码少,封装程度高,不便于后续扩展,一般只在本地测试使用
    {{ forms_obj.as_p }}

    {{ forms_obj.as_ul }}

    {{ forms_obj.as_table }}

    # 第二种渲染方式: 可扩展性强,但代码多,一般不用
    <p>{{ forms_obj.username.label }}: {{ forms_obj.username }}</p>
    <p>{{ forms_obj.password.label }}: {{ forms_obj.password }}</p>
    <p>{{ forms_obj.email.label }}: {{ forms_obj.email }}</p>

    # 第三种渲染方式(推荐使用): 可扩展性强,代码少

    <!-- for循环forms_obj拿到 forms_obj.字段 对象 -->
    {% for forms_ele in forms_obj %}
        <p>{{ forms_ele.label }}: {{ forms_ele }}</p>
    {% endfor %}

    '''
    label属性默认展示的时类中定义的字段名首字母大写的形式,
    也可以自己修改,直接给字段对象添加label属性
    username = forms.CharField(min_length=3,max_length=8,label="用户名")
    '''

前端渲染效果:

展示信息提示

#views.py
class MyForms(forms.Form):

# initial   初始值,展示在前端input框里面的初始值

# error_messages 自定义不合规信息提示

# 为该标签添加属性 forms.widgets.xxx(attr={'属性1':'value1','属性2':'value2'})

    username = forms.CharField(
                            min_length=3,max_length=8,label="用户名", initial="张三疯",
                            error_messages={
                            'min_length':'密码最少3位',
                            'max_length':'密码最大8位',
                            'required':'密码不能为空'
                            },                 
                            # 给此标签添加了form-control c1 c2三种类,还给边框加了样式
                            widget = forms.widgets.TextInput(attrs={'class':'form-control c1 c2','style':'border:solid medium blue'}),
    )
    email = forms.EmailField(label="邮箱",
                            # error_messages={
                            #     'invalid':'邮箱格式不正确',
                            #     'required':'邮箱不能为空'
                            # }
    )

def is_forms(request):
    forms_obj = MyForms()
    if request.method == 'POST':
        # request.POST刚好返回了字典类型数据,直接传入MyForms()即可
        forms_obj = MyForms(request.POST)

    return render(request,"is_forms.html",locals())

#is_forms.html 表单代码:
    <!-- novalidate 添加后将禁止前端HTML5的表单校验功能 -->
    <form action="" method="post" novalidate>

    {% for forms_ele in forms_obj %} 
        <!--添加span标签展示提示信息 因为一个字段内容可能不符合该字段的多条规则,所以forms_ele.errors返回的是列表形式 -->
        <p>{{ forms_ele.label }}: {{ forms_ele }} <span style="color: red;">{{ forms_ele.errors.0 }}</span></p>
    {% endfor %}
    
    <input type="submit">
    </form>

    '''
    总结:  
        1. 前端传回的数据request.POST刚好是字典形式,传入自定义类MyForms中;
        2. novalidate 添加后将禁止前端HTML5的表单校验功能
        3. span 标签占位提示信息,get请求没有提示信息为空,不显示
        4. 必备的条件:以下两行代码变量名必须一样,以实现数据不合法展示提示信息的同时,还会显示之前输入的信息,人性化功能
           forms_obj = MyForms()
           forms_obj = MyForms(request.POST)
        5. error_messages 可自定义提示信息
    '''

前端html5表单校验功能需要关闭,form表单添加novalidate属性

后端提示信息展示:

  • 未设置error_messages字段时,提示信息根据限制条件,系统默认生成。
  • 设置error_messages字段后,展示自定义的信息提示

字段校验

除了简单的校验字段(类似于min_length等)外,常用的还有三种更灵活也更复杂的定义校验规则的方式:

RegexValidator验证器

from django import forms
from django.core.validators import RegexValidator

class MyForms(forms.Form):
    phone = forms.CharField(validators=[RegexValidator(r'^[0-9]+$','请输入数字'),RegexValidator(r'[0-9]{11}','数字必须是11位')])

自定义验证函数

from django import forms
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
import re


def re_phone(value):
    phone_re=re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not re.search(phone_re,value):
        raise ValidationError('手机号码格式错误')

class MyForms(forms.Form):
    phone = forms.CharField(validators=[re_phone,],error_messages={'required': '手机不能为空'},)

Hook方法

我们还可以在Form类中定义钩子函数,来实现自定义的验证功能。

根据钩子函数所作用的字段不同分为:

  • 局部钩子: 只作用于一个字段
  • 全局钩子: 作用于多个字段
class MyForms(forms.Form):
    username = forms.CharField(
                        min_length=3,max_length=8,label="用户名", initial="张三疯",
                        error_messages={'min_length':'密码最少3位','max_length':'密码最大8位','required':'密码不能为空'}, )
    password = forms.CharField(min_length=3,max_length=8)
    confirm_pwd = forms.CharField()
    
    # 局部钩子的使用 用户名中不能有 "一般般"

    # 局部钩子函数名字为 clean_字段名
    def clean_username(self):
        # 拿到username字段对应的值
        value = self.cleaned_data.get("username")
        if "一般般" in value:
            # 抛出提示信息
            self.add_error('username', '一般般不配,最好不一般')
            # raise ValidationError("一般般不配,最好不一般")
        return value

    # 全局钩子的使用 确认comfirm_pwd与password字段数据是否一致

    # 全局钩子函数名字为clean
    def clean(self):
        password = self.cleaned_data.get("password")
        confirm_pwd = self.cleaned_data.get("confirm_pwd")
        print(password, confirm_pwd)
        if not password == confirm_pwd:
            print('in if')
            self.add_error('confirm_pwd', '两次密码不一致')
        # 全局钩子需要归还所有数据
        return self.cleaned_data

    '''
    总结:
      1. 钩子函数命名是固定的,乱命名该函数不生效
        - 局部: clean_字段名
        - 全局: clean

      2. 当数据验证通过,钩子函数必须返回勾到的数据,以供后续处理
        - 局部: 返回勾到的单个数据
        - 全局: 返回全部数据 return self.cleaned_data

      3. 钩子函数中两种返回提示信息的方式:
        self.add_error('字段名', '提示信息')  
          - 能对指定字段返回提示信息
        raise ValidationError('提示信息')    
          - 直接抛出带有提示信息的异常,适用于只有一个字段的局部钩子函数,全局钩子函数有多个字段,不会生效
    '''

Forms组件知识点汇总

常用字段与插件

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

class MyForms(forms.Form):

# initial   初始值,展示在前端input框里面的初始值
# error_messages 自定义不合规信息提示
# TextInput()指定input的type类型为text,CharField也默认是text
    username = forms.CharField(
                            min_length=3,max_length=8,label="用户名", initial="张三疯",
                            error_messages={
                            'min_length':'密码最少3位',
                            'max_length':'密码最大8位',
                            'required':'密码不能为空'
                            },
                            widget = forms.widgets.TextInput(),
    )

# required 默认为True,为False时 该字段可以为空

    email = forms.EmailField(label="邮箱",required=False,)

# password 此字段使用了CharField类型,django默认input type='text',添加以下PasswordInput配置使type='password'
    password = forms.CharField(
                            min_length=3,max_length=8,label="密码",
                            widget = forms.widgets.PasswordInput(),
    )

# radioSelect   redio 单选类型

    gender = forms.ChoiceField(
                            choices = ((1,'男'),(2,'女'),(3,'保密')),
                            label = "性别",
                            initial = 3,  #默认值为保密
                            widget = forms.widgets.RadioSelect(),  #html页面展示为radio类型
    )

# 单选Select

    gender2 = forms.ChoiceField(
                            choices = ((1,'男'),(2,'女'),(3,'保密')),
                            label = "性别",
                            initial = 3,  #默认值为保密
                            widget = forms.widgets.Select(),  #html页面展示为单选select类型,没有此行 ChoiceField字段也默认为此类型
    )

# 多选Select 

    hobby = forms.MultipleChoiceField(
                            choices = (('A','足球'),('B','篮球'),('C','橄榄球'),('D','双色球')),
                            initial = ['A','D'],
                            #使用MultipleChoiceField字段,不写此行也默认是多选select
                            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()

效果:

choice字段注意事项

在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。

方式一:在__init__中获取choices选项

from django import forms
from app01 import models 
 
class MyForm(Form):
 
    # 多选
    hobby2 = forms.MultipleChoiceField(
        # choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        # choices = [(1, '唱歌'), (2, '篮球'), (3, '足球'), (4, '双色球')],
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )
    # 单选
    city = forms.ChoiceField(
        label = '所在城市'
    )

    def __init__(self, *args, **kwargs):
        super(MyForms,self).__init__(*args, **kwargs)
        # self.fields['hobby2'].choices = [(1, '唱歌'), (2, '篮球'), (3, '足球'), (4, '双色球')]
        self.fields['hobby2'].choices = models.Hobby.objects.all().values_list('pk','hobby')
        self.fields['city'].choices = models.City.objects.all().values_list('pk','city')

方式二: 直接在choices处请求数据库

from django import forms
from app01 import models 
 
class MyForm(Form):
    # 多选
    hobby2 = forms.MultipleChoiceField(
        # choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        choices = models.Hobby.objects.all().values_list('pk','hobby'),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )
    # 单选
    city = forms.ChoiceField(
        label = '所在城市',
        choices = models.City.objects.all().values_list('pk','city'),
    )

效果:

Django Form所有内置字段

点击查看所有
Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
 
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型

Forms源码解析

简单展示一下它的核心源码
Forms组件源码

切入点: forms_obj.is_valid()

    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors
		# 如果要返回True,self.is_bound要为True  self.errors为False,即self.errors为空
		
	#self.is_bound	
	# 只要给 forms对象传值了,就肯定为true
	self.is_bound = data is not None or files is not None
	
	#self.errors
	
    @property
    def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors
		
	#self.full_clean()
	
	def full_clean(self):
		...
        self._clean_fields()   
        self._clean_form()
        self._post_clean()
		
		
    def _clean_fields(self):
    for name, field in self.fields.items():
        # value_from_datadict() gets the data from the data dictionaries.
        # Each widget type knows how to retrieve its own data, because some
        # widgets split data over several HTML fields.
        if field.disabled:
            ...这里是一些校验过程
            self.cleaned_data[name] = value    #将字段和值放到self.cleaned_data这个字典中
            if hasattr(self, 'clean_%s' % name):    #判断该字段有没有局部钩子
                value = getattr(self, 'clean_%s' % name)() #执行局部钩子并拿到返回值
                self.cleaned_data[name] = value  #把返回值放回字典中
        except ValidationError as e:
            self.add_error(name, e)       # 给该字段返回提示信息
				
		
    def _clean_form(self):
        try:
            cleaned_data = self.clean()  #内部也定义了一个空的clean方法,当有同名的全局钩子函数时,会覆盖原本定义的方法,调用全局钩子,并获取到返回值
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data

ModelForm

ModelForm组件将models.py定义的表字段 与 Form组件 的相结合,代码更为简单。
详细参考以下博客:
https://www.jianshu.com/p/3d04309445e4

app01/models.py 自定义表()

class FormUser(models.Model):
    name = models.CharField(max_length=10,verbose_name='用户名')
    pwd = models.CharField(max_length=10)
    age = models.IntegerField()
    email = models.EmailField()

定义 form组件代码

from django import forms
from django.forms import ModelForm
from app01.models import FormUser

class MyForms(ModelForm):

    confire_pwd = forms.CharField(max_length=10,min_length=6,label='确认密码')

    class Meta:
        model = FormUser
        fields = "__all__"
        # 只定义表字段的labels,上面的新增的不在这里定义。
        # 如果没有labels,会展示 models 中的 verbose_name
        labels = {
            'name': '用户名',
            'pwd': '密码',
            'email': '邮箱',
            'age': '年龄'

        }

    # 全局钩子用于校验数据
    def clean(self):
        pwd = self.cleaned_data.get('pwd','')
        confire_pwd = self.cleaned_data.get('confire_pwd','')
        print(pwd,confire_pwd)
        if pwd and (pwd != confire_pwd):
            self.add_error('confire_pwd','密码不一致')
        return self.cleaned_data

视图函数

@csrf_exempt
def formtest(request):
    form_obj=MyForms()
    if request.method == 'POST':
        print(request.POST)
        form_obj=MyForms(request.POST)

        if form_obj.is_valid():
            print(form_obj.cleaned_data)
            # 存储该条数据到数据库
            form_obj.save()
            
    return render(request,'formtest.html',locals())

html 页面 formtest.html

    <form action="" method="post" novalidate>
        {% for obj in form_obj %}
        <p>{{obj.label}}: {{obj}} <span>{{obj.errors.0}}</span></p>
        {% endfor %}
        <input type="submit">
    </form>

cookie与session

发展史:
    1. 最先开始所有网站都没有保存用户功能的需求 所有用户访问返回的结果都一样(http协议是无状态的)
		eg: 新闻、博客、文章...
	
    2. 开始出现一些需要保存用户信息/状态的网站
		eg: 购物网站、支付平台...
		
	以登陆为例,如果不保存用户登录状态 也就意味着用户每加载一次网页就需要重新登录一次, 这样的情况是不能够出现的。
	
	解决方案:
	当用户成功登录一次后,将用户的用户名、密码返回给用户浏览器,让其保存在本地,后续加载网页时浏览器自动将用户名、密码发送到服务端验证。()
	
	此种用户名密码保存到本地的方式很不安全。
	
    3. 优化:
	当用户登陆成功后,服务端产生一个随机字符串session_id(在服务端以k:v键值对的形式保存数据data,并随机生成一个与之对应的session_id,保存在服务端),session_id交由客户端浏览器保存。
	随机字符串1:用户1相关信息
	随机字符串2:用户2相关信息
	之后访问服务端时,都带着该随机字符串,服务端去数据库中比对是否有对应的随机字符串,从而获得对应的用户信息。
	
	如果你截获到了该随机字符串,还是可以冒充当前用户,所以也是有安全隐患的
	
	web领域没有绝对的安全和不安全
  • cookie:
    服务端传送的保存在客户端浏览器上的信息都可以称之为cookie,它的表现形式一般都是k:v键值对,可以有多条。

  • session:
    数据是保存在服务端的,并且它的表现形式也是键值对,可以有多条。

  • token:(扩展知识点)
    session虽然能将数据保存在服务端,但是用户很多时,架不住量大。
    而使用token,服务端无需保存数据。
    用户登录成功后,服务端将一段用户信息进行加密处理(加密算法只有该公司人员知道),再将加密后的密文与用户信息明文相拼接,返给客户端浏览器保存,浏览器下次请求时,带着该信息(明文+密文),服务端将明文截取再用加密算法得到加密字段,与浏览器带来的加密字段相比较,一样则验证通过。

服务端设置cookie后返回给客户端浏览器,浏览器存储cookie键值对,下一次访问一个地址时,会带上cookie键值对供服务端校验。

如何在浏览器上查看cookie

虽然cookie是很多网站访问必要的东西,但浏览器也可以选择拒绝保存数据在本地,那么很多需要登录网站的访问就会出问题了。
谷歌浏览器可以在 安全和隐私设置 --> cookie及其他网站数据 --> 点击你要设置的cookie选项

操作cookie

#一般返回数据只需要:

return HttpResponse()
return render()

# 当你需要操作cookie时,就需要利用返回的数据对象:

obj = HttpResponse()
#在这里操作cookie
return obj

"""
- 设置cookie
  obj.set_cookie(key,value)
  设置cookie超时时间
  obj.set_cookie(key,value,max_age=3,expires=3)

  - max_age
  - expires
    二者都能设置超时时间,都以秒为单位,
    针对IE浏览器只能用 expires,其他浏览器二者都能用。
- 获取cookie
  request.COOKIES.get(key)

- 删除cookie
  obj.delete_cookie(key)

- 设置加密cookie
  obj.set_signed_cookie('key','value',salt='666',max_age=xxx)
- 获取加密cookie
  get_cookie = request.get_signed_cookie('key',default=None,salt='666')
  # default=None必须要加上,因为如果cookie中没有'key'这个键,会报错。

"""

使用举例

使用cookie完成一个登录功能,一次登陆后访问该网站其他页面无需再登录

第一版: cookie校验基础功能实现

## urls.py
    url(r'login/',views.login),
    url(r'home/',views.home),

## views.py
def login(request):
    # post 请求对用户名密码进行判断
    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        if name =='yxf' and password == '123':
            home_obj = redirect('/home/')
            # 用户名密码校验后设置一个内容为status:login_success的cookie,跳转网站home页面
            home_obj.set_cookie('status','login_success')
            return home_obj
        else:
            return HttpResponse('用户名/密码 错误')
    # get请求直接返回登录页面
    return render(request,'login.html')

def home(request):
    # 如果请求带有status:login_success的cookie,则正常返回home页面,否则返回登录页面
    if request.COOKIES.get('status') == 'login_success':
        return render(request,'home.html')
    else:
        return redirect('/login/')

## templates
### login.html
    <form action="" method="post">
        <p>name:
            <input type="text" name="name">
        </p>
        <p>password:
            <input type="password" name="password">
        </p>
        <input type="submit" name="" id="">
    </form>
### home.html
ITs HOME PAGE 

## 效果
1. 未登录时,访问 /home/ 跳转到/login/
2. 访问 /login,登录成功后自动跳转到 /home
3. 登陆成功后,可在浏览器看到 status:login_success这个cookie,也能正常访问/home/

当有很多需要登录成功状态才能访问时,按照上面的方法需要在每个页面的视图函数中都加上cookie判断,未免太过冗余,所以 使用装饰器,让视图函数只专注于自己内部的逻辑。

版本二:校验cookie功能的装饰器引入,解决代码冗余问题,添加注销功能

## urls.py

    url(r'login/',views.login),
    url(r'home/',views.home),
    url(r'index/',views.index),
    url(r'logout/',views.logout),

## views.py

# 装饰器
def login_auth(func):
    # 如果cookie校验成功,则直接返回视图函数执行结果,否则跳转登陆页面
    def inner(request,*args,**kwargs):
        if request.COOKIES.get('status') == 'login_success':
            return func(request,*args,**kwargs)
        else:
            return redirect('/login/')
    return inner


def login(request):
    # post 请求对用户名密码进行判断
    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        if name =='yxf' and password == '123':
            home_obj = redirect('/home/')
            # 用户名密码校验后设置一个内容为status:login_success的cookie,跳转网站home页面
            home_obj.set_cookie('status','login_success')
            return home_obj
        else:
            return HttpResponse('用户名/密码 错误')
    # get请求直接返回登录页面
    return render(request,'login.html')

@login_auth  #home调用装饰器
def home(request):
        return render(request,'home.html')

@login_auth
def index(request):
    return render(request,'index.html')

@login_auth
# 注销登录
def logout(request):
    obj = redirect('/login')
    obj.delete_cookie('status')
    return obj

## 效果:
1. 未登录时,访问加了装饰器login_auth的url都跳转 /login/页面
2. 已登录时,访问/login/,跳转到/home/ 页面
3. 已登录时,访问/logout 退出登录状态,浏览器上的status:login_success cookie清除,跳转/login/

怎么做到未登录状态访问 a 页面,跳转登陆页面,登录成功后还能自动跳转 a 页面

版本三: /login/?next='/a/' 实现登陆后自动跳转需访问页面/a/

## views.py
# 装饰器
def login_auth(func):
    # 如果cookie校验成功,则直接返回视图函数执行结果,否则跳转登陆页面
    def inner(request,*args,**kwargs):
        if request.COOKIES.get('status') == 'login_success':
            return func(request,*args,**kwargs)
        else: 
            # request.get_full_path()拿到当前url的路由与参数,用于登陆后的跳转
            return redirect('/login/?next_url=%s' % request.get_full_path())
    return inner


def login(request):
    # 拿到下一跳的url,没有next_url参数,则为None
    next_url = request.GET.get('next_url')
    print(next_url)
    # post 请求对用户名密码进行判断
    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        if name =='yxf' and password == '123':
            # 如果有下一跳则跳转下一跳,没有则跳转到/home/页面
            if next_url:
                home_obj = redirect(next_url)
            else:
                home_obj = redirect('/home/')
            # 用户名密码校验后设置一个内容为status:login_success的cookie,跳转网站home页面
            home_obj.set_cookie('status','login_success')
            return home_obj
        else:
            return HttpResponse('用户名/密码 错误')
    # get请求直接返回登录页面
    return render(request,'login.html')

@login_auth  #home调用装饰器
def home(request):
        return render(request,'home.html')

@login_auth
def index(request):
    return render(request,'index.html')

@login_auth
# 注销登录
def logout(request):
    obj = redirect('/login')
    obj.delete_cookie('status')
    return obj

## 效果:
1. 未登录时,访问/xxx页面,跳转登录页面登录后,自动跳转 /xxx
2. 未登录时,直接访问登录页面,登录后默认跳转/home/ 页面

session

session数据保存在服务端,给客户端发送的是session数据对应的随机生成的字符串session_id。

- 设置session
  request.session['key'] = value

- 获取session
  request.session.get('key')

- 设置过期时间
  request.session.set_expiry()
  括号内能放四种类型参数:
    1. 整数                 多少秒
    2. datetime/timedelta 格式日期对象   到指定日期就失效
    3. 0                    一旦浏览器窗口都关闭就失效(苹果电脑还要后台退出浏览器才失效)
    4. 不写None             取决于django 内部默认的失效时间

- 清除session:
  request.session.delete()  # 只删客户端的,服务端不删
  request.session.flush()   # 浏览器和服务端session数据都清空(推荐)

session数据默认存储在数据库的django_session表中,所以需要执行两条migrate数据库生成命令,不用写在models.py中,django会自动生成该表。

django默认session过期时间为14天,你也可以人为修改。
过期时间为django_session表的expire_date字段,是一个具体的时间点,每一次访问都会刷新过期时间。
即这个14天是指最后一次访问过后,14天内未访问就会过期。

同一个计算机(ip)上同一个浏览器只会有一条session数据生效。
(当session过期是可能会出现同一个ip的同一个浏览器对应多条session数据,但内部会自动识别过期数据并清除)

# django_session表除了存储session,你也可以考虑将多个视图函数都需要用到的数据存储在这张表中,供后续使用

设置/获取session的简单展示

# 设置session

# urls.py
url(r'set_session/',views.set_session),

# views.py
def set_session(request):
    # 生成一条session,其中有两个键值对数据
    request.session['status'] = 'success'
    request.session['name'] = 'yxf'
    # request.session.set_expiry(0)  # 设置session浏览器窗口都关闭就过期
    return HttpResponse('set session success!')
    '''
    内部发生的事:
    1. django内部自动生成一个随机字符串
    2. 将这个随机字符串 与 对应的数据(两个键值对)存在django_session表中
       先在内存中产生操作数据库的缓存,在响应中间件的时候才操作数据库
    3. 将随机字符串返回给客户端保存
    '''

# 效果
访问http://127.0.0.1:8000/set_session/可以看到一个name为sessionid的cookie,如下图:

# 获取、删除session

# urls.py
url(r'get_session/',views.get_session),
url(r'del_session/',views.del_session),

# views.py
def get_session(request):
    session_status = request.session.get('status') #返回status对应的value 'success'
    if session_status:
        print(session_status)  #打印status对应的value 'success'
        return HttpResponse('get session success!')
    else:
        return HttpResponse('get session fail!')
    '''
    内部发生的事:
    1. 从浏览器获取sessionid对应的随机字符串
    2. 去数据库寻找与随机字符串匹配数据
    3. 如果匹配上,则将对应的数据(session key:value)以字典的形式封装到request.session中,
       如果未匹配上,则request.session.get('xxx') 返回 None
    '''

def del_session(request):
    # 删除浏览器(客户端)与服务端的session数据
    request.session.flush()
    return HttpResponse('delete session success')

使用session实现登陆需求

点击查看代码
# urls.py

    url(r'^admin/', admin.site.urls),
    url(r'login/',views.login),
    url(r'home/',views.home),
    url(r'index/',views.index),
    url(r'logout/',views.logout),

## views.py

#装饰器 用来实现session校验功能
def login_auth(func):
    def inner(request,*args,**kwargs):
        if request.session.get('status'):
            return func(request,*args,**kwargs)
        else: 
            return redirect('/login/?next_url=%s' % request.get_full_path())
    return inner


def login(request):
    # 拿到下一跳的url,没有next_url参数,则为None
    next_url = request.GET.get('next_url')
    print(next_url)
    # post 请求对用户名密码进行判断
    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        if name =='yxf' and password == '123':
            # 如果有下一跳则跳转下一跳,没有则跳转到/home/页面
            if next_url:
                home_obj = redirect(next_url)
            else:
                home_obj = redirect('/home/')
            # 用户名密码校验后设置一个session,跳转网站home页面
            request.session['status'] = 'login_success'
            return home_obj
        else:
            return HttpResponse('用户名/密码 错误')
    # get请求直接返回登录页面
    return render(request,'login.html')

@login_auth  #home调用装饰器
def home(request):
        return render(request,'home.html')

@login_auth
def index(request):
    return render(request,'index.html')

@login_auth
# 注销登录
def logout(request):
    # 删除session
    request.session.flush()
    return redirect('/login')

## templates

#login.html

    <form action="" method="post">
        <p>name:
            <input type="text" name="name">
        </p>
        <p>password:
            <input type="password" name="password">
        </p>
        <input type="submit" name="" id="">
    </form>

#home.html
in home page

# index.html
in indexpage

Django中的Session配置

Django中默认将session存放在django_session表中,也可以通过添加配置,存放在其他地方(文件、缓存、其他)。

点击查看
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)session数据存在数据库中

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎  session数据存在缓存中
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎  session数据存在文件中
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其他公用设置项:
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

CBV中添加装饰器

不建议直接使用 @装饰器名 的方式给CBV中方法添加装饰器。

这里提供三种方式:

#装饰器
def login_auth(func):
  ...

from django.utils.decorators import method_decorator

##方式一: #哪个方法要装饰器就在上面添加 @method_decorator(login_auth)

class My_page(View):

    @method_decorator(login_auth)   
    def get(self,request):
        return HttpResponse('IN get')

    @method_decorator(login_auth)
    def post(self,request):
        return HttpResponse('IN post')

##方式二:在类的上面添加装饰器与对应的方法名

@method_decorator(login_auth,name='get')
@method_decorator(login_auth,name='post')
class My_page(View):
    def get(self,request):
        return HttpResponse('IN get')

    def post(self,request):
        return HttpResponse('IN post')

##方式三: 使用dispatch方法给类中所有方法都添加装饰器

class My_page(View):
    @method_decorator(login_auth)
    def dispatch(self, request, *args, **kwargs):
        return super.dispatch(request, *args, **kwargs)

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

    def post(self,request):
        return HttpResponse('IN post')

django中间件

django自带7个中间件,每个都有对应的功能,它还支持自定义中间件。
当你要实现涉及到全局相关的功能时,就可以考虑使用中间件。
例如:
- 全局用户身份校验(白名单,黑名单)
- 全局用户权限校验(普通用户,VIP用户)
- 全局用户访问频率校验(频率过高很可能是爬虫程序)

#django默认的7个中间件

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',
]

中间件配置注释掉某一行就相当于关闭那个中间件。
'django.middleware.security.SecurityMiddleware',  就相当于:

from django.middleware.security import SecurityMiddleware

查看这些中间件源代码就能发现普遍都有 process_request 与 process_response方法。
说明中间件有一些固定方法可供使用。

自定义中间件

1. 在项目根目录或应用下创建一个任意名称文件夹
2. 在该文件夹下创建一个任意名称py文件
3. 在py文件内定义类(这个类必须继承MiddlewareMixin)
   然后在类中就可以使用django提供的5个自定义方法。(需要用哪些方法就写哪些)
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',
    '自己写的中间件路径2',
]

'''
django支持程序员自定义中间件,并提供5个可以自定义的方法
1. 必须掌握:
   process_request
       1. 请求来的时候需要经过每一个中间件里的process_request方法
          执行顺序是按照配置文件中注册中间件的顺序 从上往下依次执行
       2. 如果某中间件中没有定义该方法,则直接跳过,执行下一个中间件
       3. 如果该方法返回了HttpResponse对象,那么请求将不再往下执行,而是原路返回
          (此点可以实现全局校验失败,不允许访问的功能)
          process_request方法就是用来实现全局相关的所有限制功能

   process_response
       1. 响应走的时候需要经过每一个中间件里的process_response方法,该方法有两个额外参数 request,response
          执行顺序是按照配置文件中注册中间件的顺序 从下往上依次执行
       2. 如果某中间件中没有定义该方法,则直接跳过,执行下一个中间件
       3. 该方法必须返回一个HttpResponse对象
           1)可以直接返回形参接收的response
           2) 你也可以根据需求自定义返回内容

   如果在第一个中间件的process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有中间件的process_response,还是?
   在flask框架中,响应走的时候是经过所有中间件的process_response,即只要返回了数据就必须经过所有中间件的类似于process_response的方法;
   在django框架中,会从返回了HttpResponse对象的中间件开始(包括),往回执行process_response。

2. 了解:
  process_view
      1. 在路由匹配成功之后,执行视图函数之前,会自动执行此方法
         执行顺序是按照配置文件中注册中间件的顺序 从上往下依次执行
      2. 此方法有4个参数,可以直接写成 def process_view(self, request, *args, **kwargs):
         (需到用到此方法再研究每个参数)
      3.  此方法如果返回None/没有返回值,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图;
          如果它返回一个HttpResponse对象,那么将不会执行Django的视图函数,而是直接在中间件中掉头,倒叙执行一个个process_response方法,最后返回给浏览器

  process_template_response
      1. 在视图函数执行完成后立即执行,但有个前提,返回的HttpResponse对象有render属性时才会触发
         执行顺序是按照配置文件中注册中间件的顺序 从下往上依次执行
      2. 执行了所有的process_template_response后,还会触发HttpResponse对象的render方法的执行。

  process_exception
      1. 当视图函数抛出异常时才会触发process_exception
         执行顺序是按照配置文件中注册中间件的顺序 从下往上依次执行
      2. 三个参数 def process_exception(self,request,exception): exception为异常信息
'''

自定义中间件方法探索

1. 先创建一个普通的路由
#urls.py
    url(r'index/',views.index),
#views.py
def index(request):
    print('index 视图函数中')
    return HttpResponse('index')

2. 创建自定义中间件
创建文件(app01为应用目录) app01\my_midware\my_mdware.py

#自定义中间件的代码 app01\my_midware\my_mdware.py
from django.utils.deprecation import MiddlewareMixin

class Mymidware1(MiddlewareMixin):
    pass

class Mymidware2(MiddlewareMixin): 
    pass

3. settings.py中注册中间件
MIDDLEWARE = [
    ...
    'app01.my_midware.my_mdware.Mymidware1',
    'app01.my_midware.my_mdware.Mymidware2',
]

process_request与process_response

class Mymidware1(MiddlewareMixin):
    def process_request(self, request):
        print('in Mymidware1 process_request')
    def process_response(self, request, response):
        print('in Mymidware1 process_response')
        # 必须返回HttpResponse对象
        return response

class Mymidware2(MiddlewareMixin): 
    def process_request(self, request):
        print('in Mymidware2 process_request')
    def process_response(self, request, response):
        print('in Mymidware2 process_response')
        return response

访问 /index

自定义中间件的process_request方法返回HttpResponse对象

process_template_response

class Mymidware1(MiddlewareMixin):
    def process_request(self, request):
        print('in Mymidware1 process_request')
        # return HttpResponse("Mymidware1 response")
    def process_response(self, request, response):
        print('in Mymidware1 process_response')
        return response
    def process_template_response(self,request,response):
        print('in Mymidware1 process_template_respons')
        return response

class Mymidware2(MiddlewareMixin): 
    def process_request(self, request):
        print('in Mymidware2 process_request')
    def process_response(self, request, response):
        print('in Mymidware2 process_response')
        return response
    def process_template_response(self,request,response):
        print('in Mymidware2 process_template_respons')
        return response

#views.py
# 视图函数必须有一个render方法,并给HttpResponse 封装为render属性,才能触发process_template_response

def index(request):
    print('index 视图函数中')
    def render():
        print("in render")
        return HttpResponse('index page')
    obj = HttpResponse()
    obj.render = render
    return obj

process_exception

视图函数抛出异常时触发

csrf中间件

之前当需要向后端提交post请求时都要注释掉 'django.middleware.csrf.CsrfViewMiddleware', 那么这个csrf中间件究竟是干嘛的呢?

跨站请求伪造案例

钓鱼网站:
我搭建一个和正规xx银行网站一样界面的钓鱼网站
用户不小心进入了我的网站,给某个用户aaa打钱
打钱的操作确实发生,用户的钱也扣了,但不一样的地方是:
用户aaa没收到钱,钱到了我自定义的账户

钓鱼网站的本质:
钓鱼网站的页面中,针对收钱账户的input标签没有name属性,
我自己定义了一个隐藏的有name和value的input标签,提交到银行时,收款账户信息是隐藏标签的内容。  

钓鱼网站代码实现

#正规网页:http://127.0.0.1:8000/bank/

#正规网页代码 (注释掉crsf校验)
#urls.py
url(r'bank/',views.banck),  #正规银行页面
#views.py
def bank(request):
    if request.method == 'POST':
        master_account = request.POST.get('master_account')
        receipt_account = request.POST.get('receipt_account')
        money = request.POST.get('money')
        print('%s 给 %s 转了 %s 元' % (master_account,receipt_account,money))
    return render(request,'bank.html')
#bank.html
    <form action="" method="post">
        <p>你的账户:<input type="text" name="master_account"></p>
        <p>收款账户:<input type="text" name="receipt_account"></p>
        <p>转账数额:<input type="text" name="money"></p>
        <input type="submit">
    </form>

正规操作效果:

#钓鱼网页:http://127.0.0.1:8001/fish_bank/

#钓鱼网页代码 (注释掉crsf校验)
#urls.py
url(r'fish_bank/',views.fish_banck),  #钓鱼网页页面
#views.py
def fish_bank(request):
    return render(request,'fish_bank.html')
#fish_bank.html
    <!-- 将信息提交到正规网站 -->
    <form action="http://127.0.0.1:8000/banck/" method="post" >
        <p>你的账户:<input type="text" name="master_account"></p>
        <!-- 用户输入收款账户的input框没有name属性,所以这个没有传值 -->
        <p>收款账户:<input type="text"></p>
        <!-- 这是隐藏的自己的收款账户信息 -->
        <p><input type="hidden" name="receipt_account" value="yxf"></p>
        <p>转账数额:<input type="text" name="money"></p>
        <input type="submit">
    </form>

钓鱼网站操作效果

from表单使用csrf功能

如何规避上述钓鱼网站问题?
用csrf跨站请求伪造校验
    打开csrf功能后,网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识
    当这个页面朝后端发送post请求时,后端会先校验唯一标识,如果校验失败,会返回403,校验成功,则正常走逻辑。

form表单使用csrf功能:

1. 打开csrf中间件的注释: 'django.middleware.csrf.CsrfViewMiddleware',
2. 在form表单页码加载csrf_token
    <form action="" method="post">
        {% csrf_token %}
        <p>你的账户:<input type="text" name="master_account"></p>
        <p>收款账户:<input type="text" name="receipt_account"></p>
        <p>转账数额:<input type="text" name="money"></p>
        <input type="submit">
    </form>

正规网站打开csrf功能后,可以看到多了一行token的代码,而钓鱼网站提交数据也显示403.

ajax使用csrf校验功能

ajax 传递csrf校验键值对的三种方式:

方式一:

    <!-- 此种方法需要必须加载csrf_token,因为下面js代码会获取这个标签的值 -->
    {% csrf_token %}
    <input type="button" id="b1" value="click">
    <script>
        $('#b1').click(
        function(){
            $.ajax({
                url:'',
                type:'post',
                // 第一种 利用标签查找获取页面上的随机字符串
                // data:{'name':'yxf','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
                success:function(){}
            })
        }
        )
    </script>

方式二:

    <!-- 这里的csrf_token直接在模板语法中加载 -->

    <input type="button" id="b1" value="click">
    <script>
        $('#b1').click(
        function(){
            $.ajax({
                url:'',
                type:'post',
                // 第二种 利用模板提供的快捷书写
                data:{'name':'yxf','csrfmiddlewaretoken':'{{ csrf_token }}'},
                success:function(){}
            })
        }
        )
    </script>

方式三(推荐使用)

将以下代码放到静态文件夹下的 js/mysetup.js文件中

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // 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);
      }
    }
  });

在html页面中加载该文件,即可生效

    <!-- 第三种 加载js文件 -->
    {% load static %}
    <script src="{% static '/js/mysetup.js' %}"></script>

    <input type="button" id="b1" value="click">

    <script>
        $('#b1').click(
        function(){
            $.ajax({
                url:'',
                type:'post',
                data:{'name':'yxf'},
                success:function(){}
            })
        }
        )
    </script>

csrf相关装饰器

对于以下需求:

  • 网站整体不校验csrf,就个别视图函数需要校验
  • 网站整体校验csrf,就个别视图函数不需要校验

可以用到csrf的两个装饰器

csrf_protect 需要校验
csrf_exempt 不用校验

只需要将装饰器加到对应的视图 函数/类 上,就可以实现 打开/关闭 校验。

FBV两个装饰器的使用

####csrf功能全局关闭,csrf_protect实现个别开启csrf验证####

#settings.py中注释掉csrf
# 'django.middleware.csrf.CsrfViewMiddleware',

# 视图函数添加csrf_protect装饰器
from django.views.decorators.csrf import csrf_exempt,csrf_protect

@csrf_protect
def banck(request):
    if request.method == 'POST':
        pass
    return render(request,'bank.html')

####csrf功能全局开启,csrf_exempt实现个别关闭csrf验证####

#settings.py中开启csrf
'django.middleware.csrf.CsrfViewMiddleware',

# 视图函数添加csrf_exempt装饰器
from django.views.decorators.csrf import csrf_exempt,csrf_protect

@csrf_exempt
def banck(request):
    if request.method == 'POST':
        pass
    return render(request,'bank.html')

CBV两个装饰器的使用

CBV添加装饰的三种方式

对于csrf_protect,使用CBV添加装饰器的三种方法任意一种添加就可以

而csrf_exempt只能用第三张 添加到dispath函数上的方式,其他两种不生效

**基于django中间件的编程思想总结

现在有一个用多种媒介发送通知的需求:
写一个能使用wechat、qq、email发送通知的程序,方便用户使用

一般情况下我们是这样操作的:

在一个文件中定义多个函数对应多种通知方式,然后导入这个模块,调用对应的方法发送信息。

# send_type.py  定义各种方式的发送操作对应的逻辑
def wechat(content):
    print("send wechat message:%s" % content)

def qq(content):
    print("send qq message:%s" % content)

def email(content):
    print("send email message:%s" % content)

# send.py 在这个文件发送信息
import send_type

def send(content):
    send_type.wechat(content)
    send_type.qq(content)
    send_type.email(content)

if __name__ == "__main__":
    send("hello")

基于django中间件的编程思想,可以做如下优化:

1. 将发送程序封装为一个程序包,以面向对象的方式编写各种发送类型逻辑
2. 各种发送类型互为鸭子类型,需要有相同的入口,send()
3. 为了让用户更方便使用本程序,需要创建一个配置文件,里面以字符串的形式加载每种发送方式,关闭某种方式,直接注释即可
4. 包中在__init__.py 使用importlib,以字符串的形式加载每一个发送类
5. __init__.py中定义统一的入口,send_all(),用户直接调用此函数即可

优点:
代码层次清晰,使用时需要写的 代码很少

程序包中的文件,后续其他发送方式直接在包中创建新的文件,写好类即可

# __init__.py文件

import settings
import importlib

def send_all(content):
    for path_str in settings.NOTIFY_LIST:
        module_path,class_name = path_str.rsplit('.',maxsplit=1)
        # 利用字符串导入模块
        module = importlib.import_module(module_path)
        # 利用反射获取类名
        cls = getattr(module,class_name)
        # 生成类的对象
        obj = cls()
        # 调用鸭子类型都有的send()方法
        obj.send(content)

# email.py 文件

class Email:
    def __init__(self):
        pass

    def send(self,content):
        print("email msg: %s" % content)

# qq.py 文件

class QQ:
    def __init__(self):
        pass

    def send(self,content):
        print("qq msg: %s" % content)

# wechat.py 文件

class Wechat:
    def __init__(self):
        pass

    def send(self,content):
        print("wechat msg: %s" % content)

调用程序包发送信息,只需要一个配置文件,一个主程序文件,如果不需要某种发送方式,在配置文件中注释即可。

# settings.py 文件
NOTIFY_LIST = [
    'notify.email.Email',
    'notify.wechat.Wechat',
    'notify.qq.QQ',
]

#send.py 文件

import notify

notify.send_all('hello')

Auth模块

Auth模块是Django自带的用户认证模块。

在开发一个网站的时候,无可避免的需要设计实现网站的用户系统,包括用户注册、用户登录、用户认证、注销、修改密码等功能,django自带的Auth模块能帮我们很轻松的实现这些功能。在执行了数据库迁移命令后,django会默认创建一张 auth_user 的用户表,用来存储用户信息。

在执行了数据库迁移命令后,生成的auth_user中是没有用户的,可以执行一条命令,生成一个超级用户:

python manage.py createsuperuser

后续会依赖于 auth_user表完成用户相关的所有功能。

#auth使用指南

1. 比对用户名 与 密码是否正确
user_obj = auth.authenticate(request,username=username,password=password) #必须同时传入用户名与密码
print('user_obj',user_obj)          # 用户表匹配上了返回的是用户对象,未匹配上返回None。这里输出了用户名,是因为内部代码定义了__str__
# if user_obj:   # 如果没匹配上返回None,user_obj.username会报错
#     print(user_obj.username) #huandada
#     print(user_obj.password) #加密后的密码

2. 保存用户登陆状态
auth.login(request,user_obj) #类似于request.session[key] = user_obj

3. login_required装饰器
login_required装饰器,用来快捷的给某个视图添加登录校验
加上此装饰器之后,用户只有登陆状态才能访问此函数,否则跳转到定义的login_url
两种配置方式:
    a. 局部配置 @login_required(login_url='/login')  没有登录将跳转到login_url
    b. 全局配置
        在配置文件中添加 LOGIN_URL = '/login'
        调用时直接 @login_required
    优先级: 局部 > 全局

4. request.user 获取用户对象
自动将session表里查找到的用户对象封装在request.user中,打印用户名
如果没有登录的对象,将返回 AnonymousUser
request.user

5. 判断该用户是否为登陆状态 返回布尔值
request.user.is_authenticated

6. 判断传入的密码是否正确,返回布尔值
is_right = request.user.check_password(old_password)

7. 修改密码
request.user.set_password(new_password) #修改用户对象的属性
request.user.save()  # 将修改提交到数据库

8. 注销登录
auth.logout(request)  #相当于 request.session.flush()

9. 用户注册
# 使用create,录入到表中的密码是明文的,所以不行
# User.objects.create(username=username,password=password) 
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户,这种创建方式必须要有email,一般不在这里创建
User.objects.create_superuser(username=username,password=password,email=email)

使用演示: 一套包含基础功能的用户系统

# urls.py
urlpatterns = [
    url(r'^admin/', admin.site.urls),

    #登录功能
    url(r'^login/', views.login),

    # 校验用户是否登录
    url(r'^index/', views.index),
    url(r'^home/', views.home),

    #修改密码
    url(r'^set_password/', views.set_password),
    url(r'^logout/', views.logout),

    # 注册功能
    url(r'^register/', views.register),
]
# views.py
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.contrib import auth
from django.contrib.auth.decorators import login_required
# Create your views here.
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        # 1. 比对用户名 与 密码是否正确
        user_obj = auth.authenticate(request,username=username,password=password)
        # print(user_obj)          ## 输出了用户名,是因为内部代码定义了__str__
        # if user_obj:
        #     print(user_obj.username) #huandada
        #     print(user_obj.password) #加密后的密码
        '''
        1. auth.authenticate() 必须同时传入用户名与密码
        2. 用户表比对上了返回用户对象,未比对上返回None

        这里做了两件事:
            a. 自动查找auth_user表
            b. 自动给前端传入的明文密码加密再对比
        '''
        # 2. 保存用户登陆状态
        if user_obj:
            auth.login(request,user_obj) #类似于request.session[key] = user_obj
            '''
            执行了此方法后:
            1. 可以在任何地方(前后端)通过request.user获取到当前登录的用户对象
            2. 内部自动生成一条session,存在django_session表中
            '''
            return redirect('/home/')
    return render(request,'login.html')

# 3. login_required装饰器
'''
login_required装饰器,用来快捷的给某个视图添加登录校验
加上此装饰器之后,用户只有登陆状态才能访问此函数,否则跳转到定义的login_url
两种配置方式:
    1. 局部配置 @login_required(login_url='/login')  没有登录将跳转到login_url
    2. 全局配置
        在配置文件中添加 LOGIN_URL = '/login'
        调用时直接 @login_required
    优先级: 局部 > 全局
'''
# @login_required(login_url='/login')  #局部配置
@login_required                        #全局配置
def home(request):
    # 4. request.user 获取用户对象
    # 自动将session表里查找到的用户对象封装在request.user中
    # 如果没有登录的对象,将返回 AnonymousUser
    print(request.user) 
    
    # 5. 判断该用户是否为登陆状态 返回布尔值
    print(request.user.is_authenticated) # CallableBool(True)
    return HttpResponse('in home')

@login_required
def index(request):
    return HttpResponse('in index')

@login_required
def set_password(request):
    if request.method == 'POST':
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')

         # 6. 判断传入的密码是否正确,返回布尔值
        is_right = request.user.check_password(old_password)

        if new_password == confirm_password and is_right:
            # 7. 修改密码
            request.user.set_password(new_password) #修改用户对象的属性
            request.user.save()  # 将修改提交到数据库
            return redirect('/login/')
        else:
            return HttpResponse('输入错误,请重新输入')

    return render(request,'set_password.html',locals())

@login_required
def logout(request):
    # 8. 注销登录
    auth.logout(request)  #相当于 request.session.flush()
    return redirect('/login')

# 这是用户表
from django.contrib.auth.models import User
def register(request):
    if request.user.is_authenticated:
        return HttpResponse('您已是本站用户')
    else:
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
            confirm_password = request.POST.get('confirm_password')
            email = request.POST.get('email')
            if password == confirm_password:
                # 9. 用户注册
                #这样操作录入到表中的密码是明文的,所以不行
                # User.objects.create(username=username,password=password) 
                #创建普通用户
                User.objects.create_user(username=username,password=password)
                #创建超级用户,这种创建方式必须要有email,一般不在这里创建
                # User.objects.create_superuser(username=username,password=password,email=email)
                return HttpResponse('注册成功')
            else:
                return HttpResponse('输入错误')
        return render(request,'register.html')
# login.html
    <h2>登陆页面</h2>
    <form action="" method="post">
        {% csrf_token %}
        <p>name:<input type="text" name="username"></p>
        <p>password:<input type="password" name="password"></p>
        <input type="submit">
    </form>

# set_password.html
    <h2>修改密码</h2>
    <form action="" method="post">
        {% csrf_token %}
        <!-- 用户名只做展示效果,不能更改 -->
        <p>name:<input type="text" name="username" disabled value="{{ request.user.username }}"></p>
        <p>old_password:<input type="text" name="old_password"></p>
        <p>new_password:<input type="text" name="new_password"></p>
        <p>confirm_password:<input type="text" name="confirm_password"></p>
        <input type="submit">
    </form>

# register.html
    <h2>注册页面</h2>
    <form action="" method="post">
        {% csrf_token %}
        <p>name:<input type="text" name="username"></p>
        <p>password:<input type="password" name="password"></p>
        <p>confirm_password:<input type="password" name="confirm_password"></p>
        <p>email:<input type="email" name="email"></p>
        <input type="submit">
    </form>

扩展auth_user表
因为它自动创建的auth_user表的字段是固定的,那么如何对用户信息进行扩展呢?

有两种方式

方式一(不推荐):新建一张表,一对一的关系外键到用户表

from django.db import models
from django.contrib.auth.models import User

class UserDetail(models.Model):
    phone = models.CharField(max_length=11)
    user = models.OneToOneField(to=User)


方式二(推荐):面向对象的继承

from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.

class UserInfo(AbstractUser):
    phone = models.CharField(max_length=11)

'''
如果继承了AbstractUser,那么在执行数据库迁移命令时auth_user表就不会创建出来了
这里自定义的UserInfo表会出现auth_user表的所有字段,外加自己定义的字段

注意继承的是 AbstractUser,不是AbstractBaseUser

此方法使用前提:
    1. 在继承之前没执行数据库迁移命令
        即auth_user表未被创建,若已存在,则换个新库
    2. 继承的类里面自定义字段不要覆盖 AbstractUser 里的字段名
    3. 需要在配置文件中告诉django你要用UserInfo替代auth_user
        AUTH_USER_MODEL = "app01.UserInfo"  #应用名.表名
'''
posted @ 2022-01-07 18:37  huandada  阅读(173)  评论(0编辑  收藏  举报