Django5.0版本项目笔记

Django 项目

基础知识点

系统的 pip源 设置文件在: C:\Users\xjbieke\AppData\Roaming\pip\

# 永久更换pip源 为清华大学:https://mirrors.aliyun.com/pypi/simple/
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
# 更改pip源后,可以通过以下命令验证:
pip config get global.index-url
# 临时使用使用清华大学镜像源,重新安装 xxxname库
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ xxxname


几个常用镜像源:
清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
阿里云:https://mirrors.aliyun.com/pypi/simple/
豆瓣:https://pypi.douban.com/simple/
中科大:https://pypi.mirrors.ustc.edu.cn/simple/

Django 与 Python 版本关系

Django 版本 Python 版本
2.2 3.5,3.6,3.7,3.8(2.2.8 添加),3.9(2.2.17 添加)
3.0 3.6,3.7,3.8,3.9 (3.0.11 添加)
3.1 3.6,3.7,3.8,3.9(3.1.3 添加)
3.2 3.6, 3.7, 3.8, 3.9, 3.10 (在 3.2.9 中就已经加入了)
4.0 3.8,3.9,3.10
4.1 3.8, 3.9, 3.10, 3.11 (added in 4.1.3)
4.2 3.8, 3.9, 3.10, 3.11, 3.12 (added in 4.2.8)
5.0 3.10, 3.11, 3.12

请求在 Django 的处理流程

【前端请求】 -- 原始请求 --> 【wsgi】 -- 封装(request=WSGIRequest) -->【 路由匹配】 -- 添加request.resolver_match 属性 -->【中间件】---->【视图函数】

1、项目相关

新项目

系统解释器 + 命令行方式【用于学习了解知识】
创建项目
# 在项目要保存的目录文件内运行
django-admin startproject name # name为项目名称
目录结构
>x:\project\mysite
	- mysite
		- __init__.py # 初始化文件,一般情况下无须修改。
		- urls.py # 项目的路由设置,URL 和函数对应关系
		- wsgi.py # 全称为Python Web Server Gateway Interface,即Python服务器网关接口,是Python应用与Web服务器之间的接口,用于Django项目在服务器上的部署和上线,一般不需要修改。底层请求处理入口【同步】,接收请求传递给Django。
		- asgi.py # 开启一个ASGI服务,ASGI是异步网关协议接口。底层请求处理入口【异步】,接收请求传递给Django
		- settings.py # 项目的配置文件(Django有内置的配置文件,此文件的配置项优先权高于Django内置配置文件,内置配置文件相同配置项会被替代)
	- manage.py # 【管理项目】命令行工具,内置多种方式与项目进行交互。在命令提示符窗口下,将路径切换到MyDjango项目并输入python manage.py help,可以查看该工具的指令信息。
运行项目
>项目文件夹>python manage.py runserver

编写对应关系 urls.py

from django.http import HttpResponse

def demo(request):
	return HttpResponse('hello world!!!')

urlpattens = [
	path('demo/', demo),
]
虚拟环境+命令行方式【线上部署使用】
安装 virtulenv 工具
# 安装 virtulenv 工具
# Python的virtualenv是一个用于创建独立的Python环境的工具。它允许你在同一台机器上为不同的项目或不同的Python版本创建和管理多个虚拟环境。这样可以避免不同项目之间的依赖冲突,使得项目的部署和维护更加方便。
>pip install virtulenv
创建虚拟环境
# virtualenv 是一个Python包,用于创建隔离的Python环境。你可以使用以下命令来创建一个新的虚拟环境:
>virtualenv --python=python3.10 D:\envs\v001
# 请注意,路径 D:\envs\v001 应该是一个存在的目录,虚拟环境将被创建在这个目录下。--python=python3.10 参数指定了要使用的Python解释器版本。
# 创建后的目录结构
D:\envs\v001
	- python.exe
	- scripts
		- pip.exe
		- pip3.10.exe
		- activate.exe # 激活虚拟环境命令
	- lib
		site-pakages
激活虚拟环境
>cd D:\envs\v001
>activate.exe
(v001)>  # 虚拟环境激活了,就可以配置和使用虚拟环境了
创建项目
(v001)># 在项目要保存的目录文件内运行
(v001)>django-admin startproject name # name为项目名称
Pycharm + 虚拟环境【本地开发使用】

注意:

将虚拟环境放在项目目录下,这样在删除项目的同时可以同时删除虚拟环境

采用 Pycharm 创建 Djiango 项目虚拟环境时,Pycharm自动安装的 Django 是最新版的

如果要使用老版本的 Dango ,在创建项目的开始,选择使用python 的纯净环境,不要选择 Django,环境创建完毕后单独安装指定版本的 Django,如:pip install django==4.0。之后创建 Django 项目命令时带点参数,如:django-admin startproject name . 点参数表示在当前目录下创建Django 项目。

终端的使用cmd,在 Pycharm 中选择设置(settings)-->工具(tool)--->终端(terminal)--->应用设置(ApplicationSettings)--->外部路径(ShellPath)--->选择cmd

Pycharm项目配置:项目+环境

添加配置(AddConfiguration)--->DjangoServer

给项目配置名称及其他等相关信息

老项目

Pycharm 打开项目文件夹,创建或者选择原有虚拟环境 + 项目绑定

选择 设置 --->添加虚拟环境并绑定

安装项目依赖包 requirement.txt 文件

pip install -r requirements.txt 可以简写 pop install -r re

requirement.txt 文件生成 pip freeze > requirement.txt

纯净版项目

Django 内置的功能有很多

创建Django项目,默认配置内置功能,可以对默认配置进行剔除。

INSTALLED_APPS = [
    # 'django.contrib.admin',        # django内置后台管理,简单数据库的增删改查。建议剔除
    # 'django.contrib.auth',         # 用户登录和认证权限。 建议剔除
    'django.contrib.contenttypes', # 复杂表结构关系
    'django.contrib.sessions',     # 如果项目中有登录成功让用户可以访问。
    'django.contrib.messages',     # 消息展示,依赖Session
    'django.contrib.staticfiles',  # 静态资源处理,图片、css、js等
]
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    # 'django.contrib.auth.middleware.AuthenticationMiddleware',  # auth相关剔除
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                # 'django.contrib.auth.context_processors.auth', # auth相关剔除
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

数据库表结构构建命令

# 找功能模块,将功能模块中需要创建表结构,创建出来
>python manage.py makemigrations   【找功能模块】【表结构】【生成配置文件并放到指定目录】
>python manage.py migrate          【读取生成配置文件,提交到数据库】

makemigrations/migrate 只负责创建、修改、删除表结构,只针对表级别来操作。

项目应用 App

主要用于业务功能模块的开发

创建app: python manage.py startapp nameapp

注册app: 在setting.py 内 添加 nameapp.apps.nameappConfig

创建应用模块后目录下文件介绍

- nameapp
	- migrations:用于生成数据迁移文件,通过数据迁移文件可自动在数据库里生成相应的数据表。
	- __init__.py:app01文件夹的初始化文件。
	- admin.py:用于设置当前 nameApp 默认的后台管理功能。
	- apps.py:当前App的配置信息,在Django 1.9版本后自动生成,一般情况下无须修改。
	- models.py:定义数据库的映射类,每个类可以关联一张数据表,实现数据持久化,即MTV里面的模型(Model)。
	- tests.py:自动化测试的模块,用于实现单元测试。
	- views.py:视图文件,处理功能的业务逻辑,即MTV里面的视图(Views)。
多个 app 应用的项目

一般情况下一个app就够了;开源组件;公共模块,拆分刀单独的app模块

多个 app 嵌套模式:多个app统一放在apps的文件夹中

  • 在项目根目录下新建名为 apps 的文件夹
  • 新app创建
    • 在apps目录下创建新 appname 的文件夹
    • 运行 python manage.py startapp appname apps/appname
    • 在应用目录下 apps.py 文件中,更改name选项的值 name = 'apps.appname'
    • 在setting 注册时的路径添加 apps.appname.apps.AppnameConfig

3、Django 配置信息

Django 配置文件 settings.py 用于配置整个网站的环境和功能,核心配置必须有项目路径、密钥配置、域名访问权限、App列表、中间件、资源文件、模板配置、数据库的连接方式。

基本配置信息

from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
# 项目路径配置项:BASE_DIR
BASE_DIR = Path(__file__).resolve().parent.parent
# 密钥配置项 SECRET_KEY
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '@p_m^!ha=$6m$9#m%gobzo&b0^g2obt4teod84xs6=f%$4a66x'
# SECURITY WARNING: don't run with debug turned on in production!
# 调试模式 
DEBUG = True
# 域名访问权限
ALLOWED_HOSTS = []
# App列表
# Application definition
INSTALLED_APPS = [
    # 内置的后台管理系统。
    'django.contrib.admin',
    # 内置的用户认证系统。
    'django.contrib.auth',
    # 记录项目中所有model元数据(Django的ORM框架)。
    'django.contrib.contenttypes',
    # Session会话功能,用于标识当前访问网站的用户身份,记录相关用户信息。
    'django.contrib.sessions',
    # 消息提示功能。
    'django.contrib.messages',
    # 查找静态资源路径。
    'django.contrib.staticfiles',
]
  • BASE_DIR【项目路径】:主要通过os模块读取当前项目在计算机系统的具体路径,该代码在创建项目时自动生成,一般情况下无须修改。
  • SECRET_KEY【密钥配置】:这是一个随机值,在项目创建的时候自动生成,一般情况下无须修改。主要用于重要数据的加密处理,提高项目的安全性,避免遭到攻击者恶意破坏。密钥主要用于用户密码、CSRF机制和会话Session等数据加密。
  • DEBUG【调试模式】:该值为布尔类型。如果在开发调试阶段,那么应设置为True,在开发调试过程中会自动检测代码是否发生更改,根据检测结果执行是否刷新重启系统。如果项目部署上线,那么应将其改为False,否则会泄漏项目的相关信息。
  • ALLOWED_HOSTS【域名访问权限】:设置可访问的域名,默认值为空列表。当DEBUG为True并且ALLOWED_HOSTS为空列表时,项目只允许以localhost或127.0.0.1在浏览器上访问。当DEBUG为False时,ALLOWED_HOSTS为必填项,否则程序无法启动,如果想允许所有域名访问,可设置ALLOW_HOSTS =['*']。
  • INSTALLED_APPS【App列表】:告诉Django有哪些App。

资源文件配置

静态资源配置
  • 设置静态资源的路由地址 STATIC_URL = '/static/'
  • 设置多个静态资源的集合路由地址 STATICFILES_DIRS = [BASE_DIR /'static', BASE_DIR / 'index/Mystatic']
  • 资源部署【STATIC_ROOT】,在服务器上部署项目,实现服务器和项目之间的映射。STATIC_ROOT主要收集整个项目的静态资源并存放在一个新的文件夹,然后由该文件夹与服务器之间构建映射关系。
媒体资源 -- MEDIA
# 设置媒体路由地址信息
MEDIA_URL = '/media/'
# 获取media文件夹的完整路径信息
MEDIA_ROOT = BASE_DIR / 'media'

配置属性设置后,还需要将media文件夹注册到Django里,让Django知道如何找到媒体文件,否则无法在浏览器上访问该文件夹的文件信息。打开MyDjango文件夹的urls.py文件,为媒体文件夹media添加相应的路由地址,代码如下:

from django.contrib import admin
from django.urls import path, re_path
# 导入项目应用index
from index.views import index
# 配置媒体文件夹media
from django.views.static import serve
from django.conf import settings
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index),
   # 配置媒体文件的路由地址
   re_path('media/(?P<path>.*)',serve,
       {'document_root': settings.MEDIA_ROOT}, name='media'),
]

数据库配置

settings.py 中的数据库配置项 【DATABASES】

Sqlite3 配置(默认)

DATABASES 默认值

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
MySQL 配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'XhlDB_a',  # 数据库的名称
        'USER': 'root',  # 连接数据库的用户名
        'PASSWORD': '123456',  # 连接数据库的密码
        'HOST': '127.0.0.1',  # 连接数据库的主机名/IP地址
        'PORT': '3307'  # 连接数据库所使用的端口号
    }
}

使用 pymysql 模块,安装后,需要在项目文件夹 __init__.py 中设置数据库连接模块,代码如下:

import pymysql
pymysql.install_as_MySQLdb()

如果使用 MySQL 8.0版本,需要注意:

连接数据库时会提示:django.db.utils.OperationalError的错误信息,这是由于MySQL8.0版本的用户码采用的是CHA2加密方式。不兼容。

解决问题:通过 SQL 语句将MySQL8.0版本的加密方式改回原来的加密方式。在MySQL可视化工具中运行下列代码,

# newpassword是已设置的用户密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
FLUSH PRIVILEGES;

FLUSH PRIVILEGES;命令是MySQL中用于重新加载授权表的命令。当用户更改了MySQL的权限设置后,需要使用该命令来使更改生效。执行FLUSH PRIVILEGES命令后,MySQL会重新读取授权表并更新用户的权限信息。

MySQL8.0 版本在创建数据库时,排序规则的选择

对于主要包含汉字、英文和数字的数据,并且MySQL版本为8.0的情况,推荐使用的排序规则是utf8mb4_general_ci。因为utf8mb4字符集能够支持更多的字符,包括各种语言的特殊字符,同时也兼容utf8字符集。而utf8mb4_general_ci排序规则则是一种通用的排序规则,它能够处理包括英语在内的大多数语言,且性能较优。同时需要注意的是,该排序规则不区分大小写和重音符号。

当然,如果需要在某些具体情况下进行优化,例如区分大小写或重音符号,还可以选择其他的排序规则。例如,如果需要对土耳其语、意大利语或法语等涉及不同形式的同一单词的语言进行搜索和比较,并希望区分这些形式的差异,可以选择像utf8mb4_unicode_520_ci这样能够区分大小写和重音符号的排序规则。但总的来说,针对于数据主要以汉字,英文,数字三种组成的情况,utf8mb4_general_ci是一个较好的选择。

多个数据库的连接方式
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'XhlDB_a',  # 数据库的名称
        'USER': 'root',  # 连接数据库的用户名
        'PASSWORD': '123456',  # 连接数据库的密码
        'HOST': '127.0.0.1',  # 连接数据库的主机名/IP地址
        'PORT': '3307'  # 连接数据库所使用的端口号
    },
    'mysql01': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'XhlDB_c',  # 数据库的名称
        'USER': 'root',  # 连接数据库的用户名
        'PASSWORD': '123456',  # 连接数据库的密码
        'HOST': '127.0.0.1',  # 连接数据库的主机名/IP地址
        'PORT': '3307'  # 连接数据库所使用的端口号
    },
    'mysqlite3': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

配置属性DATABASES设有3个键值对:default、mysql01和mysqlite3,每个键值对代表Django连接了某个数据库。

若项目中连接了多个数据库,则数据库之间的使用需要遵从一定的规则和设置。比如项目中定义了多个模型,每个模型所对应的数据表可以选择在某个数据库中生成,如果模型没有指向某个数据库,模型就会在key为default的数据库里生成。

使用配置文件动态连接数据库

修改 settings.py 的配置后,是需要重新启动 Django项目的。且只能是后端开发者手动修改,如何将 数据库的设置变成动态信息,可以随时修改,甚至前端客户也可以修改。

解决方法:

  1. 在项目目录下创建数据库配置文件mydb.cnf,在文件中添加数据库信息如下:

    # mydb.cnf
    [client]
    database=django_db
    user=root
    password=123456
    host=127.0.0.1
    port=3307
    
    • [client]在配置信息中代表分组的意思
    • [client]里面,每个配置信息分别代表MySQL的数据库名称、用户名、密码、IP地址和端口信息。
  2. 在配置文件 settings.py 中编写 DATABASES 的信息如下

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'OPTIOCNS': {'read_default_file':str(BASE_DIR / 'mydb.cnf')}, 
        }
    

    只需在default->OPTIONS->read_default_file设置配置文件mydb.cnf的地址路径即可,Django会自动读取配置文件mydb.cnf的数据库连接信息,从而实现数据库连接。

通过SSH隧道远程连接MySQL

在企业开发中,数据库和Web系统很可能部署在不同的服务器,当Web系统连接数据库的时候,如果数据库所在的服务器禁止了外网直连,只允许通过SSH方式连接服务器,再从已连接服务器的基础上连接数据库,遇到这一种情况,如何在Django中实现数据库连接呢?

4、路由系统(URL)

路由称为URL(Uniform Resource Locator,统一资源定位符),也可以称为URLconf,是对可以从互联网上得到的资源位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的路由,用于指出网站文件的路径位置。简单地说,路由可视为我们常说的网址,每个网址代表不同的网页。

路由规则

路由定义

完整的路由包含:路由地址、视图函数(或者视图类)、可选变量和路由命名。其中基本的信息必须有:路由地址和视图函数(或者视图类),路由地址即我们常说的网址,视图函数(或者视图类)即App的views.py文件所定义的函数或类。

在App里的 urls.py 是所有属于该App的路由。这样更容易管理和区分每个App的路由地址。在项目文件夹下的 urls.py 是将每个App的urls.py 统一管理。这种路由设计模式是Django常用的。其工作原理简单描述如下:

  1. 运行项目时,Django从项目name文件夹的urls.py找各个App所有定义的路由信息,生成完整的路由列表。

    • 在项目name文件夹的 urls.py 内定义路由分发,指向各个 App 的 urls.py。

      # 路由分发
      from django.urls impprt path, include
      urlpatterns = [
          path('app01/', include('app01.urls')),
          path('app02/', include('app02.urls')),
      ]
      
    • Django 从setting.py 读取属性ROOT_URLCONF的值(默认值:项目name.urls),根据ROOT_URLCONF的值来生成整个项目的路由列表。

  2. 当用户访问某个路由地址时,Django收到请求信息获取路由地址,在路由列表里匹配响应的路由信息,在执行路由信息所指向的视图函数(或视图类),完成整个请求响应过程

路由文件urls.py的路由定义规则是相对固定的,路由列表由urlpatterns表示,每个列表元素代表一条路由。路由是由Django的path函数定义的,该函数第一个参数是路由地址,第二个参数是路由所对应的处理函数(视图函数或视图类),这两个参数是路由定义的必选参数。

路由变量的设置

在日常开发过程中,有时一个路由可以代表多个不同的页面,如编写带有日期的路由,若根据前面的编写方式,按一年计算,则需要开发者编写365个不同的路由才能实现,这种做法明显是不可取的。因此,Django在定义路由时,可以对路由设置变量值,使路由具有多样性。路由的变量类型有字符类型、整型、slug和uuid,最为常用的是字符类型和整型。各个类型说明如下:

  • 字符类型:匹配任何非空字符串,但不含斜杠。如果没有指定类型,就默认使用该类型。
  • 整型:匹配0和正整数。
  • slug:可理解为注释、后缀或附属等概念,常作为路由的解释性字符。可匹配任何ASCII字符以及连接符和下画线,能使路由更加清晰易懂。比如网页的标题是“13岁的孩子”,其路由地址可以设置为“13-sui-de-hai-zi”。
  • uuid:匹配一个uuid格式的对象。为了防止冲突,规定必须使用“-”并且所有字母必须小写,例如075194d3-6885-417e-a8a8-6c931e272f00。
# urls.py
from django.urls import paht
from views import appname
urlpatterns = [
    path('<year>/<int:month>/<slug:day>',views.demo)
]
# views.py
from django.http import HttpResponse
def demo(request, year, month, day):
    return HttpResponse(str(year) + '/' +str(month)+ '/' + str(day))

在路由中,使用变量符号“<>”可以为路由设置变量。在括号里面以冒号划分为两部分,冒号前面代表的是变量的数据类型,冒号后面代表的是变量名,变量名可自行命名,如果没有设置变量的数据类型,就默认为字符类型。上述代码设置了3个变量,分别是<year>、int:month和slug:day,变量说明如下:

  • <year>:变量名为year,数据格式为字符类型,与<str:year>的含义一样。
  • <int:month>:变量名为month,数据格式为整型。
  • <slug:day>:变量名为day,数据格式为slug。
正则表达式的路由定义

路由的正则表达式是由路由函数 re_path 定义的,其作用是对路由变量进行截取与判断,正则表达式是以小括号为单位的,每个小括号的前后可以使用斜杠或者其他字符将其分隔与结束。

# urls.py
from django.urls import re_path
from views import appname
urlpatterns = [
    re_path('^(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.demo)
]
# ^是开始标记,$是结尾标记
# 分别将变量year、month和day以斜杠隔开,每个变量以一个小括号为单位,在小括号内,可分为3部分
# 以(?P<year>[0-9]{4})为例。
#  - ?P是固定格式,字母P必须为大写。
#  - <year>为变量名。
#  - [0-9]{4}是正则表达式的匹配模式,代表变量的长度为4,只允许取0~9的值。

# views.py
from django.http import HttpResponse
def demo(request, year, month, day):
    return HttpResponse(str(year) + '/' +str(month)+ '/' + str(day))

涉及转移字符,需要再前面添加 r

路由底层源码分析

主要是对 path 函数的分析

1. 定义路由的本质
2. 路由匹配的流程
3. 自定义扩展点

命名空间与路由命名

为了更好地管理和使用路由,可以为每条路由设置路由命名或命名空间

路由命名 name

Django的路由命名name是对路由进行命名的,其作用是在开发过程中可以在视图或模板等其他功能模块里使用路由命名name来生成路由地址。

路由命名 name 是对某个路由进行命名,直接对应 urls.py 内的路由。

path 函数的可选参数 name,通过这个参数对路由进行命名。它是路由函数path或re_path的可选参数。

# urls.py
from django.urls import paht
from views import appname
urlpatterns = [
    path('<year>/<int:month>/<slug:day>',views.demo, name='app01')
]
# views.py
from django.http import HttpResponse
def demo(request, year, month, day):
    return HttpResponse(str(year) + '/' +str(month)+ '/' + str(day))

在实际开发过程中,我们支持使用路由命名,因为网站更新或防止爬虫程序往往需要频繁修改路由地址,倘若在视图或模板等其他功能模块里使用路由地址,当路由地址发生更新变换时,这些模块里所使用的路由地址也要随之修改,这样就不利于版本的变更和维护;相对而言,如果在这些功能模块里使用路由命名来生成路由地址,就能避免路由地址的更新维护问题。

命名空间 namespace

命名空间 namespace 是对某个项目应用下 urls.py 进行命名,对应 urls.py 所包含的路由,均在这个名称之下。

通过 include 函数的可选参数 namespace 设置命名空间

from django.urls impprt path, include
urlpatterns = [
    path('app01/', include(('app01.urls', 'app01'),namespace='app01')),
    path('app02/', include(('app02.urls', 'app02'),namespace='app02')),
]

函数include并且分别指向app01文件夹的urls.py和app02文件夹的urls.py。在函数include里设置了可选参数namespace,该参数是函数include特有的参数,这就是Django设置路由的命名空间。

路由函数include设有参数arg和namespace,参数arg指向项目应用App的urls.py文件,其数据格式以元组或字符串表示;可选参数namespace是路由的命名空间。

注:namespace 是可选参数,如果在路由中使用,则 include 函数的第一参数必须是元组形式,且元组长度必须为2。

  • 元组的第一个元素:对应项目应用目录下的 urls.py 文件;
  • 元组的第二个元素:不能为空,自行命名,一般是项目应用的名称

因为路由的命名空间namespace是路由函数include的可选参数,而路由命名name是路由函数path或re_path的可选参数,两者隶属于不同的路由函数,因此可在同一路由里共存。

当 项目app urls.py设置 namespace 时,在使用 reverse 函数时:

from  django.urls import reverse

url = reverse('空间名称:路由名称’)
prit(url)
# 有多级嵌套时
url = reverse('1级空间名称:2级空间名称:路由名称’)
prit(url)
反向解析 reverse 与 resolve

路由除了在模板里面使用之外,还可以在视图里使用。

Django 根据网址在路由列表里查找相应的路由,再从路由里找到视图函数或视图类进行处理,将处理结果作为响应内容返回。这个生命周期是不可逆的,而在视图里使用路由这一过程被称为反向解析。

Django 的反向解析主要由函数 reverse 和 resolve 实现:

  • 函数 reverse 是通过路由命名或可调用视图对象来生成路由地址的;

    # 简单路由 path('app01/', views.demo, name='demo01')
    # 复杂路由 path('<year>/<int:month>/<slug:day>',views.mydate, name='mydate)
    from django.urls import reverse
    
    result01 = reverse('demo01')
    print(result) # 'app01/'
    
    result02 = reverse('mydate','2023','12','09')
    print(result) # '2023/12/09'
    
  • 函数 resolve 是通过路由地址来获取路由对象信息的。

name 的应用示例
# 网页跳转的应用
# path('api/auth/login/', views.login, name='n1')
# path('api/user/account/', view.account, name='n2')

def login(request):
    # 当用户登录成功后,跳转到用户信息页面 '/api/user/account/'
    # return redirect('/api/user/account/')
    
    # url = reverse('n2')
    # return redirect(url)
    
    return redirect('n2')
# 权限管理中的应用
A用户有权访问的网址
	/api/auth/login/
    /api/user/account/
    /api/user/order/
B用户有权访问的网址
    /api/user/account/
    /api/user/order/

# 采用name 替换后
A用户有权访问的网址
	n1,n2,n3
B用户有权访问的网址
	n2,n3

路由的本质

导入和反射
  • 反射,以字符串的形式去操作对象中的成员

5、视图(View)

3.1 FBV 与 CBV

应用目录下的 views.py文件

  • 视图函数 FBV

    # 视图函数简单实例
    from django.http import HttpResponse
    def login(request):
        return HttpResponse('hello world !!!')
    
  • 视图类 CBV

    from django.views import View
    class LoginView(View):
    	def get(self, request):
            return HttpResponse('hello world !!!')	
    	def post(self, request):
            return HttpResponse('hello world !!!')
    
  • 路由对应关系

    urlpattens = [
        # 对象内部存储对应关系: login/  ==> views.login
        path('login/',  views.login),
        
        # 对象内部存储对应关系: login/  ==> as_view()方法的返回值
        path('login01/' views.LoginView.as_view())
    ]
    

3.2 参数

  • request 对象,读取请求相关所有的数据
  • v1,v2 或 *args,**kwargs,用于接收动态路由中的参数

3.3 业务处理

3.4 返回值

构建 响应体和响应头

  • HttpResponse

  • JsonResponse

6、模板(Template)

7、中间件

中间件(Middleware)是一个用来处理Django的请求(Request)和响应(Response)的框架级别的钩子,它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。

Django 默认中间件注释

MIDDLEWARE = [
	# 做了一些安全处理的中间件。比如设置xss防御的请求头,做了http协议转https协议的工作等。
    'django.middleware.security.SecurityMiddleware',
    # session中间件,会给request添加一个处理好的session对象。
    'django.contrib.sessions.middleware.SessionMiddleware',
    # 通用中间件,可以限制settings.DISALLOWED_USER_AGENTS中指定的请求头来访问本站,DISALLOWED_USER_AGENTS是一个正则表达式的列表,
    # 如果开发者在定义url时最后有斜杠,但用户在访问的时候没有添加这个斜杠,那么该中间件会自动重定向到加了斜杠的url上。
    'django.middleware.common.CommonMiddleware',
    # csrf保护的中间件
    'django.middleware.csrf.CsrfViewMiddleware',
    # 会给request添加一个user对象的中间件
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    # 消息处理相关的中间件
    'django.contrib.messages.middleware.MessageMiddleware',
    # 做了clickjacking攻击的保护
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  • SecurityMiddleware:内置的安全机制,保护用户与网站的通信安全。
  • SessionMiddleware:会话Session功能。
  • LocaleMiddleware:国际化和本地化功能。
  • CommonMiddleware:处理请求信息,规范化请求内容。
  • CsrfViewMiddleware:开启CSRF防护功能。
  • AuthenticationMiddleware:开启内置的用户认证系统。
  • MessageMiddleware:开启内置的信息提示功能。
  • XFrameOptionsMiddleware:防止恶意程序单击劫持。

7.1 使用

7.1.1 编写类

在类型定义:process_requestprocess_viewprocess_response

可以写在任意的目录:自定义的类、继承一个类(推荐)

from django.utils.deprecation import MiddlewareMixin
# 基本格式
class BieKeMiddleware(MiddlewareMixin):
    # 可以三个都写,也可以只写一个
    def process_request(self, request):
        # request 是请求相关所有数据
        pass
        
    def process_view(self,request, view, *args, **kwargs):
        # request是请求相关所有数据;view是视图函数;路由参数是*args,**kwargs
        pass
    
    def process_response(self, request, response):
        # request是请求相关所有数据;# response是视图函数返回的对象
        return response
  • 中间件做什么
    • 根据请求周期,对 request 进行赋值,后续方便进行调用。
    • 根据请求周期,对业务逻辑代码进行自定义,决定是否可以继续向后
      • return None,继续向后走
      • return HttpResponse 或 JsonResponse对象
    • 根据请求周期,对返回的数据进行自定义:添加或删除数据、cookie、响应头...
7.1.2 中间件注册

在设置文件(setting)中添加

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    'corsheaders.middleware.CorsMiddleware',  # 第三方跨域应用包中间件
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    # 将自定义中间件文件的路径以字符串形式书写在这里, utils.tool为文件夹名
    "utils.tool.BieKeMiddleware",
]
7.1.3 测试

8、Cookie

本质:保存在用户浏览器的键值对,

  • 前端与后端建立联系,后端 HttpResponse 将将 Cookie 信息包裹在响应头。传递到客户端。
  • 之后客户端发送请求会在请求头携带 Cookie 信息,后端通过 Cookie 确认身份响应操作及返回响应信息
  • Cookie 是浏览器自带的内置功能,Django是加以利用
  • Cookie 只能保存在当前用户的当前浏览器中,如果更换浏览器 Cookie 就不好使了。

9、Session

Session 是什么

Session 是 Django 内置的会话控制,为用户提供基础的数据存储。数据存储在服务器上。
Session 与 Cookie 关系

  • 存储位置不同:Session 服务器端,Cookie 客户端浏览器。
  • 从 Session 读取信息时,从 Cookie 里获取用户id,根据id找到对应session

运行与配置

在创建Django项目时,Django默认启用 Session 功能,每个用户的 Seession 通过Django 的中间件(Middleware)处理。
可以在 settings.py 文件 MIDDLEWARE 配置项中找到 "django.contrib.sessions.middleware.SessionMiddleware"

这个 SessionMiddleware 相当于HTTP请求拦截器,根据信息判断结果来调用相应执行程序 "django.contrib.sessions"
可以在 settings.py 文件 INSTALLED_APPS 配置项中找到 "django.contrib.sessions"

django.contrib.sessions 程序实现 对 session 对象的创建、存储、管理Session的生命周期(过期时间)等。

django.contrib.sessions 程序默认的存储是数据库,项目执行数据迁移时,自动在项目数据库中生成名为 django_sesstion 数据表。

django 提供了5种 Sesson 的存储方式,可以在 settings.py 添加配置信息 SESSION)ENGING 来指定 Session 的保存方式。

# 1. 数据库保存方式(默认方式)
#    -Django默认的保存方式,使用该方法无须在settings.py中设置
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# 2. 以文件形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
#    -使用文本保存可设置文件保存路径
#    -/MyDjango代表将文本保存在项目MyDjango的根目录
SESSION_FILE_PATH = '/MyDjango'
# 3. 以缓存形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
#    -设置缓存名,默认是内存缓存方式,此处的设置与缓存机制的设置相关
SESSION_CACHE_ALIAS = 'default'
# 4. 以数据库+缓存形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# 5. 以Cookie形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'

浏览器 Cookie 的生命周期,传输方式和保存路径等相关配置,在settings.py 添加

# 浏览器 Cookie 以键值对形式保存 Session_key,默认键名:sessionid
SESSION_COOKIE_NAME = "sessionid"
# 设置浏览器的Cookie生效路径,默认值为“/”,即127.0.0.1:8000
SESSION_COOKIE_PATH = "/"
# 设置浏览器的Cookie生效域名
SESSION_COOKIE_DOMAIN = None
# 设置传输方式,若为False,则使用HTTP,否则使用HTTPS
SESSION_COOKIE_SECURE = False
# 设置Cookie的有效期,单位为秒,默认时间为两周
SESSION_COOKIE_AGE = 1209600
# 是否关闭浏览器使得Cookie过期,默认值为False
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# 是否每次发送后保存Cookie,默认值为False
SESSION_SAVE_EVERY_REQUEST = False

操作

Session 数据类型可以理解为 Python 的字典类型,主要在视图函数中执行读写操作,从用户请求对象中获取,也就视图函数的参数 request。

# request为视图函数的参数request
# 获取存储在Session的数据k1,若k1不存在,则会报错
request.session['k1']
# 获取存储在Session的数据k1,若k1不存在,则为空值
# get和setdefault实现的功能是一致的
request.session.get('k1', '')
request.session.setdefault('k1', '')
# 设置Session的数据,键为k1,值为123
request.session['k1'] = 123
# 删除Session中k1的数据
del request.session['k1']
# 删除整个Session
request.session.clear()
# 获取Session的键
request.session.keys()
# 获取Session的值
request.session.values()
# 获取Session的session_key
# 即数据表django_session的字段session_key
request.session.session_key

Django 基础

参考:Django配置日志:https://www.cnblogs.com/Neeo/articles/17588553.html

python内置模块logging模块:https://www.cnblogs.com/Neeo/articles/10951734.html

Django4.2 日志管理

Django 使用并扩展了 Python 内置的 logging 模块来执行系统日志记录。

所以现学习了解

官网地址:https://docs.djangoproject.com/zh-hans/4.2/topics/logging/

关于

Django 使用并扩展了 Python 内置的 logging 模块来执行系统日志记录。

日志信息从严重程度由高到低,分5个等级

由logging模块默认提供了5个操作方法,分别可以记录以下5个等级日志的。

CRITICAL(fatal):    致命错误,程序根本跑不起来。
ERROR:              运行错误,程序运行发生错误的地方时就会退出程序。
WARNING:            运行警告,程序运行发生警告的地方时会显示警告提示,但是程序会继续往下执行。
INFO:               运行提示,一般的系统信息,并非日志
DEBUG:              调试信息,排查故障时使用的低级别系统信息

配置

日志的配置是在 Django 项目中的 setting.py 文件中添加配置,根据日志文件切割方式来分别配置:

  • 日志文件切割方式按文件大小配置

    #  决定给日志保存到哪个文件夹下,这里将自动在项目根目录下创建 logs文件夹
    LOGS_DIRS = os.path.join(BASE_DIR, 'logs')
    if not os.path.exists(LOGS_DIRS):
        os.makedirs(LOGS_DIRS)
    
    # 日志
    LOGGING = {
        'version': 1,  # 使用的日志模块的版本,目前官方提供的只有版本1,但是官方有可能会升级,为了避免升级出现的版本问题,所以这里固定为1
        'disable_existing_loggers': False,  # 是否禁用其他的已经存在的日志功能?肯定不能,有可能有些第三方模块在调用,所以禁用了以后,第三方模块无法捕获自身出现的异常了。
        'formatters': {  # 日志格式设置,verbose或者simple都是自定义的
            'verbose': {  # 详细格式,适合用于开发人员不在场的情况下的日志记录。
                # 格式定义:https://docs.python.org/3/library/logging.html#logrecord-attributes
                # levelname 日志等级
                # asctime   发生时间
                # module    文件名
                # process   进程ID
                # thread    线程ID
                # message   异常信息
                'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
                'style': '{',  # 变量格式分隔符
            },
            'simple': {  # 简单格式,适合用于开发人员在场的情况下的终端输出
                'format': '{levelname} {message}',
                'style': '{',
            },
        },
        'filters': {  # 过滤器
            'require_debug_true': {
                '()': 'django.utils.log.RequireDebugTrue',
            },
        },
        'handlers': {  # 日志处理流程,console或者mail_admins都是自定义的。
            'console': {
                'level': 'DEBUG',  # 设置当前日志处理流程中的日志最低等级
                'filters': ['require_debug_true'],  # 当前日志处理流程的日志过滤
                'class': 'logging.StreamHandler',  # 当前日志处理流程的核心类,StreamHandler可以帮我们把日志信息输出到终端下
                'formatter': 'simple'  # 当前日志处理流程的日志格式
            },
            # 'mail_admins': {
            #     'level': 'ERROR',                  # 设置当前日志处理流程中的日志最低等级
            #     'class': 'django.utils.log.AdminEmailHandler',  # AdminEmailHandler可以帮我们把日志信息输出到管理员邮箱中。
            #     'filters': ['special']             # 当前日志处理流程的日志过滤
            # }
            'file': {
                'level': 'INFO',
                'class': 'logging.handlers.RotatingFileHandler',
                # 日志位置,日志文件名,日志保存目录logs必须手动创建
                'filename': '%s/django.log' % LOGS_DIRS,
                # 单个日志文件的最大值,这里我们设置300M
                'maxBytes': 300 * 1024 * 1024,
                # 备份日志文件的数量,设置最大日志数量为10
                'backupCount': 10,
                # 日志格式:详细格式
                'formatter': 'verbose',
                'encoding': 'utf-8',  # 输出日志编码
            },
        },
        'loggers': {  # 日志处理的命名空间
            'django': {
                'handlers': ['console', 'file'],  # 当基于django命名空间写入日志时,调用那几个日志处理流程
                'propagate': True,  # 是否在django命名空间对应的日志处理流程结束以后,冒泡通知其他的日志功能。True表示允许
            },
        }
    }
    
  • 按日期配置

    LOGS_DIRS = os.path.join(BASE_DIR, 'logs')
    if not os.path.exists(LOGS_DIRS):
        os.makedirs(LOGS_DIRS)
    
    # 日志
    LOGGING = {
        'version': 1,  # 使用的日志模块的版本,目前官方提供的只有版本1,但是官方有可能会升级,为了避免升级出现的版本问题,所以这里固定为1
        'disable_existing_loggers': False,  # 是否禁用其他的已经存在的日志功能?肯定不能,有可能有些第三方模块在调用,所以禁用了以后,第三方模块无法捕获自身出现的异常了。
        'formatters': {  # 日志格式设置,verbose或者simple都是自定义的
            'verbose': {  # 详细格式,适合用于开发人员不在场的情况下的日志记录。
                # 格式定义:https://docs.python.org/3/library/logging.html#logrecord-attributes
                # levelname 日志等级
                # asctime   发生时间
                # module    文件名
                # process   进程ID
                # thread    线程ID
                # message   异常信息
                'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
                'style': '{',  # 变量格式分隔符
            },
            'simple': {  # 简单格式,适合用于开发人员在场的情况下的终端输出
                'format': '{levelname} {message}',
                'style': '{',
            },
        },
        'filters': {  # 过滤器
            'require_debug_true': {
                '()': 'django.utils.log.RequireDebugTrue',
            },
        },
        'handlers': {  # 日志处理流程,console或者mail_admins都是自定义的。
            'console': {
                'level': 'DEBUG',  # 设置当前日志处理流程中的日志最低等级
                'filters': ['require_debug_true'],  # 当前日志处理流程的日志过滤
                'class': 'logging.StreamHandler',  # 当前日志处理流程的核心类,StreamHandler可以帮我们把日志信息输出到终端下
                'formatter': 'simple'  # 当前日志处理流程的日志格式
            },
            # 'mail_admins': {
            #     'level': 'ERROR',                  # 设置当前日志处理流程中的日志最低等级
            #     'class': 'django.utils.log.AdminEmailHandler',  # AdminEmailHandler可以帮我们把日志信息输出到管理员邮箱中。
            #     'filters': ['special']             # 当前日志处理流程的日志过滤
            # }
            'file': {
                'level': 'INFO',
                'class': 'logging.handlers.TimedRotatingFileHandler',
                # 日志位置,日志文件名,日志保存目录logs必须手动创建
                'filename': '%s/django.log' % LOGS_DIRS,
                # TimedRotatingFileHandler的参数
                # 目前设定每天一个日志文件
                # 'S'         |  秒
                # 'M'         |  分
                # 'H'         |  时
                # 'D'         |  天
                # 'W0'-'W6'   |  周一至周日
                # 'midnight'  |  每天的凌晨
                'when': 'S',  # 间间隔的类型,指定秒就不要在Windows上运行测试
                'interval': 5,  # 时间间隔
                'backupCount': 5,  # 能留几个日志文件;过数量就会丢弃掉老的日志文件
                'encoding': 'utf-8',  # 日志文本编码
            },
        },
        'loggers': {  # 日志处理的命名空间
            'django': {
                'handlers': ['console', 'file'],  # 当基于django命名空间写入日志时,调用那几个日志处理流程
                'propagate': True,  # 是否在django命名空间对应的日志处理流程结束以后,冒泡通知其他的日志功能。True表示允许
            },
        }
    }
    
  • 另外,在需要的文件,也可以调用logging模块,将简单的日志输出到控制台:

    import logging
    logger = logging.getLogger('django')
    logger.warning('xxxx')
    
  • 报错处理

    • 当运行环境为 Wiindows 时,配置了日志切割(按文件大小和按日期都会出现)之后,当日志管理模块准备切割日志时,会出现权限报错,具体的现象就是:

      PermissionError: [WinError 32] 锛岃繘绋嬫棤娉曡闂��: 'D:\\xxx\\logs\\django.log' -> 'D:\\xxx\\logs\\django.log.2023-xx-xx_xx-xx-xx'

    • 原因是源码中,会有个re.rename的操作,将django.log原来的名成新名django.log.2023-xx-xx_xx-xx-xx,但是因为django.log被别的进程占用着,导致改名失败,导致权限错误。

    • 其原因就是logging日志是线程安全的,也就是说一个进程中多个线程同时往一个日志文件中写入日志是安全的。但是在Django项目运行时,是多进程的,导致出现多进程操同一个文件,就出现了权限问题了,这个问题在Windows上尤为明显。

    • 解决方法:通过三方模块 concurrent-log-handler

      • 如果使用上面两个默认的日志配置,那么出现权限问题只会出现在Windows上,在Linux上目前我没发现问题,而通常Django项目时部署在Linux上的,所以,这个问题,可以忽略,因为开发阶段,一般不需要配置日志,上线到Linux上,又没有这个问题了。
      • 非要在在Windows下使用,结合按照文件大小切割的方式使用日志。到了Linux上,想要使用按照日期切割日志,那就按照原来的方式配置就完了;如果想按照文件大小切割日志,就将 concurrent-log-handler 替换为 ConcurrentLogHandler 就行了。

如果在Windows上,按照日期进行切割,报错,建议,不要在Windows上用按照时间进行切割的方式使用日志,请按照文件大小进行切割的方式用就行了。另外,默认的按照日期进行切割的配置,只在Windows上报错,在Linux上不报错。

最终,建议,无论是Windows还是Linux,如果不想报错,就都用按照文件大小进行切割就行了。

用于测试 .log 的文件

# 可以写个request脚本去测试请求某个视图,测下日志切割效果:
import requests

count = 1
while True:
	# 请求某个URL
    url = 'http://127.0.0.1:8000/index/'
    res = requests.get(url, headers={
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
        'Connection': 'close'
    })
    print(res.text, count)
    res.close()
    count += 1

单独使用 logger

在视图内或脚本 单独使用 logger

在项目某个路径下,创建一个日志的配置文件,比如我将它创建在项目根目录下的utils/logger.py中,填写代码:

import logging.config
from django.conf import settings

logger = logging.getLogger('django')
logging.config.dictConfig(settings.LOGGING)  # logging配置

然后在你需要的地方,引入logger对象就行了:

import datetime
from django.shortcuts import render, HttpResponse
from utils.logger import logger
def index(request):
    now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    logger.info('info.....')
    logger.warning('warning.....')
    logger.error('error.....')
    return HttpResponse(now)

Django4 信号

参考:https://www.cnblogs.com/Neeo/articles/17589746.html

Django有一个"信号调度器(signal dispatcher)"。其实就是观察者模式,又叫发布-订阅(Publish/Subscribe) 。当发生一些动作的时候,发出信号,然后监听了这个信号的函数就会执行。

通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。用于在框架执行操作时解耦。

在Django中,我们可以使用Django提供的内置信号,也可以自定义信号

内置信号

Django4.2信号官档:https://docs.djangoproject.com/zh-hans/4.2/ref/signals/#signals

常用的内置信号
# -------------- 模型信号 --------------
# 每当实例化一个 Django 模型时,这个信号都会在模型的 __init__() 方法的开头发出
django.db.models.signals.pre_init

# 和 pre_init 一样,但这个是在 __init__() 方法完成后发送的
django.db.models.signals.post_init


# 这是在模型的 save() 方法开始时发送的
django.db.models.signals.pre_save

# 就像 pre_save 一样,但在 save() 方法的最后发送
django.db.models.signals.post_save
"""
注意,pre_save和post_save拿到的模型类对象是一样的,
所以,不要认为,如果想要实现拿到数据更改前和更改后的数据,用这俩是不行的,因为这俩拿到的模型类对象是一样的
后面我会举例子说明
"""


# 在模型的 delete() 方法和查询集的 delete() 方法开始时发送
django.db.models.signals.pre_delete

# 就像 pre_delete 一样,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送
django.db.models.signals.post_delete


# -------------- 请求/响应信号 --------------
# 当 Django 开始处理一个 HTTP 请求时发送
django.core.signals.request_started

# 当 Django 完成向客户端发送 HTTP 响应时发送
django.core.signals.request_finished

# 当 Django 在处理一个传入的 HTTP 请求时遇到异常时,就会发出这个信号
django.core.signals.got_request_exception

警告

信号会使代码更难维护。在使用请求/响应信号之前,请考虑使用中间件。

完整的内置信号
# -------------- 模型信号 --------------
# 每当实例化一个 Django 模型时,这个信号都会在模型的 __init__() 方法的开头发出
django.db.models.signals.pre_init

# 和 pre_init 一样,但这个是在 __init__() 方法完成后发送的
django.db.models.signals.post_init


# 这是在模型的 save() 方法开始时发送的
django.db.models.signals.pre_save

# 就像 pre_save 一样,但在 save() 方法的最后发送
django.db.models.signals.post_save
"""
注意,pre_save和post_save拿到的模型类对象是一样的,
所以,不要认为,如果想要实现拿到数据更改前和更改后的数据,用这俩是不行的,因为这俩拿到的模型类对象是一样的
后面我会举例子说明
"""

# 在模型的 delete() 方法和查询集的 delete() 方法开始时发送
django.db.models.signals.pre_delete

# 就像 pre_delete 一样,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送
django.db.models.signals.post_delete


# 当一个模型实例上的 ManyToManyField 被改变时发出。严格来说,这不是一个模型信号,
# 因为它是由 ManyToManyField 发送的,但由于它是对 pre_save/post_save 和 pre_delete/post_delete 的补充,
# 当涉及到跟踪模型的变化时,它被包含在这里
django.db.models.signals.m2m_changed

# 当一个模型类被“准备好”时发送——也就是说,一旦一个模型被定义并注册到Django的模型系统中。
# Django在内部使用这个信号;它通常不用于第三方应用程序。
django.db.models.signals.class_prepared


# -------------- 管理信号 --------------
# 管理信号是django-admin 发出的信号。
# 由 migrate 命令在开始安装应用程序之前发出。对于缺乏 models 模块的应用程序,它不会发出
django.db.models.signals.pre_migrate

# 在 migrate (即使没有运行迁移)和 flush 命令结束时发出。对于缺乏 models 模块的应用程序,它不会被发出
# 该信号的处理者不能进行数据库模式的改变,因为如果在 migrate 命令期间运行 flush 命令,可能会导致 flush 命令失败。
django.db.models.signals.post_migrate


# -------------- 请求/响应信号 --------------
# 当 Django 开始处理一个 HTTP 请求时发送
django.core.signals.request_started

# 当 Django 完成向客户端发送 HTTP 响应时发送
django.core.signals.request_finished

# 当 Django 在处理一个传入的 HTTP 请求时遇到异常时,就会发出这个信号
django.core.signals.got_request_exception


# -------------- 测试信号 --------------
# 只有当 运行测试 时才会发出信号
# 当通过 django.test.TestCase.settings() 上下文管理器或 django.test.override_settings() 装饰器/上下文管理器# 改变配置值时,会发出这个信号。
# 它实际上被发送了两次:当应用新的值时("setup")和当恢复原始值时("drawdown")。使用 enter 参数来区分这两种情况。
# 你也可以从 django.core.signals 导入这个信号,以避免在非测试情况下从 django.test 导入
django.test.signals.setting_changed

# 当测试系统渲染一个模板时发出。这个信号在 Django 服务器正常运行时不会发出,只有在测试时才会发出
django.test.signals.template_rendered


# -------------- 数据库包装器 --------------
# 当数据库连接启动时,数据库包装器发出的信号
# 当数据库包装器与数据库进行初始连接时发送。 如果你想向 SQL 后端发送任何连接后的命令,这一点特别有用。
django.db.backends.signals.connection_created

内置信号的使用

内置信号的基本写法

相关信号的回调函数所在文件

项目应用目录/_init_.py中实现信号相关代码,里有两种方式触发信号的回调函数。

  • 方式1:

    # 导入相关信号
    from django.db.models.signals import post_save
    
    def my_callbak(sender, **kwargs):
        """ 回调函数 """
        print(sender, kwargs)
    
    post_save.connect(my_callback)  # 信号触发回调函数
    
  • 方式2:

    # 导入相关信号
    from django.core.signals import request_started, request_finished
    # 以装饰器的形式激活信号,所以要先导入装饰器
    from django.dispatch import receiver
    
    @receiver(post_save)
    def my_callbak(sender, **kwargs):
        """ 回调函数 """
        print(sender, kwargs)
    
具体示例演示

当对表做新增/更新/删除操作时,触发信号执行,我们可以通过示例中的代码执行情况,来决定什么场景下用什么信号搭配。

相关信号的回调函数所在文件都在demo4\__init__.py,并且代码不变了:

from django.db.models.signals import pre_init, post_init, pre_save, post_save, pre_delete, post_delete
from django.dispatch import receiver

@receiver(pre_init)
def pre_init_callback(sender, **kwargs):
    """ 每当实例化一个 Django 模型时,这个信号都会在模型的 __init__() 方法的开头发出 """
    print('pre_init_callback', sender)
    print('pre_init_callback', kwargs)


@receiver(post_init)
def post_init_callback(sender, **kwargs):
    """ 和 pre_init 一样,但这个是在 __init__() 方法完成后发送的 """
    print('post_init_callback', sender)
    print('post_init_callback', kwargs)


@receiver(pre_save)
def pre_save_callback(sender, **kwargs):
    """ 这是在模型的 save() 方法开始时发送的 """
    print('pre_save_callback', sender)
    print('pre_save_callback', kwargs)
    print('pre_save_callback', kwargs['instance'].name)


@receiver(post_save)
def post_save_callback(sender, **kwargs):
    """ 就像 pre_save 一样,但在 save() 方法的最后发送 """
    print('post_save_callback', sender)
    print('post_save_callback', kwargs)
    print('post_save_callback', kwargs['instance'].name)


@receiver(pre_delete)
def pre_delete_callback(sender, **kwargs):
    """ 在模型的 delete() 方法和查询集的 delete() 方法开始时发送 """
    print('pre_delete_callback', sender)
    print('pre_delete_callback', kwargs)


@receiver(post_delete)
def post_delete_callback(sender, **kwargs):
    """ 就像 pre_delete 一样,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送 """
    print('post_delete_callback', sender)
    print('post_delete_callback', kwargs)

Django 缓存

意义与功能

在Django项目中,缓存的主要意义和功能在于提高数据读取速度并减少对数据库的访问次数,从而提高网站的性能。缓存通常是一类可以更快地读取数据的介质,如内存,常用于存储临时数据。

Django 自带强大的缓存系统,可以让你保存动态页面,这样就不必为每次请求计算。为了方便,Django 提供了不同级别的缓存粒度。你可以缓存特定视图的输出,你可以只缓存难以生成的部分 ,或者你可以缓存整个王治郅。

当用户请求到达视图时,视图通常会从数据库提取数据并放到模板中进行动态渲染。如果每次用户请求都从数据库提取数据并渲染,将极大降低性能,不仅服务器压力大,而且客户端也无法即时获得响应。但如果使用了缓存,在一定时间内有人来访问这些数据时,系统会直接从缓存中取得数据并返回给用户,而不再去执行数据库查询及渲染等操作。

在Django REST framework中,使用缓存是有一定优势的。对于经常被用户查询使用且数据基本不变化的数据,我们可以通过使用缓存来处理,从而减少数据库的查询次数,提升系统的性能。尤其在前后端分离的架构中,由于前端是由Vue3.0构建的,可能会有大量的数据请求,这时适当地使用缓存可以有效地缓解服务器的压力,提高响应速度。

具体来说,你可以在Django REST framework中使用drf-extensions扩展来实现缓存功能。同时,也可以根据需要选择使用内存缓存、文件缓存或数据库缓存等不同的缓存方式。此外,还可以结合分页器来优化数据的获取和展示。

总的来说,根据你的项目具体情况,合理使用缓存技术可以有效地提升应用的性能和用户体验。但是也需要注意,过度依赖缓存或者不合理的使用缓存策略也可能带来一些问题,如数据一致性问题、缓存穿透问题等,所以在使用时需要谨慎对待。

注意事项

在后端Django项目中设置缓存时,有一些重要的注意事项需要遵循:

  1. 合理控制缓存粒度:在设计缓存方案时,需要对缓存的粒度进行合理的控制。例如,如果数据更新的频率较高,那么应选择较小的缓存粒度;反之,如果数据更新的频率较低,可以选择较大的缓存粒度。
  2. 缓存的有效期:需要为缓存数据设定一个合理的有效期,以保证用户获取到的数据是最新的。例如,对于一些更新频率不高的信息,如博客文章和商品描述信息等,可以设置较长的缓存有效期。
  3. 并发控制:由于多个用户可能同时访问和操作同一份数据,因此需要确保并发控制的正确性。当数据发生更新时,需要保证所有读取该数据的用户的缓存都被正确地失效。
  4. 缓存穿透问题:当某个查询请求的数据在缓存和数据库中都不存在时,会引发缓存穿透问题。对此,可以通过在缓存中为这些查询请求预留一个空值来解决。
  5. 缓存雪崩问题:当大量数据同时过期并尝试从数据库重新加载时,可能会导致缓存雪崩问题。为了避免这种情况,可以采取随机延迟缓存加载或者将缓存的过期时间分散开的策略。

局部视图缓存

缓存指定的视图函数,有两种方式:

原始示例代码

import datetime
from django.shortcuts import render
from django.http import HttpResponse

def demo(request):
    now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return render(request, 'index.html', {'now': now})
装饰器方式
import datetime
from django.shortcuts import render
from django.http import HttpResponse
# 导入缓存装饰器
from django.views.decorators.cache import cache_page

# 视图函数头上加装饰器的方式,实现视图缓存
cache_page(6)  # 缓存单位:秒
def demo(request):
    now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return render(request, 'index.html', {'now': now})
路由方式
from django.urls import path, include
from app01 import views
# 导入缓存装饰器
from django.views.decorators.cache import cache_page

# 在路由中指定缓存的视图函数的方式,实现视图缓存
urlpatterns = [
    path('cache/', cache_page(6)(views.demo_cache)),
]

模板片段缓存

模板缓存,粒度更新,,相当于对于页面的局部进行缓存

全栈缓存

就是整个项目进行缓存,粒度是最大的

全站缓存的配置需要用到中间件和其他参数,setting.py

MIDDLEWARE = [
    # 这个缓存中间件必须放在所有中间件的最上面
    "django.middleware.cache.UpdateCacheMiddleware",
    ...
    # 这个缓存中间件必须放在所有中间件的最下面
    "django.middleware.cache.FetchFromCacheMiddleware",
]
# 这里是将 cache 缓存有本地内存缓存更换为 Redis
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://:@127.0.0.1:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "123456",  # 密码,如果没有设置密码,这个参数可以注释掉
            # "MAX_ENTRIES": 300,    # 最大缓存个数(默认300)
            # "CULL_FREQUENCY": 3,   # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
        }
    }
}
# 默认超时时间是300秒,我们可以通过CACHE_MIDDLEWARE_SECONDS来修改
CACHE_MIDDLEWARE_SECONDS = 20
# 下面是关于key的,咱们这里保持默认就完了
CACHE_MIDDLEWARE_KEY_PREFIX = ""
# 用于存储的缓存别名,没想好的,指定个default就行
CACHE_MIDDLEWARE_ALIAS = "default"

注意:如果同时使用了全栈缓存和局部模板片段缓存,那么全栈缓存的优先级别高。

Redis

redis是一个独立的非关系型数据。

官方建议,将 Redis 安装到 Linux 系统,所以,官网上找不到 redis 关于 Windows 的安装包。

安装

redis3 for Windows

参考:https://www.cnblogs.com/Neeo/articles/12673194.html#windows

redis3 for Centos

参考:https://www.cnblogs.com/Neeo/articles/12673194.html#redis307-for-centos79

注意:必须关闭 centos 系统的防火墙

# 查看防火墙状态
systemctl status firewalld.service
# 关闭防火墙
systemctl stop firewalld.service
# 禁止开机启动防火墙
systemctl disable firewalld.service
# 启动防火墙
systemctl start firewalld.service
# 防火墙随系统开启启动
systemctl enable firewalld.service

# 关闭selinux
[root@r ~]# sed -i.ori 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config

附件

项目中集成云短信

原来,推荐使用腾讯云短信。但最近,发现腾讯云短信的功能开通,大家可能会卡在签名这里,因为要认证。比如说,企业资质,我们个人开发学习阶段,不容易开通, 导致用不了。

这是咱们一期项目中,同学遇到的关于短信的主要问题。

我个人建议:如果你自己学习阶段,去开通腾讯云短信服务,卡在某个环节,进行不下去了。那么你没比较纠结。可以专用其它厂家提供的短信服务。

  • 云通讯。
  • 互亿无限。

其实用哪个厂家的短信服务,没关系,你会发现,其实都没几行代码。

参考:https://www.cnblogs.com/Neeo/articles/16672659.html

腾讯云短信

  1. 下载模块
pip install --upgrade tencentcloud-sdk-python
  1. 创建好应用,并获取应用ID,打开连接: https://console.cloud.tencent.com/smsv2/app-manage/detail/1400793930 ,遇到登录,就微信扫码登录。

image-20230730104854342

image-20230730104913334

  1. 准备好秘钥,用于获取SecretId和SecretKey,秘钥管理:https://console.cloud.tencent.com/cam/capi

image-20230730104956114

image-20230730105058996

  1. 签名,准备好签名,这一步有点麻烦,因为需要审核。好多学生卡在这里了。连接:https://console.cloud.tencent.com/smsv2/csms-sign

image-20230730105153588

image-20230730105403319

image-20230730105451450

  1. 准备好模板,比如| 验证码为:{1},您正在登录,若非本人操作,请勿泄露。其中的{1}就是我们将来要自定义

    image-20230730105613116

image-20230730105632718

  1. 下面是我整理好的腾讯云短信发送的示例,可以拷贝到你的项目中进行使用。
# -*- coding = utf-8 -*-
import random
import string
from tencentcloud.common import credential
from tencentcloud.sms.v20210111 import sms_client, models

# 注意,下面写法是固定的,你只需要按照上面给的链接中,获取对应的值,来修改即可

# 下面这俩值从这个页面中获取:https://console.cloud.tencent.com/cam/capi
SecretId = "AKIDQdZFemCGdlcCo9sQ9sKkwriIq776rUu4"
SecretKey = "FEvLZWBZgX7JFzRcW8VbgU7UZOvCuc3H"


cred = credential.Credential(
    secret_id=SecretId,    # 注意必须以关键字的形式传参
    secret_key=SecretKey   # 注意必须以关键字的形式传参
)  # secretId secretKey
client = sms_client.SmsClient(cred, "ap-guangzhou")  # 固定写法无需变动

req = models.SendSmsRequest()  # 固定写法无需变动

# 应用id,注意这个是你创建的应用id,是这个链接:https://console.cloud.tencent.com/smsv2/app-manage 中的应用ID,
# 不是密钥页面的APPID,既不是这个页面的ID:https://console.cloud.tencent.com/cam/capi
req.SmsSdkAppId = "1400793930"

# 模板相关的值从这个页面中获取:https://console.cloud.tencent.com/smsv2/csms-template
req.SignName = "张开与老虎"   # 模板签名
req.TemplateId = "575000"   # 模板id(可以有多套模板进行更换,这样发送的短信内容就可以改变了)

# 下面就是结合模板填写具体要发送的验证码和手机号了
code = ''.join(random.sample(string.digits, 6))  # 随机生成的6位验证码
print(code)  # 671093
req.TemplateParamSet = [code]  # 发送的6位验证码
req.PhoneNumberSet = ["+8618211101742"]   # 目标手机号

resp = client.SendSms(req)  # 发送
print(resp)
"""
{
	"SendStatusSet": [
		{
			"SerialNo": "2433:326237685316756579077330174", 
			"PhoneNumber": "+8618211101742", 
			"Fee": 1, "SessionContext": "", 
			"Code": "Ok", "Message": "send success", "IsoCode": "CN"
			}
	], 
	"RequestId": "25147cb8-9abd-4100-bdbb-bb61cd3c2828"
}

# 你的手机号,应该接收到了短信:
【张开与老虎】验证码为:671093,您正在登录,若非本人操作,请勿泄露。
"""

优化一版:

# -*- coding = utf-8 -*-
import random
import string
from tencentcloud.common import credential
from tencentcloud.sms.v20210111 import sms_client, models

# 注意,下面写法是固定的,你只需要按照上面给的链接中,获取对应的值,来修改即可

# 下面这俩值从这个页面中获取:https://console.cloud.tencent.com/cam/capi
SecretId = "AKIDQdZFemCGdlcCo9sQ9sKkwriIq776rUu4"
SecretKey = "FEvLZWBZgX7JFzRcW8VbgU7UZOvCuc3H"

def getcode(num):
    # 下面就是结合模板填写具体要发送的验证码和手机号了
    code = ''.join(random.sample(string.digits, num))  # 根据需要随机生成的指定位数的验证码
    print(code)  # 671093
    return code

def send_sms(phone, temp_id):
    cred = credential.Credential(
        secret_id=SecretId,    # 注意必须以关键字的形式传参
        secret_key=SecretKey   # 注意必须以关键字的形式传参
    )  # secretId secretKey
    client = sms_client.SmsClient(cred, "ap-guangzhou")  # 固定写法无需变动

    req = models.SendSmsRequest()  # 固定写法无需变动

    # 应用id,注意这个是你创建的应用id,是这个链接:https://console.cloud.tencent.com/smsv2/app-manage 中的应用ID,
    # 不是密钥页面的APPID,既不是这个页面的ID:https://console.cloud.tencent.com/cam/capi
    req.SmsSdkAppId = "1400793930"

    # 模板相关的值从这个页面中获取:https://console.cloud.tencent.com/smsv2/csms-template
    req.SignName = "张开与老虎"   # 模板签名
    req.TemplateId = temp_id   # 模板id(可以有多套模板进行更换,这样发送的短信内容就可以改变了)


    req.TemplateParamSet = [getcode(num=4)]  # 发送的4位验证码
    req.PhoneNumberSet = [f"+86{phone}"]   # 目标手机号

    resp = client.SendSms(req)  # 发送
    # print(333, resp.SendStatusSet[0].__dict__)
    # print(333, resp.SendStatusSet[0].__dict__['_Code'])
    # 我这里发送太频繁了,导致下面代码没法测了,后续你们自己可以根据打印内容进行测试,主要就是判断发送成功还是失败
    if resp.SendStatusSet[0].__dict__['_Code'] == 'OK':
        # 短信发送成功
        print(11)
        return True
    else:
        # 短信发送失败
        print(22)
        return False
    """
    {
        "SendStatusSet": [
            {
                "SerialNo": "2433:326237685316756579077330174", 
                "PhoneNumber": "+8618211101742", 
                "Fee": 1, "SessionContext": "", 
                "Code": "Ok", "Message": "send success", "IsoCode": "CN"
                }
        ], 
        "RequestId": "25147cb8-9abd-4100-bdbb-bb61cd3c2828"
    }

    # 你的手机号,应该接收到了短信:
    【张开与老虎】验证码为:671093,您正在登录,若非本人操作,请勿泄露。

    # 更换不同的模板id,其它代码可以不变,就能动态的发送不同的短信
    【张开与老虎】您的动态验证码为:036724,您正在进行密码重置操作,如非本人操作,请忽略本短信!

    # 发送太频繁了,报错了
    {'_SendStatusSet': [{"SerialNo": "", "PhoneNumber": "+8618211101742", "Fee": 0, "SessionContext": "", "Code": "LimitExceeded.PhoneNumberThirtySecondLimit", "Message": "the number of SMS messages sent from a single mobile number within 30 seconds exceeds the upper limit", "IsoCode": "CN"}], '_RequestId': '7bd7d567-61a2-479a-b37c-b342f79792aa'}

    """
if __name__ == '__main__':
    res = send_sms('18211101742', "575001")  # 第二个参数是模板id,不同的模板id,可以发送不同的短信
    # 伪代码
    # if not res:
    #     return JsonResponse({"code": 1000, 'msg':"短信发送失败"})
    # # 接着往下写短信发送成功的代码

image-20230730110607066

云通信短信(如果搞不定腾讯云短信,推荐这个)

为啥推荐,因为这个简单。

参考:https://www.cnblogs.com/Neeo/articles/16672659.html#容联云短信

注册

https://console.yuntongxun.com/user/reg/init

image-20230730113205659

登录

image-20230730113253365

登录成功自动跳转到控制台主页。遇到下面的认证提示,直接点击关闭即可。

image-20230730113157329

进入控制台首页:https://console.yuntongxun.com/member/main

image-20230730113400867

应用

用默认的就就行了。

image-20230730113708519

模板部分

想要自定义模板,需要充值5000块,算了吧,用默认的就行了,免费。

手机号

测试阶段,你只能向下面预留的手机号发送测试短信。

image-20230730114130962

具体使用

image-20230730114350640

image-20230730114402609

常见报错

{'statusCode': '112310', 'statusMsg': '【短信】应用未上线,模板短信接收号码外呼受限'}

Response body:  {"statusCode":"112310","statusMsg":"【短信】应用未上线,模板短信接收号码外呼受限"}

image-20230730115448494

{'statusCode': '161125', 'statusMsg': '请输入1到4位的数字'}

image-20230730115607013

# -*- coding = utf-8 -*-
import random
import json
from ronglian_sms_sdk import SmsSDK

def send_sms(mobile, datas, tid=1):
    """
    发送短信
    @params tid: 模板ID,默认测试使用1
    @params mobile: 接收短信的手机号,多个手机号使用都逗号隔开
            单个号码: mobile="13312345678"
            多个号码: mobile="13312345678,13312345679,...."
    @params datas: 短信模板的参数列表
            例如短信模板为: 【云通讯】您的验证码是{1},请于{2}分钟内正确输入。
            则datas=("3456",2,)
            最终发送的短信为:【云通讯】您的验证码是{3456},请于{5}分钟内正确输入。
    """
    # 下面的配置根据你的控制台首页主账号那里获取
    RONGLIANYUN_CONFIG = {
        # 下面这三个值都可以从控制台首页的开发者主账号中进行获取
        "accId": '2c94811c87fb7ec601881e50a8ed0b39',  # 对应:ACCOUNT SID
        "accToken": '23d2e5f9f7694d9e888eb2a6848dae42',  # 对应:AUTH TOKEN
        "appId": '2c94811c87fb7ec601881e50aa210b40',  # 对应:AppID(默认)
        "reg_tid": 1,       # 注册短信验证码的模板ID,测试阶段固定为1,不需要修改
        # 下面两个根据需要修改
        "sms_expire": 120,  # 短信有效期,单位:秒(s),这是真正的超时时间,注意和datas=("3456",2,)中第二个参数进行换算,保持一致
        "sms_interval": 60,  # 短信发送的冷却时间,单位:秒(s)
    }
    # 不需要修改
    sdk = SmsSDK(RONGLIANYUN_CONFIG.get("accId"), RONGLIANYUN_CONFIG.get("accToken"), RONGLIANYUN_CONFIG.get("appId"))

    # 发送短信
    resp = sdk.sendMessage(str(tid), mobile, datas)
    # 拿到结果
    response = json.loads(resp)
    print(response, type(response))
    """
    Sign plaintext:  2c94811c87fb7ec601881e50a8ed0b3923d2e5f9f7694d9e888eb2a6848dae4220230524140059
    Authorization plaintext: 2c94811c87fb7ec601881e50a8ed0b39:20230524140059
    Request url:  https://app.cloopen.com:8883/2013-12-26/Accounts/2c94811c87fb7ec601881e50a8ed0b39/SMS/TemplateSMS?sig=6FB880CDC9671A41674C17DE348D300B
    Request headers:  {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json', 'Accept-Charset': 'UTF-8', 'Authorization': b'MmM5NDgxMWM4N2ZiN2VjNjAxODgxZTUwYThlZDBiMzk6MjAyMzA1MjQxNDAwNTk='}
    Request body:  {"to": "18211101742", "appId": "2c94811c87fb7ec601881e50aa210b40", "templateId": "1", "datas": [4653, 2]}
    Response body:  {"statusCode":"000000","templateSMS":{"smsMessageSid":"8bcc77c455084a6b8d52983e95f10977","dateCreated":"20230524140100"}}
    {'statusCode': '000000', 'templateSMS': {'smsMessageSid': '8bcc77c455084a6b8d52983e95f10977', 'dateCreated': '20230524140100'}} <class 'dict'>
    """
    # statusCode是'000000'表示发送成功
    # 手机上接收到的短信长这样
    """
    【云通讯】您使用的是云通讯短信模板,您的验证码是2491,请于2分钟内正确输入(变量仅支持1-4位数字)
    """
    return response.get("statusCode") == "000000"

def get_code(num=4):
    """
    生成指定位数的验证码,如果不传值,就默认生成4位的验证码
    :param num: 要生成几位的验证码
    :return: 生成的验证码
    """
    return random.randint(
        int('1{}'.format('0' * (num - 1))),
        int('9{}'.format('9' * (num - 1)))
    )

if __name__ == '__main__':

    res = send_sms(
        mobile='18211101742',    # 这个手机号必须在你在容联云中填写的那个3个测试手机号中
        datas=(get_code(4), 2),  # 元组的第一个参数是4位的验证码,注意免费测试的只支持发送4位的验证码,不支持6位的,第二个参数是短信模板中替换为:请于{2}分钟内正确输入
        tid=1                    # 这个模板id目前测试阶段固定为1
    )
    # 伪代码
    # if not res:
    #     return JsonResponse({"code": 1000, 'msg':"短信发送失败"})
    # # 接着往下写短信发送成功的代码

互亿无线106短信

实名认证

1. 注册

实名注册:https://user.ihuyi.com/new/register.html?e=400

image-20230730140850579

2. 登录之后,在控制台进行个人认证

image-20230730141026596

image-20230730141000579

image-20230730141132354

短信demo

首先,准备这两个值

官网链接:https://user.ihuyi.com/new/sms/overview

image-20230730141327916

代码:

# -*- coding = utf-8 -*-
# python3
# 接口类型:互亿无线触发短信接口,支持发送验证码短信、订单通知短信等。
# 账户注册:请通过该地址开通账户https://user.ihuyi.com/new/register.html
# 注意事项:
# (1)调试期间,请用默认的模板进行测试,默认模板详见接口文档;
# (2)请使用 用户名 及 APIkey来调用接口,APIkey在会员中心可以获取;
# (3)该代码仅供接入互亿无线短信接口参考使用,客户可根据实际需要自行编写;
import json
import random
import urllib.parse
import urllib.request


def get_code(num=4):
    """
    生成指定位数的验证码,如果不传值,就默认生成4位的验证码
    :param num: 要生成几位的验证码
    :return: 生成的验证码
    """
    return random.randint(
        int('1{}'.format('0' * (num - 1))),
        int('9{}'.format('9' * (num - 1)))
    )


def send_sms(mobile, code_num=4):
    """
    发送短信验证码
    :param mobile: 你要发给谁
    :param code_num: 发送几位的验证码
    :return:
    """
    # 接口地址,咱们不需要更改
    url = 'http://106.ihuyi.com/webservice/sms.php?method=Submit'

    # 定义请求的数据
    values = {
        'account': 'C05062020',  # 这个是对应的APIID
        'password': 'aff16e275e46618efd14f21b00d6de91',  # 这个是对应的APIKEY
        'mobile': mobile,  # 发给谁
        'content': '您的验证码是:{}。请不要把验证码泄露给其他人。'.format(get_code()),
        # 没有购买套餐的,这个模板只能使用默认的,即你收到的短信长这样:【互亿无线】您的验证码是:7835。请不要把验证码泄露给其他人。
        'format': 'json',  # 不要动
    }

    # 将数据进行编码,下面代码不要动
    data = urllib.parse.urlencode(values).encode(encoding='UTF8')

    # 发起请求,下面代码不要动
    req = urllib.request.Request(url, data)
    response = urllib.request.urlopen(req)
    res = response.read()

    # 打印结果,然后你的手机应该就能接到短信了
    print(res.decode("utf8"), type(res.decode("utf8")))  # {"code":2,"msg":"提交成功","smsid":"16842079209571524017"}
    dict_res = json.loads(res.decode("utf8"))
    if dict_res.get("code", 0) == 2:
        print('短信发送成功,请留意手机')
        return True
    else:
        print('发送失败')
        return False
    """
    {"code":2,"msg":"提交成功","smsid":"16906979568648248914"} <class 'str'>
    短信发送成功,请留意手机
    
    # 短信长这样:
        【互亿无线】您的验证码是:3476。请不要把验证码泄露给其他人。
    """

if __name__ == '__main__':
    send_sms('18211101742')

响应结果的状态码查询,https://www.ihuyi.com/api/sms.html

image-20230730142304263

小结:

  • 如果腾讯云能通过签名认证,则首选腾讯云短信。想发谁就发谁,可以自定义短信模板和签名。
  • 其次是云通信短信。测试开发阶段,可以不认证,就可以进行测试,但模板和签名都是默认的,而且收短信的手机号必须是在平台注册的。而且它免费赠送了一些短信额度,比较划算。
  • 最后备选是互亿无限,它发短信稍微慢了点。有十条免费的短信,重置也不贵,10块钱200条。

image-20230730142626744

常用第三方

验证码(Django Simple Captcha)

安装配置

  • 安装:pip install django-simple-captcha

  • 注册:在 settings.py 文件中 INSTALLED_APPS 内注册登记,

    INSTALLED_APPS = [
    	...
        'captcha',  # 注册验证码应用
        ...
    ]
    # ================================================= #
    # **************** 验证码配置  ******************* #
    # ================================================= #
    CAPTCHA_STATE = True
    CAPTCHA_IMAGE_SIZE = (150, 46)  # 设置 captcha 图片大小
    CAPTCHA_LENGTH = 4  # 设置字符个数
    CAPTCHA_TIMEOUT = 1  # 设置超时(minutes)
    # CAPTCHA_OUTPUT_FORMAT 是设置三者的显示顺序
    CAPTCHA_OUTPUT_FORMAT = '%(image)s %(text_field)s %(hidden_field)s '
    CAPTCHA_FONT_SIZE = 36  # 设置字体大小
    CAPTCHA_FOREGROUND_COLOR = '#0033FF'  # 设置图片前景色
    CAPTCHA_BACKGROUND_COLOR = '#F5F7F4'  # 设置图片背景色
    # 设置图片噪点
    CAPTCHA_NOISE_FUNCTIONS = (
        'captcha.helpers.noise_arcs',  # 设置干扰线
        'captcha.helpers.noise_dots',  # 设置干扰点
    )
    # CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' # 随机字母验证码
    # CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.word_challenge' # 英文单词验证码
    CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge'  # 加减乘除验证码
    
  • Django Simple Captcha设有多种验证码的生成方式,如设置验证码的内容、图片噪点和图片大小等,这些功能设置可以在Django Simple Captcha的源码文件(site-packages\captcha\conf\settings.py)中查看。

  • 完成配置后,执行数据迁移,因为验证码需要依赖数据表才能得以实现。

功能实现

from django.urls import path
from apps.system import views

urlpatterns = [
    ...
    path('captcha/', views.CaptchaView.as_view()),
    ...

]
import base64
from django.http import HttpResponse, JsonResponse
from rest_framework.views import APIView
from captcha.views import CaptchaStore, captcha_image
class CaptchaView(APIView):
    """
    获取验证码
    """
    authentication_classes = []
    permission_classes = []
    def get(self, request):
        hashkey = CaptchaStore.generate_key()
        id = CaptchaStore.objects.filter(hashkey=hashkey).first().id
        image = captcha_image(request, hashkey)
        # 将图片转换为base64 image_base
        image_base = base64.b64encode(image.content)
        # 'code': 2000 是与前端的约定
        json_data = {'code': 2000, "key": id, "image_base": "data:image/png;base64," + image_base.decode('utf-8')}
        return JsonResponse(data=json_data)

前端

const getCaptcha = async() =>{
  api.getCaptcha().then((ret)=>{
    console.log('@@@' + ret)
    loginForm.captchaImgBase = ret.image_base
    loginForm.captchaKey = ret.key
  })
}
posted @ 2023-12-17 00:56  学习是一生要做的事  阅读(896)  评论(0)    收藏  举报