Django框架

Django框架相关

推导模拟实现Django框架

代码编写web框架

web框架的本质

z2rE80.png

从上图来看,web框架就是连接前端与数据库的中间介质,负责对数据进行处理,以主要的业务逻辑为支持

编写思路

如果是从代码角度来看,web框架就是我们之前所写的socket服务端,对客户端传来的数据与请求进行处理,将数据库的数据反馈到客户端

那么我们使用代码编写web框架的思路大概如下

1.编写socket服务端代码
2.浏览器访问响应无效>>>:HTTP协议
sock.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 添加相应的数据打包格式
3.根据网址后缀的不同获取不同的页面内容
/index、/login、
4.想办法获取到用户输入的后缀>>>:请求数据
5.找到网址后缀所在位置
	GET /login HTTP/1.1
    请求文件的首行位置
    	请求的两种类型
        GET请求
        索要数据
  		POST请求
        提交数据
6.对请求数据进行处理获取网址后缀
利用字符串的切割属性,用空格对数据进行切割,索引1
	data.split(' ')[1]

整体代码

import socket


server = socket.socket()  # TCP UDP
server.bind(('127.0.0.1', 8080))  # IP PORT
server.listen(5)  # 半连接池


while True:
    sock, address = server.accept()  # 等待连接
    data = sock.recv(1024)  # 字节(bytes)
    # print(data.decode('utf8'))  # 解码打印
    sock.send(b'HTTP/1.1 200 OK\r\n\r\n')
    data_str = data.decode('utf8')  # 先转换成字符串
    target_url = data_str.split(' ')[1]  # 按照空格切割字符串并取索引1对应的数据
    # print(target_url)  # /index /login /reg
    if target_url == '/index':
        # sock.send(b'index page')
        with open(r'myhtml01.html','rb') as f:
            sock.send(f.read())
    elif target_url == '/login':
        sock.send(b'login page')
    else:
        sock.send(b'home page!')
代码初步优化

实际的代码写出来之后,只是初步具备了一些功能,但是还是存在一些问题
1.socket代码过于重复

2.针对请求数据处理繁琐

3.后缀匹配逻辑过于低端(if...elif...分支重复)
优化思路:

借助于wsgiref模块对代码进行优化

作为python的内置模块,它有很强大的功能,是大多数web框架底层使用的代码,对于socket代码进行了封装,对数据的处理方法也进行了封装,极大地简化了我们的代码编写

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']
    return [b'hello wsgiref module']


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

1.固定代码启动服务端
2.查看处理之后的request,数据是以字典的形式组成的
3.根据不同的网址后缀返回不同的内容>>>:研究大字典键值对

PATH_INFO对应的就是网址的后缀,直接用.get方法取出

根据不同结果进行判定,决定返回数据内容

4.立刻解决上述纯手写的问题一与问题二

函数封装优化

那么,最后的问题,关于条件的筛选,我们可以参考以往的内容

将代码进行函数封装,将他们以网页的后缀作为键,函数名作为值封装为功能字典,当后缀名符合时直接在字典中取值进行函数调用即可

views.py		存储核心业务逻辑(功能函数)
urls.py			存储网址后缀与函数名对应关系
templates目录	   存储html页面文件

如果仅仅是这样还是不够的,request中包含了所有用户请求信息,那么如果在之后的业务拓展中我们需要使用达到数据,那么我们就需要在传递数据时将这个字典传递出去

那么我们在拆分封装函数时,就可以将这个字典当做参数传递给函数,这样在之后拓展时我们就可以调用字典中的数据

到这里我们的框架基本已经趋于完善

但是网页中的内容也决定了网页分为动态网页与静态网页两种

动静态网页

动态网页
页面数据来源于后端,实时从后端获取数据并进行展示
静态网页
页面数据直接写在前端,不会与后端产生互动

我们可以在后端定义一个函数,当用户访问某个指定的网页时,后端代码获取当前时间,并将时间传递到网页进行展示

z2ol8S.png

这样的话,我们需要将获取到的时间传递到HTML文档中指定的标签中,首先要先读取到HTML内的内容,将指定标签的内容更换为我们所获取到的时间然后返回到浏览器页面

字符串类型的数据我们可以使用replace进行替换,那么如果是其他类型的数据呢,这个时候就需要使用到另一个内置模块:jinja2

jinja2模块

它同样也是python的内置模块,直接使用pip工具进行下载或者在pycharm中进行下载即可

pip3 install jinja2
from jinja2 import Template


def get_dict_func(request):
    user_dict = {'name': 'jason', 'age': 18, 'person_list': ['阿珍', '阿强', '阿香', '阿红']}
    with open(r'templates/get_dict_page.html', 'r', encoding='utf8') as f:
        data = f.read()
    temp_obj = Template(data)  # 将页面数据交给模板处理
    res = temp_obj.render({'d1': user_dict})  # 给页面传了一个 变量名是d1值是字典数据的数据
    return res

<p>{{ d1 }}</p>
<p>{{ d1.name }}</p>
<p>{{ d1['age'] }}</p>
<p>{{ d1.get('person_list') }}</p>

jinja2模块最大的作用就是可以让我们在HTML文档中以类似于在python中操作数据一样去操作我们传递到HTML中的数据

{% for user_dict in user_data_list %}
    <tr>
        <td>{{ user_dict.id }}</td>
        <td>{{ user_dict.name }}</td>
        <td>{{ user_dict.age }}</td>
    </tr>
{% endfor %}

它还支持进行for循环,但是我们需要搞清楚的是,所有数据的处理都是在jinja2模块处理好之后才传递到HTML文件中的,并不是真的在HTML文档中操作数据

那么对于前端、后端以及数据库三者的联动,真正实现web框架我们也就可以实现了

服务端主体代码

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


def run(request, response):
    """
    :param request: 请求相关数据
    :param response: 响应相关数据
    :return: 返回给客户端的真实数据
    """
    response('200 OK', [])  # 固定格式 不用管它
    path_info = request.get('PATH_INFO')  # /index
    func_name = None  # 定义一个用于后续存储函数名的变量
    for url_tuple in urls:  # (后缀,函数名)
        if path_info == url_tuple[0]:
            func_name = url_tuple[1]
            break  # 一旦匹配成功 后续无序匹配直接结束
    if func_name:
        res = func_name(request)
    else:
        res = error_func(request)
    return [res.encode('utf8')]


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

urls.py

from views import *

urls = [
    ('/index', index_func),
    ('/login', login_func),
    ('/reg', reg_func),
    ('/logout', logout_func),
    ('/get_time', get_time_func),
    ('/get_dict', get_dict_func),
    ('/get_user', get_user_func)
]

views.py

def index_func(request):
    return 'index function'


def login_func(request):
    with open(r'templates/myhtml02.html', 'r', encoding='utf8') as f:
        return f.read()
    # return 'login function'


def reg_func(request):
    return 'reg function'


def error_func(request):
    return '404 Not Found'


def logout_func(request):
    return 'logout function'


def get_time_func(request):
    import time
    ctime = time.strftime('%Y-%m-%d %X')
    with open(r'templates/get_time_page.html', 'r', encoding='utf8') as f:
        data = f.read()
    # 在后端先把数据传递到文件内容上 之后再发送给浏览器 字符串的替换
    res = data.replace('sadkasjdklad', ctime)
    return res


from jinja2 import Template


def get_dict_func(request):
    user_dict = {'name': 'jason', 'age': 18, 'person_list': ['阿珍', '阿强', '阿香', '阿红']}
    with open(r'templates/get_dict_page.html', 'r', encoding='utf8') as f:
        data = f.read()
    temp_obj = Template(data)  # 将页面数据交给模板处理
    res = temp_obj.render({'d1': user_dict})  # 给页面传了一个 变量名是d1值是字典数据的数据
    return res


import pymysql


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

templates目录内文件

user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <div class="row">
            <h1 class="text-center">数据展示</h1>
            <a href="/index">点我试试</a>
            <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 user_dict in user_data_list %}
                        <tr>
                            <td>{{ user_dict.id }}</td>
                            <td>{{ user_dict.name }}</td>
                            <td>{{ user_dict.age }}</td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>

这样我们也就实现了整个web框架的手写工作

Python中主流web框架

所谓网络框架是指这样的一组Python包,它能够使开发者专注于网站应用业务逻辑的开发,而无须处理网络应用底层的协议、线程、进程等方面。这样能大大提高开发者的工作效率,同时提高网络应用程序的质量

对于最初接触框架的程序员,建议不要混着学习,否则对于框架的把我不够熟练容易彻底搞混而导致任何框架都不能熟练掌握

z2HVu4.png

大而全,自身自带的功能组件非常的多,类似于航空母舰

z2HnER.png

异步非阻塞,速度极快效率极高

z2HUUI.png

小而精 自身自带的功能组件非常的少 类似于游骑兵
几乎所有的功能都需要依赖于第三方模块

还有sanic、fastapi等比较小众的框架

Django简介

到目前为止,Django的最新版本发展到了4.0,其中1.X与2.X属于同步架构,3.X与4.X都已经支持异步架构,而在上图中,LTS版本是历代Django框架中最为稳定的版本

对于版本而言,个版本之间的底层差异并不大,可能只是在旧版的基础上添加了新的功能,而在学习过程中,建议使用2.2版本

运行Django的注意事项

1.django项目中所有的文件名目录名不要出现中文
2.计算机名称尽量也不要出现中文
3.一个pycharm窗口尽量是一个完整的项目(不要嵌套,不要叠加)
4.不同版本的python解释器与不同版本的django可能会出现小问题(例如配置文件中的路径拼接问题)

z2bzYq.png

解决方法:

os.path.join(BASE_DIR,'templates')

上图中的问题只需根据提示查找widgets.py源码第152行,删除掉最后的逗号

Django基本使用

下载

pip3 install django 				默认最新版
pip3 install django==版本号		  指定版本
pip3 install django==2.2.22
pip下载模块会自动解决依赖问题(会把关联需要用到的模块一起下了)

验证是否下载成功可以在cmd中输入以下指令

django-admin

常用命令

1.创建django项目
    django-admin startproject 项目名
2.启动django项目
    cd 项目名
	python38 manage.py runserver ip:port

pycharm自动创建Django项目

z2qzHH.png

会自动创建templates文件夹 但是配置文件中可能会报错
 		os.path.join(BASE_DIR,'templates')

Django app的概念

这里的APP并不是我们日常使用的软件,我们可以用类比方法来进行理解

django类似于是一所大学 app类似于大学里面的各个学院

django里面的app类似于某个具体的功能模块
	user	app 所有用户相关的都写在user app下
 	goods	app 所有商品相关的都写在goods app下

命令行创建应用

python38 manage.py startapp 应用名

pycharm创建应用

新建django项目可以默认创建一个 并且自动注册

app注册机制

创建的app一定要去settings.py中注册
	INSTALLED_APPS = [
    	'app01.apps.App01Config', # 完整写法
		'app02'] # 简写

django主要目录结构

django项目目录名
	django项目同名目录
    settings.py		    配置文件
    urls.py			    存储网址后缀与函数名对应关系(不严谨)
   	wsgi.py			     wsgiref网关文件
	db.sqlite3文件	    django自带的小型数据库(项目启动之后才会出现)
	manage.py			  入口文件(命令提供)
应用目录
    migrations目录    存储数据库相关记录
    admin.py		  django内置的admin后台管理功能
    apps.py			  注册app相关
   	models.py		  与数据库打交道的(非常重要)
    tests.py		  测试文件
    views.py		  存储功能函数(不严谨)
templates目录		 
	存储html文件(命令行不会自动创建 pycharm会)

配置文件中还需要配置路径
	[os.path.join(BASE_DIR,'templates'),]
 

名词讲解

	网址后缀			路由
	函数				 视图函数
	类				  视图类
重要名词讲解
	urls.py				 路由层	
	views.py			 视图层
	models.py			 模型层
	templates			 模板层

Django必懂三功能

from django.shortcuts import render,HttpResponse,redirect

HttpResponse		返回字符串类型的数据

render				返回html页面并且支持传值

redirect			重定向

静态文件

当我们使用Django框架写一些项目时,就避免不了要使用html文件,但是当我们启动框架访问指定页面,我们在HTML中引入的本地的一些CSS\JS文件是无法访问到的,而这些CSS\JS文件,我们就可以理解为静态文件

静态文件概念

  • 静态文件指的是不怎么经常变化的文件,主要针对html文件所使用的到的各种资源

如:css文件、js文件、img文件、第三方框架文件

  • django针对静态文件资源需要单独开始一个目录统一存放

目录名称:static目录

该目录下如果各种类型的文件都多,还可以继续创建目录

css目录(存放css的文件)

js目录(存放js的文件)

img目录(存放图片)

存放插件:utils目录/plugins目录/libs目录/others目录

资源访问

当我们访问网站时,指定的路由会返回对应的界面,且静态文件可以成功加载,网页的样式不会受到影响的原因在于,编写该页面的程序员已经提前用代码将文件的接口开放,相当于高速公路上的关卡和收费站已经提前打过招呼,可以直接通过

静态文件资源访问

·静态文件默认情况下是无法访问的,前端的内容是无法直接获取后端数据的,如果想要实现该功能,我们可以在后端开放一个接口,使前端可以访问到固定的静态文件

静态文件相关配置

想让前端能访问后端的文件需要设置配置信息开放权限

zfmvqO.png

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

ps:Django所有配置文件中的名称需要纯大写,否则不会执行

接口前缀

STATIC_URL = '/xxx/'  
# 访问静态文件资源的接口前缀(相当于通行证)
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),  # 存储静态文件资源的目录名称
    os.path.join(BASE_DIR, 'static1'),  # 存储静态文件资源的目录名称,可以同时创建多个目录
    os.path.join(BASE_DIR, 'static2'),  # 存储静态文件资源的目录名称
]

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

img

接口前缀动态匹配

当我们在配置文件中开放了接口后,如果我们想要修改接口名称就需要在html文件中同时进行修改,当网页很多的时候工作量就会很大,这时候就需要设置接口前缀的动态匹配

<head>
    <meta charset="UTF-8">
    <title>blogs</title>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'blog.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>

form表单

action属性

我们给表单标签form添加属性action控制数据提交的地址

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

action属性的三种配置

	1.action=""  数据默认提交给当前页面所在的地址
	2.action="https://www.baidu.com/"  完整地址
 	3.action="/index/"  朝当前服务端的index地址提交

method属性

method属性用来控制数据提交的方法,默认为get获取数据,post为传递数据

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

请求方法补充

get

  • 朝服务端索要数据,也可以携带一些额外的要求

携带额外数据的方式: URL?xxx=yyy&uuu=zzz

也就是说网址后面用?隔开数据,数据之间用&符号分隔

问号后面携带数据的大小是有限制(2KB)的并且不能携带敏感数据

  • 当我们打开浏览器访问网页的时候就是get方式

img

post

  • 朝服务端提交数据

携带额外数据的方式: 请求体

请求体携带数据安全性较高并且没有大小限制

  • 当我们使用按钮提交新数据的时候使用的就是post方式

img

前期发送post请求需要注释掉配置文件中的某一行

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

img

request对象

request对象相关方法

request.method	获取请求方式 结果是纯大写的字符串数据
	结果:GET\POST
    
request.POST	获取post请求请求体里面携带的数据
	request.POST.get()		获取列表最后一个数据值
 	request.POST.getlist()	 获取整个列表数据,获取了列表后可以使用索引获取列表中的值
    
request.GET		获取网址问号后面携带的数据
	request.GET.get()		获取列表最后一个数据值
 	request.GET.getlist()	 获取整个列表数据,跟POST一样,获取了列表后可以使用索引获取列表中的值
"""
在视图函数中针对不同的请求代码编写套路
	if request.method == 'POST':
		return HttpResponse()
	return HttpResponse()
"""

Django连接数据库

Django自带的sqlite3是一个小型的数据库,功能比较少,主要用于本地测试
我们实际项目中都会替换掉它

默认配置sqlite3

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

我们在学习过程中使用mysqlclient模块替换默认的数据库

需要指定模块

	django1.X版本需要在项目目录下或者app目录下的__init__.py编写代码
    	import pymysql
    	pymysql.install_as_MySQLdb()
	django2.X及以上都可以直接通过下载mysqlclient模块解决
    	pip3.8 install mysqlclient
        或是pycharm中下载

img

ps:mac电脑下载该文件可能会报错

修改配置文件

DATABASES = {
    'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'day51',
            'HOST': '127.0.0.1',
            'PORT': 3306,
            'USER': 'root',
            'PASSWORD': '123',
            'CHARSET': 'utf8'
        }
}

app文件目录中的双下init文件也需要添加配置

import pymysql
pymysql.install_as_MySQLdb()

img

pycharm连接MySQL数据库

方式一:

在pycharm右上角点击图标后创建连接

img

之后弹出一个弹窗,输入数据库的信息

img

第一次连接的时候会要求下载插件,这里点击下载就好了,很快的

img

接着点击下方的Test Connection测试连接,成功了就可以点击ok保存退出了

img

方式二:

使用左下角的图标连接数据库

img
后续操作参考方法一

ORM简介

ORM概念

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术

简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。因此实现了让不会SQL语句的python程序员,使用python面向对象的语法来操作数据库的目的

ORM在业务逻辑层和数据库层之间充当了桥梁的作用

我们可以简单的进行以下类比

类				    表
对象				   一条条数据
对象点名字			 数据获取字段对应的值

ps:ORM由于高度封装了SQL,所以有时候效率较低,我们需要自己写SQL

ORM由来

让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)

几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中

按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是重复的

ORM的优势和劣势

ORM的优势

ORM解决的主要问题是对象和关系的映射

它通常把一个类和一个表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段

ORM提供了对数据库的映射,不用直接编写SQL代码,只需像操作对象一样从数据库操作数据

让软件开发人员专注于业务逻辑的处理,提高了开发效率

ORM的劣势

ORM的缺点是会在一定程度上牺牲程序的执行效率

ORM长时间使用导致sql语句使用的不熟练

ORM基本操作

因为ORM相当于是对数据库进行映射操作,所以我们需要跟数据库中的表建立关系

在app中的models.py中编写模型类

	class GirlsInfo(models.Model):
        # 字段名 = 字段类型 + 约束条件
        id = models.AutoField(primary_key=True)  
        name = models.CharField(max_length=32)
        age = models.IntegerField()

执行数据库迁移相关命令

	python38 manage.py makemigrations  将操作记录到小本本上(migrations)
    执行成功后migrations文件夹下会多出一个py文件
	python38 manage.py migrate		  将操作同步到数据库上
'''注意每次在models.py修改了与数据库相关的代码 都需要再次执行上述命令'''

在下图位置输入以上代码

zfumX6.png

如果出现下方问题

img

解决方案是修改配置文件

img

成功的结果如下图

img

img

img

ORM基本语句

from app01 import models
models.类名.objects.create()		创建记录
models.UserInfor.objects.create(name='zzh',pwd='123')

models.类名.objects.filter()		查看记录
res = models.UserInfor.objects.filter(name=name)
print(res)
print(res[0])
print(res[0].id)
print(res[0].name)
print(res[0].pwd)

models.类名.objects.update()		修改记录
models.UserInfor.objects.filter(id=2).update(name='xiaozhu',pwd='666')

models.类名.objects.delete()		删除记录
models.UserInfor.objects.filter(id=2).delete()

可视化界面---数据增删改查

PS:数字对象的主键字段可以使用obj.pk获取

  在模型类中可以定义双下str方法,在数据对象被执行打印操作时查看数据对象内容
'''
form表单中能够触发调剂动作的按钮只有两个
	<input type='submit'/>
	<button></button>
'''
数据展示功能
	1.开设接口
    2.获取数据
    3.传递页面
    4.展示数据
数据添加功能
	1.开设接口
    2.获取数据
    3.发送数据
    4.校验数据
    5.录入数据
    6.重定向
数据编辑功能
	1.开设接口
    2.后端如何区分所要编辑的数据(问号携带参数)
    3.后端获取用户数据
    4.前端展示默认数据
    5.获取用户并完成更新
数据删除功能
	1.开设接口
    2.问号携带参数
    3.删除二次确认

⭐Django请求生命周期流程图(重点)

z4lBZt.png

学习该图的流程

graph LR A(路由层)-->B(视图层) B-->C(模板层) C-->D(模型层) D-->E(相关组件) E-->F(BBS项目)

Django路由层

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

PS:无论什么版本django都自带加斜杠后缀的功能,也可以取消

Django2.X以上版本在浏览器中输入的路由如果没有带/的话
第一次路由匹配失败后
会自动为路由添加/进行第二次匹配

配置文件中 APPEND_SLASH = False取消该功能

转换器

正常情况下,很多网站会有多个相似的网址,例如:

https://www.cnblogs.com/RocketMQ/p/16976803.html
https://www.cnblogs.com/peachyy/p/16976775.html
https://www.cnblogs.com/flydean/p/16976716.html
https://www.cnblogs.com/Naylor/p/16976661.html

像这种情况下,如果我们为了其中不同的组成部分单独开设路由,不仅初期工作量很大,且不利于后期的调整与变更

那么我们就可以使用到转换器

Django2.X及以上版本路由动态匹配有转换器(五种)

str:匹配除路径分隔符外的任何非空字符串。
int:匹配0或者任意正整数。
slug:匹配任意一个由字母或数字组成的字符串。
uuid:匹配格式化后的UUID。
path:能够匹配完整的URL路径

PS:转换器也支持自定义使用,可以通过正则表达式来匹配更加细化的内容

# 转换器 将对应位置匹配到的数据转换成固定的数据类型
path('index/<str:info>/', views.index_func),
# index_func(实参request对象,info='转换器匹配到的类型转换之后的内容')
path('index/<str:info>/<int:id>/', views.index_func)  
# index_func(实参request对象,info='转换器匹配到的类型转换之后的内容',id='转换器匹配到的类型转换之后的内容')
正则匹配
Django版本 匹配功能区别
1.X re_path,第一个参数是正则
2.X及以上版本 url(),功能与Django2.X及以上的re_path()一致

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

re_path('test', views.test)
re_path('testapp', views.test)
# 像以上情况,在匹配到test后就会自动停止
# 即便输入的是testapp,也会返回test对应的页面
# 如果要解决该问题,就需要有明确的起止符号进行限制
re_path('^test/$', views.test)
re_path('^testapp/$', views.test)
正则匹配有名分组与无名分组

在正则匹配时使用分组功能,分组中匹配到的数据会以不同的形式作为参数传递到视图函数中,所以在定义视图函数式要添加对应的形参

无名分组
re_path('^test/(\d{4})/', views.test)
# 会将括号内正则匹配到的内容当做位置参数传递给视图函数
# 在视图函数中如果不确定参数怎么定义时可以使用args进行接收
def index(request,args):
    return HttpResponse('')
有名分组
re_path('^test/(?P<year>\d{4})/', views.test)
# 会将括号内正则匹配到的内容当做关键字参数传递给视图函数
# ?P<别名>是为正则分组起别名的方法
# 视图中必须有与别名对应的形参接收关键字传参
def index(request,year):
	pass

PS:有名分组与无名分组不可以混合使用

反向解析

反向解析的作用在于通过在前端页面的操作,传递到后端的数据中包含一个名称,通过对该名称的反向解析得出的结果,我们就可以访问到某个指定路由

反向解析基本使用


1.对路由匹配关系进行起别名操作

path('login001/', views.login, name='login_view')
# 将login001/与视图层的login的绑定关系起别名为login_view

2.HTML页面模板语法

{% url 'login_view' %}

3.后端代码语法

reverse('login_view')

动态路由反向解析


1.对路由匹配关系进行起别名操作

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

2.HTML页面模板语法

{% url 'func1_view' 'jason' %}

3.后端代码语法

reverse('func1_view', args=('嘿嘿嘿',))

路由分发

Django支持每个应用都可以有自己独立的路由层、静态文件、模板层,基于该特性,多人共同开发的项目即便有许多应用,也可以在后期进行解耦和,再利用路由分发将所有应用整合到一起

既然每个应用都有自己的路由层、视图层、模板层,那么我们可以将每个应用内都设置路由层,存储各自的路由与视图的绑定关系

而在整合时我们需要另外导入一个方法include

我们只需要在settings文件中修改一下配置,默认是没有include

from django.urls import path, include

z5Mrbq.png

使用路由分发前的代码

path('index/', index)

使用路由分发后的代码

path('app01/', include('app01.urls'))

z5Kt1J.png

路由名称空间

使用路由分发难免会遇到的问题是,如果在App编写期间,不同的App中的路由于视图函数的绑定关系采用起别名的方式编写,那么如果名称冲突后,前端的路由默认情况下是无法直接识别的,那么我们应该如何避免这种现象呢

可供我们选择的方法目前有两种

方式一:名称空间

我们可以在总路由中不再使用name作为别名标识,而是使用namespace

代码示例

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

那么在使用反向解析时,我们就可以使用名称空间来进行区分

代码示例

reverse('app01:index_view')
reverse('app02:index_view')
方式二:确保别名不冲突

如果是单独进行前后端项目编写,那么我们就可以在编写时对路由对应关系的别名添加一些唯一标识,例如采用App名作为别名前缀

代码示例

path('index/', views.index, name='app01_index_view')
path('index/', views.index, name='app02_index_view')

虚拟环境

当多个项目需要使用同一版本解释器,但是需要用到不同版本的模块时:

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

这就要求到了同一个版本的解释器可以共存而且各自配置的模块文件不冲突也不混用,而在实际的开发过程中,除了项目所必需的环境,我们也不会去配置额外的环境

那么这个时候我们就需要用到虚拟环境,它的作用就相当于以某一个版本的解释器为基础,创建出多个该解释器的分身,且每个分身的环境配置都是独立的

pycharm创建虚拟环境

每创建一个虚拟环境就相当于重新下载了一个全新的解释器,只不过是基于已有的解释器进行环境的搭建

创建流程:

z51t56.png

创建完成后界面:

z516at.png

CMD命令行创建

在初期我们曾对python进行过多版本共存的操作,在这一步我们需要注意的是,在使用CMD命令创建虚拟环境时,CMD是不会识别多版本共存中的解释器的,也就意味着下列代码是无法运行的

python38 -m venv pyvenv38
python36 -m venv pyvenv38
python27 -m venv pyvenv38

z51ORU.png

正确的代码为

python -m venv pyvenv38

命令行创建的方法,系统会按照环境变量路径添加时的顺序自上而下使用最先查找到的python解释器版本进行创建

z53ii6.png

也就是说,系统会优先以上图中3.6版本的解释器为基础创建虚拟环境

而在使用命令行创建成功后,我们需要对虚拟环境进行激活

首先切换目录到虚拟幻境所在文件夹下的Scripts文件夹下,然后输入以下指令进行激活

activate

激活后的界面会发生变化

z5YVht.png

当我们需要退出时只需要执行以下指令

deactivate.bat

新建的虚拟环境是没有任何模块文件的,这样我们就可以根据项目的需要去下载新的环境配置

视图层

视图层之必会三功能

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

这句话是完全正确的,我们可以通过看renderredirect的源码来证明,即便它们不是HttpResponse方法,它们的返回值也是HttpResponse对象

首先HttpResponse方法返回的一定是HttpResponse对象

class HttpResponse:
    pass
return HttpResponse()

那么render方法的源码中是这样的

z5YIUA.png

redirect方法的源码是这样的

z5Ybgf.png

图中是我们之前学过的三元表达式,如果条件成立则使用前方的数据,如果条件不成立,则使用后面的数据

z5tP2V.png

z5tM26.png

而经过继续查找其父类及父类的父类,我们可以得出,redirect方法也是继承了HttpResponse

所以这句话是成立的

视图层之JsonResponse对象

当看到JSON时,我们可以联想到该方法可以将数据序列化后返回到页面中

from django.http import JsonResponse
def index_func(request):
    # return HttpResponse('哈哈哈')
    # return render()
    # return redirect()
    # 返回给浏览器一个json格式的字符串
    # 在未接触JsonResponse对象时我们的代码写法
    user_dict = {'name': 'jason老师', 'age': 18}
    import json
    user_json = json.dumps(user_dict, ensure_ascii=False)
    return HttpResponse(user_json)
    
    # 接触到JsonResponse对象后的写法
    return JsonResponse(user_dict)

那么除了字典之外它还可以序列化什么数据呢,我们可以查看它的源码来判断

z5tXz6.png

这说明该方法也是封装了JSON模块的,那么理论上所有可被JSON模块序列化的数据都可以被序列化到页面,但是:

z5NeOS.png

这段代码限制了其余数据格式的序列化,那么我们只需要在返回页面时,将默认的safe = Ture利用关键字传参改为safe = False即可

而在文本内容序列化时,依旧会出现字符编码为ASCII码对照关系的问题,那么这个时候我们就可以参照源码

        if json_dumps_params is None:
            json_dumps_params = {}
        kwargs.setdefault('content_type', 'application/json')
        data = json.dumps(data, cls=encoder, **json_dumps_params)
        super().__init__(content=data, **kwargs)

**kwargs的作用在**json_dumps_params这里的作用是将json_dumps_params作为关键字参数打包为字典,而在super().__init__(content=data, **kwargs)中,**kwargs又可以将字典的键和值打散为关键字参数传递,在源码中默认json_dumps_params为none,但是我们可以在传值时修改该属性为json_dumps_params = {'ensure_ascii:False'},这样子就可以在调用JSON模块式以ensure_ascii=False的方式传值,解决该问题

视图层之request对象获取文件

在前端页面编写时,我们可以使用form表单工具来向后端传递数据,而当我们使用该工具来获取用户提交的文件并向后端传递时,却只能收到文件名称

z5Nq0g.png

那么我们应该如何获取文件内容呢

首先要确保以下两点

1.method必须是post
2.enctype必须是multipart/form-data

z5US10.png

前端页面form表单定义时必须有以上属性

z5UF74.png

我们在后端才可以如上图所示以request.FILES来获取文件内容并进行其他操作

视图层之FBV与CBV

FBV

基于函数的识图(Function Based View),在之前的学习过程中我们使用的视图都属于FBV

def index(request):return HttpResponse对象
CBV

基于类的识图(Class Based View),利用创建类的方式来进行视图函数的操作

from django import views


class MyLoginView(views.View):
    def get(self, request):
        return HttpResponse('from CBV get function')

    def post(self, request):
        return HttpResponse('from CBV post function')
创建类时必须继承views.View

如果使用CBV视图的话,路由与视图的绑定关系编写方法如下

path('login/', views.MyLoginView.as_view())

这样就可以实现自动根据不同的数据请求匹配对应的方法,我们也可以在对应的函数下添加操作,根据不同的路由返回不同的页面

那么views.MyLoginView.as_view()中的as_view()来自于哪里呢,根据名称空间的查找顺序,首先排除我们自己定义的类,那么我们通过查看源码的方式查找

z5BKTU.png

这样看来,本质上绑定关系的的代码本质为

path('login/', views.view)

那么CBV的本质也还是FBV,那么它是怎么运行的呢

首先,当我们访问指定路由时,view加括号产生会产生我们自己编写的类的对象

z5DiB6.png

利用反射获取到请求方式后就相当于完成了请求方式的自动匹配,这也就是CBV的实现原理

关键代码如下:

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)

模板层

模板语法相关

模板语法中接收值的两种语法的区别
{{}}:主要与数据值相关
{%%}:主要与逻辑相关
{##}:模板语法的注释语法


django的模板语法是自己编写的,所以与jinja2是有区别的
PS:虽然二者都支持一些数据类型的基础方法,jinja2对字典依旧保留了get方法,但是Django只能使用句点符

1.针对需要加括号调用的名字,django模板语法会自动加括号调用
2.模板语法的注释前端浏览器是无法查看的 

模板语法传值

传值方式一
return render(request, 'demo02.html', {'n1': name, 'a1': age})

手动传值,一个一个的填写需要传递的数据,传递数据足够精准,节省资源,但是当数据量大时工作量也会加大

传值方式二
return render(request,'demo02.html', locals()) 

借助locals方法,一次性将函数中所有可用名称对应的数据进行传递,虽然很方便,但是如果名称很多且大多数名称用不到的情况下会造成资源的浪费

模板语法传值特性

1.基本数据类型正常展示
2.文件对象也可以展示并调用方法
3.函数名会自动加括号执行并将返回值展示到页面上,不支持有参函数,因为不支持额外传递参数
4.类名也会自动加括号调用产生对象
5.对象中加双下call也不会自动调用,因为不符合自动调用的条件限制

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

模板语法之过滤器

我们可以将过滤器理解为模板语法的内置函数

<p>{{ i|add:10 }}</p>
<p>{{ s|add:'baby' }}</p>
对数字及字符串支持add相加的操作,字符串中为字符串的拼接

<p>{{ l|length }}</p>
支持对数据进行长度统计的length方法

<p>{{ s|slice:'1:4' }}</p>
支持对可以进行切片操作的数据类型进行切片操作

<p>{{ s|truncatechars:5 }}</p>
<p>{{ s|truncatewords:3 }}</p>
对字符串类型进行隐藏操作,数字代表要展示的字符数
其余的内容以...进行隐藏

<p>{{ ctime|date:'Y年-m月-d日 H时:i分:s秒 ' }}</p>
支持对时间的格式化操作

<p>{{ file_size|filesizeformat }}</p>
支持对文件的大小单位智能选择并进行展示,传递之前以字节为单位,自动格式化为KB,MB,GB,TB等

<p>{{ h1|safe }}</p>
<p>{{ s1|safe }}</p>
支持对后端传输过来的前端代码进行展示,默认不展示,加safe后进行展示

ORM执行SQL语句

ORM的底层是封装了pymysql模块的,但是根据我们所学,封装程度越高的功能,底层效率其实会越低,只不过因为封装后,我们的使用更加方便,那么如果在执行一些简单的SQL命令时,ORM的效率其实还不如我们自己编写SQL语句

在ORM中,我们编写SQL语句的方式有两种

raw()方法
models.User.objects.raw('select * from app01_user;')
直接执行自定义SQL语句

首先我们需要导入一个模块,代码如下

from django.db import connection

然后我们就可以用类似于pymysql的方式执行SQL语句

from django.db import connection
    cursor = connection.cursor()
    cursor.execute('select name from app01_user;')
    print(cursor.fetchall())

双下划线查询

queryset对象的特性是可以无限制的用句点符调用queryset对象的方法,例如

queryset.filter().values().filter().values_list().filter()...

但是这样的用法并没有多大的实际意义

在queryset对象中,使用句点符去对数据进行筛选时,是不支持我们之前所使用的</>这样的比较运算符的,那么我们在使用时如何进行这样的操作呢

它支持使用内置的关键字来进行比较运算

__gt=  查找大于目标值的数据
# 查询年龄大于18的数据
models.User.objects.filter(age__gt=18)
__lt=  查找小于目标值的数据
# 查询年龄小于38的数据
models.User.objects.filter(age__lt=38)
__gte=  查找大于等于目标值的数据
# 查找年龄大于等于18的数据
models.User.objects.filter(age__gte=18)
__lte=  查找小于等于目标值的数据
# 查找年龄小于等于18的数据
models.User.objects.filter(age__lte=18)
__in=  查询存在与指定某几个数据范围内的数据
# 查询年龄是18或者28或者38的数据(多选一)
models.User.objects.filter(age__in=(18, 28, 38))
__range=  查询存在于某两个数据值范围内的数据
# 查询年龄在18到38范围之内的数据(不限制固定的值,范围内即可)
models.User.objects.filter(age__range=(18, 38))
__contains=  限制区分大小写的模糊查询
# 查询名字中含有字母j的数据(如果名字中为J,则不支持)
models.User.objects.filter(name__contains='j')
__icontains=  不区分大小写的模糊查询
# 查询名字中含有字母j的数据(即使名字中为J也能支持)
models.User.objects.filter(name__contains='j')
__year=  查询某一年的数据
# 查询注册年份是2022的数据
models.User.objects.filter(register_time__year=2022)
# Django默认时区为格林威治时间,需要在配置文件修改时区

zozAII.png

ORM外键字段创建

表关系分类

一对多
外键字段建在多的一方
多对多
外键字段统一建在第三张关系表
一对一
建在任何一方都可以,但是建议建在查询频率较高的表中

首先我们需要在数据库中建立所需的表

class Teacher(models.Model):
    name = models.CharField(max_length=32, verbose_name='教师姓名')
    password = models.CharField(max_length=32, verbose_name='教师密码')
    register_time = models.DateTimeField(auto_now_add=True, verbose_name='注册时间')


class Student(models.Model):
    name = models.CharField(max_length=32, verbose_name='学生姓名')
    password = models.CharField(max_length=32, verbose_name='学生密码')
    register_time = models.DateField(auto_now_add=True, verbose_name='入学时间')


class School(models.Model):
    name = models.CharField(max_length=32, verbose_name='学校名称')
    address = models.CharField(max_length=64, verbose_name='学校地址')


class Course(models.Model):
    name = models.CharField(max_length=64, verbose_name='课程名称')
    cycle = models.IntegerField(verbose_name='课程周期')
    price = models.IntegerField(verbose_name='课程价格')

在创建外键时,一对多与一对一的情况与SQL中是保持一致的,一对多建立在多的一张表中,一对一建立在查询频率高的表中

但是在多对多的情况下,我们只需要将外键建在查询频率比较高的表中,ORM内部会自动创建第三张关系表

创建外键的语法

class Teacher(models.Model):
    """教师表"""
    name = models.CharField(max_length=32, verbose_name='教师姓名')
    password = models.CharField(max_length=32, verbose_name='教师密码')
    register_time = models.DateTimeField(auto_now_add=True, verbose_name='注册时间')
    teacher = models.ForeignKey(to='School', on_delete=models.CASCADE)
    # 一所学校对应多个教师,外键建立在教师表

    """
    models.ForeignKey是创建外键的方法
    to= 参数是要建立关系的表名
    表名必须采用字符串,因为代码执行由上而下
    填写类名会因为找不到名称报错
    """


class Student(models.Model):
    """学生表"""
    name = models.CharField(max_length=32, verbose_name='学生姓名')
    password = models.CharField(max_length=32, verbose_name='学生密码')
    register_time = models.DateField(auto_now_add=True, verbose_name='入学时间')
    student = models.ForeignKey(to='School', on_delete=models.CASCADE)
    # 一所学校对应多个学生,建立在学生表
    c_student = models.ForeignKey(to='Class', on_delete=models.CASCADE)
    # 一个班级对应多个学生,建立在学生表


class School(models.Model):
    """学校表"""
    name = models.CharField(max_length=32, verbose_name='学校名称')
    address = models.CharField(max_length=64, verbose_name='学校地址')


class Course(models.Model):
    """课程表"""
    name = models.CharField(max_length=64, verbose_name='课程名称')
    s_course = models.ManyToManyField(to='Student')
    # 学生可以对应多门课程,课程也可以对应多个学生,建在课程表
    t_course = models.ManyToManyField(to='Teacher')
    # 教师可以对应多门课程,课程也可以对应多个教师,建在课程表
    """
    models.ManyToManyField是创建多对多外键的语法
    多对多不会在原表中展示,会建立第三张表
    """


class CourseDetail(models.Model):
    """课程详情表"""
    cycle = models.IntegerField(verbose_name='课程周期')
    price = models.IntegerField(verbose_name='课程价格')
    course_detail = models.OneToOneField(to='Course', on_delete=models.CASCADE)
    # 一门课程只能对应一个课程详情,建立在详情表
    """
     models.OneToOneField是创建一对一外键的语法
    """


class Class(models.Model):
    """班级表"""
    name = models.CharField(max_length=32, verbose_name='班级名称')
    t_class = models.ForeignKey(to='Teacher', on_delete=models.CASCADE)
    # 一个教师对应多个班级,建立在班级表
    s_class = models.ForeignKey(to='School', on_delete=models.CASCADE)
    # 一所学校对应多个班级,建立在班级表

外键字段相关操作

    # 针对一对多 插入数据可以直接填写表中的实际字段
    models.Book.objects.create(title='三国演义', price=888.88, publish_id=1)
    models.Book.objects.create(title='人性的弱点', price=777.55, publish_id=1)
    # 针对一对多 插入数据也可以填写表中的类中字段名
    publish_obj = models.Publish.objects.filter(pk=1).first()
    models.Book.objects.create(title='水浒传', price=555.66, publish=publish_obj)
    '''一对一与一对多一致,既可以传数字也可以传对象'''
    
    
    # 针对多对多关系绑定
    # 添加记录
    book_obj = models.Book.objects.filter(pk=1).first()
    book_obj.authors.add(1)  # 在第三张关系表中给当前书籍绑定作者

    book_obj = models.Book.objects.filter(pk=1).first()
    book_obj.authors.add(2, 3)  # 书和作者的外键是authors

    book_obj = models.Book.objects.filter(pk=3).first()
    author_obj1 = models.Author.objects.filter(pk=1).first()
    author_obj2 = models.Author.objects.filter(pk=2).first()
    book_obj.authors.add(author_obj1)

    book_obj = models.Book.objects.filter(pk=4).first()
    author_obj1 = models.Author.objects.filter(pk=1).first()
    author_obj2 = models.Author.objects.filter(pk=2).first()
    book_obj.authors.add(author_obj1, author_obj2)

    # 修改记录
    # 这里是用值修改
    book_obj = models.Book.objects.filter(pk=1).first()
    book_obj.authors.set((1, 3))  # 修改关系

    book_obj = models.Book.objects.filter(pk=1).first()
    book_obj.authors.set([2, ])  # 修改关系

    # 这里是用获取到的对象进行修改
    book_obj = models.Book.objects.filter(pk=1).first()
    author_obj1 = models.Author.objects.filter(pk=1).first()
    author_obj2 = models.Author.objects.filter(pk=2).first()
    book_obj.authors.set((author_obj1,))
    book_obj.authors.set((author_obj1, author_obj2))

    # 删除记录,也是分成根据值对象进行删除
    book_obj = models.Book.objects.filter(pk=1).first()
    book_obj.authors.remove(2)
    book_obj.authors.remove(1, 3)
    book_obj.authors.remove(author_obj1,)
    book_obj.authors.remove(author_obj1,author_obj2)
    book_obj.authors.clear()

参数使用讲解

add()

把指定的model对象添加到第三张关联表中。

参数可以是多个位置参数(数字或对象)

remove()

从关联对象集中移除执行的model对象(移除对象在第三张表中与某个关联对象的关系)

参数可以是多个位置参数(数字或对象)

set()

更新某个对象在第三张表中的关联对象。不同于上面的add是添加,set相当于重置

可迭代对象(参数要放到元组或列表中,元组或列表中存放的参数可以是数据值或对象)

clear()

从关联对象集中移除一切对象。(移除所有与对象相关的关系信息)

ORM跨表查询

复习MySQL跨表查询的思路

子查询

分步操作:将一条SQL语句用括号括起来当做另外一条SQL

语句的条件

连表操作

先整合多张表之后基于单表查询即可

inner join	内连接

left join	左连接

right join	右连接

union	全连接

正反向查询的概念(重要)

  • 正向查询

    由外键字段所在的表数据查询关联的表数据是正向

    或者说外键在自己手上则是正向查询

  • 反向查询

    没有外键字段的表数据查询关联的表数据是反向

    或者说外键在别人手上则是反向查询

ps:正反向的核心就看外键字段在不在当前数据所在的表中

ORM跨表查询的口诀(重要)

正向查询按外键字段

反向查询按表名小写

基于对象的跨表查询

    '''基于对象的跨表查询'''
    # 1.查询主键为1的书籍对应的出版社名称
    # 先根据条件获取数据对象
    book_obj = models.Book.objects.filter(pk=1).first()
    # 再判断正反向的概念  由书查出版社 外键字段在书所在的表中 所以是正向查询
    print(book_obj.publish.name)

    # 2.查询主键为4的书籍对应的作者姓名
    # 先根据条件获取数据对象
    book_obj = models.Book.objects.filter(pk=4).first()
    # 再判断正反向的概念  由书查作者 外键字段在书所在的表中 所以是正向查询
    print(book_obj.authors)  # app01.Author.None
    print(book_obj.authors.all())
    print(book_obj.authors.all().values('name'))
    
    # 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())

    # 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())

    # 6.查询电话号码是110的作者姓名
    author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
    print(author_detail_obj.author)
    print(author_detail_obj.author.name)

ps:在进行反向查询的时候,因为会用掉小写的表名,这时候需要在表名后面跟下_set才能获取到信息,这时候获取到的内容如下:

app01.Book.None

获取到内容后点all方法就能看到内部的数据值

基于双下划线的跨表查询

    '''基于双下划线的跨表查询'''
    # 1.查询主键为1的书籍对应的出版社名称
    res = models.Book.objects.filter(pk=1).values('publish__name','title')
    print(res)

    # 2.查询主键为4的书籍对应的作者姓名
    res = models.Book.objects.filter(pk=4).values('title', 'authors__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','name')
    print(res)

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

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

进阶操作

    '''基于双下划线的跨表查询'''
    # 1.查询主键为1的书籍对应的出版社名称
    res = models.Book.objects.filter(pk=1).values('publish__name','title')
    print(res)

    # 2.查询主键为4的书籍对应的作者姓名
    res = models.Book.objects.filter(pk=4).values('title', 'authors__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','name')
    print(res)

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

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

    '''进阶操作'''
    # 1.查询主键为1的书籍对应的出版社名称
    res = models.Publish.objects.filter(book__pk=1).values('name')
    print(res)

    # 2.查询主键为4的书籍对应的作者姓名
    res = models.Author.objects.filter(book__pk=4).values('name','book__title')
    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(authors__name='jason').values('title')
    print(res)

    # 6.查询电话号码是110的作者姓名
    res = models.Author.objects.filter(author_detail__phone=110).values('name')
    print(res)

    '''补充'''
    # 查询主键为4的书籍对应的作者的电话号码
    res = models.Book.objects.filter(pk=4).values('authors__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)

图书管理系统详解

1.表设计
	先考虑普通字段再考虑外键字段
    """
    建立外键首先分清楚对应关系,根据实际需求建表
    """
	数据库迁移、测试数据录入
2.首页展示
"""
前端搭建框架,进行内容填充后分配路由,实现首页的展示
"""
3.书籍展示
"""
从后端获取数据后使用模板语法发送到前端展示
一定要清楚数据库中取出的数据是什么
怎样操作才能真正展示我们想要展示的类型
"""
4.书籍添加
"""
添加书籍要考虑到业务的独立性,添加书籍就只能是书籍添加
添加书籍时与书籍相关的其他信息应当以数据库中已存在的为基础提供选择
可以开放其他接口用来添加其他业务逻辑
"""
5.书籍编辑
	后端如何获取用户想要编辑的数据、前端如何展示出
待编辑的数据
"""
前端获取数据是什么类型,如何变成我们可存如数据类型
"""
6.书籍删除
"""
删除书籍时根据业务合理性,相关数据的绑定关系也应当一同删除
"""

聚合查询

聚合查询相当于SQL语句中的聚合函数,两者的区别在于Django中的聚合查询不需要以分组为依据也可以单独使用

Max最大值、Min最小值、Sum总和、Avg平均值、count统计

from django.db.models import Max, Min, Sum, Count, Avg
# 导入相关模块
res = models.Book.objects.aggregate(Max('price'), Count('pk'), 最小价格=Min('price'), allPrice=Sum('price'),平均价格=Avg('price'))
print(res)
"""
Max('price') 最高价格
Count('pk')  统计主键个数
Min('price')  最低价格
Sum('price')  价格总和
Avg('price')  价格平均值
"""

分组查询

如果执行orm分组查询报错 并且有关键字sql_mode

说明当前处于严格模式strict mode
解决方法:移除sql_mode中的only_full_group_by

# 统计每一本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
print(res)
# 统计出每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)
# 统计不止一个作者的图书
    # 1.先统计每本书的作者个数
	res = models.Book.objects.annotate(author_num=Count('authors__pk'))
    # 2.筛选出作者个数大于1的数据
    res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title','author_num')
    print(res)
    # 查询每个作者出的书的总价格
    res = models.Author.objects.annotate(总价=Sum('book__price'),count_book=Count('book__pk')).values('name','总价','count_book')
    print(res)
    """
    models.表名.objects.annotate()                            按照表分组
    models.表名.objects.values('字段名').annotate()  按照values括号内指定的字段分组
    """
    res = models.Book.objects.values('publish_id').annotate(count_pk=Count('pk')).values('publish_id', 'count_pk')
    print(res)

F查询与Q查询

当查询条件不是明确的,也需要从数据库中获取,就需要使用F查询

# 1.查询库存数大于卖出数的书籍
from django.db.models import F

res = models.Book.objects.filter(kucun__gt=F('maichu'))
print(res)

# 2.将所有书的价格涨800
models.Book.objects.update(price=F('price') + 800)
# 3.将所有书的名称后面追加爆款
from django.db.models.functions import Concat  
# Django中字符拼接需要专用模块完成,+或add不支持
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('新款')))
# concat为字段名,value为字段名后添加的字符串

在Django中查询条件的连接默认为逗号,默认为and关系

res = models.Book.objects.filter(pk=1, price__gt=2000) 
"""
查询主键是1或者价格大于2000的书籍,默认为and关系
所以使用这个方法无法达到要求
"""

改变查询条件的连接需要使用Q查询

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))  # |是or
res = models.Book.objects.filter(~Q(pk=1) | Q(price__gt=2000))  # ~是not
print(res.query)

Q查询进阶操作

Q查询可以用来改变数据过滤条件的连接方式,默认情况下使用逗号进行连接采用的是and的关系,| Q可以改为or关系,~Q可以改为not关系,但是我们可以通过进阶操作进行优化

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', 2000))
res = models.Book.objects.filter(q_obj)
# 直接使用定义好的Q对象作为查询条件,以定义好的关系查询结果

ORM查询优化

ORM 内所有的 SQL 查询语句,都是惰性查询,只有当你需要使用查询结果时,才会真正去执行 SQL 语句访问数据库,否则是不会执行查询的,当queryset被执行后,其查询结果会载入内存并保存在queryset内置的cache中,再次使用就不需要重新去查询了

ORM的查询操作自身带有分页处理的功能

only与defer

only会产生对象结果集 ,对象点括号内出现的字段不会再走数据库查询,但是如果点击了括号内没有的字段也可以获取到数据 但是每次都会走数据库查询

res = models.Book.objects.only('title', 'price')
print(res)  # queryset [数据对象、数据对象]
for obj in res:
    print(obj.title)  # 点击括号内填写的字段 不走SQL查询
    print(obj.price)
    print(obj.publish_time)  # 可以点击括号内没有的字段获取数据 但是会走SQL查询

deferonly刚好相反 对象点括号内出现的字段会走数据库查询,如果点击了括号内没有的字段也可以获取到数据 每次都不会走数据库查询

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

select_related类似于连表操作,但是括号内只能传一对一和一对多字段的外键字段,不能传多对多字段
效果是内部直接连接表(inner join) 然后将连接之后的大表中所有的数据全部封装到数据对象中
后续对象通过正反向查询跨表,内部不会再走数据库查询

res = models.Book.objects.select_related('authors')  # 先连表后查询封装
res1 = models.Author.objects.select_related('author_detail')  # 括号内不支持多对多字段 其他两个都可以
    print(res1)
    for obj in res:
        print(obj.publish.name)  # 不再走SQL查询

prefetch_related类似于子查询,会将多次查询之后的结果封装到数据对象中
后续对象通过正反向查询跨表 内部不会再走数据库查询

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

ORM事物操作

1.事务的四大特性(ACID)
原子性、一致性、隔离性、持久性
2.相关SQL关键字
start transaction;
rollback;
commit;
savepoint;
3.相关重要概念
脏读、幻读、不可重复读、MVCC多版本控制...

ORM中开启事务的方式至少有三种

方式1:配置文件数据库相关添加键值对

全局开启事务,该方法创建的事务全局有效

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db03',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': '222',
        'CHARSET': 'utf8'
    	'ATOMIC_REQUESTS': True 
        # 每次请求所涉及到的orm操作同属于一个事务
        # 如果哪个orm报错了,事务会自动回滚
    }
方式2:装饰器

局部有效,用装饰器装饰视图函数,这个视图函数内部就属于一个事务,只要函数内报错,事务就会回滚,数据库会恢复到进行事务之前的状态

from django.db import transaction
       
    
@transaction.atomic
def index():pass
方式3:with上下文管理

局部有效,with的子代码内是一个事务:(这种方式用于更小的局部,只能在某个代码块)

from django.db import transaction

def reg():
	with transaction.atomic():
		pass

ORM常用字段类型

数值型

  • AutoField对应int(11) 自增主键,Django Model默认提供,可以被重写
  • BooleanField对应tinyint(1) 布尔类型字段,一般用于记录状态标记
  • DecimalField对应decimal 开发对数据精准要求较高大的业务时考虑使用,比如:cash=models.DecimalField(max_length, decimal_places=2, default=0, verbose_name=“消费金额”),就是定义长度为8位、精度位2位的数字,例如数字:666666.66
  • IntergerField对应**int(11) **同AutoField一样,唯一的差别就是不自增。
  • PositiveIntegerField 同IntegerField,只包含正整数
  • SmallIntegerField 对应smallint,小整数时一般会用到

字符型

django对应到Mysql中有两种类型:longtext和varchar

除了TextField是longtext类型外,其他属于varchar类型

  • CharField对应varchar 基础的varchar类型。
  • URLField 继承自CharField,但是实现了对URL特特殊处理。用来存储URL数据,非URL数据可以在业务层就拒绝掉,不会存入数据库中
  • UUIDField对应char(32) 除了在PostgreSQL中使用的是uuid类型外,在其他数据库中均是固定长度char(32),用来存放生成的唯一id
  • EmailField 同URLfield一样继承自CharField,多了对email的特殊处理
  • FileField 同URLField一样,它继承自CharField,对了对文件的特殊处理
  • TextField对应longtext 一般用于存放大量文本内容,比如新闻正文、博客正文
  • ImageField 继承自FileField,用来处理图片相关的数据,在展示上会有所不同

日期类型

django中有三种日期类型,分别对应Mysql的date、datetime和time

  • DateField对应date
  • DateTimeField对应datetime
  • TimeField对应time

关系类型

  • ForeignKey 外键
  • OneToOneField 一对一
  • ManyToManyField 多对多

自定义字段类型

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)

ORM常用字段参数

  • null 可以同blank进行对比,null用于设定在数据库层面是否允许为空
  • blank 针对业务层面,该值是否允许为空。
  • choices 配置字段的choices后,在admin页面上就可以看到对应的可选项展示,当某个字段的可能性能够被列举完全的情况下使用
性别、学历、工作状态、...
	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()
  • db_column 默认情况下,定义的field就是对应数据库中的字段名称,通过这个参数可以指定Model中的某个字段对应数据库中的哪个字段
  • db_index 数据库索引配置,给字段添加索引
  • default 默认值配置
  • editable 是否可编辑,默认是True,如果不想这个字段显示在页面上,可以配置为False
  • error_messages 用来自定义字段值校验失败时的异常提示,它是字典格式,key的值可选项为null、blank、invalid、invalid_choice、unique和unique_for_date。
  • help_text 字段提示语,配置这一项后,在页面对应字段的下方会展示此配置
  • primary_key 主键,一个Model只允许设置一个字段为primary_key
  • unique 唯一约束,当需要配置唯一值时,设置unique=True,设置此项后,不需要设置db_index
  • unique_for_date 针对date(日期)的联合约束,比如说一天只能写一篇博文,即:unique_for_date=“博文”
  • unique_for_month 针对月份的联合约束
  • unique_for_month 针对年份的联合约束
  • verbose_name 字段对应的注释说明
  • validators 自定义校验逻辑,同form类似
  • max_length 字段长度

  • max_digits 小数总共多少位

  • decimal_places 小数点后面的位数

  • auto_now 每次操作数据自动更新事件

  • auto_now_add 首次创建自动更新事件后续不自动更新

  • to 关联表

  • to_field 关联字段(不写默认关联数据主键)

  • on_delete 当删除关联表中的数据时,当前表与其关联的行的行为

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

forms组件渲染标签

在学习forms组件渲染标签前,我们需要知道,forms组件渲染的只有获取用户数据的标签,而form标签以及触发提交的按钮都需要我们手动编写

而前端的数据校验往往并不是那么可靠,所以数据的校验还是需要在后端完成,那么我们就需要在使用forms组件的时候取消前端进行的数据校验

<form action="" novalidate>

后端代码

def login(request):
    # 1.先生成一个对象
    form_obj = MyForm()
    # 2.将该对象传递给html页面
    return render(request,'login.html',locals()) 
渲染方式1:直接渲染所有标签

封装程度过高,扩展性差,主要用于本地测试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <title>Title</title>
</head>
<body>

            {#第一种渲染方式#}
            
                    
                    {{ form_obj.as_p }}
                    
                    {{ form_obj.as_table }}
                    
                    {{ form_obj.as_ul }}
                    
                    <input type="submit" value="提交" class="btn btn-success">
</body>
</html>
渲染方式2:渲染单个标签

封装程度过低,扩展性高,编写麻烦

{{ form_obj.username.label }}
{{ form_obj.username }}
{{ form_obj.age.label }}
{{ form_obj.age }}
{{ form_obj.email.label }}
{{ form_obj.email }}
渲染方式3:for循环遍历对象

封装程度较高,扩展性高,编写简单,推荐使用

{% for form in form_obj %}
    <p>
        {{ form.label }}
        {{ form }}
    </p>
{% endfor %}

forms组件展示信息

后端不同请求返回的forms对象一定要是相同的变量名,这是因为前端接收数据的变量名必须与后端保持一致

def ab_forms_func(request):
    # 1.产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        form_obj = MyForm(request.POST)  
        # request.POST可以看成是一个字典
        # 直接传给forms类校验
        # 字典中无论有多少键值对都没关系
        if form_obj.is_valid():  # 校验数据是否合法
            print(form_obj.cleaned_data)
        else:
            print(form_obj.errors)
    # 2.将该对象传递给html文件
    return render(request, 'formsPage.html', locals())

前端代码

{% for form in form_obj %}
            <p>
                {{ form.label }}
                {{ form }}
                <span>{{ form.errors.0 }}</span>
            </p>
{% endfor %}

针对错误信息的提示可以修改成各国语言,也可以自定义

修改方法

from django.conf import global_settings

查看导入模块源码,获取支持的语言选项

zXSde0.png

修改图中的LANGUAGE_CODE选项

自定义方法

1.自定义内容
给字段对象添加errors_messages参数
	username = forms.CharField(min_length=3, max_length=8, label='用户名',
                               error_messages={
                                   'min_length': '用户名最少三个字符',
                                   'max_length': '用户名最多八个字符',
                                   'required': '用户名不能为空'
                               }
                               )

forms组件校验补充

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

  • 直接填写参数 max_length
class PublishForm(Form):
    title = fields.CharField(max_length=20,
                            min_length=5,
                            error_messages={'required': '标题不能为空',
                                            'min_length': '标题最少为5个字符',
                                            'max_length': '标题最多为20个字符'},
                            widget=widgets.TextInput(attrs={'class': "form-control"}))
  • 第二种类型:使用正则表达式 validators
# Validator
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )

# 自定义验证函数
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
 
# 自定义验证规则
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')

# 使用自定义验证规则
    phone = fields.CharField(validators=[mobile_validate, ],
                            error_messages={'required': '手机不能为空'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': u'手机号码'}))

email = fields.EmailField(required=False,
                            error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
                            widget=widgets.TextInput(attrs={'class': "form-control"
  • 第三种类型:钩子函数 编写代码自定义校验规则
注意点:局部钩子拿什么,校验通过就返回什么,全局钩子拿什么,校验通过返回所有

'''钩子函数都是在数据校验的最后一个环节执行'''
-局部钩子单个字段校验使用局部
       -def clean_字段名(self):
            -校验规则
            -如果通过,return 值
            -如果不通过,抛异常
-全局钩子(多个字段校验使用全局)
        -def clean(self):
            -如果通过,return clean_data
            -如果不通过,抛异常

局部钩子

def clean_name(self):  # name字段的局部钩子
        # 获取用户输入的用户名
        name = self.cleaned_data.get('name')
        # 校验名字不能以sb开头
        if name.startswith('sb'):
            # 校验不通过,必须抛异常,
            raise ValidationError('不能以sb开头')
        else:
            # 校验通过,再返回name对应的值
            return name

全局钩子

def clean(self):   # 全局钩子
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if re_password != password:
            # 校验不通过
            self.add_error('re_password','两次密码不一致')
        else:
            # 局部钩子拿什么返回什么,全局钩子所有都返回
            return self.cleaned_data

forms组件字段与参数

字段

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

forms组件源码剖析

1 为什么局部钩子要写成 clean_字段名,为什么要抛异常
2 入口在 is_valid()
3 校验流程
    -先校验字段自己的规则(最大,最小,是否必填,是不是合法)
    -校验局部钩子函数
    -全局钩子校验
    
    
4 流程
    is_valid()---》return self.is_bound and not self.errors
    self.errors:# 方法包装成了数据数据
        # 一旦self._errors有值,就不进行校验了(之前调用过了)
    self.full_clean():# 核心
        self._errors = ErrorDict()
        if not self.is_bound:  
            return
        self.cleaned_data = {}
        self._clean_fields()
        self._clean_form()
        self._post_clean()
        
        
    self._clean_fields():# 核心代码,局部钩子执行位置
    
     value = field.clean(value)# 字段自己的校验规则
     self.cleaned_data[name] = value #把校验后数据放到cleaned_data
     if hasattr(self, 'clean_%s' % name): # 判断有没有局部钩子
        value = getattr(self, 'clean_%s' % name)() #执行局部钩子
        self.cleaned_data[name] = value #校验通过,把数据替换一下
       # 如果 校验不通过,会抛异常,会被捕获,捕获后执行
    self.add_error(name, e)
    
    def _clean_form(self):#全局钩子执行位置
    def _clean_form(self):
        try:
            #如果自己定义的form类中写了clean,他就会执行
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)

modelform组件

我们学习校验性组件的目的,是为了数据录入数据库之前的各项审核
forms组件使用的时候需要对照模型类编写代码,不够方便

modelform是forms组件的强化版本,更好用更简单更方便

from django import forms
from app01 import models


class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        labels = {
            'username':'用户名'
        }


def ab_mf_func(request):
    modelform_obj = MyModelForm()
    if request.method == 'POST':
        modelform_obj = MyModelForm(request.POST,instance=User_obj)
        if modelform_obj.is_valid():
            modelform_obj.save()  # models.UserInfo.objects.create(...)/update(...)
        else:
            print(modelform_obj.errors)
    return render(request, 'modelFormPage.html', locals())

Django中间件

Django 中间件是修改 Django request 或者 response 对象的钩子,可以理解为是介于 HttpRequest 与 HttpResponse 处理之间的一道处理过程

浏览器从请求到响应的过程中,Django 需要通过很多中间件来处理,可以看如下图所示:

img

Django 中间件作用:

  • 修改请求,即传送到 view 中的 HttpRequest 对象
  • 修改响应,即 view 返回的 HttpResponse 对象

中间件配置在 settings.py 文件的 MIDDLEWARE 选项列表中

配置中的每个字符串选项都是一个类,也就是一个中间件

Django 默认的中间件配置:

MIDDLEWARE = [
  'django.middleware.security.SecurityMiddleware',
  'django.contrib.sessions.middleware.SessionMiddleware',
  'django.middleware.common.CommonMiddleware',
  'django.middleware.csrf.CsrfViewMiddleware',
  'django.contrib.auth.middleware.AuthenticationMiddleware',
  'django.contrib.messages.middleware.MessageMiddleware',
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
自定义中间件

中间件可以定义四个方法,分别是:

process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_exception(self, request, exception)
process_response(self, request, response)

自定义中间的步骤:

在 app 目录下新建一个 py 文件,名字自定义,并在该 py 文件中导入 MiddlewareMixin:

from django.utils.deprecation import MiddlewareMixin

img

自定义的中间件类,要继承父类 MiddlewareMixin:

class MD1(MiddlewareMixin): 
    pass

在 settings.py 中的 MIDDLEWARE 里注册自定义的中间件类:

MIDDLEWARE = [
  'django.middleware.security.SecurityMiddleware',
  'django.contrib.sessions.middleware.SessionMiddleware',
  'django.middleware.common.CommonMiddleware',
  'django.middleware.csrf.CsrfViewMiddleware',
  'django.contrib.auth.middleware.AuthenticationMiddleware',
  'django.contrib.messages.middleware.MessageMiddleware',
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
  
  'app01.middlewares.MD1',
]

自定义中间件类的方法

自定义中间件类的方法有:process_request 和 process_response

img

process_request 方法

process_request 方法有一个参数 request,这个 request 和视图函数中的 request 是一样的

process_request 方法的返回值可以是 None 也可以是 HttpResponse 对象

  • 返回值是 None 的话,按正常流程继续走,交给下一个中间件处理。
  • 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法

process_request 方法是在视图函数之前执行的

当配置多个中间件时,会按照 MIDDLEWARE中 的注册顺序,也就是列表的索引值,顺序执行

不同中间件之间传递的 request 参数都是同一个请求对象

实例

from django.utils.deprecation import MiddlewareMixin

from django.shortcuts import render, HttpResponse

class MD1(MiddlewareMixin):
    def process_request(self, request):
        print("md1  process_request 方法。", id(request)) #在视图之前执行

img

process_response

process_response 方法有两个参数,一个是 request,一个是 response,request 是请求对象,response 是视图函数返回的 HttpResponse 对象,该方法必须要有返回值,且必须是response

process_response 方法是在视图函数之后执行的

当配置多个中间件时,会按照 MIDDLEWARE 中的注册顺序,也就是列表的索引值,倒序执行

实例

class MD1(MiddlewareMixin):
	def process_request(self, request):
	    print("md1  process_request 方法。", id(request)) #在视图之前执行


	def process_response(self,request, response): :#基于请求响应
    print("md1  process_response 方法!", id(request)) #在视图之后
    return response

从下图看,正常的情况下按照绿色的路线进行执行,假设中间件1有返回值,则按照红色的路线走,直接执行该类下的 process_response 方法返回,后面的其他中间件就不会执行

img

process_view

process_exception

process_template_response

上面三个方法作为了解即可

posted @ 2022-12-21 20:55  逐风若梦  阅读(195)  评论(0编辑  收藏  举报