django框架

目录

一.django框架

1.django框架推导流程

1)纯手撸web框架

web框架本质:
理解1:连接前端与数据库的中间介质
理解2:socket服务端

image

(1)搭建socket服务端
import socket

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    sock, addr = server.accept() #等待连接
    data = sock.recv(1024) #接收字节
    # 此处一会儿需要按照步骤2、3做修改
    sock.send(b'hello')
(2)浏览器发送请求

image

#发现请求失败
#原因是服务端响应的数据需符合HTTP响应格式

#解决:发的消息前加上响应格式
sock.send(b'HTTP1.1 200 OK\r\n\r\n hello')
(3)路由对应响应

1.想在页面后面加不同的后缀返回不同的页面内容

#想办法找到用户输入的后缀
sock, addr = server.accept()  # 等待连接
data = sock.recv(1024)  # 接收1024字节
print(data.decode('utf8'))  # 解码打印(可以获取到大一堆请求数据格式)

image

# 请求数据格式中的【请求首行】:
GET /index HTTP/1.1

"""
GET请求    朝别人索要数据
POST请求   朝别人提交数据
"""

2.处理请求数据格式中的请求首行得到网址后缀

#分析获取到的数据格式,通过空格切割区索引1得到对应的网址后缀
target_url=data.decode('utf8').split(' ')[1]

image

3.通过该后缀可以返回不同的页面数据

#通过if判断来操作
if target_url == '/index':
    sock.send(b'hello index')
elif target_url == '/login':
    sock.send(b'hello login')
else:
    sock.send(b'404 not found')

image

虽然上面的代码实现了不同后缀返回不同页面,但是有几个缺陷!

1.socket代码过于繁琐且重复
2.针对请求数据格式处理过于繁琐
3.后缀名匹配逻辑过于没水准

#解决方法:采用wsgire模块来搭建!

2)基于wsgiref模块搭建web框架

很多web框架底层使用的模块

功能1:封装了socket代码
功能2:处理了请求数据格式
from wsgiref.simple_server import make_server

def run(request, response):
    """
    :param request: 请求相关数据
    :param response: 响应相关数据
    :return: 返回给客户端的真实数据
    """
    response('200 OK', [])  # 固定格式(响应头等数据)
    # print(request)  # 请求数据格式(被处理成字典格式)
    return [b'hello']

if __name__ == '__main__':
    # 实时监听地址,当有请求过来自动给第三个三处加括号并传参调用
    server = make_server('127.0.0.1', 8080, run)
    # 启动服务端
    server.serve_forever()

image

1.根据不同网址后缀打开不同页面

1.固定代码启动服务端
2.查看处理之后的request大字典
3.根据不同的网址后缀返回不同的内容(大字典中的键值对)
4.立刻解决了上述纯手撸问题
5.针对后缀名匹配逻辑过于没水准采用函数封装
from wsgiref.simple_server import make_server

def run(request, response):
    """
    :param request: 请求相关数据
    :param response: 响应相关数据
    :return: 返回给客户端的真实数据
    """
    response('200 OK', [])  # 固定格式(响应头等数据)
    # print(request)  # 请求数据格式(被处理成字典格式)
    path_info = request.get('PATH_INFO')  # 获取大字典中的值(后缀名)
    if path_info == '/index':
        return [b'index']
    elif path_info == 'login':
        return [b'login']
    else:
        return [b'404 not found']

if __name__ == '__main__':
    # 实时监听地址,当有请求过来自动给第三个三处加括号并传参调用
    server = make_server('127.0.0.1', 8080, run)
    # 启动服务端
    server.serve_forever()

3)代码封装优化

初步优化

1.针对后缀名匹配逻辑过于没水准采用函数封装
2.每个后缀匹配成功后执行的代码有多有少
	面条版	函数版
3.将分支的代码封装成一个个函数
4.将网址后缀与函数名做对应关系
5.获取网址后缀循环匹配
6.如果想新增功能只需要先写函数再添加一个对应关系即可
from wsgiref.simple_server import make_server

def index_func():
    return 'index'

def login_func():
    return 'login'

def errors_func():
    return '404 not found'

urls = [
    ('/index', index_func),
    ('/login', index_func)
]

def run(request, response):
    response('200 OK', [])  # 固定格式(响应头等数据)
    path_info = request.get('PATH_INFO')  # 获取大字典中的值(后缀名)
    func_name = None  # 用来存储匹配成功后的函数名
    for i in urls:  # ('/后缀',函数名)
        if path_info == i[0]:
            func_name = i[1]
            break
    if func_name:
        res = func_name()
    else:
        res = errors_func()
    return [res.encode('utf8')]

if __name__ == '__main__':
    # 实时监听地址,当有请求过来自动给第三个三处加括号并传参调用
    server = make_server('127.0.0.1', 8080, run)
    # 启动服务端
    server.serve_forever()

继续优化

7.根据不同的功能拆分成不同的py文件
	views.py		存储核心业务逻辑(功能函数)
 	urls.py			存储网址后缀与函数名对应关系
 	templates目录	   存储html页面文件
8.为了使函数体代码中业务逻辑有更多的数据可用
	'将request大字典转手传给这个函数(可用不用但是不能没有)''
#【views.py】

def index_func(request):
    with open(r'templates/index1.html', 'r', encoding='utf8')as f:
        return f.read()

def login_func(request):
    return 'login'

def errors_func(request):
    return '404 not found'
#【urls.py】

from views import *

urls = [
    ('/index', index_func),
    ('/login', login_func)
]
#【启动文件】

from wsgiref.simple_server import make_server
from urls import urls
from views import *

def run(request, response):
    response('200 OK', [])  # 固定格式(响应头等数据)
    path_info = request.get('PATH_INFO')  # 获取大字典中的值(后缀名)
    func_name = None  # 用来存储匹配成功后的函数名
    for i in urls:  # ('/后缀',函数名)
        if path_info == i[0]:
            func_name = i[1]
            break
    if func_name:
        res = func_name(request)
    else:
        res = errors_func(request)
    return [res.encode('utf8')]

if __name__ == '__main__':
    # 实时监听地址,当有请求过来自动给第三个三处加括号并传参调用
    server = make_server('127.0.0.1', 8080, run)
    # 启动服务端
    server.serve_forever()

4)动静态网页

动静态网页不是看网页动不动,而是看数据来源是动态获取还是写死的!!!

动态网页
页面上的数据不是全部写死的,有些是动态获取(后端传入)
静态网页
页面上的数据直接写死,想修改只能修改代码

  • 需求:访问某个网站后缀,后端代码获取当前时间并将当前时间在浏览器上展示

读取html内容(字符串类型),然后利用字符串替换 返回给浏览器

#【views.py】

def get_time_func(request):
    import time
    # 获取当前时间
    ctime = time.strftime('%Y-%m-%d %X')
    with open(r'templates/get_time.html', 'r', encoding='utf8')as f:
        data = f.read()
    # 利用字符串替换把html页面上随便写的文字替换成当前时间
    res = data.replace('TIME', ctime)
    return res
#【urls.py】

from views import *

urls = [
    ('/get_time',get_time_func),
]
<!--【templates目录中的get_time.html】-->

<body>
    <h1>展示当前时间:TIME</h1>#TIME会被替换成当前时间
</body>
#【启动文件】

from wsgiref.simple_server import make_server
from urls import urls
from views import *

def run(request, response):
    response('200 OK', [])  # 固定格式(响应头等数据)
    path_info = request.get('PATH_INFO')  # 获取大字典中的值(后缀名)
    func_name = None  # 用来存储匹配成功后的函数名
    for i in urls:  # ('/后缀',函数名)
        if path_info == i[0]:
            func_name = i[1]
            break
    if func_name:
        res = func_name(request)
    else:
        res = errors_func(request)
    return [res.encode('utf8')]

if __name__ == '__main__':
    # 实时监听地址,当有请求过来自动给第三个三处加括号并传参调用
    server = make_server('127.0.0.1', 8080, run)
    # 启动服务端
    server.serve_forever()
  • 需求:以上是把字符串替换传递给页面,如何把字典传递给页面且可以通过类似后端的操作方式去操作该数据?
# 需用模板语法>>jinja2模块

5)jinja2模板语法

jinja2能够让我们在html文件内使用类似后端的语法来操作各种数据

(1)jinja2下载
jinja2是第三方模块,需先下载

# cmd命令行终端:
pip3.8 install jinja2

# pycharm:
Jinja2 3.1.2
(2)jinja2使用
#【views.py】:

from jinja2 import Template

def get_dict_func(request):
    d1 = {'name': 'jason', 'age': 18,'l1':[11,22,33]}
    with open(r'templates/get_dict.html', 'r', encoding='utf8')as f:
        data = f.read()
    # 把页面数据交给模板方法处理
    temp_obj = Template(data)  
    # 给页面传递一个键是dict,值是上面字典名称的字典过去
    res = temp_obj.render({'dict': d1})  
    return res

'这样页面上就可以通过'dict'操作d1字典'
——————————————————————————————————————————————————————

#页面上就可以用以下方法去操作页面:

    <h1>{{ dict }}</h1>
    <p>{{ dict.name }}</p>
    <p>{{ dict['age'] }}</p>
    <p>{{ dict.get('age') }}</p>
    
#还可以用for循环 如以下6)中例子
{% for i in user_list %}
    <tr>
        <td>{{ i.id }}</td>
        <td>{{ i.name }}</td>
        <td>{{ i.age }}</td>
    </tr>
{% endfor %}

image

6)前端、后端、数据库三者联动

需求:前端浏览器访问一个页面, 后端连接数据库查询user表,把每条数据传递到html页面并弄好样式然后发送给浏览器展示(把数据库user表里的内容用for循环展示在页面中)

#【cmd中创建user表】:

+----+-------+------+
| id | name  | age  |
+----+-------+------+
|  1 | jason |   18 |
|  2 | torry |   20 |
|  3 | jack  |   22 |
+----+-------+------+
#【views.py】

import pymysql
from jinja2 import Template

def get_user_func(request):
    # 连接数据库操作数据
    conn = pymysql.connect(
        user='root',
        password='123',
        host='127.0.0.1',
        port=3306,
        database='db1',
        charset='utf8',
        autocommit=True
    )
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    sql1 = "select * from user"
    cursor.execute(sql1)
    user_data = cursor.fetchall()  # [{},{},{}]
    with open(r'templates/get_user.html', 'r', encoding='utf8')as f:
        data = f.read()
    temp_obj = Template(data)
    res = temp_obj.render({'user_list': user_data})
    return res
#【urls.py】

from views import *

urls = [
    ('/get_user',get_user_func),
]
<!--【templates目录中的get_user.html】-->

<body>
    <div class="container">
        <div class="row">
            <h1 class="text-center">数据展示</h1>
            <div class="col-md-8 col-md-offset-2">
                <table class="table table-hover table-striped">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Name</th>
                            <th>Age</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for i in user_list %}
                            <tr>
                                <td>{{ i.id }}</td>
                                <td>{{ i.name }}</td>
                                <td>{{ i.age }}</td>
                            </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
#【启动文件】

from wsgiref.simple_server import make_server
from urls import urls
from views import *

def run(request, response):
    response('200 OK', [])  # 固定格式(响应头等数据)
    path_info = request.get('PATH_INFO')  # 获取大字典中的值(后缀名)
    func_name = None  # 用来存储匹配成功后的函数名
    for i in urls:  # ('/后缀',函数名)
        if path_info == i[0]:
            func_name = i[1]
            break
    if func_name:
        res = func_name(request)
    else:
        res = errors_func(request)
    return [res.encode('utf8')]

if __name__ == '__main__':
    # 实时监听地址,当有请求过来自动给第三个三处加括号并传参调用
    server = make_server('127.0.0.1', 8080, run)
    # 启动服务端
    server.serve_forever()

image

此时如果在数据库中把某条数据删除,网页中也动态删除了

image

2.django框架简介

1)python主流web框架

框架建议优先精通一种,再考虑其他的

  • django框架

    优势:大而全 自身携带的功能非常多 类似航空母舰
    缺陷:开发小项目会显的笨重(大材小用)

  • flask框架

    优势:小而精 自身携带的功能非常少 类似于游骑兵

    缺陷:所有功能受限于第三方模块的开发

  • tornado框架

    优势:异步非阻塞框架 效率高 速度快到可以作为游戏服务器

    缺陷:上手难度最高

  • astapi框架、sanic框架..等

    最近流行的 小公司在用

2)django框架版本选择

  • django1.X (默认同步) django1.11
  • django2.X (默认同步) django2.2
  • django3.X (支持异步) django3.2
  • django4.X (支持异步)

版本间差异不大 主要是添加了额外功能。
建议1:使用带LTS的版本(LTS:官方维护的版本)
建议2:不要用最新不要用最新不要用最新!
建议3:目前用django2.2即可

image

3)django下载

(1)下载
(1)cmd命令行终端中:
pip3.8 install django==2.2.22

'如果之前下载了其他版本不用管,会自动替换'
'pip下载模块会自动解决依赖问题(会把关联需要用到的模块一起下载)可能会有一些小问题'

(2)pycharm中下载
Django2.2.2
(2)验证是否下载成功

cmd终端输入:django-admin,有反应则下载成功

需注意是否配置了环境变量 否则pip会找不到

image

image

(3)运行注意事项
  • 1.django项目中所有文件名、目录名不许有中文
  • 2.计算机名称不许有中文
  • 3.一个pycharm尽量就是一个完整的项目(不要嵌套、叠加)
  • 4.不同版本的python解释器与不同版本的django会有报错

image

3)django创建框架项目命令

可以把django项目简单理解为是一个文件夹

(1)cmd创建
1.创建django项目
cmd>>D:>>django-admin startproject 项目名
cmd>>D:>>django-admin startproject mysite
    
2.启动django项目
  (1)先切换到项目目录中
    cmd>>D:>>cd 项目名
  (2)启动django项目
    python38 manage.py runserver ip:port
    # ip和端口不写默认就是127.0.0.1:8000
    # django默认端口号:8000
    # ctrl+c停止项目(用完项目随手停止项目)

image

image

(2)pycharm创建
直接New Project创建django项目
#不全(只是创建项目,没创建app),可直接看下面pycharm创建django app方式二!

image

①注意:

pycharm创建django项目会自动创建templates文件夹,但是配置文件中拼接路径会出错!!

运行一定要点最上面项目旁的箭头,不要右键运行

image

此时运行会报错是pycharm的问题,每次创建都需修改settings.py里的拼接路径
'DIRS':[os.path.join(BASE_DIR, 'templates'),]

image

②可手动修改端口

image

4)django创建应用(app)命令

django框架类似于一所大学 app类似于大学里的各个系

django框架类似于一个空壳子,只负责提供环境
django里的app类似于某个具体的功能模块

应用名后期应根据业务逻辑自行更改,这里暂用app01

#【cmd创建django】 app
'进入项目中后创建'
python38 manage.py startapp 应用名
python38 manage.py startapp app01


#【pycharm创建】django app
方式一:最下方Terminal中创建:
'不用进入项目中,自动就在该项目中'
python38 manage.py startapp app01

※方式二:如下图
'更多app02等需手动创建'
#注意:方式二新建项目时创建app时会自动注册一个app01

image

注意:

创建完app后需手动在settings.py中注册一下,否则不能用

image

5)django主要目录结构

django项目目录中:
    
    -db.sqlite3文件        django自带的小型数据库(项目启动之后才会出现)
    -manage.py             django入口文件(命令提供)
    -项目同名目录
   	    settings.py       配置文件(大写变量名)
   	    urls.py           路由层:做对应关系的(存储网址后缀与函数名对应关系)
   	    wsgi.py           wsgiref网关文件(基本不用)
    -app01应用目录(可有多个)
   	    migrations目录    orm相关(存储数据库相关记录)
   	    admin.py          django内置的admin后台管理功能
   	    apps.py           注册app相关
   	    models.py         模型层:存储与数据库表相关的类【重要】
   	    tests.py          测试文件
   	    views.py          视图层:存储功能函数
    -templates目录        存储html文件(cmd中不会自动创建 pycharm会)
   	      配置文件中还需要修改配置路径[os.path.join(BASE_DIR,'templates'),]
———————————————————————————————————————————————————————————— 
"""
以上最核心的文件:
   	urls.py	     路由层   做对应关系的(存储网址后缀与函数名对应关系)
   	views.py     视图层   存储功能函数
   	models.py    模型层   存储与数据库表相关的类【重要】
   	templates    模板层   存储html文件(cmd中不会自动创建 pycharm会)
   	
重要名词讲解:
   	网址后缀            路由
   	函数                视图函数
   	类                  视图类
"""

3.django小白必会三板斧

from django.shortcuts import HttpResponse,render,redirect

1)HttpResponse

返回字符串类型的数据

【views.py】:

from django.shortcuts import HttpResponse,render,redirect

def index_func(request):
    print('嘿嘿')
    return HttpResponse('我是HttpResponse返回的字符串')
——————————————————————————————————————
【urls.py】:

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index_func),
]

image

2)render

返回html页面 且可以传值

【views.py】:

from django.shortcuts import HttpResponse,render,redirect

def login_func(request):
    print(request)
    user_dict = {'name': 'jason', 'pwd': 123, 'l1': [11, 22, 33]}
    return render(request, 'login.html', {'d1': user_dict})
——————————————————————————————————————
【urls.pu】:

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login_func),
]
——————————————————————————————————————
【login.html】:

<body>
    <p>我是render返回的页面</p>
    <p>{{ d1 }}</p>
    <p>{{ d1.name }}</p>
</body>

image

3)redirect

用于页面重定向

【views.py】:

from django.shortcuts import HttpResponse,render,redirect

def play_func(request):
    return redirect('https://4399.com/')
——————————————————————————————————————
【urls.py】:

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('play/', views.play_func),
]

image

4.静态文件配置

  • 不经常变化的文件,主要针对html文件所使用到的各种资源就是静态文件
    (css文件、js文件、img文件、第三方框架文件)

  • 针对静态文件资源需创建一个static目录统一存放各种静态文件

'以下均为手动创建:'

-static目录
       css目录       # 所有的css文件
       js目录         # 所有的js文件
       img目录        # 所有的img文件
    
      # 第三方框架目录(可不创因为本身就是目录)
       utils/plugins/libs/others目录  
  • 提前开设静态文件资源访问接口就无法在地址栏访问路由获取对应资源
# 接口可以理解为网址,当给该网址(接口)发送请求时会返回对应的数据

'''
以上简单理解就是如果你用的本地css、js样式(不是联网cdn),如果没做静态资源配置会没有对应样式的!!!! 应该把他当作路由一样需要做相应的关系才可以
'''

静态文件资源配置

也就是当浏览器地址输入对应路由后,让后端的静态文件资源里的css、js等加载出来样式

①settings.py中配置

在配置文件最下方STATIC_URL='/static/'下加一段把static路径添加环境变量中

# static是访问静态文件资源的接口前缀(通行证)
STATIC_URL = '/static/'
# static是存储静态文件资源的目录名称
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
'支持写入多个静态文件资源目录路径(当后期内容多的时候)'

接口前缀正确之后 会拿着后面的路径依次去到列表中自上而下查找 一旦找到对应资源就返回

——————————————————————————————————————————————————————————————

"""
第一个static是导入本地样式地址的前缀:
<link rel="stylesheet" href="/static/bootstrap-3.4.1-dist/css/bootstrap.css">
<script src="/static/bootstrap-3.4.1-dist/js/bootstrap.js"></script>
"""

image

②html中的操作

应该让接口前缀动态匹配

用django模板语法包起来好让接口前缀可以动态获取,当配置文件里的STATIC_URL = '/static/'前缀被要求改名字时可以不用改我的各个html页面,直接动态获取就好

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

image

这是前后端一起写的时候最优解,如果是前后端分离那就不用考虑这个

5.form表单中的网络请求方式

1)form表单中两个属性含义

URL:统一资源定位符(简称网址)

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

action   /*数据提交地址*/
方式一:action=""                      '朝当前页面所在的地址提交'
方式二:action="/index/"               '朝当前服务端的index地址提交'※
方式三:action="https//www.baidu.com/" '完整地址'
———————————————————————————————————————
method   /*数据提交方式*/
'默认不写是get   可手动改为post'

2)form表单中的两种请求方式

get请求 (默认不写就是get请求)
朝服务端索要数据,可以携带额外数据
携带额外数据的方式:URL?aaa=bbb&ccc=ddd
不能是敏感数据(密码),且问号后面携带的数据大小限制在2KB~4KB左右

post请求
朝服务端提交数据,可以携带额外数据
携带额外数据的方式:请求体中
请求体中携带的数据安全性高,且数据大小没有限制

3)提交post请求需修改配置

前期提交post请求需在配置文件中注释一行代码:csrf什么的

csrf:跨站请求伪造 目前还无法理解在后面会写

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

6.request对象方法

views.py视图函数中根据以上form表单中的get/post请求应该有不同的处理结果:
如果是get请求:就返回一个html页面
如果是post请求:就获取发送过来的数据

此时应该考虑使用下述代码来操作:

def login_func(request):
    if request.method == 'POST':
		return HttpResponse('数据相关操作')
	return HttpResponse(request,'login.htme')

1)request对象常用方法

后端获取到前端输入的数据 一般都是字符串格式

需注意get()获取的是列表中最后一个数据值!

html有三个标签需要输入不同名字时,会放在一个列表中
如果就一个标签需要输入一个名字时,用get()反而方便

request.method           # 获取纯大写的【请求方式】字符串(GET/POST)

request.POST             # 获取post请求发送来的普通数据(不包含文件)
request.POST.get()       # 获取列表中最后一个数据值
request.POST.getlist()   # 获取整个列表数据(可通过索引获取需要的数据)

request.GET              # 获取网址问号后携带的数据
request.GET.get()        # 获取列表中最后一个数据值
request.GET.getlist()    # 获取整个列表数据(可通过索引获取需要的数据)

2)POST举例1:(GET同理)

def login_func(request):
    if request.method == 'POST':
        print(request.POST)
        # <QueryDict: {'name':['jason','torry','jack'],'pwd':['123']}>
        
        print(request.POST.get('name'))
        # jack
        
        print(request.POST.getlist('name'))
        # ['jason', 'torry', 'jack']
        
        print(request.POST.getlist('name')[0])
        # jason
        return HttpResponse("数据相关操作")
    return render(request, 'login.html')

3)根据以上做简易登录校验

以下仅是简易版,还需连接数据库去做校验,可当作推导流程去了解

image

【html页面】:(注意静态文件资源配置)

<body>
    <div class="container">
        <div class="row">
            <h1 class="text-center">登录页面</h1>
            <div class="col-md-6 col-md-offset-3">
                <form action="/login/" method="post">
                    <p>username:
                        <input type="text" class="form-control" name="name">
                    </p>
                    <p>password:
                        <input type="password" class="form-control" name="pwd">
                    </p>
                    <input type="submit" class="btn btn-danger btn-block" value="用户登录">
                </form>
            </div>
        </div>
    </div>
</body>

【views.py】:

def login_func(request):
    if request.method == 'POST':
        name = request.POST.get('name')
        pwd = request.POST.get('pwd')
        if name == 'jason' and pwd == '123':
            return HttpResponse("登陆成功")
        return HttpResponse("用户名或密码错误")
    return render(request, 'login.html')

【urls.py】:

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login_func),
]

6.连接MySQL

1)pycharm连接MySQL

# 1.点击pycharm提供的Database按钮
    在左下角或右侧边栏>>Data Source>>MySQL
  '如果没有则去Settings>>Plugins>>Installed下载Database tools and SQL插件或卸载pycharm重装即可'
# 2.首次连接数据库需在下方找到警示标下载一下对应的驱动
# 3.填好需要输入的数据后需检查一下是否成功,不成功就更换驱动
   '更换驱动:Driver>>MySQL不行就换MySQL fro 5.1'

image

2)django连接MySQL

django默认使用的是sqlite3 但是这款数据库一般只用于本地测试 功能较少,实际项目中都会替换掉它

1.settings.py配置文件中修改配置 把原本的DATABASE里的sqlite3改为以下mysql
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # 指定数据库软件名称
        'NAME': 'db1',  # 指定库名
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': '123',
        'CHARSET': 'utf8' 
    }
}

2.第一次使用需准备指定连接MySQL的mysqlclient模块
    django1.X 版本需要在项目或者应用目录下随便一个__init__.py中写一行代码
        import pymysql
        pymysql.install_as_MySQLdb()
  	django2.X 以上版本需要下载mysqlclient模块
        pip3.8 install mysqlclient
     ps:该模块windows下载问题不大 主要是mac电脑可能有问题

image

针对mac电脑下载mysqlclient模块报错只需要点最下面进入源码改成encode即可

image

image

7.ORM操作

1)ORM简介

ORM:关系映射表
作用:用ORM去数据库中查数据时会把表变成对象,可以用点的方式操作
如果想创建一张表,则自己在代码中写一个类,ORM就可以把类转换成表

#ORM把操作类映射成操作表:
类            映射成      表
对象          映射成      一条条数据记录
对象点名字    映射成      字段对应的值

#ORM由于高度封装了数据库,导致有时效率不高,所以还是需要自己写SQL语句

2)ORM基本操作

举例:

1.在django中创建一个表

#应用目录下的【models.py】中编写模型类(创建表)

class Student(models.Model):
    '字段名 = 字段类型 + 约束条件'
    # 类似于SQL里的自增主键
    id = models.AutoField(primary_key=True)
    # 类似于SQL里的创建varchar(32)
    name = models.CharField(max_length=32)
    # 类似于SQL里的创建int型
    age = models.IntegerField()

2.创建完后必须执行数据库迁移、同步命令

python38 manage.py makemigrations  #将操作记录到migrations目录中
python38 manage.py migrate         #将操作同步到数据库上※

image

当执行完右侧会创建出多个表,只用研究app01_student表。(app01仅仅为了区分不同app)

image

注意:只要在models.py中修改了与数据库相关的代码,都必须再次执行数据库迁移、同步命令!!!

3)ORM基本语句

#filter就是筛选过滤

1.增
models.类名.objects.create()
2.查
models.类名.objects.filter()   # 数据对象列表   [数据对象,]
models.类名.objects.filter().first()  # 数据对象
3.改
models.类名.objects.update()
4.删
models.类名.objects.delete()
【增】:

def login_func(request):
    if request.method == 'POST':
        name = request.POST.get('name')
        pwd = request.POST.get('pwd')
   
        models.Student.objects.create(name=name,pwd=pwd)
        return HttpResponse('新增数据成功')
    
    return render(request, 'login.html')
【查】:

def login_func(request):
    if request.method == 'POST':
        name = request.POST.get('name')
        pwd = request.POST.get('pwd')

        # user_obj=models.Student.objects.filter(name=name,pwd=pwd)
        # print(user_obj)  # [数据对象,]
        # print(user_obj[0])  # 数据对象
        # print(user_obj[0].name)  # 数据对象.name
        user_obj = models.Student.objects.filter(name=name, pwd=pwd).first()
        
        if user_obj:
            return HttpResponse(f'{user_obj.name}登录成功')
        return HttpResponse('用户名或密码错误')
    return render(request, 'login.html')
【改】:

models.Student.objects.filter(id=1).update(name='上海第一帅')
return HttpResponse('数据修改成功')
【删】:

models.Student.objects.filter(id=1).delete()
return HttpResponse('数据删除成功')

8.

9.django请求生命周期流程图

image

10.django路由层

1)路由匹配

(1)了解路由后的斜杠
#【urls.py】:

# path('路由/', 函数名)
# 一但网址后缀匹配上就会自动执行后面的函数,并结束整个路由的匹配
urlpatterns = [
    path('user_list/', views.user_list),
]

django2.X及以上版本 path第一个参数写什么就匹配什么
django1.X版本 path第一个参数是正则表达式

无论什么版本django路由后都自带加斜杠的功能,第一次匹配不上,django会加斜杠再次让浏览器请求,也可以取消加斜杠的功能(但不建议)
配置文件中加一句:APEND_SLASH=False

(2)path转换器动态匹配

正常情况下很多网站都会有很多相似的网址,如果每一个都单独开设路由会不合理,应该采用动态匹配

image

django2.X及以上版本 路由动态匹配有五种转换器
当网址后缀不固定时,可以使用转换器来匹配常用的就str和int

#并转成固定数据类型并传给后面的函数,还可以使用

str    # 匹配除路径分隔符外任何非空字符串  ※
int    # 匹配0或任意正整数   ※

slug   # 匹配任意由字母或数字组成的字符串
uuid   # 匹配格式化后的UUID
path   # 匹配完整的URL路径

ps:也支持自定义转换器(自己写正则表达式匹配更细的内容)

需注意:转换器有几个叫什么名字 那么视图函数的形参必须对应

eg:

str:

#【urls.py】中:
urlpatterns = [
    path('index/<str:xx>/', views.index_func),
]

#【views.py】中:
def index_func(request,xx):
    print(xx,type(xx))  # jason <class 'str'>
    return HttpResponse('index func')
"""
当浏览器输入127.0.0.1:8000/index/jason时
print(xx)打印的就是jason
"""
str + int

#【urls.py】中:
urlpatterns = [
    path('index/<str:xx>/<int:aa>', views.index_func),
]

#【views.py】中:
def index_func(request,xx ,aa):
    print(xx, type(xx))  # jason <class 'str'>
    print(aa, type(aa))  # 12345 <class 'int'>
    return HttpResponse('index func')
"""
当浏览器输入127.0.0.1:8000/index/jason/12345时
print(xx)打印的就是jason
print(aa)打印的就是12345
""""""

image

(3)re_path正则匹配

需导入from django.urls import re_path

django2.X及以上版本才有re_path() 第一个参数是正则
django1.X路由匹配使用的是url(),功能与django2.X及以上的re_path()一样

匹配的本质:只要第一个正则表达式能够从用户输入的路由中匹配到数据,就算匹配成功。会立刻停止路由曾其他的匹配直接执行对应视图函数

#【urls.py】中:
urlpatterns = [
    re_path('test',views.test),
    re_path('testadd',views.testadd),
]

#【views.py】中:
def test(request):
    return HttpResponse('test')

def testadd(request):
    return HttpResponse('testadd')
"""
此时如果在浏览器上输入127.0.0.1:8000/testadd会发现能匹配到test的打印结果。原因是re_path是正则匹配,只要urls.py中的'test'能匹配到 那就会直接执行后面的视图函数
"""

image

上述问题:匹配不准确,且如果遇到下列情况也可以匹配成功

image


如何解决上述问题:

^限制开头 $限制结尾

re_path('^test/$', views.test)

#限制开头必须是test 结尾必须是/ 
(4)re_path正则动态匹配

\d{4} 匹配四个任意数
.*? 匹配任意字符

无名分组

# test/四个任意数字/
re_path('^test/(\d{4})/', views.test)

#会将括号内正则匹配到的内容当做位置参数传递给视图函数

有名分组

# test/四个任意数字/
re_path('^test/(?P<id>\d{4})/', views.test),

#test/四个任意数字/任意字符
re_path('^test/(?P<id>\d{4})/(?P<name>.*?)/', views.test),

#会将括号内正则匹配到的内容当做关键字参数传递给视图函数
#视图函数的参数名必须是有名分组里的名字

image

注意:无名分组与有名分组不能混合使用!!!

(5)总结:

普通的路由匹配、正则匹配最多只能传一个request参数
而动态的路由匹配、正则匹配可以传额外的参数

2)反向解析

反向解析:通过一个名字可以反向解析出一个结果,该结果可以访问到某个对应的路由

应用场景:页面上提前写死了很多路由,一但路由被更改会导致所有页面相关路由失效,为了防止出现该问题所以需要使用反向解析

①如何使用反向解析

前端中使用

给路由匹配关系起别名,html页面用模板语法{% url '别名' %},这样不管url里的前缀改成什么名字,html页面中的前缀都不用动

#【urls.py】中:
urlpatterns = [
    path('home/', views.home),
    path('login/', views.login, name='login_view'),
]


#【views.py】中:
def login(request):
    return HttpResponse('登录页面')

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

#【html】页面中
<body>
    <h1>home页面</h1>
    <a href="{% url 'login_view' %}">111</a>
    <a href="{% url 'login_view' %}">111</a>
    <a href="{% url 'login_view' %}">111</a>
    <a href="{% url 'login_view' %}">111</a>
</body>

image

后端中使用

image

image

3)动态反向解析

path('func1/<str:others>/', views.func1_func, name='func1_view')

html页面上模板语法 {% url 'func1_view' 'jason' %}
 后端语法           reverse('func1_view', args=('嘿嘿嘿',))

2)路由分发

django中每个应用都可以有自己独立的 urls.py(路由层)、templates文件夹(模板层)、static文件夹(静态文件资源)。
基于以上特性多人开发项目就可以完全解耦合,之后利用路由分发还能整合到一起

路由分发:总路由去匹配各应用下的子路由,让子路由去匹配各自的视图函数

image

使用路由分发:

①.创建不同的app应用

最下方命令行Terminal中>>python38 manage.py startapp app02
# 执行完创建app命令后不要忘记去配置文件中注册一下app

②.在每个app应用下创建urls.py文件并编写子路由

#【app01中的urls.py】

from django.urls import path
from app01 import views

urlpatterns = [
    path('index/', views.index),
]
#【app02中的urls.py】

from django.urls import path
from app02 import views

urlpatterns = [
    path('index/', views.index),
]

③.在各自app应用下的views.py(视图层)中写视图函数

#【app01中的views.py】:

def index(request):
    return HttpResponse('这是app01中的index')
#【app02中的views.py】:

def index(request):
    return HttpResponse('这是app02中的index')

④总路由中的路由分发

需导入from django.urls import include

from django.urls import path, include

urlpatterns = [
    path('app01/', include('app01.urls')),
    path('app02/', include('app02.urls')),
]

image

3)名称空间

路由分发后,针对相同的别名去做反向解析时无法识别各自的应用前缀

#【app01与app02中的urls.py】中起了相同的别名:
urlpatterns = [path('index/', views.index, name='index_view'),]

解决方案一:名称空间

#【总路由urls.py】中:

path('app01/', include(('app01.urls','app01'),namespace='app01')),
path('app02/', include(('app02.urls','app02'),namespace='app02')),
#【app01与的urls.py】中:
path('index/', views.index, name='index_view'),

——————————————————————————————————————————————————
#【app02与的urls.py】中:
path('index/', views.index, name='index_view'),
#【app01中的views.py】中

def index(request):
    # 反向解析
    print(reverse('app01:index_view'))
    return HttpResponse('这是app01中的index')

——————————————————————————————————————————————————
#【app02中的views.py】中

def index(request):
    # 反向解析
    print(reverse('app02:index_view'))
    return HttpResponse('这是app02中的index')

以上方法虽然可以实现但是很麻烦,建议用方案二

解决方案二:别名别冲突

让不同app中起的别名别一样,前面带上各自app的名字如:

#【总路由urls.py】中:

path('app01/', include('app01.urls')),
path('app02/', include('app02.urls')),
#【app01与的urls.py】中:
path('index/', views.index, name='app01_index_view'),

——————————————————————————————————————————————————
#【app02与的urls.py】中:
path('index/', views.index, name='app02_index_view'),
#【app01中的views.py】中

def index(request):
    # 反向解析
    print(reverse('app01_index_view'))
    return HttpResponse('这是app01中的index')

——————————————————————————————————————————————————
#【app02中的views.py】中

def index(request):
    # 反向解析
    print(reverse('app02_index_view'))
    return HttpResponse('这是app02中的index')

4)虚拟环境

假如:

项目1需要使用:python38 django1.11
项目2需要使用:python38 django2.22 pymysql requests
项目3需要使用:python38 django3.11 request_html flask urllib3

#实际项目中只会给项目配备所需的环境,不需要的不配。这个时候就需要用到虚拟环境

虚拟环境:能够针对相同版本解释器可以实现多个分身,每个分身可以有自己独立的环境。(每创建一个虚拟环境就相当于重新下载了一个全新的解释器)

(1)创建虚拟环境
①pycharm创建方式

image

②命令行创建方式
Terminal终端>>python -m venv py38venv 
# python命令此处不支持多版本共存的操作 python27 python36 python38

要想使用需先激活:
Terminal终端>>cd py38venv
            >>cd Scripts
            >>activate   # 激活虚拟环境
            >>deactivate # 关闭虚拟环境
————————————————————————————————————————————————————

安装django1.11.1报错:
pip install django==1.11.1 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
    
想删除虚拟环境直接找到对应文件删除即可

11.django视图层

1)视图层之必备三板斧

用来处理请求的视图函数都必须返回一个HttpResponse对象

如果没有返回HttpResponse对象则会报以下错误

image

#查看源码

class HttpResponse:
    pass
return HttpResponse()	

def render():
    return HttpResponse()
return render()

def redirect():
    redirect_class = 三元表达式结果是个类(祖先有个类是HttpResponse)
    return redirect_class()
return redirect()
'''
通过查看三板斧里的(HttpResonse、render、redirect)底层源码发现他们最终都是继承了HttpResponse。所以视图函数必须返回一个HttpResponse对象是没问题的
'''

2)JsonResponse对象

(1)把字典返回给浏览器json格式字符串

json_dumps_params={'ensure_ascii':False}让中文不编码

#django提供的序列化模块

from django.http import JsonResponse

def index_func(request):
    # 返回给浏览器一个json格式字符串
    user_dict = {'name': 'jason老哥', 'age': 18}
    return JsonResponse(user_dict,json_dumps_params={'ensure_ascii':False})

image

从源码中确定不让中文编码的方式:

image

(2)其他数据返回给浏览器json格式字符串

JsonResponse主要序列化字典 针对非字典的【其他可以被序列化的数据】需要修改safe参数为False

from django.http import JsonResponse

def index_func(request):
    # 返回给浏览器一个json格式字符串
    l1 = ['张三', 'jason', 'torry']
    return JsonResponse(l1, json_dumps_params={'ensure_ascii': False}, safe=False)

image

3)request对象获取文件

form表单携带文件数据需要具备的条件:
 【前端html页面中:】
   1.method属性值必须是"post"
   2.enctype属性值必须是"multipart/form-data"

<form action="" method="post" enctype="multipart/form-data">
——————————————————————————————————————————————————————————————————
 【后端views.py中:】
    print(request.FILES)
    
request.POST  # 获取的是其他数据
request.FILES # 获取的是文件数据
'可以自动区分出来各自获取各自的'

image

1)案例

把用户上传来的文件保存在pycharm中并保存在本地

#【urls.py】中:
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/',views.index_func)
]
#【views.py】中:
def index_func(request):
    if request.method == 'POST':
        # 一个用get() 列表多个可用getlist()
        file_obj = request.FILES.get('file')
        # print(file_obj.name)  # 获取文件名
        with open(r'%s' % file_obj.name,'wb')as f:
            for i in file_obj:
                f.write(i)
    return render(request, 'index.html')
#【index.html】中:
<body>
    <h1>获取数据</h1>
    <form action="" method="post" enctype="multipart/form-data">
        <p>name:
            <input type="text" name="name">
        </p>
        <p>hobby:
            <input type="checkbox" name="hobby" value="篮球">篮球
            <input type="checkbox" name="hobby" value="足球">足球
            <input type="checkbox" name="hobby" value="双色球">双色球
        </p>
        <p>file:
            <input type="file" name="file">
        </p>
        <input type="submit" value="提交1">
        <button>提交2</button>
    </form>
</body>

image

4)FBV与CBV

FBV基于函数的视图

区分不同的请求需做if判断来执行不同功能

#【urls.py】
path('index/', views.index)

#【views.py】
def index(request):
     return HttpResponse()

CBV基于类的视图

在类中只要写和请求方法相同的函数,CBV会自动根据请求方式的不同匹配类中定义的方法并自动执行

#【urls.py】
from app01 import views
path('func/', views.MyViews.as_view())

#【views.py】
from django import views
class MyViews(views.View):
    def get(self, request):
        return HttpResponse('我是FBV里的get方法')

    def post(self, request):
        return HttpResponse('我是FBV里的post方法')

5)CBV源码刨析

1.源码分析入口:
    path('func/', views.MyViews.as_view())
    """
    1.类名点名字(名字的查找问题)
        自己写的MyView里没有这个名字,那肯定在父类、祖先类里有
    2.类名点名字并加括号调用
        两种情况:类中定义的静态方法 或 绑定给类的方法
    """
    
2.函数名加括号执行优先级最高 项目一启动就会自动执行as_view()方法
    源码中一大堆往下看到有一个return view。所以:
	path('login/', views.view)  # 发现CBV路由本质还是FBV
3.当浏览器地址栏访问login路由,则会执行view函数
	(1)产生我们自己编写类的对象
 	(2)对象调用dispatch方法(注意查找顺序,先找自身有没有该名字的方法)
4.研究父类中的dispatch方法
	获取当前请求方法并转小写 之后利用反射获取类中对应的方法并执行
    '反射:通过字符串拿对象对应的属性名或方法名'
  

class View:
     @classmethod
     def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            return self.dispatch(request, *args, **kwargs)
     def dispatch(self, request, *args, **kwargs):
         handler = getattr(self, request.method.lower())
         return handler(request, *args, **kwargs)

12.django模板层

1.django的模板语法只有两种
    {{ }}:主要与数据值相关
    {$ $}:主要与逻辑相关
    
2.django的模板语法是自己写的 跟jinja2不一样
3.针对需要加括号调用的名字 django模板语法会自动加括号调用你只需要写名字就行
4.模板语法的注释前端浏览器是无法查看的 {# xxx #}

1)模板语法传值

方式1:指名道姓的传    (数据较少时使用)
       缺点:数据多的时候要写很多键值对 
def modal(request):
    name='jason'
    age=18
    return render(request,'modal.html',{'n1':name,'a1':age})

—————————————————————————————————————————————————
方式2:一次性全传 locals()  (数据较多时使用)	  
        #将整个局部名称空间中的名字去全部传入页面
def modal(request):
    name='jason'
    age=18
    ...
    return render(request,'modal.html',locals())

前端中用:{{ 变量名 }}即可使用

image

2)模板语法传值特性

【基本数据类型】正常展示
【文件对象】可以展示,并可以调用方法{{ f.read }}
【函数名】会'自动加括号'执行并将'返回值'展示到页面上
          #模板语法不支持有参函数
【类名】会'自动加括号'调用变成对象
【对象】可以去调用对象的方法

#ps:针对可以加括号调用的名字模板语法都会自动加括号调用

3)模板语法之过滤器

<p>统计长度:{{ s|length }}</p>
<p>加法运算:{{ i|add:123 }}  加法运算:{{ s|add:'heiheihei' }}</p>
<p>日期转换:{{ s|date:'Y-m-d H:i:s' }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>数据切片:{{ l|slice:'0:10' }}</p>
<p>字符截取(...算一个字符):{{ s1|truncatechars:6 }}</p>#hello...
<p>单词截取(根据空格算一个单词):{{ s1|truncatewords:6 }}</p>
 
#语法转义就类似于在后端写一个前端的代码,前端展示时显示代码还是样式。显示样式需要加safe
<p>语法转义:{{ script_tag|safe }}</p>
from django.utils.safestring import mark_safe
script_tag1 = '<script>alert(666)</script>'
res = mark_safe(script_tag1)
#ps:有时候html页面上的数据不一定非要在html页面上编写了 也可以后端写好传入

4)模板语法之标签

(1)if循环
# 条件可以自己写的 也可以是后端传递过来的数据
{% if 条件1 %}
    条件1成立执行的代码
{% elif 条件2 %}
    条件2成立执行的代码
{% else %}
    条件都不成立执行的代码
{% endif %}

image

(2)for循环
{% for 变量名 in 待循环的数据集 %}
    循环体代码
{% endfor %}

image

(3)for循环关键字
# forloop关键字可标识数据的状态

first        # 第一次的循环
last         # 最后一次的循环
counter0     # 索引
counter      # 计数
revcounter0  # 倒序索引
revcounter   # 倒序计数

image

(4)for与if混合使用
{% for i in l1 %}
    {% if forloop.first %}
        <p>这是第一次for循环:{{ i }}</p>
    {% elif forloop.last %}
        <p>这是最后的for循环:{{ i }}</p>
    {% else %}
        <p>这是中间的for循环:{{ i }}</p>
    {% endif %}
    {% empty %}
        <p>当传来的数据是空的无法循环时执行(空字符串/空列表/空字典等)</p>
{% endfor %}

image

django模板语法取值只支持句点符,句点符可以【点索引】也可以【点键】

案例:在页面上显示d1字典中的run键对应的值

#【urls.py】:
path('index/', views.index_func),

#【views.py】:
def index(request):
    d1 = {'name': 'jason', 'hobby': ['read', {'rap': 'a', 'run': 'b'}]}
    return render(request, 'index_func.html', locals())

#【index_func.html】:
<p>{{ d1.hobby.1.run }}</p>

image

(5)with起别名

还可以对传到页面上的值进行起别名 今后用别名即可

<p>
    {% with d1.hobby.1.run as AA %}  # 起别名
        <a href="">{{ AA }}</a>
    {% endwith %}
</p>

image

6)自定义过滤器、标签及inclusion_tag(了解即可)

如果想自定义模板方法 必须先做以下三件事
①.在app应用文件下创建一个名为templatetags文件夹
②.在该文件夹下创建任意名称的py文件如:mytags.py
③.在该py文件内编写下面的自定义相关代码

from django.template import Library
register = Library()

{% load mytags %}加载

(1)自定义过滤器

最多只能接收两个参数

# 自定义过滤器
@register.filter(name='myadd')
def func1(a, b):
    return a + b

# 具体使用
{% load mytags %} 
<p>{{ i|myadd:1 }}</p>

image

(2)自定义标签

参数没限制

# 自定义标签
@register.simple_tag(name='mytag')
def func2(a, b, c, d):
    return f'{a}-{b}-{c}-{d}'
或:
def func2(*args):
    return a,b,c,d

# 具体使用
{% load mytags %}
{% mytag 'a' 'b' 'c' 'd' %}

image

(3)自定义inclusion_tag

底层原理:
新建一个inclusion_tag方法,该方法里会产生一些数据并传给一个局部html页面渲染,然后放到inclusion_tag方法中。等页面调用该方法时还可以传值

# 自定义inclusion_tag
@register.inclusion_tag('menu.html', name='mymenu')
def func3(n):
    l1 = []
    for i in range(n):
        l1.append('<li>第%s页</li>' %i)
    return locals()

#新建【menu.html】清空后写入:
<ul>
    {% for i in l1 %}
        {{ i|safe }}
    {% endfor %}
</ul>
    
# 具体使用
{% load mytags %}
{% mymenu 10 %}  # 指定个数

image

7)模板的继承与导入 ※

(1)模板继承 ※

当有多个页面有很多相似的地方,可以采用模板继承来减少我们复制粘贴的步骤

image

1.在模板(母版)中使用block划定子板今后可以修改的区域

{% block 区域名称 %}   # 可以给子板修改的区域名字
    母板内容           # 母板中可以被子板修改的地方
{% endblock %}         # 结束
2.子板继承模板 '需先清空子版中的内容'

{% extends 'home.html' %}   # 要继承的页面
{% block 区域名 %}          # 要更改母板中哪个区域名中的区域
    子板自己的内容          # 替换成要修改的内容
{% endblock %}              # 结束

注意:模板中至少应该有三个区域,让各个子板中有各自的css样式和js:
模板内容区域、CSS样式区、JS代码区

(3)子板额外补充

当子板继承了模板内容后,如果还想留着模板中的内容,可以在子板修改内容里用: {{ block.super }}如果不要了可以不写

image

案例:

#【urls.py】
urlpatterns = [
    # 模板的继承
    path('home/', views.home_func),
    path('login/', views.login_func),
    path('register/', views.register_func),
]
#【views.py】
def home_func(request):
    return render(request, 'home.html')

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

def register_func(request):
    return render(request, 'register.html')
#【home.html】
    {% block jccss %}
            <style>
                h1{
                    color:red;
                }
            </style>
    {% endblock %}

<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="#">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-fluid">
        <div class="row">
            <div class="col-md-2">
                <div class="list-group">
                      <a href="/home/" class="list-group-item active">
                        首页资源
                      </a>
                      <a href="/login/" class="list-group-item">登录资源</a>
                      <a href="/register/" class="list-group-item">注册资源</a>
                      <a href="#" class="list-group-item">更多资源</a>
                      <a href="#" class="list-group-item">其他资源</a>
                </div>

            </div>
            <div class="col-md-10">
                <div class="panel panel-primary">
                      <div class="panel-heading">
                        <h3 class="panel-title">Panel title</h3>
                      </div>
                      <div class="panel-body">

                          {% block jc1 %}
                              <div class="page-header">
                                  <h1>首页 <small>Subtext for header</small></h1>
                              </div>
                              <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 %}

                      </div>
                </div>
            </div>
        </div>
    </div>
    {% block jcjs %}
         <script>alert('首页')</script>
    {% endblock %}
#【login.html】
{% extends 'home.html' %}  # 要继承的页面

{% block jccss %}
    <style>
        h1 {
            color: blue;
        }
    </style>
{% endblock %}


{% block jcjs %}
    <script>alert('登录')</script>
{% endblock %}


{% block jc1 %}
    {{ block.super }}
    {{ block.super }}
    <h1 class="text-center">登录页面</h1>
    <form action="">
        <p>name:
            <input type="text" class="form-control">
        </p>
        <p>pwd:
            <input type="password" class="form-control">
        </p>
        <input type="submit" class="btn btn-warning">
    </form>
{% endblock %}
#【register.html】
{% extends 'home.html' %}

{% block jccss %}
    <style>
        h1{
            color: yellow;
        }
    </style>
{% endblock %}

{% block jcjs %}
    <script>alert('注册')</script>
{% endblock %}

{% block jc1 %}
    <h1 class="text-center">注册页面</h1>
    <form action="">
        <p>name:
            <input type="text" class="form-control">
        </p>
        <p>pwd:
            <input type="password" class="form-control">
        </p>
        <input type="submit" class="btn btn-success">
    </form>
{% endblock %}

image

(2)模板导入(了解)

将某个html的部分提前写好 之后很多html页面想使用就可以导入

直接在要导入的地方用以下代码即可:
    {% include 'myform.html' %}
    
#仅适用于页面某个小的区域,不适用整个页面

image

13django模型层

1)前期准备

django自带的sqlite3数据库对时间字段不敏感,会展示错乱 所以习惯会切换成常见数据库:MySQL
django orm不会自动创建库需要提前准备好

1.cmd中创建库
create database 库名;

2.settings.py配置文件中把库sqlite3改为mysql
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'day55', # 库名
        'USER': 'root',
        'PASSWORD': '123',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'CHARSET': 'utf8'}
    
3.模型层models.py中写入模型类(写张表)
class User(models.Model):
    name = models.CharField(max_length=32, verbose_name='用户名')
    age = models.IntegerField(verbose_name='年龄')
    register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)

    def __str__(self):
        return f'用户对象:{self.name}'
    
4.写完模型类后执行数据库迁移命令
    python38 manage.py makemigrations
    python38 manage.py migrate
创建模型类补充知识
#字段参数:
verbose_name='用户名' #类似加一个注释
————————————————————————————————————————————————
#时间字段:
DateTimeField #年月日时分秒
DateField #年月日

#时间字段参数:
auto_now=True #每次操作数据都会自动更新当前时间
auto_now_add=True #创建数据自动获取当前时间,后续不人为修改则不更新
________________________________________________
# 模型类下创建__str__方法,可以让对象在执行打印操作时更好区分这是哪个对象
def __str__(self):
    return f'用户对象:{self.name}'

2)测试环境搭建

需求:单独测试django某个功能层
django默认不允许单独测试某个py文件 如果想要测试某个py文件(主要models.py):

#方法一: 不建议使用,关掉就没
pycharm最下面选择Python Console 命令行测试环境

from app01 import models
models.User.objects.filter()

————————————————————————————————————————————————

#方法二:自己搭建 【推荐】
1.去manage.py文件中把前四行代码拷贝出来
2.在tests.py中下面一行粘贴上面四行代码 再额外加两句即可写测试代码

import os
import sys
def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day55.settings')
    
    import django
    django.setup()
    
    from app01 import models
    print(models.User.objects.filter())

if __name__ == '__main__':
    main()

image

3)ORM常见关键字

orm查看底层sql语句

由于orm底层还是sql语句,所以支持查看底层sql语句怎么生成的

1.当手上的结果是一个QuerySet对象 那么可以直接点query查看SQL语句

res = models.User.objects.all().values('name')
print(res)
"""
<QuerySet [{'name': 'jason'}, {'name': 'torry'}, {'name': 'kevin'}]
"""
print(res.query)
"""
SELECT `app01_user`.`name` FROM `app01_user`
"""

image

2.如果想查看所有orm底层的SQL语句 也可以在配置文件添加日志记录

#【settings.py】中:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

当执行ORM语句后就会在下面打印出对应的SQL语句:

image

(1)create()

创建数据 打印的返回值是当前创建的数据对象

res = models.User.objects.create(name='阿兵', age=28)
res = models.User.objects.create(name='oscar', age=18)
res = models.User.objects.create(name='jerry', age=38)
res = models.User.objects.create(name='jack', age=88)
print(res)

image

(2)filter()

根据条件筛选数据 结果是QuerySet [数据对象1,数据对象2] 列表套数据对象

res = models.User.objects.filter()#QuerySet [数据对象1,数据对象2]
res = models.User.objects.filter(name='jason')
res = models.User.objects.filter(name='jason', age=18)#括号内支持多个条件但是默认是and关系(都成立结果才成立)

image

(3)all()

查询所有数据(不能筛选) 结果是列表套对象

res = models.User.objects.all()
(4)first()、last()

first()获取Queryset中第一个数据对象 如果为空不存在则返回None
last()获取Queryset中最后一个数据对象 如果为空则返回None

推荐用first不推荐索引的原因是:如果没有该数据索引会报错

# res = models.User.objects.filter(pk=100)[0]
res = models.User.objects.filter(pk=100).first()
res = models.User.objects.filter().last()

image

(5)update()

更新/更改数据

models.User.objects.filter().update()     批量更新
models.User.objects.filter(id=1).update() 单个更新
(6)delete()

删除数据(批量删除)

models.User.objects.filter().delete()      批量删除
models.User.objects.filter(id=1).delete()  单个删除
(7)values()

根据指定字段获取数据 结果是列表套字典数据

#获取所有条件中的name对应的值(后面跟.first()可以获取第一个)
res = models.User.objects.all().values('name')
res = models.User.objects.filter().values('name')
res = models.User.objects.values('name')
#获取主键是1的age对应的值
res = models.User.objects.filter(pk=1).values('age')
(8)values_list()

根据指定字段获取数据 结果是列表套元组数据

res = models.User.objects.values_list('name')
(9)distinct()

去重 需注意:数据必须一样(包括主键),一般都会去掉主键后去重

#把所有数据的年龄字段去重
res = models.User.objects.filter().values('age').distinct()
res1 = models.User.objects.all().values('age').distinct()

image

(10)order_by()

根据指定条件排序 默认是升序 字段前面加负号就是降序

res = models.User.objects.all().order_by('age')
res = models.User.objects.all().order_by('-age')
(11)get()

直接根据条件查询具体的数据对象 一旦条件不存在会直接报错 不建议使用

#获取主键是1的数据
res = models.User.objects.get(pk=1)
(12)exclude()

针对括号内的条件'取反'进行数据查询 QuerySet(可以看成是列表套对象)

#获取id不等于1的数据对象
res = models.User.objects.exclude(pk=1)
(13)reverse()

颠倒顺序 针对已排了序的结果集做颠倒 用的较少因为排序前面加-号就可以实现

res1 = models.User.objects.all().order_by('age').reverse()
(14)count()

统计结果集中数据的个数

res = models.User.objects.all().count()
(15)exists()

判断结果集中是否含有数据 如果有则返回True 没有则返回False

几乎不用因为所有数据自带布尔值

res = models.User.objects.all().exists()

4)ORM执行SQL语句

有时候ORM的操作效率可能偏低,其实也支持自己写SQL语句

#方式1:
#注意表名
    res = models.User.objects.raw('select * from app01_user where id<3;')
    print(list(res))
    """
    [<User: 用户对象:jason>, <User: 用户对象:torry>]
    """

image

#方式2:
    from django.db import connection
    cursor = connection.cursor()
    cursor.execute('select name from app01_user where id<4;')
    
    # print(cursor.fetchall())
    """
    (('jason',), ('torry',), ('kevin',))
    """
    print(cursor.fetchone())
    print(cursor.fetchone())
    """
    ('jason',)
    ('torry',)
    """

image

5)神奇的双下划线查询

以下直接打印res返回的结果是QuerySet数据对象

只要还是queryset对象,就可以无限制的去点queryset对象的方法

(1)比较运算符
字段__gt     # 大于
字段__gte    # 大于等于
字段__lt     # 小于
字段__lte    # 小于等于
#查询年龄大于18的数据
res = models.User.objects.filter(age__gt=18)
#查询年龄大于等于18的数据
res = models.User.objects.filter(age__gte=18)
(2)成员运算
字段__in=()   # 在某几个数据中 可以给多个值
#查询年龄是18、28、38的数据
res = models.User.objects.filter(age__in=(18,28,38))
(3)范围查询
字段__range=[]    # 在某个范围内 最多给两个值
#查询年龄在10~20之间的数据(包含10和20)
res = models.User.objects.filter(age__range=(10,20))
(4)模糊查询
字段__contains   # 区分大小写
字段__icontains  # 不区分大小写
#查询姓名中有字母j的数据
res = models.User.objects.filter(name__contains='j')
(5)日期处理

需注意settings.py中时间为UTC时间,所以月和日不准。后面BBS项目再改

字段__year     # 年
字段__month    # 月
字段__day      # 日
#查询注册年份是2022的数据
res = models.User.objects.filter(register_time__year=2022)

6)ORM外键字段的创建

"""
复习MySQL外键关系
    一对多
        外键字段建在多的一边
    多对多
        外键字段键在第三张关系表中
    一对一
        外键字段键在查询频率较高的表中
"""
(1)前期准备
1.cmd中创建库
create database 库名;

2.settings.py配置文件中把库sqlite3改为mysql
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'day06', # 库名
        'USER': 'root',
        'PASSWORD': '123',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'CHARSET': 'utf8'}
创建模型类补充知识
#小数字段:
DecimalField        #小数

#小数字段参数:
max_digits=8      #总共8位
decimal_places=2  #小数点后占2位
————————————————————————————————————————
创建完表后建议用"""图书表"""标注一下
————————————————————————————————————————
3.模型层models.py中写入模型类(写4张表)

class Book(models.Model):
    """图书表"""
    title = models.CharField(max_length=32, verbose_name='书名')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    publish_time = models.DateField(auto_now_add=True, verbose_name='出版日期')


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


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


class AuthorDetail(models.Model):
    """作者详情表"""
    phone = models.BigIntegerField(verbose_name='手机号')
    address = models.CharField(max_length=64, verbose_name='家庭住址')
4.分析四张表的表关系 确定外键字段建在哪里
#  图书表   与   出版社表
     一本书对应多个出版社  ×
     一个出版社对应多本书  √
     一对多,书为多,外键在多(书)
    #ForeignKey
————————————————————————————————    
#  图书表   与   作者表
     一本书对应多个作者  √
     一个作者对应多本书  √
     多对多,书频率高,外键在书
    '需有绑定两个表关系的第三张表'
    #ManyToManyField
————————————————————————————————
#  作者表   与   作者详情表
    一个作者对应多个作者详情  ×
    一个作者详情对应多个作者  ×
    一对一,作者频率高,外键在作者
    #OneToOneField
创建外键字段补充知识

一对多、一对一会给外键字段名自动加一个_id后缀
多对多不会只是虚拟字段,不会在表中显示,会去创建第三张表

#创建一对多外键字段
publish = models.ForeignKey(to='关联的类名(表)',on_delete=models.CASCADE)
"""
ORM在表中会自动给publish加一个_id后缀用来表示是该表的外键
on_delete=models.CASCADE  级联删除

django1.X版本所有外键字段默认都是级联更新级联删除
django2.X版本及以上需自己声明
"""
————————————————————————————————————————————————

#创建多对多外键字段
author = models.ManyToManyField(to='关联的类名(表)')
"""

多对多外键字段为虚拟字段,表中看不到该字段。用来告诉ORM自动创建第三张表
多对多不需要声明级联更新级联删除

    ORM比SQL有更多优化
    1.外键字段可以在某张表中(查询频率最高的)
       内部会自动创建第三张表关系 (表名:app01_表1_表2)
    2.自己创建第三张关系表,并自己创建两个外键字段
       其实也可以写外键,后续会了解
      PS:暂不考虑第二种方式
"""
——————————————————————————————————————————
#创建一对一外键字段
author_detail=models.OneToOneField(to='关联的类名(表)',on_delete=models.CASCADE)
"""
ORM在表中会自动给author_detail加一个_id后缀用来表示是该表的外键
on_delete=models.CASCADE  级联删除

django1.X版本所有外键字段默认都是级联更新级联删除
django2.X版本及以上需自己声明
"""
5.给需要的表添加外键字段
#【Book图书表】:

# 创建书籍与出版社的一对多外键字段
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
# 创建书籍与作者表的多对多外键字段
author = models.ManyToManyField(to='Author')
——————————————————————————————————————————————
#【Author作者表】:

# 创建作者与作者详情表的一对一外键字段
author_detail=models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE)
6.写完模型类后执行数据库迁移命令
    python38 manage.py makemigrations
    python38 manage.py migrate

7)外键字段数据的增改删

(1)前期准备

1.先给三张表提前录入需要的数据 图书表、图书与作者关系的第三张表用orm操作

#【app01_authordetail作者详情表】
录入数据
#【app01_author作者表】
录入数据
#【app01_publish出版社表】
录入数据

2.搭建测试环境

import os
import sys

def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoday06.settings')
    import django
    django.setup()
(2)增
①一对一和一对多

create()

给图书表app01_book增数据

1.#可直接填写表中的字段名添加主键
models.Book.objects.create(title='三国演义',price=88.88,publish_id=1)

2.#也可填写对应的对象
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='水浒传',price=66.66,publish=publish_obj)

image

②多对多

add()

给 书跟作者的第三张关系表app01_book_author增数据

由于给某张表中添加数据需要用models去点一个表,发现自动创建的第三张关系表没办法点出来。所以就要想其他办法:需要获取一个含有多对多关系字段的数据对象即可(书籍表中有一个authors虚拟字段,所以获取书籍对象即可)

1.#找到含有多对多关系字段的对象 然后在第三张表中绑定关系
book_obj=models.Book.objects.filter(pk=1).first()#找到id=1的书籍对象
book_obj.author.add(1) #在第三张关系表中给该书籍对象绑定作者id
book_obj.author.add(2,3)#且支持绑定多个作者id

2.#也可以填写作者对象
book_obj=models.Book.objects.filter(pk=2).first()#找到id=2的书籍对象
author_obj1=models.Author.objects.filter(pk=1).first()#找id=1的作者对象
author_obj2=models.Author.objects.filter(pk=2).first()#找id=2的作者对象
book_obj.author.add(author_obj1,author_obj2)#在第三张表中给该书籍对象绑定作者对象

"""
orm会自动识别对象或id绑定
"""

image

(3)改

注意:set([])中必须跟列表或元组

①多对多

set([])或set(())修改

1.#找到要修改外键绑定关系的书籍对象 然后在第三张表中修改关系
book_obj=models.Book.objects.filter(pk=1).first()#找到对应id的书籍对象
book_obj.author.set([1,])#在第三章表中修改绑定关系
"""
注意:如果没有该关系则是先删除再新增
"""

2.#也可以填写作者对象
book_obj=models.Book.objects.filter(pk=2).first()#找到对应id的书籍对象
author_obj=models.Author.objects.filter(pk=3).first()#找到对应id的作者对象
book_obj.author.set([author_obj])#在第三章表中修改绑定关系

image

(4)删
①多对多

remove()
clear()清空

1.#找到要删除绑定关系的书籍对象 然后在第三张表中修改关系
book_obj=models.Book.objects.filter(pk=1).first()
book_obj.author.remove(2,3)#在第三章表中修改绑定关系

2.#也可以填写作者对象
book_obj=models.Book.objects.filter(pk=2).first()
author_obj=models.Author.objects.filter(pk=3).first()
book_obj.author.remove(author_obj)

3.#直接把某本书的所有关系清空
book_obj=models.Book.objects.filter(pk=2).first()
book_obj.author.clear()

8)ORM跨表查询

"""
复习MySQL跨表查询
    子查询
        分步操作:将一条SQL语句用括号括起来当作另外一条SQL语句的条件
    连表操作
        先整合多张表之后基于单表查询
        inner join  内连接
        left join   左连接
        right join  右连接
"""
正反向查询的概念(重要)
#正向查询
    由外键字段所在的表数据查询关联的表数据 正向
    '通过书查询出版社 外键字段在书表中'
#反向查询
    没有外键字段的表数据查询关联的表数据	 反向
    '通过出版社查询书 外键字段不在出版社表中'
    
#ps:正反向的核心就看外键字段在不在当前数据所在的表中
 
"""
ORM跨表查询的口诀:
	正向查询按外键字段     (模型类里写的外键字段)
	反向查询按表名小写_set (当结果可能是多的时候用_set.all(),单个时不用加)
"""
(1)基于对象的跨表查询
'''基于对象的正向跨表查询'''
1.查询主键为1的书籍对应的出版社名称
    # 先根据条件获取数据对象
    book_obj = models.Book.objects.filter(pk=1).first()
    # 再判断正反向的概念  由书查出版社 外键字段在书所在的表中 所以是正向查询
    # print(book_obj.publish) # Publish object (1)
    print(book_obj.publish.name) # 北方出版社

2.查询主键为1的书籍对应的作者姓名
    # 先根据条件获取数据对象
    book_obj = models.Book.objects.filter(pk=1).first()
    # 再判断正反向的概念  由书查作者 外键字段在书所在的表中 所以是正向查询
    # print(book_obj.authors)  # app01.Author.None
    # print(book_obj.authors.all())
    # <QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]>
    # 如果在模型表modles.py中写了__str__方法 可打印出对象的名字
    print(book_obj.authors.all().values('name'))
    # <QuerySet [{'name': 'zy'}, {'name': 'mm'}]>
    
3.查询jason的电话号码
    author_obj = models.Author.objects.filter(name='jason').first()
    print(author_obj.author_detail.phone)

'''基于对象的反向跨表查询'''
4.查询北方出版社出版过的书籍
    # 先根据条件获取数据对象
    publish_obj = models.Publish.objects.filter(name='北方出版社').first()
    # 再判断正反向的概念  由出版社查书 外键字段在书所在的表中 所以是反向查询
    # print(publish_obj.book_set)  # app01.Book.None
    print(publish_obj.book_set.all().values('title'))

5.查询jason写过的书籍
    author_obj = models.Author.objects.filter(name='jason').first()
    # print(author_obj.book_set)  # app01.Book.None
    print(author_obj.book_set.all().values('title'))

6.查询电话号码是110的作者姓名
    authordetail_obj=models.AuthorDetail.objects.filter(phone=110).first()
    print(authordetail_obj.author.name)
(2)基于双下划线的跨表查询
'''基于双下划线的正向跨表查询'''
1.查询主键为1的书籍对应的该书籍名称和出版社名称
    # 先根据条件获取数据对象 然后判断书籍和出版社是正向查询 values里支持之反向__字段名获取值
    res=models.Book.objects.filter(pk=1).values('title','publish__name')
    print(res)
2.查询主键为3的书籍对应的该书籍名称和作者姓名
    res=models.Book.objects.filter(pk=3).values('title','author__name')
    print(res)
3.查询jason的电话号码
    res=models.Author.objects.filter(name='jason').values('author_detail__phone')
    print(res)

'''基于双下划线的反向跨表查询'''
4.查询北方出版社出版过的书籍名称和价格
    res=models.Publish.objects.filter(name='北方出版社').values('book__title','book__price')
    print(res)

5.查询jason写过的书籍名称
    res=models.Author.objects.filter(name='jason').values('book__title')
    print(res)

6.查询电话号码是110的作者姓名
    res=models.AuthorDetail.objects.filter(phone=110).values('author__name')
    print(res)
(3)进阶操作

如果不能用已知的条件去查,则需要用另一个已知去推,(原本正向变成了反向)

'''原本正向变反向'''
1.查询主键为1的书籍对应的出版社名称
	# 用第二张表里筛选中反向操作获取对象 然后values取自己表中的数据
    res=models.Publish.objects.filter(book__pk=1).values('name')
    print(res)
    
2.查询主键为3的书籍对应的作者姓名
    res=models.Author.objects.filter(book__pk=3).values('name')
    print(res)
    
3.查询jason的电话号码
    res=models.AuthorDetail.objects.filter(author__name='jason').values('phone')
    print(res)
    
'''原本反向变正向'''
4.查询北方出版社出版过的书籍名称和价格
    res=models.Book.objects.filter(publish__name='北方出版社').values('title','price')
    print(res)
    
5.查询jason写过的书籍名称
    res=models.Book.objects.filter(author__name='jason').values('title')
    print(res)
    
6.查询电话号码是110的作者姓名
    res=models.Author.objects.filter(author_detail__phone=110).values('name')
    print(res)
    



# 三表:查询主键为1的书籍对应的作者的电话号码
	#支持跨表查询,当查到作者表时,可继续点外键字段获取电话号
方法一:res=models.Book.objects.filter(pk=1).values('author__author_detail__phone')
        print(res)
    
方法二:res = models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
        print(res)
    
方法三:res = models.Author.objects.filter(book__pk=4).values('author_detail__phone')
        print(res)

9)图书管理系统项目

(1)表设计
1.新建django文件并完成基本配置
'''
注:
    需先创建库然后才能在settings.py中配置mysql
    
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'day07',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': '123',
        'CHARSET': 'utf8'
    }
}
'''
2.模型层models.py中创建4个模型类(创建表)

class Book(models.Model):
    """图书表"""
    title = models.CharField(max_length=32, verbose_name='书名')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    publish_time = models.DateField(auto_now_add=True, verbose_name='出版日期')


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


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


class AuthorDetail(models.Model):
    """作者详情表"""
    phone = models.BigIntegerField(verbose_name='电话')
    address = models.CharField(max_length=64, verbose_name='家庭住址')
3.分析四张表的表关系 确定外键字段建在哪里

#图书表   与   出版社表
  一本书对应多个出版社  ×
  一个出版社对应多本书  √
  一对多 外键在多(书)
  #ForeignKey

#图书表   与   作者表
  一本书对应多个作者  √
  一个作者对应多本书  √
  多对多 外键在查询高(书)
  #ManyToManyField

#作者表   与   作者详情表
  一个作者对应多个作者详情  ×
  一个作者详情对应多个作者  ×
  一对一,外键在查询高(作者)
  #OneToOneField
4.给需要的表添加外键字段
'注意外键字段会自动加一个_id后缀'
'多对多会自动创建第三张关系表,不需要加级联更新级联删除'

# 图书表与出版社表的一对多外键字段
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)

# 图书表与作者表的多对多外键字段
author = models.ManyToManyField(to='Author')

# 作者表与作者详情表的一对一外键字段  
authordetail=models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE) 

5.表外键字段写完执行数据库迁移命令
python38 manage.py makemigrations
python38 manage.py migrate
6.手动录入数据
出版社表、作者详情表、作者表、临时给图书表加一些数据便于检查

7.手动把书籍表和作者表绑定关系
(2)首页展示
#首页
1.urls.py中绑定首页路由
    path('home/',views.home_func,name='home_view')#起别名后续可反向解析跳转
    
2.views.py中写入返回首页页面
    def home_func(request):
        return render(request,'home.html')
    
3.home.html中搭建
  1)导航条
    (1)<nav class="navbar navbar-inverse"> #inverse可改黑色
    (2)把一些英文改成中文
  2)搭建页面主体内容
    (1)两边有小留白
    <div class="container-fluid"></div> 
    (2)搭建布局
    <div class="row">
            <div class="col-md-2">放一个列表组-连接</div>
            <div class="col-md-10">放一个带标题面板</div>#<div class="panel panel-primary">可改颜色
        </div>
        # 其他自行添加:页头、巨幕、默认样式得实例
#图书列表展示页
【urls.py】
path('book_list/', views.book_list_func, name='book_list_views'),

【views.py】
from app01 import models
def book_list_func(request):
    # 1.获取所有图书数据
    book_queryset = models.Book.objects.all()  # queryset[数据对象,数据对象..]
    # 2.返回html页面并展示图书数据
    return render(request, 'booklist.html', locals())

【booklist.html】
  (1)首先在home.html页面中提前划好三个区域:
    
    {% block content %}
        内容区(可被子板更换的内容)
    {% endblock %}
    
    {% block css %}
        css区
    {% endblock %}
    
    {% block js %}
        js区
    {% endblock %}
    
  (2)booklist.html中继承母板
    {% extends 'home.html' %}
    
    {% block content %}
        
    {% endblock %}
    
(3)书籍展示
(4)书籍添加

return redirect('book_list_view')只支持没有动态匹配的别名反向解析

(5)书籍编辑

​ 重点考虑:后端如何获取用户想要编辑的数据、前端如何展示出待编辑的数据

(6)书籍删除

注意先删除书与作者的绑定关系,再清除书的所有关系用clear()

10)聚合查询

聚合函数:最大Max()、最小Min()、求和Sum()、平均Avg()、计数Count()

在ORM中可以单独使用聚合函数不用分组,需关键字:aggregate()

聚合函数必须先导模块才能使用,不能直接使用
from django.db.models import Max,Min,Sum,Avg,Count

from django.db.models import Max, Min, Sum, Avg, Count
res=models.Book.objects.aggregate(最大价格=Max('price'),
                                      最小价格=Min('price'),
                                      共多少钱=Sum('price'),
                                      平均价格=Avg('price'),
                                      共多少书=Count('pk'),
                                      )
print(res)
"""
{'最大价格': Decimal('18.88'), 
 '最小价格': Decimal('10.22'), 
 '共多少钱': Decimal('77.99'), 
 '平均价格': Decimal('12.998333'), 
 '共多少书': 6}
"""
# 可起别名(没起别名的放前面,起了的放后面)

11)分组查询

注意:分组有一个特性:默认只能够直接获取分组的字段,其他字段需要使用方法获取。可以忽略该特性:

# 将sql_mode中only_full_group_by配置移除即可(否则会报错)
cmd>mysql>
    show variables like '%mode%';  #可以看到sql_mode中有一个only_full_group_by
    set global sql_mode='STRICT_TRANS_TABLES'; #即可移除,用这一个
(1)表名分组练习

别名可以用中文,但是尽量还是不要用!!!

"""
models.Book.objects.annotate() 
# models后写的哪个表,就以哪个表来分组
"""
————————————————————————————————————————
1.统计每一本书的作者个数
# 书查作者为正向查询:用外键字段
res = models.Book.objects.annotate(作者个数=Count('author__pk')).values('title', '作者个数')
print(res)
"""
<QuerySet [{'title': '三国演义', '作者个数': 1}, 
           {'title': '红楼梦', '作者个数': 2}, 
           {'title': '西游记', '作者个数': 1},
           {'title': '水浒传', '作者个数': 1}]>
"""
——————————————————————————————————————————————

2.统计出每个出版社卖的最便宜的书的价格
# 出版社查书为反向查询:用表名小写
res=models.Publish.objects.annotate(最低书的价格=Min('book__price')).values('name','最低书的价格')
print(res)
"""
<QuerySet [{'name': '北方出版社', '最低书的价格': Decimal('10.22')}, 
           {'name': '南方出版社', '最低书的价格': Decimal('11.22')}, 
           {'name': '东方出版社', '最低书的价格': Decimal('12.22')}, 
           {'name': '西方出版社', '最低书的价格': Decimal('13.22')}]>
"""
——————————————————————————————————————————————

3.统计不止一个作者的图书与作者个数
# (1)先统计每本书的作者个数
# res=models.Book.objects.annotate(作者个数=Count('author__pk'))
# (2)再筛选出作者个数大于1的书与作者个数
res = models.Book.objects.annotate(作者个数=Count('author__pk')).filter(作者个数__gt=1).values('title','作者个数')
print(res)
"""
<QuerySet [{'title': '红楼梦', '作者个数': 2}, 
           {'title': '钢铁是怎样炼成的', '作者个数': 2}, 
           {'title': '封神榜', '作者个数': 2}]>
"""
——————————————————————————————————————————————

4.查询每个作者出了几本书和书的总价格
res=models.Author.objects.annotate(总价格=Sum('book__price'),书个数=Count('book__pk')).values('name','总价格','书个数')
print(res)
"""
<QuerySet [{'name': 'zy', '总价格': Decimal('65.77'), '书个数': 5}, 
           {'name': 'lqm', '总价格': Decimal('42.33'), '书个数': 3}, 
           {'name': 'jason', '总价格': Decimal('12.22'), '书个数': 1}]>
"""
(2)字段分组练习
"""
models.表名.objects.annotate()                     # 按照表分组
models.表名.objects.values('字段名').annotate()    # 按照values括号内指定的字段分组
"""

1.统计每个出版社id下有几本书
res=models.Book.objects.values('publish_id').annotate(书个数=Count('pk')).values('publish_id','书个数')
print(res)
"""
<QuerySet [{'publish_id': 1, '书个数': 1}, 
           {'publish_id': 2, '书个数': 3}, 
           {'publish_id': 3, '书个数': 1}, 
           {'publish_id': 4, '书个数': 1}]>
"""

12)F与Q查询

补充知识:

当想给已经有数据的表额外添加字段执行数据库迁移命令时会弹出提示:

image

如果选择2退出,则去models.py中给额外添加的字段新增
方法一:默认值:default=xxx
方法二:可为空:null=True

(1)F查询

当查询条件不是明确的 而是来自于表中其他字段就需要使用F查询
F:获取表中指定字段对应的数据作为查询条件
from django.db.models import F

#由于查询的都是对象,可以在Models.py中加__str__方法打印出来
#   def __str__(self):
#       return f'图书对象:{self.title}'
————————————————————————————————————————————————————
from django.db.models import F

1.查询库存数大于卖出数的书籍
res=models.Book.objects.filter(kucun__gt=F('maichu'))
print(res)
"""
<QuerySet [<Book: 图书对象:钢铁是怎样炼成的>, <Book: 图书对象:封神榜>]>
"""

2.将所有书的价格涨1000(不常用)
# update(price=原价格+1000)
models.Book.objects.update(price=F('price') + 1000)

3.将所有书的名称后面追加爆款(不常用)
# 字符串类型拼接 需导入两个拼接模块
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
(2)Q查询

Q:可以将多个查询条件的关系修改
from django.db.models import Q

1.查询主键是1或者价格大于2000的书籍
#res = models.Book.objects.filter(pk=1, price__gt=2000)  # 逗号默认是and关系 所以查不出来
from django.db.models import Q
#res = models.Book.objects.filter(Q(pk=1), Q(price__gt=2000))    # 逗号是and
#res = models.Book.objects.filter(~Q(pk=1) | Q(price__gt=2000))  # ~是not
res = models.Book.objects.filter(Q(pk=1) | Q(price__gt=2000))    # |是or
print(res)
"""
<QuerySet [<Book: 图书对象:三国演义爆款>, 
           <Book: 图书对象:红楼梦爆款>, 
           <Book: 图书对象:封神榜爆款>]>
"""
(3)Q查询进阶操作

研究查询条件的左边是什么? 字段名还是变量名?
models.Book.objects.filter(pk=1) 发现是变量名
如果想让左边不是变量名而是字段名,则需要用到Q查询

Q:还可改变条件左侧变量名改为字段名,目的是将来更好的跟用户交互
比如在前端页面有个搜索框,让用户输入按什么条件搜索数据,传入到后端的数据都是字符串,就可以用该字符串去做筛选

案例:查询id是1或价格大于1000的书名

from django.db.models import Q
q_obj=Q()# 产生Q对象
q_obj.connector='or' #默认多个条件的连接时and可以改为or
q_obj.children.append(('pk',1))#添加查询条件
q_obj.children.append(('price_gt',1000))#支持添加第二个查询条件
res=models.Book.objects.filter(q_obj)# 查询支持直接填写q对象
print(res)

image

使用q_obj.connector='or'可从默认and关系改为or关系

from django.db.models import Q
    # 1.由于Q是一个类,类名加括号可以产生一个对象
    q_obj = Q()
    # 多个查询条件改为or关系
    q_obj.connector = 'or'
    # 2.往q对象中添加查询条件
    q_obj.children.append(('pk', 1))
    # 可以添加第二个条件(可添加多个)
    q_obj.children.append(('price__gt', 1000))
    # 查询支持直接填写q对象
    res = models.Book.objects.filter(q_obj)
    print(res)

image

13)ORM四个查询优化(面试)

(1)惰性查询

ORM的查询默认都是惰性查询。目的是为了节省效率

res = models.Book.objcets.all()
# 不执行 除非真正需要使用才会执行 如:print(res)
(2)自带分页处理

ORM的查询自带分页处理。目的是为了减轻数据库及服务端的压力

res=models.Book.objects.all()
print(res)
#当要查询book表中所有数据时,如果表中有几亿条数据一下全找出来电脑会承受不住,所以查看底层sql发现会自动做分页LIMIT处理,每次拿的数据都不多

image

(3)only与defer

主要适用于单表操作

①only

作用:可以将括号里列举的字段封装到数据对象中。当点括号里的字段时不会走SQL查询,但是一但点括号里没有的字段则会每点一次走一次数据库查询
缺点:一但数据特别多时,点括号里没有的字段,会走多次SQL查询,电脑会扛不住

res = models.Book.objects.only('title','price')
# print(res)  # queryset [数据对象、数据对象]
for obj in res:
    # print(obj.title)  # 点括号内的字段 不走SQL查询
    print(obj.publish_time) # 点括号里没有的字段 走SQL查询

image

②defer

与only相反

作用:可以将括号里列举的字段封装到数据对象中。当点括号里没有的字段不会走SQL查询,但是一但点括号里有的字段则会每点一次走一次数据库查询
缺点:一但数据特别多时,点括号里有的字段,会走多次SQL查询,电脑会扛不住

res = models.Book.objects.defer('title','price')
# print(res)  # queryset [数据对象、数据对象]
for obj in res:
    # print(obj.publish_time) # 点括号里没有的字段 不走SQL查询
    print(obj.title)  # 点括号内的字段 走SQL查询

image

主要适用于多表操作

res = models.Book.objects.all()
for obj in res:
    print(obj.publish.name)  # 每次跨表查询都会走SQL语句

image

注:括号里不能写多对多外键字段,一对一、一对多都可以

作用:自动连表然后封装数据。当跨表查询点字段时不会走SQL查询

res=models.Book.objects.select_related('publish') # 先连表后查询封装
for obj in res:
    print(obj.publish.name) # 不走sql查询语句

image

和select_related比其实用的时候都差不多感觉不出来!

作用:基于子查询然后封装数据,先执行一条SQL,把一条SQL的结果当作另一条SQL的条件去查询。

res = models.Book.objects.prefetch_related('publish')  # 子查询
for obj in res:
    print(obj.publish.name)

image

14)ORM事物操作

# 复习事物
1.事务的四大特性(ACID)
	原子性、一致性、隔离性、持久性
	
2.相关SQL关键字
   start transaction   # 开启事物操作
   rollback            # 可回退到修改操作前的状态
   commit              # 提交确认事物
   savepoint           # 节点 类似存档,结合回退用,回退到该节点
   
3.四种隔离级别
1.read uncommitted(未提交读)
    事务中的修改即便没提交,对其他事物也都是可见的。事物可以读取未提交的数据,这一现象也叫'脏读'。
    【'脏读'】简单理解为开启事物修改数据后没有提交确认时,又有另一个事物来获取数据,获取到的就是内存里的数据而不是本应该在硬盘里的真实数据。
2.read committed(提交读 或 不可重复读)  # 大多数据库默认隔离级别
    一个事物从开始一直到提交之前所作的任何修改对其他事务都是不可见的,也叫'不可重复读'
    简单理解就是开启事物修改数据后没有提交确认时,又有另一个事物来获取数据,获取到的是修改前硬盘里的数据。
3.repeatable read(可重复度) # MySQL默认隔离级别
    能够解决'脏读'问题,但无法解决'幻读'问题
    【'幻读'】简单理解为当某个事物在读取某个范围内的记录时,另一个事物又在该范围内插入了新记录,当之前的事物再次读取该范围的记录会产生'幻行'。
    【'幻行'】简单理解为假如事物1对一个表中的5条数据做查询3条记录操作,同时事物2过来把其中的1条数据删掉了,事物1就只能查询2条。
    InnoDB和XtraDB通过'多版本并发控制'(MVCC)及'间隙锁策略'可以解决幻读的问题。
4.serializable(可串行读)
    强制多个事物串行执行(排队执行)。由于效率太低所以很少用。

django orm提供了至少三种开启事务的方式

每种都可以 全局有效更方便,局部可扩展性更高

方式1:配置文件【数据库相关】添加一个键值对    #全局有效
   "ATOMIC_REQUESTS": True  每次请求所涉及到的orm操作同属于一个事务,只要有一个报错则全部回滚。'如果没配置事物,有一个orm报错则上一个执行成功不会回滚。'

    
方式2:装饰器                              #局部有效
   from django.db import transaction
   @transaction.atomic # 被该装饰器修饰的视图函数下同属于一个事物
   def index(request):
       pass	
   return HttpResponse('xxx')

    
方式3:with上下文管理                      #局部有效
   from django.db import transaction
   def reg(request):
       with transaction.atomic(): # 视图函数中的with下同属一个事物
           pass
   return HttpResponse('xxx')

15)ORM常用字段类型

AutoField()         #自定义属性
    -primary_key=True
CharField()         #字符类型
    -max_length()
IntegerField()      #整型
BigIntergerField()  #大整型
DecimalField()
	-max_digits()#小数总长度  
    -decimal_places()#小数位长度
DateField()         #日期
    -auto_now  
    -auto_now_add
DateTimeField()     #日期时间
    -auto_now  
    -auto_now_add
BooleanField()      #传布尔值自动存0或1
TextField()         #存储大段文本
EmailField()        #邮箱格式
FileField()         #传文件对象,自动保存到指定路径,并存路径信息
自定义字段
class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):
        return 'char(%s)' % self.max_length


class User(models.Model):
    name = models.CharField(max_length=32)
    info = MyCharField(max_length=64)

16)ORM常用字段参数

primary_key      #主键
verbose_name     #注释
max_length       #字段长度
max_digits       #小数总共多少位
decimal_places   #小数点后多少位
auto_now         #每次操作自动更新时间
auto_now_add     #首次创建自动更新时间,后续不自动更新
null             #允许字段为空
default          #字段默认值
unique           #字段唯一
db_index         #给字段添加索引
choices          #当某个字段的可能性可以被列举完的情况下使用
"""
性别:男或女或其他  工作状态:在职或离职
    class User(models.Model):
        name = models.CharField(max_length=32)
        info = MyCharField(max_length=64)
        # 提前列举好对应关系
        gender_choice = (
            (1, '男性'),
            (2, '女性'),
            (3, '其他'),
        )
        gender = models.IntegerField(choices=gender_choice,null=True)
    user_obj = User.objects.filter(pk=1).first()
    user_obj.gender 
    user_obj.get_gender_display()
"""

to          #关联表
to_field    #关联字段(不写默认关联数据主键)
on_delete   #当删除关联表中的数据时,当前表与其关联的行的行为。
     -models.CASCADE
        级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
     -models.SET_NULL
        当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空
     -models.PROTECT
        当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
     -models.SET_DEFAULT
        当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值
     -models.SET()
        当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
     -models.DO_NOTHING
        什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似

14.组件

1)Ajax组件

异步提交,局部刷新
简单理解就是:注册页面,当输入注册名字时不点提交按钮后端自动校验,并把校验结果返回到页面且不影响其他内容的填写。要想实现以上情况则必须要用Ajax来实现

(1)Ajax简介

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

Ajax不是新的编程语言,而是一种实现现有标准的新方法。
Ajax最大的优点就是:不重新加载整个页面的情况下与服务器交互部分网页内容。(让用户不知不觉中完成请求和响应)。
Ajax有多个版本,以下使用jQuery封装后的版本 所以要提前导入jQuery资源。本质一样都是异步提交,局部刷新,只是参数不同。

(2)基本语法
$.ajax({
    // 数据提交地址 与action一致
    url:'',
    // 数据提交方式 默认get(小写) 与method一致
    type:'post',
    // 要发送的数据
    data:{'v1':v1Val, 'v2':v2Val}, 
    // 后端返回的数据会被args接收 不再影响整个页面(异步回调函数)
    success:function (args) {
        $('#d3').val(args)
                  }
})


'异步回调函数:只要我朝你发数据,你给我响应了就会自动触发function函数的执行'

练习:做三个input框 ,第一个框加第二个框点击一个按钮把消息发到后端计算,结果返回到第三个框中。要求不能用JS,页面不能刷新。

#【urls.py】
path('ab_ajax/', views.ab_ajax_func),
————————————————————————————————————————————————
#【views.py】
def ab_ajax_func(request):
    if request.method == 'POST':
        # print(request.POST)  # <QueryDict: {'v1': ['1'], 'v2': ['2']}>
        v1 = request.POST.get('v1')
        v2 = request.POST.get('v2')
        # print(v1,v2) # 1 2
        res = int(v1) + int(v2)
        return HttpResponse(res)
        # 后端views.py中的三板斧不再影响页面,而是交给args形参
        # return HttpResponse('123')
        # return render(request,'ab_ajax.html')
        # return redirect('https://www.baidu.com')
    return render(request, 'ab_ajax.html')
——————————————————————————————————————————————————
#【ab_ajax.html】
<body>
<input type="text" id="d1"> +
<input type="text" id="d2"> =
<input type="text" id="d3">
<button id="subBtn">点击发送ajax请求</button>
<script>
    // 1.给按钮绑定点击事件
    $('#subBtn').click(function(){
        // 2.获取前两个框里的数据
        let v1Val = $('#d1').val();
        let v2Val = $('#d2').val();
        // 3.发送ajax请求
        $.ajax({
            url:'',
            type:'post',
            data:{'v1':v1Val,'v2':v2Val},
            success:function (args){
                $('#d3').val(args)
            }
        })
    })
</script>
</body>

练习升级:把按钮取消掉,给第二个框换成失去焦点事件,当失去焦点后自动发送ajax请求

//给第二个标签绑定失去焦点事件
$('#d2').blur(function(){
(3)Content-Type(数据编码格式)
①urlencoded

1)是ajax、form表单默认的编码格式
2)数据格式:v1=123&v2=321
3)django后端会把以上数据格式自动处理到request.POST中
后端views.py中用request.POST.get('v1') 即可获取v1的值

image

image

②formdata

1)可以看成是告诉django怎么处理数据的依据,一但编码格式是formdata则证明这次发来的有文件数据
2)数据格式:二进制格式,无法查看
3)django后端会:针对普通的键值对会处理到request.POST中
但是针对文件数据会处理到request.FILES中

#复习知识点:
form表单enctype属性值必须是"multipart/form-data"才可以传文件:

<form action="" method="post" enctype="multipart/form-data">
<script>
    $('#d3').click(function () {
        // 1.先产生一个FormData对象
        let myFormDataObj = new FormData();
        // 2.往该对象中添加普通数据
        myFormDataObj.append('name', 'jason');
        myFormDataObj.append('age', 18);
        // 3.往该对象中添加文件数据
        myFormDataObj.append('file', $('#d2')[0].files[0])
        // 4.发送ajax请求
        $.ajax({
            url:'',
            type:'post',
            data:myFormDataObj,

            // ajax发送文件固定的两个配置
            contentType:false,  // 不使用任何编码#(只要携带文件就要加该参数)
            processData:false,  // 不处理数据对象#(只要携带文件就要加该参数)
            success:function (args){
                alert(args)
            }
        })
    })
</script>

image

form表单仅支持以上两种编码格式Ajax在以上两种基础上还可以支持下面一种


③application/json

1)json格式数据要用的编码格式
2)数据格式:二进制格式,可以用json.loads解码后反序列化
3)django后端会把以上数据格式放在request.body中不动

#【urls.py】中:

path('ab_ajax/', views.ab_ajax_func),
————————————————————————————————————————
#【views.py】中:

def ab_ajax_func(request):
    if request.method == 'POST':
        print(request.body)
    return render(request, 'ab_ajax.html')
#【ab_ajax.html】:

<body>
<button id="d1">发送json格式数据</button>
<script>
    $('#d1').click(function(){
        $.ajax({
            url:'',
            type:'post',
            //给对方发json格式数据,那就需要用JSON.stringify包一下
            data:JSON.stringify({'name':'jason','age':18,}),
            //声明这次来的数据是json格式数据
            contentType:'application/json',
            success:function (args){
                alert(args)
            }
        })
    })
</script>
</body>

image

(4)ajax回调函数args

主要是针对回调函数args接收到的响应数据

1.后端views.py中
    print(request.is_ajax())
    # 用于判断请求是否是ajax发出(不管是get还是post只要是ajax)
    
2.后端返回的三板斧都会被ajax中的args形参接收不再影响整个浏览器页面

3.选择使用ajax做前后端交互的时候 后端一般返回的都是字典数据
# 后续ajax可通过判断'code'的值是多少来决定执行不同操作(响应状态码)
ajax自动反序列化后端的json格式的bytes类型数据,建议加上dataType:'json',

#【views.py】中
"""方式一:用JsonResponse来传"""
def ab_ajax_func(request):
    if request.method == 'POST':
        user_dict = {'code': 10000, 'name': 'jason', }
        from django.http import JsonResponse
        return JsonResponse(user_dict)
    return render(request, 'Ajax.html')
"""方式二:自行转JSON格式字符串传"""
def ab_ajax_func(request):
    if request.method == 'POST':
        user_dict = {'code': 10000, 'name': 'jason', }
        import json
        user_data = json.dumps(user_dict)
        return HttpResponse(user_data)
    return render(request, 'Ajax.html')    
    
#【Ajax.html】中:
    <button id="d1">点击发送ajax请求</button>
    <script>
    $('#d1').click(function (){
            $.ajax({
                url:'',
                type:'post',
                data:{'name':'jason'},
                dataType:'json',  # 当后端传JSON格式数据时建议加上
                success:function (args){
                    console.log(args);
                    console.log(typeof args);
                    console.log(args.name);
                }
            })
        })
    </script>

image

2)多对多三种创建方式

推荐全自动、半自动

(1)全自动创建(建议)
class Book(models.Model):
    title = models.CharField(max_length=32)
    # 建立外键字段自动创建第三张表
    authors = models.ManyToManyField(to='Author')
class Author(models.Model):
    name = models.CharField(max_length=32)
    
#优势:自动创建第三张表 且提供了add、remove、set、clear四种操作
#劣势:第三张表无法创建更多字段 扩展性较差
(2)纯手动创建(不建议)
class Book(models.Model):
    title = models.CharField(max_length=32)
class Author(models.Model):
    name = models.CharField(max_length=32)
# 自己创建第三张表
class Book2Author(models.Model):
    # 建立外键字段
    book = models.ForeignKey(to='Book')
    author = models.ForeignKey(to='Author')
    # 由于是自己创的第三张表,可以加额外字段
    others = models.CharField(max_length=32)
    join_time = models.DateField(auto_now_add=True)
    
#优势:第三张表完全由自己创建 扩展性强
#劣势:编写繁琐 并且不再支持add、remove、set、clear以及正反向概念
(3)半自动创建(建议)

声明用下面哪个表来建立多对多关系
through='Book2Author',
声明哪两个字段来建立多对多关系
through_fields=('book','author')

class Book(models.Model):
    title = models.CharField(max_length=32)
    # 建立外键字段,不用orm创建第三张表,并告诉用下面哪个表、字段维护多对多关系
    authors = models.ManyToManyField(to='Author',
                                through='Book2Author',
                         through_fields=('book','author'))
class Author(models.Model):
    name = models.CharField(max_length=32)
class Book2Author(models.Model):
    book = models.ForeignKey(to='Book', on_delete=models.CASCADE)
    author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
    # 由于是自己创的第三张表,可以加额外字段
    others = models.CharField(max_length=32)
    join_time = models.DateField(auto_now_add=True)
    
#优势:第三张表完全由自己创建 扩展性强 正反向概念依然清晰可用
#劣势:编写繁琐 且不再支持add、remove、set、clear

3)django序列化组件(drf前身)

serializers模块

写前后端分离的项目时,如果想让前端页面能够识别到后端的数据就要用到JSON格式的数据来进行交互

小技巧:https://www.bejson.com/可以查看接口返回的数据是什么样

举例:把图书表中的数据用json格式传到页面中

#【views.py】中
from app01 import models
from django.http import JsonResponse

def ab_ser_func(request):
    # 1.查询所有的书籍对象
    book_queryset = models.Book.objects.all()  # queryset [对象、对象]
    # 2.封装成大字典返回 {{'pk':'','title':''},{'pk':'','title':''}}
    data_dict = {}  # 创建一个空字典
    for book_obj in book_queryset:  # 循环出每一个数据对象
        temp_dict = {}  # 再创建一个小的空字典
        temp_dict['pk'] = book_obj.pk  # 给小的空字典中添加键值对
        temp_dict['title'] = book_obj.title
        temp_dict['price'] = book_obj.price
        temp_dict['info'] = book_obj.info
        data_dict[book_obj.pk] = temp_dict  # {1:{},2:{}}
    return JsonResponse(data_dict)

image

但是当数据处理量特别大时上述方式写起来效率太低,所以就需要django的序列化组件serializers

序列化组件:serializers(django自带的,后续会用更厉害的drf)
serializers.serialize('序列化成什么格式',要转的数据)

def ab_ser_func(request):
    # 1.查询所有的书籍对象
    book_queryset = models.Book.objects.all()
    # 导入内置序列化模块
    from django.core import serializers
    # 调用该模块下的方法
    res=serializers.serialize('json',book_queryset)
    return HttpResponse(res)

image

4)orm批量操作数据(ORM操作优化)

bulk_create批量插入操作
bulk_update批量更新操作

要求:当访问某个路由时,往books表中插入10万条数据

方式一:循环插入(不推荐速度慢)

#【urls.py】中
path('ab_bk/', views.ab_bk_func),

#【models.py】中
class Books(models.Model):
    title = models.CharField(max_length=32)
    
#【views.py】中
def ab_bk_func(request):
    # 1.往books表中插入10万条数据
    for i in range(1, 100000):
        models.Book.objects.create(title='第%s本书' % i)
    # 2.查询出所有的表数据并展示到前端页面
    book_queryset = models.Book.objects.all()
    return render(request, 'Bk.html', locals())

#【Bk.html】中
{% for book_obj in book_queryset %}
    <p>{{ book_obj.title }}</p>
{% endfor %}

发现页面一直在加载,10w条数据太多 要频繁走数据库加载很慢!!10s大概500多条数据!!

image

方式二:类名加括号产生对象 然后追加到列表中并批量插入数据。

#【views.py】中
def ab_bk_func(request):
    book_obj_list=[] # 可用列表生成式 或 生成器表达式
    for i in range(1,100000):
        book_obj=models.Books(title='第%s本书'%i) # 只是用类名加括号产生对象没走数据库
        book_obj_list.append(book_obj) # 把对象追加到列表中
    # 批量插入数据
    models.Books.objects.bulk_create(book_obj_list)
    # 查询出所有的表数据并展示到前端页面
    book_queryset = models.Book.objects.all()
    return render(request, 'Bk.html', locals())

发现5s左右就生成了10w条数据!!所以如果大批量插入数据时建议用方式二!

5)分页器

其实分页器做后端几乎用不到,在这里说分页器主要是锻炼逻辑能力,代码可以直接cv

(1)推导思路
分页器主要听处理逻辑 代码最后很简单 
推导流程
	1.queryset是支持切片操作的(正数)

	2.研究各个参数之间的数学关系
 		需确定:每页固定展示多少条数据、起始位置、终止位置
     每页展示=10
     页数      起始位置     终止位置
      1           0            10
      2           10           20
      3           20           30
    '起始位置 = (页数 - 1) * 每页展示'
    '终止位置 = 页数 * 每页展示'
        
 	3.自定义页码参数
        以上只有用户要看的页数不确定,可以让前端get请求传过来
    	页数 = request.GET.get('page')
        
 	4.前端展示分页器样式
        去bootcss中拷贝分页器代码
        
	5.总页码数问题
    	divmod方法 # 余数只要不是0就需要在第一个数字上加一
        
        divmod(100,10)
        (10, 0)  # 10页
        divmod(99,10)
        (9, 9)  # 10页
        divmod(101,10)
        (10, 1)  # 11页
        
        由于模板语法没有range功能,但是需要循环产生很多分页标签,所以考虑后端产生数据然后传给前端html渲染
    {{ html_str|sefe}}
    #|sefe 过滤器可以让浏览器识别html语法并渲染出对应样式
        
 	6.前端页面页码个数渲染问题
    for i in range(当前页-5, 当前页+6)
(2)封装的分页器使用

django自带分页器模块但是使用起来很麻烦 所以自己封装了一个

只需要掌握使用方式即可:

1.在app01项目文件下新建一个plugirs文件夹用来存放第三方插件
2.在plugirs文件夹下创建一个mypage.py文件并粘贴以下代码

#【mypage.py】中:

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)
#【urls.py】中:
path('ab_pg/', views.ab_pg_func),
#【views.py】中:
def ab_pg_func(request):
    # 1.获取某张表中的所有数据
    book_queryset = models.Books.objects.all()
    # 2.导入分页器组件
    from app01.plugirs.mypage import Pagination
    current_page = request.GET.get('page')
    # 3.产生对象
    page_obj = Pagination(current_page, all_count=book_queryset.count())
    page_queryset = book_queryset[page_obj.start:page_obj.end]
    # 4.返回给页面
    return render(request, 'pgPage.html', locals())
<!--【pgPage.html】中:-->

    <div class="container">
        <div class="row">
            <h1 class="text-center">数据展示</h1>
            <div class="col-md-8 col-md-offset-2 text-center">
                {% for book_obj in page_queryset %}
                    <p>{{ book_obj.title }}</p>
                {% endfor %}
            </div>
        </div>
    </div>
    <!--渲染分页器-->
    <div class="text-center">
        {{ page_obj.page_html|safe }}
    </div>

image

6)forms组件

念forms组件或form组件都可以。

(1)前戏
需求:获取用户数据并发送给后端校验 后端返回不符合校验规则的提示信息
"""
form组件可以做:
	1.自动校验数据
	2.自动生成标签
	3.自动展示信息
"""    

#【views.py】中

from django import forms

class MyForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8)  # 限制字符最小与最大值
    age = forms.IntegerField(min_value=0, max_value=100)  # 限制年龄最小与最大值
    email = forms.EmailField()  # 限制邮箱格式
    
————————————————————————————————————————————
校验数据的功能:python Console中

1.使用字典的形式传入待校验的数据
    >>from app01 import views
    >>form_obj = views.MyForm({'username':'jason','age':18,'email':'123'})
2.调用校验方法完成数据的校验
    >>form_obj.is_valid()  # 判断数据是否全部符合要求
    False  # 只要有一个不符合结果都是False
3.获取符合校验条件的数据
    >>form_obj.cleaned_data
    {'username': 'jason', 'age': 18}
4.获取不符合校验规则的字段及错误原因
    >>form_obj.errors
    {'email': ['Enter a valid email address.']}
    
# 只校验类中定义好的字段对应的数据 【多传不做任何操作】
# 默认情况下类中定义好的字段都是必填的 【不传就出错】

image

(2)渲染标签

提前准备数据

#【urls.py】中:

path('ab_forms/',views.ab_forms_func),
——————————————————————————————————————————————
#【views.py】中:

from django import forms

class MyForm(forms.Form):
    # label=起一个名字,返回的html页面上可以显示该名字,不显示字段名
    username = forms.CharField(max_length=8, min_length=3, label='用户名')
    age = forms.IntegerField(min_value=0, max_value=100)
    email = forms.EmailField()

def ab_forms_func(request):
    # 1.产生一个空对象
    form_obj = MyForm()
    # 2.把该对象传给html页面
    return render(request, 'formsPage.html', locals())
①方式一:(全自动不推荐)

主要用于本地测试不在乎样式时使用
优点:可以一次性渲染出所有标签类似全自动
缺点:扩展性很差 会破坏页面结构多出p标签

#【formsPage.html】中:有以下三种

{{ form_obj.as_p }}     # p标签包裹
    
{{ form_obj.as_ul }}    # li标签包裹
    
{{ form_obj.as_table }} # 横着一排

image

②方式二:(纯手动不推荐)

优点:扩展性高
缺点:封装程度过低,编写麻烦(如果要获取很多数据则非常麻烦)

#拿username对应的label值,没有则用字段首字母大写
{{ form_obj.username.label }}
#拿username的input框
{{ form_obj.username }}

{{ form_obj.age.label }}
{{ form_obj.age }}

{{ form_obj.email.label }}
{{ form_obj.email }}

image

③方式三:(半自动推荐!)

优点:封装程度较高 扩展性高 编写简单 推荐使用!

{% for form in form_obj %}
    <p>
        #拿字段对应的label值,没有则用字段首字母大写
        {{ form.label }}  # 字段名
        #拿字段的input框
        {{ form }}  # 框
    </p>
{% endfor %}

image

注意:以上并不完整,需用下面的!

1.forms组件只负责渲染获取用户数据的标签
form标签按钮都需要自己写
2.前端的校验是弱不禁风的 最终都需要后端来校验 所以在使用forms组件的时候可以直接取消前端帮我们的校验,也可以不取消 给人看起来很牛的样子
<form action="" novalidate>前端浏览器不要做任何校验,数据发到后端来校验

<form action="" method="post" novalidate>
    {% for form in form_obj %}
        <p>
            {{ form.label }}
            {{ form }}
        </p>
    {% endfor %}
    <input type="submit">
</form>
(3)展示错误提示信息

思考校验用户输入的数据不合法时返回的提示信息如何展示到页面

首先要确保前端没有校验规则:form标签中设置了novalidate取消前端校验,否则数据发不到后端去校验。

①初代代码
#【views.py】中

def ab_forms_func(request):
    # 1.产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        # request.POST可以看成是一个字典 直接传给forms类校验
        # 该字典中无论有多少数据都无所谓,只看上面类中有的字段
        form_obj = MyForm(request.POST)
        # 判断数据是否全部符合要求(一个不符合就是False)
        if form_obj.is_valid():
            print(form_obj.cleaned_data)
        else:
            print(form_obj.errors)
    # 2.把该对象传给html页面
    return render(request, 'formsPage.html', locals())

——————————————————————————————————————————————————————————
#【formsPage.html】中:

<form action="" method="post" novalidate>
    {% for form in form_obj %}
        <p>
            {{ form.label }}
            {{ form }}
            <span>{{ form.errors }}</span> # 需加.0
        </p>
    {% endfor %}
    <input type="submit">
</form>
"""
上述代码中有两个一样的form_obj变量名
第一个是提交get的,这时没有数据只会渲染字段名和input标签
第二个是提交post的,这时携带了数据会去与上面类中做校验并把错误信息渲染到页面中,当没有错误信息时则为空。
"""

image

②优化自动生成标签问题

问题:发现上述错误信息会自动生成ul>li标签存储,因为errors的结果是一个列表,浏览器针对列表会自动生成一个ul>li标签。

解决:

#【formsPage.html】中:form.errors后加.0

<span>{{ form.errors.0 }}</span>
#点0拿到的是错误文本信息,不会有ul>li标签(可显示在一行中)

image

③优化英文转中文问题

上述错误信息都是英文看不懂,有没有办法改为其他国家语言

方式一:自定义内容
给字段对象添加errors_messages参数,后面跟字典,键是字段名,值是不符合时的错误信息。
字典中的required是当为空时
邮箱中写invalid是邮箱格式

username = forms.CharField(min_length=3, 
                           max_length=8, 
                           label='用户名',
                           error_messages={
                           'min_length': '用户名最少三个字符',
                           'max_length': '用户名最多八个字符',
                           'required': '用户名不能为空'})

方式二:直接修改系统语言环境(方便!)

"""
from django.conf import global_settings
global_settings才是django内部真正的配置文件 里面有可切换的所有语言
"""
#【settings.py中】:

# LANGUAGE_CODE = 'en-us' # 默认是英文
LANGUAGE_CODE = 'zh-hans' # 系统语言环境改为简体中文

"""
上面的英文报错信息、admin中都会变成中文。
"""
(4)三种校验方式

forms组件针对字段数据的校验 提供了三种类型的校验方式(可一起使用)

①直接写参数

max_length等参数做校验

②正则表达式
#【views.py】中:
from django.core.validators import RegexValidator

class MyForm(forms.Form):
    phone = forms.CharField(
        validators=[
            RegexValidator(r'^[0-9]+$', '请输入数字'), #第一个正则约束
            RegexValidator(r'^159[0-9]+$', '数字必须以159开头') # 第二个正则约束
        ])
③钩子函数(编写代码自定义校验)

类似于面向对象中的super方法,某个数据想做其他操作就拿出来,然后再放回去。

钩子函数:校验的最后一环 是在字段所有的校验参数完成后才触发
应用场景:当想做连接数据库校验局部钩子】或两个字段值的校验全局钩子】都需要使用钩子函数

#【views.py】中

# 字段准备,并有各字段自己的校验
class MyForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8,label='用户名')
    password = forms.CharField(min_length=3, max_length=8,label='密码')
    confirm_pwd = forms.CharField(min_length=3, max_length=8,label='确认密码')
    
    """局部钩子:每次只校验一个字段数据 """      
    # 校验用户名是否已存在
    
    # 定义局部钩子需在后面加字段名
    def clean_username(self):
        # 数据通过第一层forms字段条件筛选的正确数据都会放在cleaned_data中,
        # 拿出里面的数据去数据库进行最后的校验
        username = self.cleaned_data.get('username')
        # 校验models.py中userinfo表里的名字是否一致
        if models.UserInfo.objects.filter(username=username):
            # 当名字是jason时添加错误信息
            self.add_error('username', '用户名jason已存在')
        # 将钩子函数勾出来的数据再放回去
        return username

    
    """全局钩子:一次可以校验多个字段数据"""     
    # 校验两次密码是否一致
    
    # 定义全局钩子布局不跟字段名
    def clean(self):
        # 拿出forms字段筛选正确的数据去判断
        password = self.cleaned_data.get('password')
        confirm_pwd = self.cleaned_data.get('confirm_pwd')
        if not password == confirm_pwd:
            # 当密码不一致时添加错误信息
            self.add_error('confirm_pwd', '两次密码不一致')
        # 将钩子函数勾出来的数据再放回去
        return self.cleaned_data

image

(5)常见参数
min_length           #最小字符
max_length           #最大字符
min_value            #最小值
max_value            #最大值
label                #字段注释
error_messages       #针对某限制条件来自定义错误提示
"""
    username = forms.CharField(
              max_length=8, 
              min_length=2,
              error_messages={
                     'max_length': "超出长度咯",
                     'min_length': "长度不够哦",})
"""
validators           #正则校验器
initial              #默认值
required             #是否必填(默认都是True)

widget               #控制标签的各项属性
————————————————————————————————————————————————
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})

给标签type属性添加数据格式

widget=forms.widgets.TextInput()
widget=forms.widgets.PasswordInput()
...

image

给标签添加样式

widget=forms.widgets.TextInput(attrs={'class':'form-control'})
widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})

image

其他类型渲染

# radio
    gender = forms.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,   # 默认是3
        widget=forms.widgets.RadioSelect()
    )
# select
    hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )
# 多选
    hobby1 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
# 单选checkbox
    keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
# 多选checkbox
    hobby2 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

"""
单选框都是:ChoiceField
多选框都是:MultipleChoiceField
"""

image

(6)源码剖析入口处

切入口:form_obj.is_valid()

7)modelform组件

forms组件使用时需要对照模型类编写代码,并不方便。所以基于forms有了modelform组件
modelform组件forms组件的优化版,使用更简单、功能更强大。虽然操作更方便但是还是需要了解form才能明白modelform组件

校验型组件的目的:大多都是为了数据录入数据库前进行各项审核

(1)常见参数
model = models.Book    # 想对哪张表做校验型组件
fields = "__all__"     # 对哪个字段校验(__all__表示对所有字段)
exclude = None         # 排除的字段
labels = None          # 字段注释
help_texts = None      # 帮助提示信息
widgets = None         # 控制标签的各项属性           
error_messages = None  # 针对某限制条件来自定义错误提示
(2)简单使用
#【urls.py】中
path('ab_mf_form/',views.ab_mf_func),


#【views.py】中
from django import forms
from app01 import models

class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        labels = {
            'username': '用户名'
        }
        widgets = {
            'password': forms.widgets.PasswordInput(attrs={"class": "c1"})
        }

def ab_mf_func(request):
    # 1.产生一个空对象
    modelform_obj = MyModelForm()
    if request.method == 'POST':
        # 把字典数据传给上面modelform类校验
        modelform_obj = MyModelForm(request.POST)
        # 判断数据是否全部符合要求(一个不符合就是False)
        if modelform_obj.is_valid():
            # modelform_obj.save()  # 新增创建数据
            """新增创建数据还是编辑数据 取决于是否有instance参数"""
            edit_obj = models.UserInfo.objects.filter(pk=1).first()
            modelform_obj = MyModelForm(request.POST, instance=edit_obj)
            modelform_obj.save()  # 编辑数据
        else:
            print(modelform_obj.errors)
    return render(request, 'modelFormPage.html', locals())
#【modelFormPage.html】中
<form action="" method="post" novalidate>
    {% for model_obj in modelform_obj %}
        <div>
            {{ model_obj.label }}
            {{ model_obj }}
            {{ model_obj.errors.0 }}
        </div>
    {% endfor %}
    <input type="submit">
</form>

image

15.django中间件

1)简介

​ Django中间件相当于是Django的门户:
​ 1.request请求来的时候需先经过中间件才能到真正的django后端
​ (浏览器给后端发送请求必须经过中间件)
​ 2.response响应走的时候最后需经过中间件才能发出去
​ (后端给浏览器返回数据也必须经过中间件)

​ Django中间件是处理request(请求)和response(响应)的框架级别的钩子,用于在全局范围内改变Django的输入和输出,每个中间件分别负责不同的功能。简单讲也就是在视图函数执行前和执行后都可以做一些额外的操作,点看看源码发现本质就是一个类,类中定义了几个方法,django框架会在请求的特定时间去执行该方法。

中间件主要可以用于:网站访问频率的校验、用户权限的校验全局类型的功能需求

2)默认的七个中间件

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

image

3)自定义中间件的五个方法

注意:一定要在配置文件中注册中间件才可以生效

  • 第一步:需要在app01中创建存储自定义中间件代码的py文件或目录(如果中间件很多)

image

  • 第二步:参考自带中间件的代码编写类并继承

  • 第三步:在该类中编写可以自定义中间件的方法
    常用:process_request请求来、process_response请求走
    了解:process_viewprocess_excptionprocess_template_response

#【mymiddle.py】中:
"""以下用process_request举例"""

from django.utils.deprecation import MiddlewareMixin

class MyMiddleware01(MiddlewareMixin):
    def process_request(self, request):
        print('我是第1个自定义中间件里的process_request')

class Mymiddleware02(MiddlewareMixin):
    def process_request(self, request):
        print('我是第2个自定义中间件里的process_request')   
  • 第四步:去settings.py中注册中间件
MIDDLEWARE = [
    # 注册自己自定义的中间件
    'app01.mymiddleware.mymiddle.MyMiddleware01',
    'app01.mymiddleware.mymiddle.Mymiddleware02',
(1)process_request

研究中间件执行顺序:

#【mymiddle.py】中
from django.utils.deprecation import MiddlewareMixin

class MyMiddleware01(MiddlewareMixin):
    def process_request(self, request):
        print('我是第1个自定义中间件里的process_request')

class Mymiddleware02(MiddlewareMixin):
    def process_request(self, request):
        print('我是第2个自定义中间件里的process_request')
#【urls.py】中
path('index/',views.index_func),

#【views.py】中
def index_func(request):
    print('这是视图层views.py中的index_func')
    return HttpResponse('index view')
#【settings.py】配置文件中注册一下中间件
MIDDLEWARE = [
    # 注册自己自定义的中间件
    'app01.mymiddleware.mymiddle.MyMiddleware01',
    'app01.mymiddleware.mymiddle.Mymiddleware02',

image

以上发现执行顺序是前端浏览器发送请求后会在中间件中从上到下依次执行,最后再经过view视图层

image

以上又发现如果某个中间件中返回了HttpResonse对象,那么请求就会不往后走直接返回

经过以上研究发现可以把中间件这一层看成是全局校验,只要请求来了都可以去提前校验(访问频率、用户权限等)

#总结:
    (1)'请求'来的时候会在配置文件中'从上往下依次执行'每一个注册了的中间件里的方法。如果没有则直接跳过
    (2)如果该方法中返回了HttpResonse对象,那请求不会再往后执行而是原路返回

image

(2)process_response

研究中间件执行顺序:

#【mymiddle.py】中
# process_response需要两个形参request,response。且必须返回response
# **response其实就是后端要发送给前端的东西**
class MyMiddleware01(MiddlewareMixin):
    def process_request(self, request):
        print('我是第1个自定义中间件里的process_request')

    def process_response(self, request, response):
        print('我是第1个自定义中间件里的process_response')
        return response

class Mymiddleware02(MiddlewareMixin):
    def process_request(self, request):
        print('我是第2个自定义中间件里的process_request')

    def process_response(self, request, response):
        print('我是第2个自定义中间件里的process_response')
        return response
#【urls.py】中
path('index/',views.index_func),

#【views.py】中
def index_func(request):
    print('这是视图层views.py中的index_func')
    return HttpResponse('index view')
#【settings.py】配置文件中注册一下中间件
MIDDLEWARE = [
    # 注册自己自定义的中间件
    'app01.mymiddleware.mymiddle.MyMiddleware01',
    'app01.mymiddleware.mymiddle.Mymiddleware02',

image

以上发现执行顺序是:请求来时前端浏览器发送请求后会在中间件中从上到下依次执行,最后再经过view视图层
响应走的时候会再中间件中从下到上依次执行

#总结:
    (1)'响应'走的时候会在配置文件中'从下往上依次执行'每一个注册了的中间件里的方法。如果没有则直接跳过
    (2)该方法需要两个形参request和response。'response就是后端想返回给前端浏览器的数据'。该方法'必须返回该形参'。也可以替换为其他数据,比如后端想返回给前端“你好”,我在中间件中返回一个“滚蛋”,这样前端接收到的就是“滚蛋”。
(3)process_view
#【mymiddle.py】中

class MyMiddleware01(MiddlewareMixin):
    def process_view(self, request, view_func, view_args, view_kwargs):
        print(view_func, view_args, view_kwargs)
        print('我是第1个自定义中间件里的process_view')
"""
request:    是HttpRequest对象
view_func:  是Django即将使用的视图函数
view_args:  是传递给视图的位置参数列表
view_kwargs:是传递给视图的关键字参数字典
"""

image

#总结:
    路由匹配成功之后执行视图函数/类之前自动触发
    顺序是按照配置文件中注册的中间件从上到下依次执行
    #在视图函数提交前需要添加额外的操作可以在该方法里做
(4)process_exception
#【mymiddle.py】中

class MyMiddleware01(MiddlewareMixin):
    def process_exception(self, request, exception):
    print('我是第1个自定义中间件里的process_exception')
"""
exception:就是视图函数的报错信息
"""

image

#总结:
    视图函数/类执行报错自动触发
    顺序是按照配置文件中注册的中间件从下到上依次执行
(5)process_template_response
#【mymiddle.py】中:

class MyMiddleware01(MiddlewareMixin):
    def process_template_response(self, request, response):
    print('我是第1个自定义中间件里的process_template_response')
    return response


#【views.py】中:

def index_func(request):
    print('这是views.py中的index_func')
    def render():
        return HttpResponse('我是谁')
    obj = HttpResponse('index view')
    obj.render = render
    return obj

image

#总结:
    视图函数/类返回的HttpResponse对象含有render并且对应一个方法的时候自动触发
    顺序是按照配置文件中注册的中间件从下到上依次执行

4)基于django中间件的功能设计

django中间件会把各个功能制作成配置文件的字符串形式:(其实是模块路径)
如果想拥有该功能就编写对应的字符串
如果不想有该功能则注释掉对应的字符串

(1)importlib模块(利用字符串导模块)

那么如何利用字符串导入模块?
需要借助importlib模块,该模块中有一个方法会自动把字符串按.切割,如果字符串是aaa.bbb.ccc.b,那么就会自动转成from aaa.bbb.ccc import b

但是该方法最小单位只能是模块名,不能导模块中具体的变量名

#【bbb>>b.py】中
desc = '芜湖'

————————————————————————————————————
#【a.py】中
import importlib

s1 = 'bbb.b'
res = importlib.import_module(s1)
print(res.desc) # 芜湖

image

(2)importlib实战应用

需求:模拟编写一个消息通知功能(同时通过微信、qq、邮箱功能给客户发消息,也可以随时禁止某个功能让另外两个功能发)

普通版本:基于函数封装的版本

该版本看起来较为普通一点都不帅!很没技术含量!不建议!

#【notify.py】

def wechat(content):
    print(f'微信通知:{content}')


def qq(content):
    print(f'qq通知:{content}')


def email(content):
    print(f'邮箱通知:{content}')
#【start.py】

from notify import *


def send_all(content):
    wechat(content)
    qq(content)
    email(content)


if __name__ == '__main__':
    send_all('老板在吗今天打8折哦')

image

牛逼版本:基于django中间件的功能设计

该版本会让人眼前一亮觉得很牛逼

#【settings.py】中
NOTIFY_LIST = [
    'notify.email.Email',
    'notify.qq.Qq',
    'notify.wechat.Wechat',
]

————————————————————————————————
#【start.py】中
import notify  # 导包名其实导的是__init__.py

if __name__ == '__main__':
    notify.send_all('老板今晚7折来嘛')
#【notify>>qq.py】
class Qq(object):
    def __init__(self):
        pass  # 模拟发送消息前需要做的准备操作

    def send_msg(self, content):
        print(f'qq通知:{content}')
________________________________________________
#【notify>>wchat.py】
class Wechat(object):
    def __init__(self):
        pass  # 模拟发送消息前需要做的准备操作

    def send_msg(self, content):
        print(f'微信通知:{content}')
________________________________________________
#【notify>>email.py】
class Email(object):
    def __init__(self):
        pass  # 模拟发送消息前需要做的准备操作

    def send_msg(self, content):
        print(f'邮箱通知:{content}')
#【notify>>__init__.py】
from settings import NOTIFY_LIST
import importlib

def send_all(content):
    for full_path in NOTIFY_LIST:  # path='notify.email.Email',
        module_path, class_str_name = full_path.rsplit('.', maxsplit=1)  # 'notify.email' 'Email'
        # 1.利用字符串导入模块 拿到模块名
        module_name = importlib.import_module(module_path)  # module_name=from notify import email 也就是模块名
        # 2.利用反射从模块中获取字符串对应的类名
        class_name = getattr(module_name, class_str_name)  # Email
        # 3.利用类名加括号产生对象
        obj = class_name()
        # 4.对象调用发送消息的方法
        obj.send_msg(content)

image

16.cookie与session

1)cookie与session简介

HTTP协议四大特性:
   1.基于请求响应	
   2.基于TCP、IP作用于应用层之上的协议
   3.无状态:服务端不保存客户端的状态
"""
       互联网刚兴起时所有人访问网址获取到的都是一样的数据,服务端是无法识别客户端的。
       随着互联网的发展,出现了一些如京东、淘宝等网站服务端需保存客户端的状态(密码等),否则不知道谁是谁。
       这个时候cookie与session就应运而生了。
"""
   4.无连接

以登录为例:
如果不保存用户的登录状态就意味着用户每次访问都需要重复输入用户名和密码,甚至如果从该网站点击链接跳转到子页面也需要重复输入用户名和密码非常的不方便!

​ 所以开发者们想到了一个解决办法早期的cookie,当用户第一次登录成功后就将用户的用户名和密码返回给浏览器,浏览器会把用户名和密码保存起来,然后当下次发送请求时浏览器会自动把用户名和密码发送给服务端。但是这样并不安全因为用户名和密码是保存在客户端上的,离开电脑后其他人就可以获取到用户名和密码

​ 虽然cookie一定程度上解决了无状态,但由于在客户端种不安全容易被窃取,则有了session,当第一次登录成功后服务端会返回一个随机字符串,客户端再访问服务端时只需要把这个随机字符串给了服务端,服务端内部去比对有没有该随机字符串,有则可以正常登录。这样安全的原因是客户端上仅保存了一个随机字符串,即使被人获取到也没办法知道真正的密码。这个时候这个随机的字符串就是优化后的cookie。但是如果有人截获到了该随机字符串那就可以冒充他,其实也是有安全隐患的,所以在web领域是没有绝对的安全也没有绝对的不安全

cookie:保存在客户端与用户状态(登录)相关的信息
session:保存在服务端与用户状态(登录)相关的信息
session的工作需要依赖于cookie,目前所有能识别用户身份的网站都需要使用cookie(客户端浏览器设置中也有权拒绝保存所有的cookie数据,但是会导致所有页面的登录失效)

image

2)django操作cookie

Ctrl+Shift+del 可清除页面缓存和cookie

(1)设置cookie

设置cookie是设置给浏览器的,所以需要通过response的对象来设置,最后返回该对象

obj = HttpResponse(..)
obj.set_cookie(key,value)
return obj
——————————————————————————————
# 重定向到首页,赋值给一个变量
obj = redirect('/home/') 
# 返回前设置一下cookie属性(用name字段保存用户名)
obj.set_cookie('name', username) 
return obj
#set_cookie()中还可以跟额外参数:

key:这个cookie的key。
value:这个cookie的value。
max_age:最长的生命周期。单位是秒。
expires:过期时间。跟max_age是类似的,只不过这个参数需要传递一个具体的日期,比如datetime或者是符合日期格式的字符串。如果同时设置了expires和max_age,那么将会使用expires的值作为过期时间。
path:对域名下哪个路径有效。默认是对域名下所有路径都有效。
domain:针对哪个域名有效。默认是针对主域名下都有效,如果只要针对某个子域名才有效,那么可以设置这个属性.
secure:是否是安全的,如果设置为True,那么只能在https协议下才可用。
httponly:默认是False。如果为True,那么在客户端不能通过JavaScript进行操作。
"""
1.如果没有设置cookie失效日期,则仅保存到关闭浏览器为止
2.如果把cookie对象的expires属性设置为Minvalue,则表示永不过期
"""
(2)获取cookie
request.COOKIES.get(key)
________________________
# 获取指定键名的cookie
request.COOKIES.get('name')
(3)清除cookie
obj = HttpResponse(..)
# 删除用户浏览器上之前设置的cookie值
obj.delete_cookie(key)
return obj
——————————————————————————
obj = redirect('/home/') 
obj.delete_cookie('name')
return obj
(4)实操

编写一个真正的用户登录功能:

#【urls.py】中
path('login/', views.login_func),
path('home/', views.home_func),

#【login.html】中
<form action="" method="post">
    <p>username:
        <input type="text" name="username">
    </p>
    <p>password:
        <input type="password" name="password">
    </p>
    <input type="submit">
</form>
#【views.py】中
from django.shortcuts import render, HttpResponse, redirect

def login_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 如果用户名密码对则跳转到home页面
        if username == 'jason' and password == '123': 
            return redirect('/home/') 
    # 否则跳转到登录页面 
    return render(request, 'login.html')

def home_func(request):
    return HttpResponse('home页面(登录过的用户才能查看的页面)')

以上方法有个不足处:不足1.用户直接输入home页面就可以不登录则进入home页面中

优化不足1:有cookie才能进入

加入cookie,只有拥有cookie才可以进入home页面

#【views.py】中
from django.shortcuts import render, HttpResponse, redirect

def login_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123': # 如果用户名密码对
            obj = redirect('/home/') # 重定向到首页,赋值给一个变量
            obj.set_cookie('name', username) # 返回前设置一下cookie属性(用name字段保存用户名)
            return obj
    return render(request, 'login.html')

def home_func(request):
    # 先获取用户cookie数据判断是否登录
    if request.COOKIES.get('name'):
        # 只有携带了cookie才可以进入home页面
        return HttpResponse('home页面(登录过的用户才能查看的页面)')
    # 如果没有则跳转到登录页面
    return redirect('/login/')

image

这样如果想直接访问home页面就不允许了,必须正确登录后拥有了cookie才可以访问,且只要cookie不清除随时可以进入home页面。
但是还有不足之处:
不足2.目前只有一个home页面,如果有多个子页面则需要在每一个视图函数前做一个判断是否存在cookie
不足3.如果我想点击子页面由于没登录会自动跳转到登录页面,一但登录成功怎么才能返回到我要点击的子页面上

优化不足2:装饰器

增加验证是否登录的装饰器

#【views.py】中
def login_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            obj = redirect('/home/')  # 如果用户名密码对则重定向到首页,赋值给一个变量
            obj.set_cookie('name', username)  # 返回前设置一下cookie属性(用name字段保存用户名)
            return obj
    return render(request, 'login.html')


# 校验用户是否登录装饰器
def login_auth(func_name):
    def inner(request,*args, **kwargs):
        # 获取用户cookie数据判断是否登录
        if request.COOKIES.get('name'):
            res = func_name(request,*args, **kwargs)
            return res
        # 如果没有cookie则跳转登录页面
        return redirect('/login/')
    return inner

#主页面
@login_auth
def home_func(request):
    return HttpResponse('home页面(登录过的用户才能查看的页面)')

#子页面1
@login_auth
def home1_func(request):
    return HttpResponse('home1页面(登录过的用户才能查看的页面)')

#子页面2
@login_auth
def home2_func(request):
    return HttpResponse('home2页面(登录过的用户才能查看的页面)')

这样不管我有多少个子页面 我都可以去统一去做校验,不足2基本完成。

优化不足3:跳转原页面

想办法获取到用户第一次要进入哪个页面,且让跳转页面不要写死。

# 补充知识:获取当前用户请求的url

print(request.path)             #获取路由后缀
print(request.path_info)        #获取路由后缀
print(request.get_full_path())  #获取路由后缀+?后面携带的参数
"""
/home/
/home/
/home/?xxx=a
"""
#【views.py】中
def login_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            # 通过GET拿到用户一开始想要进入的页面后缀
            target_path = request.GET.get('next')
            if target_path:
                # 如果有值则重定向到到用户一开始要进入的页面
                obj = redirect(target_path)
            else:
                # 如果没有则则重定向到首页
                obj = redirect('/home/')
            obj.set_cookie('name', username)  # 返回前设置一下cookie属性(用name字段保存用户名)
            return obj
    return render(request, 'login.html')


# 校验用户是否登录装饰器
def login_auth(func_name):
    def inner(request, *args, **kwargs):
        # 获取输入的路由后缀
        target_path = request.path  
        # 获取用户cookie数据判断是否登录
        if request.COOKIES.get('name'):
            res = func_name(request, *args, **kwargs)
            return res
        # 如果没有cookie则跳转登录页面(且url后面会携带一个本来要进入的页面后缀)
        return redirect('/login/?next=%s' % target_path)

    return inner


# 主页面
@login_auth
def home_func(request):
    return HttpResponse('home页面(登录过的用户才能查看的页面)')


# 子页面1
@login_auth
def home1_func(request):
    return HttpResponse('home1页面(登录过的用户才能查看的页面)')


# 子页面2
@login_auth
def home2_func(request):
    return HttpResponse('home2页面(登录过的用户才能查看的页面)')

3)django操作session

由于session是保存在服务端上面的数据,应该有个地方来存储session,只需要执行数据库迁移命令即可, django会自动创建很多需要的表,会把session保存在django_session表中

django默认的session失效时间是14天

image

(1)设置session
request.session['key'] = value

#底层原理:
    1.请求来时生成一个随机字符串作为session_key字段存入django_session表中
    2.对value数据做加密处理作为session_data字段存入django_session表中
    3.将随机字符串也发送一份给客户端保存(cookie)
      名称:sessionid
      值:django_session中的session_key(随机字符串)
(2)获取session
request.session.get('key')

#底层原理:
    1.自动获取客户端cookie中的值(session_key随机字符串)
    2.去django_session表中根据随机字符串获取加密的数据
    3.自动解密数据

image

(3)设置过期时间
request.session.set_expiry(value)

"""
括号内的value可以写4种类型的参数
  1.整数                             多少秒后失效
  2.日期对象(dattatime或timedalta)   指定日期失效
  3.0                                当前浏览器窗口关闭立刻失效
  4.None                             取决于django内部全局session默认的失效时间
"""
(4)清除session
request.session.delete()  # 只删服务端的 客户端的不删
request.session.flush()   # 浏览器和服务端都清空(推荐使用)
(5)session存储位置

session的存储位置可以自定义

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

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

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
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'   # 引擎
(6)session其他设置
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,默认修改之后才保存(默认)
(7)实操

17.csrf跨站请求伪造

1)csrf简介

​ CSRF(Cross-site request forgery)跨站请求伪造,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

​ 简单理解就是:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求时合法的,但却是攻击者期望的操作。如:以你的名义发送邮件、消息、添加管理员、购买商品、转账等

csrf漏洞检测

检测CSRF漏洞是一项比较繁琐的工作,最简单的方法就是抓取一个正常请求的数据包,去掉Referer字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。随着对CSRF漏洞研究的不断深入,不断涌现出一些专门针对CSRF漏洞进行检测的工具,如CSRFTester,CSRF Request Buider等。

以CSRFTester工具为例,CSRF漏洞检测工具的测试原理如下︰使用CSRFTester进行测试时,首先需要抓取我们在浏览器中访问过的所有链接以及所有的表单等信息,然后通过在CSRFTester中修改相应的表单等信息,重新提交,这相当于一次伪造客户端请求。如果修改后的测试请求成功被网站服务器接受,则说明存在CSRF漏洞,当然此款工具也可以被用来进行CSRF攻击。

2)模拟钓鱼网站案例

​ 假设有一个和银行一样的网站页面,用户在该假银行页面上转账,账户的钱会减少但是收益人却不是要转账的账户。
​ 其实内部的本质就是在钓鱼网站页面中,登录正常登录,但是针对转账账户input框提供了一个没有name属性的框,然后在内部隐藏一个已经写好name和value的input框,那么当用户在向后端提交数据时只会提交写好name和value的input框。

真银行网站:

#【urls.py】
path('transfer/', views.transfer_func),

#【views.py】
def transfer_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_name = request.POST.get('target_name')
        target_money = request.POST.get('target_money')
        print(f"{username}给{target_name}转了{target_money}元")
    return render(request, 'tansfer.html')

#【tansfer.html】
<h1>这是真正的银行网站</h1>
<form action="" method="post">
    <p>用户名:
        <input type="text" name="username">
    </p>
    <p>转账账户:
        <input type="text" name="target_name">
    </p>
    <p>转账金额:
        <input type="text" name="target_money">
    </p>
    <input type="submit">
</form>

假银行网站

再开一个django项目,记得更改一个端口号。
#【urls.py】、【views.py】中一样

#【tansfer.html】
<h1>这是假的银行网站</h1>
<form action="http://127.0.0.1:8000/transfer/" method="post">
    <p>用户名:
        <input type="text" name="username">
    </p>
    <p>转账账户:
        # 仅在转账账户上做手脚
        <input type="text">
        <input type="text" name="target_name" value="黑客账户" style="display: none">
    </p>
    <p>转账金额:
        <input type="text" name="target_money">
    </p>
    <input type="submit">
</form>

image

可以发现以上两个网站几乎一样,那么如何区分真假网站页面发送的请求?

添加独一无二的标识信息从而区分正规网站和钓鱼网站的请求

3)csrf校验策略

网站在给用户返回一个具有提交数据功能页面的时候会给该页面加一个唯一标识,当这个页面朝后端发送post请求的时候 后端会先校验唯一标识,如果唯一标识不对直接403拒绝如果成功则正常执行

首先需要去配置文件中把csrf中间件打开

(1)方式1:form表单csrf策略

​ 在页面提交数据的地方,也就是form表单内部写 {% csrf_token %}添加唯一标识。当提交post请求后唯一标识会跟着回到服务端,服务端会先拿着唯一标识去判断然后再决定是否处理这次的请求。

<form action="" method="post">
    # 添加唯一标识
    {% csrf_token %}
    <p>用户名:
        <input type="text" name="username">
    </p>
    <p>转账账户:
        <input type="text" name="target_name">
    </p>
    <p>转账金额:
        <input type="text" name="target_money">
    </p>
    <input type="submit">
</form>

image

(2)方式2:ajax请求csrf策略

第一种:自己动手取值 太繁琐不推荐!!

<form action="" method="post">
    // 唯一标识
    {% csrf_token %}
    <p>用户名:
        <input type="text" name="username">
    </p>
    <p>转账账户:
        <input type="text" name="target_name">
    </p>
    <p>转账金额:
        <input type="text" name="target_money">
    </p>
    <input type="submit">
    <input type="button" id="d1" value="发送ajax请求">
        
<script>
    $('#d1').click(function(){
        $.ajax({
            url:'',
            type:'post',
            // 使用JQuery取出csrfmiddlewaretoken的值,拼接到data中
            data:{'csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},
            success:function (args){
            }
        })
    })
</script>

第二种:模板语法自动获取(前后端分离时不方便!!!)

// 使用JQuery取出csrfmiddlewaretoken的值,拼接到data中
data:{ 'csrfmiddlewaretoken':'{{ csrf_token }}','username':'jason'},        

第三种:通用方式直接引入js脚本即可(扩展性最高)

项目中左侧新建静态文件资源目录static>>myjs.js,把下列代码拷贝到myjs中。

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);
    }
  }
});

页面上直接把该js文件加载过来即可 不要忘记配置文件里的静态文件资源配置
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

//加载myjs.js文件
<script src="/static/myjs.js"></script>

<script>
    $('#d1').click(function(){
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'jason'},
            success:function (args){
            }
        })
    })
</script>

image

4)csrf相关装饰器

思考:

1.整个django项目都校验csrf 但是某些视图函数\类不想校验 如何处理?
2.整个django项目都不校验csrf 但是某些视图函数\类需要校验 如何处理?

(1)FBV(基于函数的视图)中添加装饰器

导入模块from django.views.decorators.csrf import csrf_protect,csrf_exempt

@csrf_protect  # 该函数校验csrf
# @csrf_exempt  # 该函数不校验csrf
def transfer_func(request):
    pass
(2)CBV(基于类的视图)中添加装饰器

导入模块from django.utils.decorators import method_decorator

针对CBV不能直接在方法上添加装饰器,需要借助于专门添加装饰器的方法

方法一:直接在类中的某个方法上添加,指名道姓的添加

@method_decorator(csrf_protect)
   def post(self, request):
       return HttpResponse('这是cbv的post视图')

方法二:直接在类名上添加并指定给类下的哪个方法添加装饰器

@method_decorator(csrf_protect, name='get')
class MyLoginView(views.View):
    def get(self, request):
        return HttpResponse("这是cbv的get视图")

方法三:重写dispatch方法并让整个类里的方法生效(当方法有很多时用)

class MyViews(views.View):
    @method_decorator(csrf_protect)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def post(self, request):
        return HttpResponse('这是cbv的post视图')

注意:
csrf_exempt只能有一种添加方法>>>方法三dispatch
csrf_protect 上述三种方法都可以生效

18.auth认证模块

​ auth认证模块是django自带的用户认证模块,在开发网站的时候无可避免的需要设计实现网站的用户系统,此时需要实现包括用户注册、登录、认证、注销、修改密码、校验用户是否登录等功能,这些功能几乎是所有软件都需要开发的功能,在写这些功能时需要花费一些时间书写代码逻辑,那么django自带的auth模块可以快速帮我们完成,它默认使用auth_user表来存储用户数据

​ 创建好django项目后执行数据库迁移命令就可以生出auth_user表

#里面的字段分别有:

password:密码。经过哈希过后的密码。
last_login:上次登录的时间。
is_superuser:是否是超级管理员。如果是超级管理员,那么拥有整个网站的所有权限。
username: 用户名。150个字符以内。可以包含数字和英文字符,以及_、@、+、.和-字符。不能为空,且必须唯一!
first_name:歪果仁的first_name,在30个字符以内。可以为空。
email:邮箱。可以为空。
is_staff:是否可以进入到admin的站点。代表是否是员工。这个字段如果不使用admin的话,可以自行忽略,不影响使用
is_active:是否是可用的。对于一些想要删除账号的数据,我们设置这个值为False就可以了,而不是真正的从数据库中删除。
date_joined:账号创建的时间。
last_name:歪果仁的last_name,在150个字符以内。可以为空。

image

​ django项目启动后可以直接访问admin路由,需要输入用户名和密码,数据参考的就是auth_user表且必须是管理员用户才可以进入。

image

创建admin管理员账户

# python38 manage.py createsuperuser

image

1)auth认证相关模块及操作

需求:基于auth_user表编写用户相关的各项功能
注册、登录、校验用户是否登录、修改密码、退出登录等

(1)用户注册创建功能
①校验用户是否存在
②创建普通用户
③创建超级用户
# 导入数据库中的User表
from django.contrib.auth.models import User

1.# 校验用户是否存在
res = User.objects.filter(username=username)

2.# 创建普通用户 (可以自动给密码加密)
res = User.objects.create_user(username=username, password=password)

3.# 创建超级用户(必须要传邮箱)
res = User.objects.create_superuser(username=username,password=password,email=email)
#【urls.py】
path('register/', views.register_func),

#【register.html】
<form action="" method="post">
    {% csrf_token %}
    <p>用户名:
        <input type="text" name="username">
    </p>
    <p>密码:
        <input type="password" name="password">
    </p>
    <input type="submit">
</form>
"""
配置文件中的csrf不要忘记打开
"""
#【views.py】

# 导入数据库中的User表
from django.contrib.auth.models import User

def register_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 1.校验用户名是否存在
        res = User.objects.filter(username=username)
        if res:
            return HttpResponse('用户名已存在')
        # 2.创建用户
        User.objects.create_user(username=username,password=password)
    return render(request, 'register.html')

image

(2)用户登录校验功能
①校验用户名和密码是否正确
②判断用户是否登录
# 导入auth模块
from django.contrib import auth

1.# 校验用户名和密码是否正确
user_obj = auth.authenticate(request, username=username, password=password)
"""
底层逻辑:
    自动查找auth_user表,自动给前端发来的密码加密然后比对,如果比对正确返回的是数据对象,错误则返回None
注意:
    括号里必须同时传用户名和密码
    
之后校验user_obj有没有值即可判断用户名和密码是否正确
"""

2.# 判断用户是否登录(当登录成功则返回给客户端登录的凭证(令牌、随机字符串))
auth.login(request, user_obj) # 类似于request_session[key]=user_obj

_____________________________
当执行完上述操作后就可以通过request.user直接获取到当前登录用户对象数据
print(request.user) # 获取要么是登录的用户名,要么是没登录的AnonymousUser(匿名用户)
print(request.user.is_authenticated) # 判断当前用户对象是否已登录

image

#【views.py】

# 导入auth模块
from django.contrib import auth

def login_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 1.校验用户名和密码是否正确(这里省时间一起校验)
        # 由于auth_user表中的密码是加密的,只能使用auth模块提供的方法
        user_obj = auth.authenticate(request, username=username, password=password)
        if user_obj:
            # 2.当登录成功则返回给客户端登录的凭证(令牌、随机字符串)
            # 自动操作django_session表
            auth.login(request, user_obj)  # 类似于request_session[key]=user_obj
            return HttpResponse('登录成功')
    return render(request, 'login.html')
③首页根据是否登录显示不同效果

额外需求:如何实现类似博客园右上角如果登录了就显示用户的名字,如果没登录就显示注册登录

#【views.py】
def home_func(request):
    return render(request,'home.html',locals())

#【home.html】
    # 判断当前用户对象是否已登录
    {% if request.user.is_authenticated %}
        <h1>{{ request.user.username }}</h1>
    {% else %}
        <a href="">注册</a>
        <a href="">登录</a>
    {% endif %}

image

(3)校验用户是否登录装饰器

需求:只有登陆的用户才可以查看Index页面

auth模块一条龙服务,连装饰器都已经提前帮我们做好了,只要导入login_required模块,给需要的地方装上装饰器即可,当访问某个路由时会自动校验是否拥有令牌(随机字符串)

①局部配置

可以让不同视图函数跳转不同页面,但是一但视图函数特别多需要跳转同一个页面时就需要用全局配置

# 导入装饰器模块
from django.contrib.auth.decorators import login_required

@login_required(login_url='/login/')  # 局部配置 当没登录时会跳转到该页面中
def index_func(request):
    return HttpResponse('这是只有登录后才能看的index页面')
②全局配置
# 导入装饰器模块
from django.contrib.auth.decorators import login_required

@login_required  # 全局配置
def index_func(request):
    return HttpResponse('这是只有登录后才能看的index页面')
————————————————————————————————————————————————————————————
然后去配置文件中加一条配置:LOGIN_URL = '/login/'

"""
这样就可以让所有的全局配置当未登录时跳转到同一个页面上
"""

当全局和局部都配置时,会听局部配置的

(4)用户修改密码
①校验原密码是否正确
②修改密码
1.# 校验原密码是否正确
is_right = request.user.check_password(原密码)
"""判断is_right 如果True则原密码正确,否则原密码错误"""
    
2.# 修改密码
request.user.set_password(新密码)
request.user.save() # 记得保存
#【urls.py】
path('set_pwd/',views.set_pwd_func),

#【set_pwd.html】
<form action="" method="post">
    {% csrf_token %}
    <p>原密码:
        <input type="text" name="old_pwd">
    </p>
    <p>新密码:
        <input type="text" name="new_pwd">
    </p>
    <input type="submit">
</form>
#【views.py】

# 只有登录用户才能修改密码
@login_required(login_url='/login/')
def set_pwd_func(request):
    if request.method == 'POST':
        old_pwd = request.POST.get('old_pwd')
        new_pwd = request.POST.get('new_pwd')
        # 1.判断原密码是否正确
        is_right = request.user.check_password(old_pwd)
        if not is_right:
            return HttpResponse('原密码错误')
        # 2.修改原密码
        request.user.set_password(new_pwd)
        # 不要忘记保存
        request.user.save()  
    return render(request, 'set_pwd.html')
(5)用户退出登录

当调用该函数时当前请求的session信息会全部清除,该用户即便未登录也不会报错

from django.contrib.auth import logout

@login_required(login_url='/login/')
def logout_func(request):
    auth.logout(request)
    return HttpResponse('已退出登录')

2)扩展auth_user表

​ 如果想使用auth模块的功能 又想扩展auth_user表的字段,可以通过继承内置的AbstractUser类,来定义一个自己的Model类。
​ django给我们自动创建的一张user表,如果要用auth模块就必须使用或继承这张表,这样既能根据项目需求灵活设计用户表,又能使用django强大的认证系统。

#步骤1:【models.py】模型层编写模型类继承AbstractUser
    from django.contrib.auth.models import AbstractUser
    class UserInfo(AbstractUser):
        # 填写AbstractUser表中没有的字段
        phone = models.BigIntegerField()
        
#步骤2:【settings.py】在配置文件中新增一个声明替换关系(把表替换成自定义的模型表)
        AUTH_USER_MODEL = 'app01.UserInfo'

需注意有一个前提条件

1.继承之前没有执行过数据库迁移命令
   auth_user不能被创建过,如果执行过该命令就需要重新换一个库
    
2.继承的类里不要覆盖AbstractUser里面的字段

"""
补充:
如果继承了AbstractUser,
那么执行数据库迁移命令时auth_user表就不会再被创建出来,
而userinfo表中会出现auth_user表中的所有字段外加扩展字段
"""
posted @ 2022-12-09 00:22  oreox  阅读(297)  评论(0编辑  收藏  举报