DJango5学习日志

Posted by Comet on February 15, 2024

Django5介绍及安装

Django5简介

Django(发音:[`dʒæŋɡəʊ]) 也有的小伙伴读成 “酱狗”,”贱狗”,”进狗”,”撞狗”,甚至还有读成”打狗”。

官方:https://www.djangoproject.com/

Django是一个高级的Python Web框架,可以快速开发安全和可维护的网站。由经验丰富的开发者构建,Django负责处理网站开发中麻烦的部分,可以专注于编写应用程序,而无需重新开发。它是免费和开源的,有活跃繁荣的社区,丰富的文档,以及很多免费和付费的解决方案。目前最新版本:5.0.1

image-20240131210528300

Django5安装

pip安装:

1
pip install Django==5.0.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

执行安装完成后,在python目录的Scripts下,会多出一个diango-admin.exe 这个是django项目创建工具

image-20240131220327128

当然同时Lib下的site-packages下,也会有一个django目录,这个是后面我们开发项目会用到的django开发包。

image-20240131220452238

Django5项目创建与项目配置

Django5创建项目(用命令方式)

首先cmd,进入命令提示符终端

image-20240202122717635

我这边是准备把项目创建在 D:\python\django5 这个目录下。

cd 切换到你需要建项目的目录:

1
cd /d D:\python\django5

image-20240202130345896

接下啦 借助 django-admin 工具,创建项目,命令如下:

1
django-admin startproject 项目名称

image-20240202134335721

执行后,我们去看下 项目目录:

image-20240202134401394

image-20240202134408937

说明项目创建成功。

Django5创建项目(用PyCharm工具)

除了在命令提示符窗口创建项目之外,还可以在 PyCharm中创建项目。PyCharm必须为专业版才能创建与调试 Django项目,社区版是不支持此功能的。打开PyCharm并在左上方单击File→New Project,选择第一个Django,创建新项目,如下图:

image-20240202205117982

相对于用命令方式,PyCharm创建的项目,多了templates目录(用来放html模版文件),以及settings.py,多了 BASE_DIR / 'templates'

image-20240202205750355

这里介绍下默认创建的文件

1
2
3
4
5
6
7
8
9
  python222_site1
    │  manage.py
    │
    └─python222_site1
            asgi.py
            settings.py
            urls.py
            wsgi.py
            __init__.py
  • manage.py :项目管理命令行工具,内置多种方式与项目进行交互,包括启动项目,创建app,数据管理等。在命令提示符窗口下,将路径切换到python222_site1项目并输入python manage.py help,可以查看该工具的指令信息;【不用修改】
  • ` init.py`:初始化文件,一般情况下无须修改;
  • settings.py:项目的配置文件,项目的所有功能都需要在该文件中进行配置;
  • urls.py:项目的路由设置,设置网站的具体网址内容;
  • wsgi.py:全 称 为 Python Web Server Gateway Interface,即Python服务器⽹关接⼝,是Python应⽤与Web服务器之间的接⼝,⽤于Django项⽬在服务器上的部署和上线;【不用修改】
  • asgi.py:开启⼀个ASGI服务,ASGI是异步⽹关协议接⼝;【不用修改】

Django5应用创建与应用配置

Django5操作命令

我们掌握了如何在命令提示符或PyCharm下创建Django项目和项目应用,无论是创建项目还是创建项目应用,都需要输入相关的指令才能得以实现,这些都是Django内置的操作指令。

在PyCharm的Terminal中输入指令 python manage.py help 并按回车键,即可看到相关的指令信息,如下图所示。

image-20240203062507128

Django5 的操作指令共有31条,每条指令的说明以表格形式展示。

指令 说明
changepassword 修改内置用户表的用户密码
createsuperuser 为内置用户表创建超级管理员账号
remove_stale_contenttypes 删除数据库中已不使用的数据表
check 检测整个项目是否存在异常问题
compilemessages 编译语言文件,用于项目的区域语言设置
createcachetable 创建缓存数据表,为内置的缓存机制提供存储功能
dbshell 进入Django配置的数据库,可以执行数据库的SOL语句
diffsettings 显示当前settings.py的配置信息与默认配置的差异
dumpdata 导出数据表的数据并以JSON格式存储,如 python manage.py dumpdata index >data.json,这是index的模型所对应的数据导出,并保存在 data.json文件中
flush 清空数据表的数据信息
inspectdb 获取项目所有模型的定义过程
loaddata 将数据文件导入数据表,如 python manage.py loaddatadata.,json
makemessages 创建语言文件,用于项目的区域语言设置
makemigrations 从模型对象创建数据迁移文件并保存在App 的migrations文件夹
migrate 根据迁移文件的内容,在数据库里生成相应的数据表
sendtestemail 向指定的收件人发送测试的电子邮件
shell 进入Django的Shell模式,用于调试项目功能
showmigrations 查看当前项目的所有迁移文件
sqlflush 查看清空数据库的SOL语句脚本
sqlmigrate 根据迁移文件内容输出相应的SQL语句
sqlsequencereset 重置数据表递增字段的索引值
squashmigrations 对迁移文件进行压缩处理
startapp 创建项目应用App
optimizemigration 允许优化迁移操作
startproject 创建新的Django项目
test 运行App里面的测试程序
testserver 新建测试数据库并使用该数据库运行项目
clearsessions 清除会话Session数据
collectstatic 收集所有的静态文件
findstatic 查找静态文件的路径信息
runserver 在本地计算机上启动Django项目

小技巧,前面每次执行命令都要在Terminal终端输入 python manage.py 命令

比较繁琐,我们借助PyCharm开发工具,在菜单Tools里,有个 Run manage.py Task...,直接点击

image-20240203064746201

直接输入命令,比如 help,结果就出来,是不是很方便,以后我们就用这种简便方式,来提高工作效率。

image-20240203064907048

Django5应用创建

前面我们创建的是一个项目,一个项目是由于一个或者多个应用组成(我们一般开发,一个项目里就创建一个应用即可)。

项目里的每个应用 都是独立的,可以拥有独立的数据库,模版代码,业务代码。

比如,一个网站,可以有前台用户应用和后台管理员应用;

image-20240203065945159

复杂的电商项目,后台甚至可以拆分用户应用,商品应用,订单应用,支付应用,积分应用,优惠券应用等。

image-20240203070100404

前面我们学过Django5的操作命令,有一个命令是 startapp,就是用来创建项目应用APP的。

我们执行 startapp app01 创建名字为app01的应用,执行完毕后,多出一个app01目录(目录里的生成文件,我们下一讲解释)

image-20240203070451378

我们还可以继续执行 startapp app02,startapp app03

image-20240203070628402

一个项目可以拥有一个或者多个应用,所以理论上,我们可以创建无数应用。但是还是强调下,一般情况,我们一个项目就之需要创建一个应用即可。

Django5应用配置

为了更好的理解Django5的应用配置,我们先来学习下Django的MTV模型。

Django的MTV分别代表:

Model(模型):业务对象与数据库的对象(ORM)

Template(模版):负责如何把页面展示给用户

View(视图):负责业务逻辑,并在适当的时候调用Model和Template

此外,Django还有一个urls分发器,它的作用是将一个URI的页面请求分发给不同的view处理,view再调用相应的Model和Template。 Django WEB框架示意图如下所示:

image-20240203073932302

前面生成应用结构如下:

1
2
3
4
5
6
7
8
9
10
+---app01
|   |   admin.py
|   |   apps.py
|   |   models.py
|   |   tests.py
|   |   views.py
|   |   __init__.py
|   |
|   \---migrations
|           __init__.py

我们来解释下这些生成的python文件。

__init__.py:说明目录是一个python模块

migrations.py目录:用于存放数据库迁移历史文件

models.py: 用于应用操作数据库的模型

views.py: 用于编写Web应用视图,接收数据,处理数据,与Model(模型),Template(模版)进行交互,返回应答

apps.py:应用配置文件。

tests.py:做单元测试。

admin.py:默认提供了admin后台管理,用作网站的后台管理站点配置相关

Django5 Hello World编写

前面对应用创建和应用配置掌握后,我们来编写第一个Hello World应用吧。体验一把Django5的项目开发过程。

第一步:创建Hello World应用

直接执行startapp helloWorld命令创建应用。

image-20240203074654173

第二步:注册应用到项目的settings.py

image-20240203074900684

把helloWorld应用的apps.py里的HelloworldConfig类注册到settings.py里去

image-20240203075050181

第三步:编写模版网页代码index.html

在templates目录下,新建index.html文件

image-20240203075648286

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>Django5大爷你好!</p>
<a href="http://python222.com/post/7" target="_blank">Python学习路线图</a>
</body>
</html>

第四步:编写视图处理请求层代码

在应用的views.py里编写index方法,request是客户端请求对象,render是渲染方法,可以携带数据渲染到指定页面

1
2
def index(request):
    return render(request,'index.html')

第五步:编写请求映射函数配置

在项目的urls.py里编写应用的index/请求,执行我们上面应用定义的请求处理代码,也就是写一个映射关系代码。

image-20240203081154387

1
2
3
4
5
6
7
import helloWorld.views

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

]

第五步,启动项目,测试

我们可以用前面讲的Django5的操作命令 runserver 启动

image-20240203081216218

默认端口 8000

当然我们还有更简单的方式。直接用PyCharm启动。直接点击绿色小三角即可。

image-20240203081416777

启动后,浏览器输入,因为我们项目urls.py里配置的请求地址就是index/ 所以请求如下

1
 http://127.0.0.1:8000/index/

image-20240203081619080

运行测试成功。

执行过程:客户端请求index/ - > 经过django url请求分发器 - > 执行到应用的views.py的index方法 - > index方法再render渲染到index.html模版代码 - > 最终显示到用户浏览器终端。

Django5项目配置settings.py文件

基本配置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 项目路径
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/

# 密钥配置
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-^+$)&&p^atz-o)&ytg&8%6dq!!ujgh7t2w#2n^i_f#r^#*vyqh'

# 调试模式
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

# 域名访问权限
ALLOWED_HOSTS = []


# Application definition
# APP列表
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'helloWorld.apps.HelloworldConfig'
]
  • BASE_DIR项目路径:主要通过os模块读取当前项目在计算机系统的具体路径,该代码在创建项目时自动生成,一般情况下无须修改。
  • SECRET_KEY密钥配置:密钥配置SECRET_KEY:这是一个随机值,在项目创建的时候自动生成,一般情况下无须修改。主要用于重要数据的加密处理,提高项目的安全性,避免遭到攻击者恶意破坏。密钥主要用于用户密码、CSRF机制和会话Session等数据加密。
    • 用户密码: Django 内置一套Auth认证系统,该系统具有用户认证和存储用户信息等功能,在创建用户的时候,将用户密码通过密钥进行加密处理,保证用户的安全性。
    • CSRF机制:该机制主要用于表单提交,防止窃取网站的用户信息来制造恶意请求。
    • 会话Session: Session的信息存放在Cookie中,以一串随机的字符串表示,用于标识当前访问网站的用户身份,记录相关用户信息。
  • DEBUG调试模式:该值为布尔类型。如果在开发调试阶段,那么应设置为True,在开发调试过程中会自动检测代码是否发生更改,根据检测结果执行是否刷新重启系统。如果项目部署上线,那么应将其改为False,否则会泄漏项目的相关信息。
  • ALLOWED_HOSTS域名访问权限:设置可访问的域名,默认值为空列表。当DEBUG为True并且 ALLOWED_HOSTS为空列表时,项目只允许以localhost或127.0.0.1在浏览器上访问。当DEBUG为False时,ALLOWED_HOSTS为必填项,否则程序无法启动,如果想允许所有域名访问,可设置ALLOW_HOSTS=[‘*’]。
  • INSTALLED_APPSAPP列表:告诉Django有哪些App。在项目创建时已有admin、auth和sessions 等配置信息,这些都是Django内置的应用功能,各个功能说明如下。
    1. admin:内置的后台管理系统。
    2. auth:内置的用户认证系统。
    3. contenttypes:记录项目中所有model元数据( Django 的ORM框架)。
    4. sessions: Session会话功能,用于标识当前访问网站的用户身份,记录相关用户信息。
    5. messages:消息提示功能。
    6. staticfiles:查找静态资源路径。

如果在项目中创建了App,就必须在App列表INSTALLED_APPS添加App类

image-20240204152115633

资源文件配置

资源文件配置分为静态资源和媒体资源。静态资源的配置方式由配置属性STATIC_URL、STATICFILES DIRS和STATIC_ROOT进行设置;媒体资源的配置方式由配置属性MEDIA_URL和MEDIA ROOT决定。

静态资源配置=STATIC_URL

静态资源指的是网站中不会改变的文件。在一般的应用程序中,静态资源包括CSS文件、JavaScript文件以及图片等资源文件。

默认配置,app下的static目录为静态资源,可以直接访问。其他目录不行。

1
STATIC_URL = 'static/'

我们在app下新建static目录,再放一个logo.png图片。

image-20240204154907810

同时在app目录下再新建一个images目录,放一个qq.jpg头像图片

image-20240204155057595

最后再项目目录下新建一个static,放一个pig.jpg,也试试看是否可以访问;

image-20240204155505474

我们启动项目测试:

先测试app下的static目录下的logo.png,能显示没问题。

1
http://127.0.0.1:8000/static/logo.png

image-20240204155620143

再试试app下的images目录下的qq.jpg

1
http://127.0.0.1:8000/static/qq.jpg

image-20240204163444518

404不存在

最后再测试下项目目录下的static下的pig.jpg

1
http://127.0.0.1:8000/static/pig.jpg

image-20240204155805759

也是404不存在。

通过测试说明,也就app下的static目录下的静态资源才能访问。

静态资源集合配置-STATICFILES DIRS

由于STATIC_URL的特殊性,在开发中会造成诸多不便,比如将静态文件夹存放在项目的根目录以及定义多个静态文件夹等。我们可以通过配置STATICFILES DIRS实现多个目录下的静态资源可以访问。

1
2
# 设置静态资源文件集合
STATICFILES_DIRS = [BASE_DIR / "static", BASE_DIR / "helloWorld/images"]

我们再测试下:

1
http://127.0.0.1:8000/static/pig.jpg

image-20240204163323694

1
http://127.0.0.1:8000/static/qq.jpg

image-20240204163405106

静态资源部署配置-STATIC_ROOT

静态资源配置还有STATIC_ROOT,其作用是在服务器上部署项目,实现服务器和项目之间的映射。STATIC_ROOT 主要收集整个项目的静态资源并存放在一个新的文件夹,然后由该文件夹与服务器之间构建映射关系。STATIC_ROOT配置如下:

1
2
# 静态资源部署
STATIC_ROOT = BASE_DIR / 'static'

当项目的配置属性 DEBUG 设为True的时候,Django 会自动提供静态文件代理服务,此时整个项目处于开发阶段,因此无须使用STATIC_ROOT。当配置属性DEBUG 设为False的时候,意味着项目进入生产环境,Django不再提供静态文件代理服务,此时需要在项目的配置文件中设置STATIC_ROOT。 设置STATIC_ROOT需要使用 Django操作指令collectstatic来收集所有静态资源,这些静态资源都会保存在STATIC_ROOT所设置的文件夹里。

媒体资源配置-MEDIA

一般情况下,STATIC_URL是设置静态文件的路由地址,如CSS样式文件、JavaScript文件以及常用图片等。对于一些经常变动的资源,通常将其存放在媒体资源文件夹,如用户头像、歌曲文件等。

媒体资源和静态资源是可以同时存在的,而且两者可以独立运行,互不影响,而媒体资源只有配置属性MEDIA_URL和 MEDIA_ROOT。

我们在项目目录下新建media目录,里面再放一个boy.jpg图片。

image-20240204172234364

然后在配置文件settings.py里设置配置属性MEDIA_URL和 MEDIA_ROOT,MEDIA_URL用于设置媒体资源的路由地址,MEDIA_ROOT用于获取 media文件夹在计算机系统的完整路径信息,如下所示:

1
2
3
4
# 设置媒体路由
MEDIA_URL = 'media/'
# 设置media目录的完整路径
MEDIA_ROOT = BASE_DIR / 'media'

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.conf import settings
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve

import helloWorld.views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', helloWorld.views.index),
    # 配置媒体文件的路由地址
    re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}, name='media')
]

我们来测试下:

1
http://127.0.0.1:8000/media/boy.jpg

image-20240204173014299

模版配置

在 Web开发中,模板是一种较为特殊的HTML文档。这个HTML文档嵌入了一些能够让Django识别的变量和指令,然后由Django的模板引擎解析这些变量和指令,生成完整的HTML网页并返回给用户浏览。模板是Django里面的MTV框架模式的T部分,配置模板路径是告诉Django在解析模板时,如何找到模板所在的位置。创建项目时,Django已有初始的模板配置信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

模板配置是以列表格式呈现的,每个元素具有不同的含义,其含义说明如下。

  • BACKEND:定义模板引擎,用于识别模板里面的变量和指令。内置的模板引擎有 DjangoTemplates 和 jinja2.Jinja2,每个模板引擎都有自己的变量和指令语法。
  • DIRS:设置模板所在路径,告诉Django在哪个地方查找模板的位置,默认为空列表。
  • APP_DIRS:是否在App里查找模板文件。
  • OPTIONS:用于填充在RequestContext 的上下文(模板里面的变量和指令),一般情况下不做任何修改。

我们是可以在应用里新建templates,供自己的应用使用。在templates下新建index2.html模版文件

image-20240205121557430

views.py里面把index.html改成index2.html

image-20240205121919651

最后就是在DIRS里面加上应用的模版路径即可。

image-20240205121940320

启动测试:

1
http://127.0.0.1:8000/index/

image-20240205122020903

但是我们这里有个疑问,如果说应用里的模版和项目里的模版名字一样,起冲突了。这时候,会选择哪个呢,或者说哪个优先级高?

我们测试下吧。把应用里的index2.html改成index.html,以及views.py里面也改下。

image-20240205122253035

然后我们重新运行测试:运行结果显示的是项目里的模版。

image-20240205122520607

锋哥经过查看Django底层源码,其实优先级顺序是根据模版配置的目录顺序来定的,我们前面项目模版在前面,所以就显示项目模版。

如果我们把应用模版配置路径放前面

image-20240205122706802

运行测试下:

image-20240205122719231

结果就是应用模版了。

数据库配置

数据库配置是选择项目所使用的数据库的类型,不同的数据库需要设置不同的数据库引擎,数据库引擎用于实现项目与数据库的连接,Django提供4种数据库引擎: ‘django.db.backends.postgresql’ ‘django.db.backends.mysql’ ‘django.db.backends.sqlite3’ ‘django.db.backends.oracle’

项目创建时默认使用Sqlite3数据库,这是一款轻型的数据库,常用于嵌入式系统开发,而且占用的资源非常少。Sqlite3数据库配置信息如下:

1
2
3
4
5
6
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

如果要把上述的连接信息改成MySQL数据库,首先需要安装MySQL连接模块 mysqlclient

1
pip install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simple

mysqlclient模块安装后,在项目的配置文件settings.py中配置MySQL数据库连接信息

1
2
3
4
5
6
7
8
9
10
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_python222',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': 'localhost',
        'PORT': '3306'
    }
}

(django5至少需要MySQL 8.0.11版本)

我们来测试下数据库连接;

我们首先在mysql里创建数据库db_python222

然后我们用Django5 manage.py 提供的内置命令 migrate 来创建Django内置功能的数据表;

image-20240212192654562

刷新数据库表:

image-20240212192803613

这些是Django内置自带的Admin后台管理系统,Auth用户系统以及会话机制等功能需要用到的表。

注意:django也支持pymysql,mysqldb等,但是用的时候会有点小问题,所以建议大家还是用mysqlclient,比较稳定。

同时django支持多数据库;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_python222',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': 'localhost',
        'PORT': '3308'
    },
    'mySqlite3': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    },
    'mySql3': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_django',
        'USER': 'root',
        'PASSWORD': '123',
        'HOST': 'localhost',
        'PORT': '3306'
    }
}

例如上面,我们定义了三个数据库,两个mysql,一个sqlite;配置属性DATABASES设有3个键值对,分别是:’default’,’mySqlite3’,’mySql3’,每个键值对代表Django连接了某个数据库。

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

中间件

中间件(Middleware)是一个用来处理 Django 的请求(Request)和响应(Response)的框架级别的钩子,它是一个轻量、低级别的插件系统,用于在全局范围内改变 Django的输入和输出。 当用户在网站中进行某个操作时,这个过程是用户向网站发送HTTP请求(Request);而网站会根据用户的操作返回相关的网页内容,这个过程称为响应处理(Response)。从请求到响应的过程中,当 Django接收到用户请求时,首先经过中间件处理请求信息,执行相关的处理,然后将处理结果返回给用户。

image-20240213114551921

django默认的中间配置如下:

1
2
3
4
5
6
7
8
9
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

django自带的中间件有:

  • SecurityMiddleware:内置的安全机制,保护用户与网站的通信安全。
  • SessionMiddleware:会话Session功能。
  • LocaleMiddleware:国际化和本地化功能。
  • CommonMiddleware:处理请求信息,规范化请求内容。
  • CsrfViewMiddleware:开启CSRF防护功能。
  • AuthenticationMiddleware:开启内置的用户认证系统。
  • MessageMiddleware:开启内置的信息提示功能。
  • XFrameOptionsMiddleware:防止恶意程序单击劫持。

我们也可以自定义中间件:

中间件可以定义五个方法,分别是:(主要的是process_request和process_response),在自己定义中间件时,必须继承MiddlewareMixin

process_request(self,request) 请求views方法之前会执行。 process_view(self, request, callback, callback_args, callback_kwargs) Django会在调用视图函数之前调用process_view方法。 process_template_response(self,request,response) 该方法对视图函数返回值有要求,必须是一个含有render方法类的对象,才会执行此方法 process_exception(self, request, exception) 这个方法只有在视图函数中出现异常了才执行 process_response(self, request, response) 请求执行完成,返回页面前会执行

新建Md1自定义中间件类,继承MiddlewareMixin,实现process_request和process_response方法。

image-20240213121211702

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"""
    自定义中间件
    作者 : 小锋老师
    官网 : www.python222.com
"""
from django.utils.deprecation import MiddlewareMixin


class Md1(MiddlewareMixin):
    def process_request(self, request):
        print("request请求来了")

    def process_response(self, request, response):
        print("请求处理完毕,将返回到页面")
        return response

setting.py里配置自定义中间件。

image-20240213121340027

views.py的index请求处理方法,我们加一句打印。

image-20240213121408003

最后我们运行测试:

1
http://127.0.0.1:8000/index/
1
2
3
request请求来了
页面请求处理中
请求处理完毕,将返回到页面

其他配置

还有一些其他settings.py配置我们了解下

ROOT_URLCONF = ‘python222_site1_pc.urls’ 它指定了当前项目的根 URL,是 Django 路由系统的入口。

WSGI_APPLICATION = ‘python222_site1_pc.wsgi.application’ 项目部署时,Django 的内置服务器将使用的 WSGI 应用程序对象的完整 Python 路径。

AUTH_PASSWORD_VALIDATORS 这是一个支持插拔的密码验证器,且可以一次性配置多个,Django 通过这些内置组件来避免用户设置的密码等级不足的问题。

LANGUAGE_CODE = ‘en-us’ TIME_ZONE = ‘UTC’

分别代表语言配置项和当前服务端时区的配置项,我们常用的配置如下所示:

  • LANGUAGE_CODE 取值是英文:’en-us’或者中文:’zh-Hans’;
  • TIME_ZONE 取值是世界时区 ‘UTC’ 或中国时区 ‘Asia/Shanghai’。

USE_I18N = True 项目开发完成后,可以选择向不同国家的用户提供服务,那么就需要支持国际化和本地化。

USE_TZ = True 它指对时区的处理方式,当设置为 True 的时候,存储到数据库的时间是世界时间 ‘UTC’。

DEFAULT_AUTO_FIELD = ‘django.db.models.BigAutoField’ 默认主键自增类型

Django5路由定义与使用

Django5路由定义

一个完整的路由包含:路由地址、视图函数(或者视图类)、可选变量和路由命名。

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

前面的Hello World项目。我们请求的地址:

1
http://127.0.0.1:8000/index/

就是一个路由地址。

index/请求地址,根据urls.py配置文件,找到对应的helloWorld views下的index视图函数;

image-20240214095458811

image-20240214095529865

然后最终执行index视图函数,然后到index.html页面。

image-20240214095615429

image-20240214095555412

Django5路由变量

在平时开发中,有时候一个路由可以代表多个不同的页面,比如博客系统里面,有1千个博客页面,按照前面学习的方式,需要写1千个路由才能实现,这种做法显然不可取,维护也麻烦。我们可以通过路由变量,来实现一个路由代表多个页面。

路由的变量类型有字符类型、整型、slug 和 uuid,最为常用的是字符类型和整型。各个类型说明如下。

  • 字符类型:匹配任何非空字符串,但不含斜杠。如果没有指定类型,就默认使用该类型。
  • 整型:匹配О和正整数。
  • slug:可理解为注释、后缀或附属等概念,常作为路由的解释性字符。可匹配任何ASCII字符以及连接符和下画线,能使路由更加清晰易懂。比如网页的标题是“15岁的孩子”,其路由地址可以设置为“15-sui-de-hai-zi”。
  • uuid:匹配一个uuid格式的对象。为了防止冲突,规定必须使用“”并且所有字母必须小写,例如175194d3-6885-437e-a8a8-6c231e272f00。

下面列举实例一 博客帖子请求:

首先urls.py里定义路由映射:

1
path('blog/<int:id>', helloWorld.views.blog)

image-20240214101929403

views.py里再定义blog函数实现:

1
2
def blog(request, id):
    return HttpResponse('id是' + str(id) + "的博客页面")

image-20240214102016747

image-20240214102024852

这样,我们就实现了一个带变量的路由的多个博客页面的实现。

当然我们也可以带多个路由变量。让博客的路由地址,在带上年月日变量。

urls.py修改

1
path('blog2/<int:year>/<int:month>/<int:day>/<int:id>', helloWorld.views.blog2)

views.py修改

1
2
def blog2(request, year, month, day, id):
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day) + '/' + '  id是' + str(id) + "的博客页面")

运行测试:

image-20240214102933139

Django5正则路由

有时候我们为了更好的进行路由匹配,可以用正则表达式。

比如前面讲的日期的路由变量,其实是有点问题的。

我们月份输入333,也是满足条件的。但是不符合实际,实际情况月份最多两位数。

image-20240214110103791

所以这时候,我们可以用正则表达式来限制。

urls.py修改:

1
re_path('blog3/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})', helloWorld.views.blog3)

views.py添加blogs方法:

1
2
def blog3(request, year, month, day):
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day) + '的博客页面')

这里有几个正则的注意点:

第一:正则urls匹配,必须用re_path方法;

第二:正则表达式?P开头是固定格式;

运行测试:

image-20240214111820848

Django5路由重定向

重定向称为HTTP协议重定向,也可以称为网页跳转,它对应的HTTP状态码为301、302、303、307、308。简单来说,网页重定向就是在浏览器访问某个网页的时候,这个网页不提供响应内容,而是自动跳转到其他网址,由其他网址来生成响应内容。

Django的网页重定向有两种方式:

第一种方式是路由重定向;

第二种方式是自定义视图的重定向。

两种重定向方式各有优点,前者是使用Django内置的视图类RedirectView实现的,默认支持HTTP的GET请求;后者是在自定义视图的响应状态设置重定向,能让开发者实现多方面的开发需求。

我们分别用实例来演示下这两种方式:

路由重定向

路由重定向方式,我们用RedirectView实现,在urls.py里面,我们再加一个路由代码:

1
path('redirectTo', RedirectView.as_view(url="index/"))

请求redirectTo,直接重定向到 index/ 地址

运行测试,请求:

1
http://127.0.0.1:8000/redirectTo

image-20240214203510469

自动重定向到index,状态是302。

自定义视图重定向

更多的情况,我们平时开发用的是自定义视图重定向,视图代码里,通过逻辑判断,通过redirect方法来实现具体的页面重定向,使用更加灵活。

我们改造下前面的views.py下的blog函数:假如id是0,重定向到错误静态页面。

1
2
3
4
5
def blog(request, id):
    if id == 0:
        return redirect("/static/error.html")
    else:
        return HttpResponse('id是' + str(id) + "的博客页面")

image-20240214205436954

static目录下新建一个error.html文件

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
系统运行有问题!
</body>
</html>

当然你也可以项目根目录下再新建一个目录,比如common,然后 STATICFILES_DIRS 静态资源文件集合里,加下 BASE_DIR / “common”,把error.html放到common目录下,我们也是可以通过static/请求地址访问的。当然如果你觉得static/请求名称不好,也可以修改 STATIC_URL 参数 比如 改成 common/ 也行,你就可以通过 common/ 也访问你的静态资源文件。

image-20240214205539794

我们访问

1
http://127.0.0.1:8000/blog/0

image-20240214210117230

302状态,自动跳转到了error.html错误页面。

访问其他id是正常的。

image-20240214210210839

Django5命名空间namespace

当我们网站项目规模越来越多,子项目很多的时候,为了方便管理路由地址,我们可以采用命名空间namespace来对路由地址根据子项目分类。

我们通过django manage.py自带的startapp命令新建两个项目,分别是user和order

image-20240215182312654

我们分别添加urls.py到user和order项目里去。

以及加下代码:

user项目的urls.py:

1
2
3
4
5
6
7
8
9
from django.contrib import admin
from django.urls import path

from user import views

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

user项目的views.py

1
2
def index(request):
    return HttpResponse("用户信息")

order项目的urls.py:

1
2
3
4
5
6
7
8
9
10
from django.contrib import admin
from django.urls import path

from order import views

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

order项目的views.py:

1
2
3
4
5
6
7
8
9
10
from django.http import HttpResponse


# Create your views here.
def index(request):
    return HttpResponse("订单信息")


def list(request):
    return HttpResponse("订单列表")

接下来,我们在主项目里,加下映射:

image-20240215183407545

1
2
path('user/', include(('user.urls', 'user'), namespace='user')),
    path('order/', include(('order.urls', 'order'), namespace='order'))

说明下:

include((‘user.urls’, ‘user’) 相当于找到user项目的urls.py文件。

namespace=’user’ 给这个映射取名是user,一般是根据项目名称来取。

第一个参数 ‘user/’ 标识 user/开头的请求,都由user项目的urls.py去管理处理映射关系。

通过这种命名空间,我们可以把复杂项目的路由映射拆分,升级维护会方便很多。

Django5路由命名与反向解析reverse与resolve

我们在urls.py里定义的路由信息,有时候需要动态获取路由信息,然后进行一些处理,统计,日志等操作,这时候我们需要在其他代码里用到路由信息,比如views.py,后面要学到的模型models.py,Admin系统等,因此我们引入路由反向解析reverse与resolve方法,再使用这两个方法前,我们还需要给路由取名,否则我们无法找到我们需要的那个路由的信息。reverse方法根据路由名称得到路由地址,resolve方法根据路由地址得到路由所有信息。

我们先举一个简单例子来体会下吧。

在order项目的urls.py里,我们对index/和list/请求路由分别取名index和list

image-20240215192246072

然后修改views.py的index方法:

1
2
3
4
5
6
def index(request):
    route_url = reverse('order:index')
    print("reverse反向解析得到路由地址:" + route_url)
    result = resolve(route_url)
    print("resolve通过路由地址得到路由信息:" + str(result))
    return HttpResponse("订单信息")

我们运行请求:

1
http://127.0.0.1:8000/order/index/

控制台输出:

1
2
reverse反向解析得到路由地址:/order/index/
resolve通过路由地址得到路由信息:ResolverMatch(func=order.views.index, args=(), kwargs={}, url_name='index', app_names=['order'], namespaces=['order'], route='order/index/')

resolve返回对象属性介绍:

函数方法 说明
func 路由的视图函数对象或视图类对象
args 以列表格式获取路由的变量信息
kwargs 以字典格式获取路由的变量信息
url_name 获取路由命名name
app names 与app name功能一致,但以列表格式表示
namespaces 与namespace功能一致,但以列表格式表示
route 获取整个路由的名称,包括命名空间

这里我们在修改下项目,来讲下参数的运用。

order的urls.py的list请求加下年月日路由变量

image-20240215193854795

1
path('list/<int:year>/<int:month>/<int:day>/', views.list, name="list")

对应的views.py的list方法我们也进行修改,要加上三个路由变量

1
2
3
4
5
6
7
8
9
def list(request, year, month, day):
    kwargs = {'year': year - 1, 'month': month + 1, 'day': day}
    args = [year, month, day]
    # route_url = reverse('order:list', args=args)
    route_url = reverse('order:list', kwargs=kwargs)
    print("reverse反向解析得到路由地址:" + route_url)
    result = resolve(route_url)
    print("resolve通过路由地址得到路由信息:" + str(result))
    return HttpResponse("订单列表")

进行反向解析路由的时候,我们也可以带上路由实参,可以通过kwargs字典键值对,也可以通过args元组;

测试请求地址:http://127.0.0.1:8000/order/list/2010/11/11/

控制台输出:

1
2
reverse反向解析得到路由地址:/order/list/2009/12/11/
resolve通过路由地址得到路由信息:ResolverMatch(func=order.views.list, args=(), kwargs={'year': 2009, 'month': 12, 'day': 11}, url_name='list', app_names=['order'], namespaces=['order'], route='order/list/<int:year>/<int:month>/<int:day>/', captured_kwargs={'year': 2009, 'month': 12, 'day': 11})

点开reverse方法:

image-20240215195534704

必须参数viewname,以及一些可选参数:

  • viewname:代表路由命名或可调用视图对象,一般情况下是以路由命名name来生成路由地址的。
  • urlconf:设置反向解析的URLconf模块。默认情况下,使用配置文件 settings.py 的ROOT_URLCONF属性( 主项目文件夹的urls.py ).
  • args:以列表方式传递路由地址变量,列表元素顺序和数量应与路由地址变量的顺序和数量一致。
  • kwargs:以字典方式传递路由地址变量,字典的键必须对应路由地址变量名,字典的键值对数量与变量的数量一致。
  • current app:提示当前正在执行的视图所在的项目应用,主要起到提示作用,在功能上并无实质的作用。

点开resolve方法:

image-20240215195754140

就两个参数:

  • path:代表路由地址,通过路由地址来获取对应的路由对象信息。
  • urlconf:设置反向解析的_URLconf模块。默认情况下,使用配置文件 settings.py 的ROOT_URLCONF属性( 主项目文件夹的urls.py ).

Django5视图定义与使用

视图(Views)是 Django的 MTV架构模式的V部分,主要负责处理用户请求和生成相应的响应内容,然后在页面或其他类型文档中显示。

Django的MTV分别代表:

Model(模型):业务对象与数据库的对象(ORM)

Template(模版):负责如何把页面展示给用户

View(视图):负责业务逻辑,并在适当的时候调用Model和Template

image-20240216110307884

Django5设置视图响应状态

客户端请求后端服务,在view.py视图层方法最终return 返回视图响应。Python内置提供了响应类型,来实现不同的返回不同的http状态码;

响应类型 解释说明
HttpResponse(‘Hello world’”) 状态码200,请求已成功被服务器接收
HttpResponseRedirect(‘/’) 状态码302,重定向首页地址
HttpResponsePermanentRedirect(‘/’) 状态码301,永久重定向首页地址
HttpResponseBadRequest(“‘400’) 状态码400,访问的页面不存在或请求错误
HttpResponseNotFound(‘404”) 状态码404,网页不存在或网页的URL失效
HttpResponseForbidden(‘403’) 状态码403,没有访问权限
HttpResponseNotAllowed(‘405’) 状态码405,不允许使用该请求方式
HttpResponseServerError(‘500’”) 状态码500,服务器内容错误
JsonResponse( {‘foo’ : ‘bar’}) 默认状态码200,响应内容为JSON数据
StreamingHttpResponse() 默认状态码200,响应内容以流式输出

下面我们举几个例子来实操下视图响应状态应用;

举例一:HttpResponse

修改helloWorld的views.py的index函数:

1
2
3
def index(request):
    html = "<font color='red'>学Python,上www.python222.com</font>"
    return HttpResponse(html, status=200)

image-20240216113028234

请求测试,状态码200,返回网页信息。status=200不写的话默认也是200.

举例二:HttpResponseNotFound 404

1
2
def index(request):
    return HttpResponseNotFound()

image-20240216113425405

请求测试,状态码404。

举例三:JsonResponse 响应json数据

1
2
def index(request):
    return JsonResponse({'foo': 'bar'})

image-20240216113717071

请求测试,状态码200,返回json格式数据。

我们第一个实例用到的是HttpResponse,简单网页我们直接可以响应到页面,但是假如是复杂网页,就会增加视图函数的代码量。所以我们引入模版,通过django提供的render方法渲染数据到模版,然后再响应到页面。

1
2
def index(request):
    return render(request, 'index.html')

这个是我们前面的的HelloWorld代码,我们ctrl点进去render方法,看下源码:

image-20240216122419239

经过模版渲染后得到content网页内容,依然返回的是HttpResponse对象。

render方法定义:

1
2
3
def render(
    request, template_name, context=None, content_type=None, status=None, using=None
):

request和template_name是必须的参数。其他参数可选。

  • request:浏览器向服务器发送的请求对象,包含用户信息、请求内容和请求方式等。
  • template_name:设置模板文件名,用于生成网页内容。
  • context:对模板上下文(模板变量)赋值,以字典格式表示,默认情况下是一个空字典。
  • content_type:响应内容的数据格式,一般情况下使用默认值即可。
  • status: HTTP状态码,默认为200.
  • using:设置模板引擎,用于解析模板文件,生成网页内容。

我们再写一个带字典参数的render渲染模版实例:

views.py改写下:

1
2
3
def index(request):
    content_value = {"msg": '学Python,上www.python222.com'}
    return render(request, 'index.html', context=content_value)

模版代码改写下,模版里取值语法

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
模版取值: 
</body>
</html>

请求测试:

image-20240216123224384

Django5设置重定向响应

Django重定向我们前面讲过一种urls.py里使用RedirectView实现

1
path('redirectTo', RedirectView.as_view(url="index/"))

这种方便书写简单,但是不灵活,我们实际开发,为了能否实现业务上的判断,来进行业务重定向跳转,还是需要用redirect方法。

重定向的状态码分为301和302,前者是永久性跳转的,后者是临时跳转的,两者的区别在于搜索引擎的网页抓取。301重定向是永久的重定向,搜索引擎在抓取新内容的同时会将旧的网址替换为重定向之后的网址。302跳转是暂时的跳转,搜索引擎会抓取新内容而保留旧的网址。因为服务器返回302代码,所以搜索引擎认为新的网址只是暂时的。

Django内置提供了重定向类HttpResponseRedirect和HttpResponsePermanentRedirect分别代表HTTP状态码302和301

image-20240219205454516

我们通过一个实例来测试下重定向

我们static下面新建一个新页面new.html

image-20240219205608640

改写helleWorld里得index方法:

1
2
def index(request):
    return redirect("/static/new.html")

我们测试下:请求 http://127.0.0.1:8000/index/ 302跳转到了new.html页面 (最好用谷歌浏览器得无痕浏览,这样没有缓存干扰测试)

image-20240219205626919

我们加上 permanent=True参数;

1
2
def index(request):
    return redirect("/static/new.html", permanent=True)

再测试下,就变成301永久跳转了。

image-20240219205715484

我们点进去看下redirect方法得源码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def redirect(to, *args, permanent=False, **kwargs):
    """
    Return an HttpResponseRedirect to the appropriate URL for the arguments
    passed.

    The arguments could be:

        * A model: the model's `get_absolute_url()` function will be called.

        * A view name, possibly with arguments: `urls.reverse()` will be used
          to reverse-resolve the name.

        * A URL, which will be used as-is for the redirect location.

    Issues a temporary redirect by default; pass permanent=True to issue a
    permanent redirect.
    """
    redirect_class = (
        HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
    )
    return redirect_class(resolve_url(to, *args, **kwargs))

第一个跳转参数支持模型,视图路由名称,还有最常用得url地址;第二个参数就是是否永久跳转,默认Flase;

redirect_class通过permanent判断,true返回HttpResponsePermanentRedirect,false返回HttpResponseRedirect;

Django5二进制文件下载响应

响应内容除了返回网页信息外,还可以实现文件下载功能,是网站常用的功能之一。

Django提供三种方式实现文件下载功能,分别是HttpResponse、StreamingHttpResponse和 FileResponse,三者的说明如下:

  • HttpResponse是所有响应过程的核心类,它的底层功能类是HttpResponseBase。
  • StreamingHttpResponse是在 HttpResponseBase的基础上进行继承与重写的,它实现流式响应输出(流式响应输出是使用Python的迭代器将数据进行分段处理并传输的),适用于大规模数据响应和文件传输响应。
  • FileResponse是在StreamingHttpResponse 的基础上进行继承与重写的,它实现文件的流式响应输出,只适用于文件传输响应。

我们通过实例来看下如何应用:

我们准备一个文件,这里我们用一个exe二进制文件。放D盘根目录。

image-20240219205729549

views.py里写方法实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 定义文件路径
file_path = "D:\\360zip_setup.exe"


def download_file1(request):
    file = open(file_path, 'rb')  # 打开文件
    response = HttpResponse(file)  # 创建HttpResponse对象
    response['Content_Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment;filename=file1.exe'
    return response


def download_file2(request):
    file = open(file_path, 'rb')  # 打开文件
    response = StreamingHttpResponse(file)  # 创建StreamingHttpResponse对象
    response['Content_Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment;filename=file2.exe'
    return response


def download_file3(request):
    file = open(file_path, 'rb')  # 打开文件
    response = FileResponse(file)  # 创建FileResponse对象
    response['Content_Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment;filename=file3.exe'
    return response

urls.py里定义下映射:

1
2
3
path('download1', helloWorld.views.download_file1),
    path('download2', helloWorld.views.download_file2),
    path('download3', helloWorld.views.download_file3)

为了方便测试,我们static目录下新建一个download.html静态文件:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>下载测试</title>
</head>
<body>
<a href="/download1">下载测试一:HttpResponse</a><br>
<a href="/download2">下载测试二:StreamingHttpResponse</a><br>
<a href="/download3">下载测试三:FileResponse</a>
</body>
</html>

页面输入:http://127.0.0.1:8000/static/download.html 测试:

image-20240219205740833

分别点击下载测试:

image-20240219205750450

Http请求&HttpRequest请求类

超文本传输协议(Hypertext Transfer Protocol,HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。

当在浏览器上访问某个网址时,其实质是向网站发送一个HTTP请求,HTTP请求分为8种请求方式,每种请求方式的说明如下:

请求方式 说明
OPTIONS 返回服务器针对特定资源所支持的请求方法
GET 向特定资源发出请求(访问网页)
POST 向指定资源提交数据处理请求(提交表单、上传文件)
PUT 向指定资源位置上传数据内容
DELETE 请求服务器删除request-URL所标示的资源
HEAD 与GET请求类似,返回的响应中没有具体内容,用于获取报头
TRACE 回复和显示服务器收到的请求,用于测试和诊断
CONNECT HTTP/1.1协议中能够将连接改为管道方式的代理服务器

在上述的HTTP请求方式里,最基本的是GET请求和POST 请求,网站开发者关心的也只有GET请求和POST请求。GET请求和 POST请求是可以设置请求参数的,两者的设置方式如下:

  • GET请求的请求参数是在路由地址后添加“?”和参数内容,参数内容以key=value 形式表示,等号前面的是参数名,后面的是参数值,如果涉及多个参数,每个参数之间就使用“&”隔开,如127.0.0.1:8000/?name=python222&pw=123456。
  • POST请求的请求参数一般以表单的形式传递,常见的表单使用HTML的 form标签,并且form标签的method 属性设为POST.

再Django5中,Http请求信息都被封装到了HttpRequest类中。

HttpRequest类的常用属性如下:

  • COOKIE:获取客户端(浏览器)的Cookie信息,以字典形式表示,并且键值对都是字符串类型。
  • FILES: django.http.request.QueryDict对象,包含所有的文件上传信息。
  • GET:获取GET请求的请求参数,它是django.http.request.QueryDict对象,操作起来类似于字典。
  • POST:获取POST请求的请求参数,它是django.http.request.QueryDict对象,操作起来类似于字典。
  • META:获取客户端(浏览器)的请求头信息,以字典形式存储。
  • method:获取当前请求的请求方式(GET请求或POST请求).
  • path:获取当前请求的路由地址。
  • session:一个类似于字典的对象,用来操作服务器的会话信息,可临时存放用户信息。
  • user:当 Django启用AuthenticationMiddleware中间件时才可用。它的值是内置数据模型User的对象,表示当前登录的用户。如果用户当前没有登录,那么user将设为django.contrib.auth.models.AnonymousUser的一个实例。

HttpRequest类常用方法如下:

  • is_secure():是否是采用HTTPS协议。
  • get_host():获取服务器的域名。如果在访问的时候设有端口,就会加上端口号,如127.0.0.1:8000。
  • get_full path():返回路由地址。如果该请求为GET请求并且设有请求参数,返回路由地址就会将请求参数返回,如/?name=python222&pw=123456。

我们搞个实例来测试下吧:

先views.py里定义两个方法,分别测试get和post:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def get_test(request):
    """
    get请求测试
    :param request:
    :return:
    """
    print(request.method)  # 请求方式
    # 常用属性
    print(request.content_type)
    print(request.content_params)
    print(request.COOKIES)
    print(request.scheme)
    # 常用方法
    print(request.is_secure())
    print(request.get_host())
    print(request.get_full_path())

    print(request.GET.get("name"))
    print(request.GET.get("pwd"))
    print(request.GET.get("aaa", "666"))
    return HttpResponse("http get ok")


def post_test(request):
    """
    post请求测试
    :param request:
    :return:
    """
    print(request.method)  # 请求方式
    print(request.POST.get("name"))
    print(request.POST.get("pwd"))
    print(request.POST.get("aaa", "666"))
    return HttpResponse("http post ok")

urls.py里定义下映射:

1
2
path('get', helloWorld.views.get_test),
    path('post', helloWorld.views.post_test)

模版里新建http.html,这里我们不能用静态页面,需要一个csrf安全机制token 需要后端server来提供,所以只能用模版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/get?name=python222&pwd=123456" target="_blank">http get请求测试</a><br><br>
<form action="/post" method="post">
    {百分号 csrf_token %}
    name:<input type="text" name="name"><br>
    pwd:<input type="text" name="pwd">
    <input type="submit" value="提交"></input>
</form>
</body>
</html>

请求index跳转到http.html

1
2
def index(request):
    return render(request, 'http.html')

浏览器输入 http://127.0.0.1:8000/index/测试:

image-20240220170942882

点击get链接地址:

后台输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
GET
text/plain
{}
{'csrftoken': 'GDgfE2kvXwRYS6WMejaYNE9ij9KPodHi'}
http
False
127.0.0.1:8000
/get?name=python222&pwd=123456
python222
123456
666
请求处理完毕,将返回到页面
[20/Feb/2024 17:09:52] "GET /get?name=python222&pwd=123456 HTTP/1.1" 200 11

再post表单测试:

后台输出:

1
2
3
4
POST
python222
123456
666

会话管理(Cookies&Session)

HTTP是一种无状态协议,每次客户端访问web页面时,客户端打开一个单独的浏览器窗口连接到web服务器,由于服务器不会自动保存之前客户端请求的相关信息,所有无法识别一个HTTP请求是否为第一次访问。这就引进了web客户端和服务器端之间的会话,这就是会话管理。

常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。

一、关于Cookie

cookie是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。

Cookie定义了一些HTTP请求头和HTTP响应头,通过这些HTTP头信息使服务器可以与客户进行状态交互。

客户端请求服务器后,如果服务器需要记录用户状态,服务器会在响应信息中包含一个Set-Cookie的响应头,客户端会根据这个响应头存储Cookie信息。再次请求服务器时,客户端会在请求信息中包含一个Cookie请求头,而服务器会根据这个请求头进行用户身份、状态等较验。

image-20240221103150293

二、关于session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识,称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

image-20240221104053204

三,Session和Cookie的区别

1、数据存储位置:cookie 数据存放在客户的浏览器上,session 数据放在服务器上。

2、安全性:cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。

3、服务器性能:session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性 能方面,应当使用cookie。

4、数据大小:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

5、信息重要程度:可以考虑将用户信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。

四,Cookies&Session再Django中使用实例

获取 cookie

1
2
3
4
5
6
7
8
9
10
request.COOKIES['key']
request.COOKIES.get('key')
这俩个方法可以获取指定键名的cookie

request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

default: 默认值
salt: 	 加密盐
max_age: 后台控制过期时间默认是秒数
expires: 专门针对IE浏览器设置超时时间

设置 cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 获取HttpResponse对象
# rep = HttpResponse(...)
rep = render(request, ...)
# 设置 cookie
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)

参数

key, 	  	   键
value='', 	   值
max_age=None,  超时时间
expires=None,  超时时间(IE requires expires, so set it if hasn't been already.)
path='/', 	   Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
domain=None,   Cookie生效的域名
secure=False,  https传输
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

删除 cookie

1
2
3
4
5
6
7
# 获取HttpResponse对象
# rep = HttpResponse(...)
rep = render(request, ...)
# 删除 cookie
rep.delete_cookie(key)

此方法会删除用户浏览器上之前设置的cookie值

Django 操作 session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
1. 获取、设置、删除Session中数据

		request.session['k1'] 					# 没有值会报错				
		request.session.get('k1',None)			# 可以获取多组
		request.session['k1'] = 123				# 可以设置多组
		request.session.setdefault('k1',123) 	# 存在则不设置
		del request.session['k1']


2. 所有 键、值、键值对

		request.session.keys()
		request.session.values()
		request.session.items()
		request.session.iterkeys()
		request.session.itervalues()
		request.session.iteritems()

4. 会话session的key
		
		request.session.session_key

5. 将所有Session失效日期小于当前日期的数据删除
		
		request.session.clear_expired()

6. 检查会话session的key在数据库中是否存在
		
		request.session.exists("session_key")

7. 删除当前会话的所有Session数据
		
		request.session.delete()	# 只删客户端
  
8. 删除当前的会话数据并删除会话的Cookie。
		
		request.session.flush() 	# 服务端、客户端都删
	    这用于确保前面的会话数据不可以再次被用户的浏览器访问
	    例如,django.contrib.auth.logout() 函数中就会调用它。

9. 设置会话Session和Cookie的超时时间
		
		'django默认的session失效时间是14天'
		request.session.set_expiry(value)
	    * 如果value是个整数,session会在些秒数后失效。
	    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
	    * 如果value是0,用户关闭浏览器session就会失效。
	    * 如果value是None,session会依赖全局session失效策略。

下面通过一个具体Django事例来深入体验下Django项目里的Cookie&Session操作

views.py里定义两个方法,分别是登录页面跳转,以及登录逻辑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def to_login(request):
    """
    跳转登录页面
    :param request:
    :return:
    """
    return render(request, 'login.html')


def login(request):
    """
    登录
    :param request:
    :return:
    """
    user_name = request.POST.get("user_name")
    pwd = request.POST.get("pwd")
    if user_name == 'python222' and pwd == '123456':
        request.session['currentUserName'] = user_name  # session中存一个用户名
        print('session获取', request.session['currentUserName'])
        response = render(request, 'main.html')  # 获取HttpResponse
        response.set_cookie("remember_me", True)  # 设置cookie
        return response
    else:
        content_value = {"error_info": '用户名或者密码错误!'}
        return render(request, 'login.html', context=content_value)

urls.py里定义映射:

1
2
    path('toLogin/', helloWorld.views.to_login),
    path('login', helloWorld.views.login)

templates下新建login.html和main.html

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<form action="/login" method="post">
    {百分号 csrf_token %}
    <table>
        <tr>
            <th>用户登录</th>
        </tr>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="user_name"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="pwd"></td>
        </tr>
        <tr>
            <td>
                <input type="submit" value="提交">
            </td>
        </tr>
        <tr>
            <td colspan="2"><font color="red"></font></td>
        </tr>
    </table>
</form>
</body>
</html>

main.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>
欢迎:
</body>
</html>

测试运行,浏览器输入:http://127.0.0.1:8000/toLogin/

image-20240221124210193

转发到login.html

我们先输入一个错误的用户名和密码:

image-20240221124240889

image-20240221124303198

携带错误信息参数,转发到登录页面,页面提示错误信息。

我们在输入一个正确的用户名和密码:,则转发到main.html主页面。

image-20240221124524413

image-20240221124639172

同时服务器返回set-cookies信息,包括内置的sessionid以及我们自己设置的remember_me。

image-20240221124744537

Django 中的 Session 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

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

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

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

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

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

Django5文件上传实现

文件上传功能是网站开发或者业务系统常见的功能之一,比如上传图片(用户头像或文章配图)和导入文件(压缩包,视频,音乐)。无论上传的文件是什么格式的,其上传原理都是将文件以二进制的数据格式读取并写入网站或者业务系统指定的目录里。

我们通过一个实例来深入体验学习下文件上传:

首先templates下新建upload.html ,前端上传文件模版页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
<form action="/upload" enctype="multipart/form-data" method="post">
    {百分号 csrf_token %}
    <input type="file" name="myfile"><br><br>
    <input type="submit" value="上传文件">
</form>
</body>
</html>

views.py里定义to_upload和upload两个方法,分别是跳转文件页面,和文件上传处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def to_upload(request):
    """
    跳转文件上传页面
    :param request:
    :return:
    """
    return render(request, 'upload.html')


def upload(request):
    """
    文件上传
    :param request:
    :return:
    """
    # 获取上传的文件,如果没有文件,就默认为None
    myFile = request.FILES.get("myfile", None)
    if myFile:
        # 打开特定的文件进行二进制的写操作
        f = open(os.path.join("D:\\myFile", myFile.name), "wb+")
        # 分块写入文件
        for chunk in myFile.chunks():
            f.write(chunk)
        f.close()
        return HttpResponse("文件上传成功!")
    else:
        return HttpResponse("没发现文件!")

最后urls.py里,定义下映射:

1
2
    path('toUpload/', helloWorld.views.to_upload),
    path('upload', helloWorld.views.upload)

运行测试:浏览器输入 http://127.0.0.1:8000/toUpload/,进入文件上传页面:

image-20240224173658650

image-20240224173709825

测试两个文件,一个压缩包,一个图片,选择文件,点击上传文件,则上传到指定目录:

image-20240224173743748

文件对象myFile提供一下属性来获取文件信息:

  • myFile.name:获取上传文件的文件名,包含文件后缀名。
  • myFile.size:获取上传文件的文件大小。
  • myFile.content_type:获取文件类型,通过后续名判断文件类型。

从文件对象myFile获取文件内容,Django提供了以下读取方式,每种方式说明如下。

  • myFile.read():从文件对象里读取整个文件上传的数据,这个方法只适合小文件。
  • myFile.chunks():按流式响应方式读取文件,在for 循环中进行迭代,将大文件分块写入服务器所指定的保存位置。
  • myFile.multiple_chunks():判断文件对象的文件大小,返回True或者False,当文件大于2.5MB(默认值为2.5MB)时,该方法返回True,否则返回False。因此,可以根据该方法来选择选用read方法读取还是采用chunks方法。

Django5列表视图ListView

为了实现快速开发,Django提供了视图类功能,封装了视图开发常用的代码,这种基于类实现的响应与请求称为CBV ( Class Base Views),我们先介绍列表视图ListView,该视图类可以将数据库表的数据以列表的形式显示到页面,常用于数据的查询和展示。

首先为了得到数据库数据,我们先定义模型,来映射数据库表;

models.py里定义StudentInfo类:

1
2
3
4
5
6
7
8
9
10
11
12
from django.db import models


# Create your models here.

class StudentInfo(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)
    age = models.IntegerField()

    class Meta:
        db_table = "t_student"

然后我们执行: python manage.py makemigrations 生成数据库迁移文件

所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件;

再执行:python manage.py migrate 执行迁移文件,同步到数据库中

注意:生成的表名默认为:app名_定义的表名,可通过db_table 指明数据库表名。

image-20240225125157804

image-20240225125230435

我们会看到 数据库t_student自动生成了:

image-20240225125307454

我们输入一些测试数据:

1
2
3
4
5
6
7
8
9
10
11
12
insert into t_student VALUES(null,'张三1',20);
insert into t_student VALUES(null,'张三2',21);
insert into t_student VALUES(null,'张三3',22);
insert into t_student VALUES(null,'张三4',23);
insert into t_student VALUES(null,'张三5',24);
insert into t_student VALUES(null,'张三6',25);
insert into t_student VALUES(null,'张三7',26);
insert into t_student VALUES(null,'张三8',27);
insert into t_student VALUES(null,'张三9',28);
insert into t_student VALUES(null,'张三10',29);
insert into t_student VALUES(null,'张三11',30);
insert into t_student VALUES(null,'张三12',31);

要使用 ListView,需要继承它并设置一些属性。以下属性是最常用的:

  • model:指定要使用的模型。
  • template_name:指定要使用的模板名称。
  • context_object_name:指定上下文变量名称,默认为 object_list。
  • paginate_by:指定分页大小。
  • extra_context:设置模型外的数据

在views.py里,我们可以定义List类:

1
2
3
4
5
6
7
8
9
10
11
class List(ListView):
    # 设置模版文件
    template_name = 'student/list.html'
    # 设置模型外的数据
    extra_context = {'title': '学生信息列表'}
    # 查询结果集
    queryset = StudentInfo.objects.all()
    # 每页展示5条数据
    paginate_by = 5
    # 设置上下文对象名称
    context_object_name = 'student_list'

除了设置属性之外,还可以重写 ListView 中的方法以进行自定义。以下是一些常见的方法:

  • get_queryset():返回要在视图中使用的查询集合。这里可以对查询集合进行筛选、排序等操作。
  • get_context_data():返回要在模板上下文中使用的变量。这里可以添加额外的变量,如表单、过滤器等。

urls.py里,我们定义映射:

1
path('student/list', helloWorld.views.List.as_view())

在模版页面,Django 给我们提供了分页的功能:PaginatorPage类都是用来做分页的。

1
2
3
4
# Paginator常用属性和方法
1.`count`: 总共有多少条数据。
2.`num_pages`: 总共有多少页。
3.`page_range`:页面的区间。比如有三页,那么就是```range``(``1``,``4``)`。
1
2
3
4
5
6
7
8
# Page常用属性和方法:
1.`has_next`: 是否还有下一页。
2.`has_previous`: 是否还有上一页。
3.`next_page_number`: 下一页的页码。
4.`previous_page_number`: 上一页的页码。
5.`number`: 当前页。
6.`start_index`: 当前页的第一条数据的索引值。
7.`end_index`: 当前页的最后一条数据的索引值。

我们在templates下新建student目录,再新建list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<table border="1">
    <tr>
        <th>编号</th>
        <th>姓名</th>
        <th>年龄</th>
    </tr>
    {百分号 for student in student_list %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
        </tr>
    {百分号 endfor %}
</table>
<br>
{百分号 if is_paginated %}
    {百分号 if page_obj.has_previous %}
        <a href="/student/list?page=">上一页</a>
    {百分号 endif %}
    {百分号 for current in paginator.page_range %}
        {百分号 if current == page_obj.number %}
            <a href="/student/list?page="><b><font color="blue"></font></b></a>
        {百分号 else %}
            <a href="/student/list?page="></a>
        {百分号 endif %}
    {百分号 endfor %}
    {百分号 if page_obj.has_next %}
        <a href="/student/list?page=">下一页</a>
    {百分号 endif %}
{百分号 endif %}
</body>
</html>

测试,浏览器输入:http://127.0.0.1:8000/student/list

image-20240226165014815

点击下一页:

image-20240226165026594

运用ListView列表视图开发,是不是非常容易,方便。

Django5详细视图DetailView

DetailView多用于展示某一个具体数据对象的详细信息的页面。

使用DetailView,你只需要指定要使用的模型和对象的唯一标识符,并可以自定义其他一些属性,例如模型名称、模板名称、上下文数据等。

以下是DetailView的一些常见属性和方法:

  • model:指定要使用的模型。

  • queryset:指定要使用的查询集,用于获取对象。如果未指定,则将使用模型的默认查询集。

  • pk_url_kwarg:指定URL中用于获取对象的唯一标识符的参数名称,默认为’pk’。

  • context_object_name:指定将对象传递给模板时的上下文变量名称,默认为’model’。

  • template_name:指定要使用的模板的名称。

  • get_object(queryset=None):获取要展示的对象。可以重写这个方法来自定义获取对象的逻辑。

  • get_context_data(kwargs):返回要传递给模板的上下文数据。你可以重写这个方法来自定义上下文数据。

  • get():处理GET请求的方法,根据配置的对象获取规则执行对象获取和展示逻辑。

  • dispatch(request, *args, **kwargs):处理请求的入口方法,根据请求的不同方法(GET、POST等)执行相应的处理逻辑。

通过继承DetailView,并根据自己的需求重写这些方法,你可以创建自定义的展示单个对象详细信息的视图,并实现你想要的功能。

总之,DetailView是Django框架中的一个便捷的通用视图,用于展示单个对象的详细信息,并提供了一些有用的属性和方法来简化对象展示逻辑。

通过重新设置model属性来指定需要获取的Model类,默认对象名称为object,也可以通过重新设置context_object_name属性来更改这个名字。

下面我们通过实例来体验下吧:

views.py里新建Detail,继承DetailView

1
2
3
4
5
6
7
8
9
10
11
class Detail(DetailView):
    # 设置模版文件
    template_name = 'student/detail.html'
    # 设置模型外的数据
    extra_context = {'title': '学生信息详情'}
    # 设置查询模型
    model = StudentInfo
    # 设置上下文对象名称
    context_object_name = 'student'
    # 指定URL中用于获取对象的唯一标识符的参数名称,默认为’pk’。
    # pk_url_kwarg = 'id'

templates下的student目录下新建detail.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
编号:<br/>
姓名:<br/>
年龄:
</body>
</html>

urls.py里加一个映射:

1
    path('student/<int:pk>', helloWorld.views.Detail.as_view()),

list.html里,加一个操作项-查看详情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<table border="1">
    <tr>
        <th>编号</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>操作</th>
    </tr>
    {百分号 for student in student_list %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td>
                <a href="/student/">查看详情</a>
            </td>
        </tr>
    {百分号 endfor %}
</table>
<br>
{百分号 if is_paginated %}
    {百分号 if page_obj.has_previous %}
        <a href="/student/list?page=">上一页</a>
    {百分号 endif %}
    {百分号 for current in paginator.page_range %}
        {百分号 if current == page_obj.number %}
            <a href="/student/list?page="><b><font color="blue"></font></b></a>
        {百分号 else %}
            <a href="/student/list?page="></a>
        {百分号 endif %}
    {百分号 endfor %}
    {百分号 if page_obj.has_next %}
        <a href="/student/list?page=">下一页</a>
    {百分号 endif %}
{百分号 endif %}
</body>
</html>

运行测试,浏览器输入:http://127.0.0.1:8000/student/list,点击“查看详情”

image-20240301204822453

image-20240301204900339

即可查询出学生详情;

Django5新增视图CreateView

视图类CreateView是对模型新增数据的视图类,它是在表单视图类FormView 的基础上加以封装的。简单来说,就是在视图类FormView的基础上加入数据新增的功能。

所有涉及到表单视图的功能开发,都需要定义form表单类:

我们新建forms.py,里面新建StudentForm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django import forms
from django.forms import ModelForm

from helloWorld.models import StudentInfo


# 定义学生form表单
class StudentForm(ModelForm):
    # 配置中心
    class Meta:
        model = StudentInfo  # 导入model
        # fields = '__all__'  # 代表所有字段
        fields = ['name', 'age']  # 指定字段
        widgets = {  # 定义控件
            'name': forms.TextInput(attrs={'id': 'name', 'class': 'inputClass'}),
            'age': forms.NumberInput(attrs={'id': 'age'})
        }

        labels = {  # 指定标签
            'name': '姓名',
            'age': '年龄'
        }

views.py里新建Create类,继承CreateView

1
2
3
4
5
6
7
8
9
class Create(CreateView):
    # 设置模版文件
    template_name = 'student/create.html'
    # 设置模型外的数据
    extra_context = {'title': '学生信息添加'}
    # 指定form
    form_class = StudentForm
    # 执行成功后跳转地址
    success_url = '/student/list'

student目录下新建create.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        .inputClass {
            width: 200px;
        }
    </style>
</head>
<body>
<h3></h3>
<form method="post">
    {百分号 csrf_token %}
    
    <input type="submit" value="确定">
</form>
</body>
</html>

urls.py里加一个映射:

1
    path('student/create', helloWorld.views.Create.as_view()),

list.html页面,加一个新增学生链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<a href="/student/create">新增学生</a>
<table border="1">
    <tr>
        <th>编号</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>操作</th>
    </tr>
    {百分号 for student in student_list %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td>
                <a href="/student/">查看详情</a>
            </td>
        </tr>
    {百分号 endfor %}
</table>
<br>
{百分号 if is_paginated %}
    {百分号 if page_obj.has_previous %}
        <a href="/student/list?page=">上一页</a>
    {百分号 endif %}
    {百分号 for current in paginator.page_range %}
        {百分号 if current == page_obj.number %}
            <a href="/student/list?page="><b><font color="blue"></font></b></a>
        {百分号 else %}
            <a href="/student/list?page="></a>
        {百分号 endif %}
    {百分号 endfor %}
    {百分号 if page_obj.has_next %}
        <a href="/student/list?page=">下一页</a>
    {百分号 endif %}
{百分号 endif %}
</body>
</html>

测试,浏览器输入: http://127.0.0.1:8000/student/list

image-20240304203332197

点击“新增学生“链接

image-20240304203359073

输入表单信息

image-20240304203419715

点击确定,则跳转到学生信息列表页面

image-20240304203705345

Django5修改视图UpdateView

视图类UpdateView是在视图类FormView和视图类DetailView的基础上实现的,它首先使用视图类 DetailView的功能(功能核心类是SingleObjectMixin),通过路由变量查询数据表某条数据并显示在网页上,然后在视图类FormView的基础上,通过表单方式实现数据修改。

views.py里新建Update类:

1
2
3
4
5
6
7
8
9
10
11
class Update(UpdateView):
    # 设置模版文件
    template_name = 'student/update.html'
    # 设置模型外的数据
    extra_context = {'title': '学生信息编辑'}
    # 设置查询模型
    model = StudentInfo
    # 指定form
    form_class = StudentForm
    # 执行成功后跳转地址
    success_url = '/student/list'

student下新建update.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        .inputClass {
            width: 200px;
        }
    </style>
</head>
<body>
<h3></h3>
<form method="post">
    {百分号 csrf_token %}
    
    <input type="submit" value="确定">
</form>
</body>
</html>

urls.py里加一个映射:

1
    path('student/update/<int:pk>', helloWorld.views.Update.as_view()),

list.html里加一个

<a href="/student/update/">修改</a>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<a href="/student/create">新增学生</a>
<table border="1">
    <tr>
        <th>编号</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>操作</th>
    </tr>
    {百分号 for student in student_list %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td>
                <a href="/student/">查看详情</a>
                <a href="/student/update/">修改</a>
            </td>
        </tr>
    {百分号 endfor %}
</table>
<br>
{百分号 if is_paginated %}
    {百分号 if page_obj.has_previous %}
        <a href="/student/list?page=">上一页</a>
    {百分号 endif %}
    {百分号 for current in paginator.page_range %}
        {百分号 if current == page_obj.number %}
            <a href="/student/list?page="><b><font color="blue"></font></b></a>
        {百分号 else %}
            <a href="/student/list?page="></a>
        {百分号 endif %}
    {百分号 endfor %}
    {百分号 if page_obj.has_next %}
        <a href="/student/list?page=">下一页</a>
    {百分号 endif %}
{百分号 endif %}
</body>
</html>

运行测试:浏览器输入 http://127.0.0.1:8000/student/list

image-20240306202723079

点击修改,进入修改页面,我们发现,django自动帮我获取了数据,并且填充到了表单

image-20240306202732436

我们修改,数据:

image-20240306202817950

点击确定提交,则django给我们做了数据库修改操作,然后准发到列表页面。是不是非常的方便。

image-20240306202849625

Django5删除视图DeleteView

视图类DeleteView的使用方法与视图类UpdateView类似,视图类DeleteView只能删除单条数据,路由变量为模型主键提供查询范围,因为模型主键具有唯一性,所以通过主键查询能精准到某条数据。查询出来的数据通过POST 请求实现数据删除。

views.py里面,我们新建Delete类,继承DeleteView

1
2
3
4
5
6
7
8
9
10
11
class Delete(DeleteView):
    # 设置模版文件
    template_name = 'student/delete.html'
    # 设置模型外的数据
    extra_context = {'title': '学生信息删除'}
    # 设置上下文对象名称
    context_object_name = 'student'
    # 设置查询模型
    model = StudentInfo
    # 执行成功后跳转地址
    success_url = '/student/list'

urls.py里加下映射:

1
path('student/delete/<int:pk>', helloWorld.views.Delete.as_view()),

student下新建delete.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<form method="post">
    {百分号 csrf_token %}
    您确定更要删除 id: name: age: 的记录吗 ?
    <input type="submit" value="确定">
</form>
</body>
</html>

list.hml加下 <a href="/student/delete/">删除</a>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<a href="/student/create">新增学生</a>
<table border="1">
    <tr>
        <th>编号</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>操作</th>
    </tr>
    {百分号 for student in student_list %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td>
                <a href="/student/">查看详情</a>
                <a href="/student/update/">修改</a>
                <a href="/student/delete/">删除</a>
            </td>
        </tr>
    {百分号 endfor %}
</table>
<br>
{百分号 if is_paginated %}
    {百分号 if page_obj.has_previous %}
        <a href="/student/list?page=">上一页</a>
    {百分号 endif %}
    {百分号 for current in paginator.page_range %}
        {百分号 if current == page_obj.number %}
            <a href="/student/list?page="><b><font color="blue"></font></b></a>
        {百分号 else %}
            <a href="/student/list?page="></a>
        {百分号 endif %}
    {百分号 endfor %}
    {百分号 if page_obj.has_next %}
        <a href="/student/list?page=">下一页</a>
    {百分号 endif %}
{百分号 endif %}
</body>
</html>

运行测试:浏览器输入:http://127.0.0.1:8000/student/list

image-20240307162905922

点击删除,进入删除确定页面:

image-20240307162931621

点击 确定,django帮我删除数据后,转发到列表页面:

image-20240307162956503

Django5模板引擎

Django 作为 Web框架,需要一种很便利的方法动态地生成HTML 网页,因此有了模板这个概念。模板包含所需HTML 的部分代码以及一些特殊语法,特殊语法用于描述如何将视图传递的数据动态插入HTML 网页中。 Django可以配置一个或多个模板引擎(甚至是О个,如前后端分离,Django只提供API接口,无须使用模板引擎),模板引擎有Django模板语言(Django Template Language,DTL)和Jinja3。Django模板语言是Django 内置的功能之一,Jinja3是当前Python流行的模板语言。本章分别讲述Django模板语言和Jinja3的使用方法。

Django5内置模板引擎

Django 内置的模板引擎包含模板上下文(亦可称为模板变量)、标签和过滤器,各个功能说明如下:

  • 模板上下文是以变量的形式写入模板文件里面,变量值由视图函数或视图类传递所得。
  • 标签是对模板上下文进行控制输出,比如模板上下文的判断和循环控制等。
  • 模板继承隶属于标签,它是将每个模板文件重复的代码抽取出来并写在一个共用的模板文件中,其他模板文件通过继承共用模板文件来实现完整的网页输出。
  • 过滤器是对模板上下文进行操作处理,比如模板上下文的内容截取、替换或格式转换等。

模板上下文

模板上下文是模板中基本的组成单位,上下文的数据由视图函数或视图类传递。它以表示,variable是上下文的名称,它支持 Python 所有的数据类型,如字典、列表、元组、字符串、整型或实例化对象等。上下文的数据格式不同,在模板里的使用方式也有所差异。

使用变量的一些注意点如下:

  • 当模板引擎遇到一个变量,将计算这个变量,然后输出结果
  • 变量名必须由字母、数字、下划线、点组成,不能由数字和下划线开头
  • 当模板引擎遇到 “ . ” 的时候,按以下顺序进行解析
    • 按照 dict 解析 var[key]
    • 按照对象的属性或方法解析 var.var/func
    • 按照索引解析 var[index]
  • 如果变量不存在,不会引发异常,模板会插入空字符串 ''
  • 在模板中使用变量或方法时,不能出现 ()、[]、{}
  • 调用方法时,不能传递参数

我们通过一个实例来学习下:

views.py,index方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 定义人类
class Person:
    # 属性 姓名
    name = None
    # 属性 年龄
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age
        
def index(request):
    str = "模板变量"
    myDict = {"tom": '666', 'cat': '999', 'wzw': '333'}
    # 创建一个对象 zhangsan
    zhangsan = Person("张三", 21)
    myList = ["java", "python", "c"]
    myTuple = ("python", 222, 3.14, False)
    content_value = {"msg": str, "msg2": myDict, "msg3": zhangsan, "msg4": myList, "msg5": myTuple}
    return render(request, 'index.html', context=content_value)

index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
字符串:<br>
字典类型:,,<br>
对象:,<br>
列表:,,,<br>
元组:,,,,
</body>
</html>

测试,浏览器输入:http://127.0.0.1:8000/index/

image-20240308213417406

模板标签

标签是对模板上下文进行控制输出,它是以{百分号 tag %}表示的,其中 tag是标签的名称,Django内置了许多模板标签,比如{百分号 if %}(判断标签)、{百分号 for %}(循环标签)或{百分号 url %}(路由标签)等。

常用内置标签如下:

标签 描述
{百分号 for %} 遍历输出上下文的内容
{百分号 if %} 对上下文进行条件判断
{百分号 csrf_token %} 生成csrf token的标签,用于防护跨站请求伪造攻击
{百分号 url %} 引用路由配置的地址,生成相应的路由地址
{百分号 with %} 将上下文名重新命名
{百分号 load %} 加载导入 Django的标签库
{百分号 static %} 读取静态资源的文件内容
{百分号 extends xxx %} 模板继承,xxx为模板文件名,使当前模板继承xxx模板
{百分号 block xxx %} 重写父类模板的代码

在for标签中,模板还提供了一些特殊的变量来获取for标签的循环信息,变量说明如下:

变量 描述
forloop.counter 获取当前循环的索引,从1开始计算
forloop.counter0 获取当前循环的索引,从0开始计算
forloop.revcounter 索引从最大数开始递减,直到索引到1位置
forloop.revcounter0 索引从最大数开始递减,直到索引到0位置
forloop.first 当遍历的元素为第一项时为真
forloop.last 当遍历的元素为最后一项时为真
forloop.parentloop 在嵌套的for循环中,获取上层for循环的forloop

我们修改index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
字符串:<br>
字典类型:,,<br>
对象:,<br>
列表:,,,<br>
元组:,,,,

<h3>模板标签</h3>
<p>遍历for标签:</p>
{百分号 for item in msg4 %}
    <p>这个是第次循环</p>
    {百分号 if forloop.first %}
        <p>这个是第一项:</p>
    {百分号 elif forloop.last %}
        <p>这个是最后一项:</p>
    {百分号 endif %}
{百分号 endfor %}
<p>判断if标签:</p>
{百分号 if msg == '模板变量' %}
    <p>模板变量</p>
{百分号 elif msg == '模板变量2' %}
    <p>模板变量2</p>
{百分号 else %}
    <p>其他</p>
{百分号 endif %}
<p>url标签</p>
<a href="{百分号 url 'index' %}">请求index</a>
<p>with标签</p>
{百分号 with info=msg %}
    
{百分号 endwith %}

</body>
</html>

用url标签的时候 第二个参数是路由名称,所以urls.py里,修改下:

1
path('index/', helloWorld.views.index, name="index"),

测试,浏览器输入:http://127.0.0.1:8000/index/

image-20240309122305103

模板继承

Django模板继承是一个强大的工具,可以将通用页面元素(例如页眉、页脚、侧边栏等)分离出来,并在多个页面之间共享他们。

模板继承和 Python 语言中类的继承含义是一样的,在 Django 中模板只是一个文本文件,如 HTML。

模板继承是 Django 模板语言中最强大的部分。模板继承使你可以构建基本的“骨架”模板,将通用的功能或者属性写在基础模板中,也叫基类模板或者父模板。子模板可以继承父类模板,子模板继承后将自动拥有父类中的属性和方法,我们还可以在子模板中对父模板进行重写,即重写父模板中方法或者属性,从而实现子模板的定制。模板继承大大提高了代码的可重用性,减轻开发人员的工作量。

在模板继承中最常用了标签就是 {百分号 block %} 与 {百分号 extends %} 标签,其中 {百分号 block% } 标签与 {百分号 endblock %} 标签成对出现

我们新建一个基础模版base.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {百分号 block title %}
            Python222学院
        {百分号 endblock %}
    </title>
</head>
<body>
<div id="head">
    <img src="http://127.0.0.1:8000/static/logo.png"/>
</div>
<div id="content">
    {百分号 block content %}
        欢迎进入Python222学院
    {百分号 endblock %}
</div>
<div id="footer">
    版权所有 www.python222.com
</div>
</body>
</html>

再写一个course.html,继承base.html

1
2
3
4
5
6
7
8
9
{百分号 extends 'base.html' %}
<!-- 重写title -->
{百分号 block title %}
    课程页面-Python222
{百分号 endblock %}
<!-- 重写content -->
{百分号 block content %}
    Django5课程-模板引擎章节
{百分号 endblock %}

我们来测试下吧。

views.py里新建一个to_course方法:

1
2
3
4
5
6
7
def to_course(request):
    """
    跳转课程页面
    :param request:
    :return:
    """
    return render(request, 'course.html')

urls.py里加一个映射:

1
path('toCourse/', helloWorld.views.to_course)

浏览器输入:http://127.0.0.1:8000/toCourse/

image-20240310230510647

我们发现模板里的标题和内容被course页面修改了,其他的没变。

这里我们再优化下,直接写死静态路径是不是很不好啊。

1
2
3
<div id="head">
    <img src="http://127.0.0.1:8000/static/logo.png"/>
</div>

这时候我们就能用上 {百分号 load static %},加载项目中的静态文件,包括图片,css,js文件,字体文件等。

1
<img src="{百分号 static 'logo.png' %}"/>

完整base.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {百分号 block title %}
            Python222学院
        {百分号 endblock %}
    </title>
</head>
{百分号 load static %}
<body>
<div id="head">
    <img src="{百分号 static 'logo.png' %}"/>
</div>
<div id="content">
    {百分号 block content %}
        欢迎进入Python222学院
    {百分号 endblock %}
</div>
<div id="footer">
    版权所有 www.python222.com
</div>
</body>
</html>

过滤器

Django过滤器是一种用于在Django模板中处理数据的技术。过滤器的作用是可以对模板中的变量进行加工、过滤或格式化,返回一个新的值供模板使用。

过滤器作用是在变量输出时,对输出的变量值做进一步的处理。 我们可以使用过滤器来更改变量的输出显示。 过滤器跟模板标签一样,也是在模板中对函数进行调用 对输出的日期进行格式化处理,或者转换大小写字母等,这些都有对应的过滤器去处理它们。

过滤器的语法格式如下:

常用内置过滤器如下:

过滤器 说明
add 加法
addslashes 添加斜杠
capfirst 首字母大写
center 文本居中
cut 切除字符
date 日期格式化
default 设置默认值
default_if_none 为None设置默认值
dictsort 字典排序
dictsortreversed 字典反向排序
divisibleby 整除判断
escape 转义
escapejs 转义js代码
filesizeformat 文件尺寸人性化显示
first 第一个元素
floatformat 浮点数格式化
force_escape 强制立刻转义
get_digit 获取数字
iriencode 转换IRI
join 字符列表链接
last 最后一个
length 长度
length_is 长度等于
linebreaks 行转换
linebreaksbr 行转换
linenumbers 行号
ljust 左对齐
lower 小写
make_list 分割成字符列表
phone2numeric 电话号码
pluralize 复数形式
pprint 调试
random 随机获取
rjust 右对齐
safe 安全确认
safeseq 列表安全确认
slice 切片
slugify 转换成ASCII
stringformat 字符串格式化
striptags 去除HTML中的标签
time 时间格式化
timesince 从何时开始
timeuntil 到何时多久
title 所有单词首字母大写
truncatechars 截断字符
truncatechars_html 截断字符
truncatewords 截断单词
truncatewords_html 截断单词
unordered_list 无序列表
upper 大写
urlencode 转义url
urlize url转成可点击的链接
urlizetrunc urlize的截断方式
wordcount 单词计数
wordwrap 单词包裹
yesno 将True,False和None,映射成字符串‘yes’,‘no’,‘maybe’

根据给定的格式格式化日期

格式字符 描述 示例输出
a ‘a.m.’ or ‘p.m.’ ‘a.m.’
A ‘AM’ or ‘PM’ ‘AM’
b 月份,文字形式,3个字幕库,小写 ‘jan’
B 未实现  
c ISO 8601格式 2008-01-02T10:30:00.000123+02:00
d 月的日子,带前导零的2位数字。 01’到’31’
D 周几的文字表述形式,3个字母。 ‘Fri’
e 时区名称 “,’GMT,’-500’,US/Eastern’等
E 月份,分地区。  
f 时间 1’,1:30’
g 12小时格式,无前导零。 “1’到’12’
G 24小时格式,无前导零。 0’到’23’
h 12小时格式。 ‘01’到’12’
H 24小时格式。 ‘00’到23’
i 分钟 00’到59’
I 夏令时间,无论是否生效。 ‘1’或0
j 没有前导零的月份的日子。 ‘1’到”31’
l 星期几,完整英文名 ‘Friday’
L 布尔值是否是—个闰年。 True或False
m 月,2位数字带前导零。 ‘01’到’12’
M 月,文字,3个字母。 “Jan”
n 月无前导零。 ‘1’到’12’
N 美联社风格的月份缩写。 ‘Jan.’ ,’Feb.’,’March’,’May’
o ISO-8601周编号 ‘1999’
O 与格林威治时间的差,单位小时。 ‘+0200’
P 时间为12小时 1:30 p.m.’ , ‘midnight’ , ‘noon’ , ‘12:30 p.m.’
r RFC 5322格式化日期。 ‘Thu,21 Dec 2000 16:01:07+0200’
s 秒,带前导零的2位数字。 ‘00’到59’
S 一个月的英文序数后缀,2个字符。 ‘st’ ,’nd’, ‘rd’或’th’
t 给定月份的天数。 28 to 31
u 微秒。 000000 to 999999
U 自Unix Epoch以来的秒数(1970年1月1日00:00:00 UTC).  
w 星期几,数字无前导零。 ‘O’(星期日)至’6’(星期六)
W ISO-8601周数,周数从星期一开始。 1,53
y 年份,2位数字。 99
Y 年,4位数。 ‘1999’
z —年中的日子 0到365
Z 时区偏移量,单位为秒。 -43200到43200

views.py index函数我们修改下:str改成”hello”,再定义一个日期对象

1
2
3
4
5
6
7
8
9
10
def index(request):
    str = "hello"
    date = datetime.datetime.now()
    myDict = {"tom": '666', 'cat': '999', 'wzw': '333'}
    # 创建一个对象 zhangsan
    zhangsan = Person("张三", 21)
    myList = ["java", "python", "c"]
    myTuple = ("python", 222, 3.14, False)
    content_value = {"msg": str, "msg2": myDict, "msg3": zhangsan, "msg4": myList, "msg5": myTuple, "date": date}
    return render(request, 'index.html', context=content_value)

index.html加下:

1
2
3
4
<p>内置过滤器</p>
capfirst:<br>
length:<br>
date: - >> 

运行测试:

image-20240311202716156

Jinja3模版引擎

Jinja是Python里面被广泛应用的模板引擎,最新版本3.1.3 它的设计思想来源于Django的模板引擎,并扩展了其语法和一系列强大的功能。其中最显著的是增加了沙箱执行功能和可选的自动转义功能,这对大多数应用的安全性来说是非常重要。此外,它还具备以下特性: ·沙箱执行模式,模板的每个部分都在引擎的监督之下执行,模板将会被明确地标记在白名单或黑名单内,这样对于那些不信任的模板也可以执行。 强大的自动HTML转义系统,可以有效地阻止跨站脚本攻击。 模板继承机制,此机制可以使得所有模板具有相似一致的布局,也方便开发人员对模板进行修改和管理。 高效的执行效率,Jinja3引擎在模板第一次加载时就把源码转换成Python字节码,加快模板执行时间。 调试系统融合了标准的Python的TrackBack功能,使得模板编译和运行期间的错误能及时被发现和调试。 语法配置,可以重新配置Jinja3,使得它更好地适应LaTeX或JavaScript 的输出。官方文档手册,此手册指导设计人员更好地使用Jinja3引擎的各种方法。 Django支持 Jinja3模板引擎的使用,由于Jinja3的设计思想来源于Django 的模板引擎,因此Jinja3的使用方法与 Django 的模板语法有相似之处。

开源主页:https://github.com/pallets/jinja

官方文档:https://jinja.palletsprojects.com/en/3.1.x/

Jinja3安装与配置

我们用pip命令安装Jinja3

1
pip install Jinja2 -i https://pypi.tuna.tsinghua.edu.cn/simple

Jinja3安装成功后,接着在 Django里配置Jinja3模板。由于 Django的内置功能是使用 Django的模板引擎,如果将整个项目都改为Jinja3模板引擎,就会导致内置功能无法正常使用。在这种情况下,既要保证内置功能能够正常使用,又要使用Jinja3模板引擎,只能将两个模板引擎共存在同一个项目里。

首先我们在helloWorld项目库里新建Jinja3.py,用来定义环境参数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from jinja2 import Environment


def environment(**options):
    env = Environment(**options)
    env.globals.update(
        {
            'static': staticfiles_storage.url,
            'url': reverse
        }
    )
    return env

然后我们找到项目配置settings.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': [BASE_DIR / 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'environment': 'helloWorld.Jinja3.environment'
        },
    },
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'helloWorld/templates', 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',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

我们找到项目目录下templates的index.html,修改下Jinja3支持的模板语法:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
</html>

运行测试:

image-20240313083415508

Jinja3模板语法

尽管Jinja3的设计思想来源于Django 的模板引擎,但在功能和使用细节上,Jinja3比Django的模板引擎更为完善,而且Jinja3的模板语法在使用上与 Django的模板引擎存在一定的差异。 由于Jinja3有模板设计人员帮助手册(官方文档: https://jinja.palletsprojects.com/en/3.1.x/),并且官方文档对模板语法的使用说明较为详细,因此这里只讲述Jinja3与 Django模板语言的使用差异。

我们把helloworld子项目下templates下的index.html复制到父项目下的templates下,然后进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
字符串:<br>
字典类型:,,<br>
对象:,<br>
列表:,,,<br>
元组:,,,,

<h3>模板标签</h3>
<p>遍历for标签:</p>
{百分号 for item in msg4 %}
    <p>这个是第次循环</p>
    {百分号 if loop.first %}
        <p>这个是第一项:</p>
    {百分号 elif loop.last %}
        <p>这个是最后一项:</p>
    {百分号 endif %}
{百分号 endfor %}
<p>判断if标签:</p>
{百分号 if msg == '模板变量' %}
    <p>模板变量</p>
{百分号 elif msg == '模板变量2' %}
    <p>模板变量2</p>
{百分号 else %}
    <p>其他</p>
{百分号 endif %}
<p>url标签</p>
{#<a href="{百分号 url 'index' %}">请求index</a>#}
<a href="">请求index</a>
<p>with标签</p>
{百分号 with info=msg %}
    
{百分号 endwith %}

</body>
</html>

运行测试:

image-20240314205429022

在遍历对象,列表,元组的时候,假如元素或者属性不存在,Jinja3会返回具体的报错信息:no such element

以及url函数用法不一样;在遍历for标签上,属性页不一样,内置的对象是loop

for函数模板变量:

Variable Description
loop.index 循环的当前迭代(索引从1开始)
loop.index0 循环的当前迭代(索引从0开始)
loop.revindex 循环结束时的迭代次数(索引从1开始)
loop.revindex0 循环结束时的迭代次数(索引从0开始)
loop.first 如果是第一次迭代,就为True
loop.last 如果是最后一次迭代,就为True
loop.length 序列中的项目数,即循环总次
loop.cycle 辅助函数,用于在序列列表之间循环
loop.depth 当前递归循环的深度,从1级开始
loop.depth0 当前递归循环的深度,从0级开始
loop.previtem 上一次迭代中的对象
loop.nextitem 下一次迭代中的对象
loop.changed(*val) 若上次迭代的值与当前迭代的值不同,则返回True

我们把base.html和course.html也复制一份到父项目templates下;

base.html需要修改下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {百分号 block title %}
            Python222学院
        {百分号 endblock %}
    </title>
</head>
{# load不支持,要去掉 #}
{#{百分号 load static %}#}
<body>
<div id="head">
{#  Django语法  #}
{#    <img src="{百分号 static 'logo.png' %}"/>#}
{#  Jinja3语法  #}
    <img src=""/>
</div>
<div id="content">
    {百分号 block content %}
        欢迎进入Python222学院
    {百分号 endblock %}
</div>
<div id="footer">
    版权所有 www.python222.com
</div>
</body>
</html>

运行测试:

image-20240314211924703

相比较Django,Jinja3在static函数用法上也有区别,模版继承用法基本一致。

Jinja3过滤器

Jinja3的过滤器与 Django内置过滤器的使用方法有相似之处,也是由管道符号“ ”连接模板上下文和过滤器,但是两者的过滤器名称是不同的,而且过滤器的参数设置方式也不同。Jinja3常用过滤器如下表格:
过滤器 说明
abs 设置数值的绝对值
default 设置默认值
escape 转义字符,转成HTML的语法
first 获取上下文的第一个元素
last 获取上下文的最后一个元素
length 获取上下文的长度
join 功能与Python的join语法一致
safe 将上下文转义处理
int 将上下文转换为int类型
float 将上下文转换为float类型
lower 将字符串转换为小写
upper 将字符串转换为大写
replace 字符串的替换
truncate 字符串的截断
striptags 删除字符串中所有的HTML标签
trim 截取字符串前面和后面的空白字符
string 将上下文转换成字符串
wordcount 计算长字符串的单词个数

具体使用可以参考下Jinja3官方文档( https://jinja.palletsprojects.com/en/3.1.x/templates/#filters )

下面我们通过实例来体检下Jinja3的用法吧:

修改index.html:

1
2
3
4
5
<p>Jinja3过滤器</p>
first: <br>
last:<br>
length:<br>
upper:

运行结果:

image-20240315161534830

Django5模型定义与使用

Django5对各种数据库提供了很好的支持,包括PostgreSQL、MySQL、SQLite和 Oracle,而且为这些数据库提供了统一的API方法,这些API统称为ORM框架。通过使用Django5内置的ORM框架可以实现数据库连接和读写操作。

Django5模型定义

ORM框架是一种程序技术,用于实现面向对象编程语言中不同类型系统的数据之间的转换。 从效果上说,它创建了一个可在编程语言中使用的“虚拟对象数据库”,通过对虚拟对象数据库的操作从而实现对目标数据库的操作,虚拟对象数据库与目标数据库是相互对应的。在 Django5中,虚拟对象数据库也称为模型,通过模型实现对目标数据库的读写操作,实现方法如下:

  1. 配置目标数据库,在settings.py中设置配置属性
  2. 构建虚拟对象数据库,在App 的models.py文件中以类的形式定义模型。
  3. 通过模型在目标数据库中创建相应的数据表。
  4. 在其他模块(如视图函数)里使用模型来实现目标数据库的读写操作。

settings.py下我们配置mysql数据库:

1
2
3
4
5
6
7
8
9
10
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_python222',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': 'localhost',
        'PORT': '3308'
    }
}

然后我们在models.py里新建两个模型类,分别是图书模型BookInfo和图书类别模型BookTypeInfo,他们是多对一的关系;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BookTypeInfo(models.Model):
    id = models.AutoField(primary_key=True)
    bookTypeName = models.CharField(max_length=20)

    class Meta:
        db_table = "t_bookType"
        verbose_name = "图书类别信息"  # 给模型取个直观的名字


class BookInfo(models.Model):
    id = models.AutoField(primary_key=True)
    bookName = models.CharField(max_length=20)
    price = models.FloatField()
    publishDate = models.DateField()
    bookType = models.ForeignKey(BookTypeInfo, on_delete=models.PROTECT)

    class Meta:
        db_table = "t_book"
        verbose_name = "图书信息"  # 给模型取个直观的名字

模型字段类型如下:

  • AutoField:自增长类型,数据表的字段类型为整数,长度为11位。
  • BigAutoField:自增长类型,数据表的字段类型为bigint,长度为20位。
  • CharField:字符类型。
  • BooleanField:布尔类型。
  • CommaSeparatedIntegerField:用逗号分割的整数类型。DateField:日期( Date)类型。
  • DateTimeField:日期时间( Datetime)类型。Decimal:十进制小数类型。
  • EmailField:字符类型,存储邮箱格式的字符串。
  • FloatField:浮点数类型,数据表的字段类型变成Double类型。IntegerField:整数类型,数据表的字段类型为11位的整数。
  • BigIntegerField:长整数类型。
  • IPAddressField:字符类型,存储Ipv4地址的字符串。
  • GenericIPAddressField:字符类型,存储Ipv4和Ipv6地址的字符串。NullBooleanField:允许为空的布尔类型。
  • PositiveIntegerFiel:正整数的整数类型。
  • PositiveSmallIntegerField:小正整数类型,取值范围为0~32767。SlugField:字符类型,包含字母、数字、下画线和连字符的字符串。
  • SmallIntegerField:小整数类型,取值范围为-32,768~+32,767。
  • TextField:长文本类型。
  • TimeField:时间类型,显示时分秒HH:MM[ :ss[.uuuuuu]]。URLField:字符类型,存储路由格式的字符串。
  • BinaryField:二进制数据类型。
  • FileField:字符类型,存储文件路径的字符串。ImageField:字符类型,存储图片路径的字符串。
  • FilePathField:字符类型,从特定的文件目录选择某个文件。

模型字段参数如下:

  • verbose_name:默认为None,在 Admin站点管理设置字段的显示名称。
  • primary_key:默认为False,若为True,则将字段设置成主键。
  • max_length:默认为None,设置字段的最大长度。
  • unique:默认为False,若为True,则设置字段的唯一属性。

  • blank:默认为False,若为True,则字段允许为空值,数据库将存储空字符串。null:默认为False,若为True,则字段允许为空值,数据库表现为NULL。
  • db_index:默认为False,若为True,则以此字段来创建数据库索引。default:默认为NOT_PROVIDED对象,设置字段的默认值。
  • editable:默认为True,允许字段可编辑,用于设置Admin的新增数据的字段。serialize:默认为True,允许字段序列化,可将数据转化为JSON格式。
  • unique_for_date:默认为None,设置日期字段的唯一性。
  • unique_for_month:默认为None,设置日期字段月份的唯一性。unique_for_year:默认为None,设置日期字段年份的唯一性。choices:默认为空列表,设置字段的可选值。
  • help_text:默认为空字符串,用于设置表单的提示信息。
  • db_column:默认为None,设置数据表的列名称,若不设置,则将字段名作为数据表的列名。
  • db_tablespace:默认为None,如果字段已创建索引,那么数据库的表空间名称将作为该字段的索引名。注意:部分数据库不支持表空间。
  • auto_created:默认为False,若为True,则自动创建字段,用于一对一的关系模型。validators:默认为空列表,设置字段内容的验证函数。
  • error_messages:默认为None,设置错误提示。

ForeignKey方法参数如下:

参数名 参数说明
to 指定关联的目标模型类。可以使用字符串表示模型类的路径,也可以直接使用模型类的引用。
on_delete 指定当关联对象被删除时的行为。CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET0、DO_NOTHING。
related_name 指定反向关联的名称,默认为模型类名_set。
to_field 指定关联的目标模型类中用于关联的字段名称。默认为主键字段。
db_index 如果为True,则在目标模型的关联字段上创建索引。
null 指定关联字段是否可以为空。如果 null=True,则数据库中该字段将允许 NULL值。
blank 指定关联字段是否可以为空。如果blank=True,则表单中该字段可以为空。
limit_choices_to 指定关联对象的过滤条件。可以是一个字典、一个 QuerySet或一个函数。
verbose_name 用于在 Django Admin后台中显示字段名称。
help_text 用于在 Django Admin后台中显示帮助文本。

on_delete的models属性有下面设置选项;

  • CASCADE:这就是默认的选项,级联删除,你无需显性指定它。
  • PROTECT: 保护模式,如果采用该选项,删除的时候,会抛出ProtectedError错误。
  • SET_NULL: 置空模式,删除的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空。
  • SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值。
  • SET(): 自定义一个值,该值当然只能是对应的实体了

Django5数据迁移

然后我们执行: python manage.py makemigrations 生成数据库迁移文件

所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件;

这个生成的迁移文件在migrations目录下;每执行一次,都会生成一个新文件。

image-20240324173810936

再执行:python manage.py migrate 执行迁移文件,同步到数据库中;

数据库里就会生成t_book和t_bookType两个表;

image-20240324173950279

最后我们再搞一些测试数据;

1
2
INSERT INTO `t_booktype` VALUES (1, '计算机类');
INSERT INTO `t_booktype` VALUES (2, '数学类');
1
2
3
INSERT INTO `t_book` VALUES (1, 'Java编程思想', 100, '2004-03-16', 1);
INSERT INTO `t_book` VALUES (2, 'Head First设计模式', 88, '2020-03-16', 1);
INSERT INTO `t_book` VALUES (3, '数学的秘密', 50, '2019-03-06', 2);

Django5模型查询(上)

我们知道数据库设有多种数据查询方式,如单表查询、多表查询、子查询和联合查询等,而 Django 的ORM框架对不同的查询方式定义了相应的API方法。下面我们通过实例来深入学习下;

我们来实现下图书信息的查询,顺便通过外键关联配置,把图书类别信息也级联查询出来。我们通过all()方法查询出所有图书信息;

views.py里我们加下bookList方法:

1
2
3
4
5
6
7
8
9
def bookList(request):
    """
    图书列表查询
    """
    # 查询所有信息
    bookList = BookInfo.objects.all()
    print(bookList)
    content_value = {"title": "图书列表", "bookList": bookList}
    return render(request, 'book/list.html', context=content_value)

urls.py里加下映射配置:

1
path('book/list', helloWorld.views.bookList)

templates下新建book目录,book目录下新建list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<table border="1">
    <tr>
        <th>编号</th>
        <th>图书名称</th>
        <th>价格</th>
        <th>出版日期</th>
        <th>图书类别</th>
    </tr>
    {百分号 for book in bookList %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
        </tr>
    {百分号 endfor %}
</table>
</body>
</html>

测试运行,浏览器输入:http://127.0.0.1:8000/book/list

image-20240325160429583

查询出了所有的图书信息;

Django5模型查询(下)

前面实例我们用了ORM框架提供的all方法,查询所有数据。

image-20240327074851936

下面我们继续学习下ORM框架给我们提供的一些其他常用方法;

1
2
3
4
5
6
7
8
9
10
# 查询所有信息
bookList = BookInfo.objects.all()
# 获取数据集的第一条数据的bookName属性值
print(bookList[0].bookName) 
# 返回前2条数据 select * from t_book limit 2
bookList = BookInfo.objects.all()[:2]
# 查询指定字段
bookList = BookInfo.objects.values("bookName", "price")
# 查询指定字段 数据以列表方式返回,列表元素以元组表示
bookList = BookInfo.objects.values_list("bookName", "price")

ORM框架提供了get()方法,返回满足条件的单个数据:

1
2
3
# 获取单个对象,一般是根据id查询
book = BookInfo.objects.get(id=2)
print(book.bookName)

ORM框架提供了filter()方法,返回满足条件的数据:

1
2
3
4
5
6
7
8
9
10
11
12
# 返回满足条件id=2的数据,返回类型是列表
bookList = BookInfo.objects.filter(id=2)
bookList = BookInfo.objects.filter(price=100, id=1)
# filter的查询条件可以设置成字典格式
d = dict(price=100, id=1)
bookList = BookInfo.objects.filter(**d)
# SQL的or查询,需要引入Q,from django.db.models import Q
# 语法格式:Q(field=value)|Q(field=value) 多个Q之间用"|"隔开
bookList = BookInfo.objects.filter(Q(id=1) | Q(price=88))
# SQL的不等于查询,在Q查询中用“~”即可
# SQL select * from t_book where not (id=1)
bookList = BookInfo.objects.filter(~Q(id=1))

ORM框架提供了exclude()方法,返回不满足条件的数据:

1
2
# 也可以使用exclude 返回满足条件之外的数据 实现不等于查询
bookList = BookInfo.objects.exclude(id=1)

ORM框架提供了count()方法,返回满足查询条件后的数据量:

1
2
3
# 使用count()方法,返回满足查询条件后的数据量
t = BookInfo.objects.filter(id=2).count()
print(t)

ORM框架提供了distinct()方法,返回去重后的数据:

1
2
3
# distinct()方法,返回去重后的数据
bookList = BookInfo.objects.values('bookName').distinct()
print(bookList)

ORM框架提供了order_by()方法,对结果进行排序;默认是升序;如果需要降序,只需要在字段前面加“-”即可;

1
2
3
# 使用order_by设置排序
# bookList = BookInfo.objects.order_by("price")
bookList = BookInfo.objects.order_by("-id")

ORM框架提供了annotate方法来实现聚合查询,比如数据值求和,求平均值等。

1
2
3
4
5
6
# annotate类似于SQL里面的GROUP BY方法
# 如果不设置values,默认对主键进行GROUIP BY分组
# SQL: select bookType_id,SUM(price) AS 'price_sum' from t_book GROUP BY bookType_id
r = BookInfo.objects.values('bookType').annotate(Sum('price'))
# SQL: select bookType_id,AVG(price) AS 'price_sum' from t_book GROUP BY bookType_id
r2 = BookInfo.objects.values('bookType').annotate(Avg('price'))

Django5模型分页查询

在Django中实现分页通常使用Paginator类。以下是一个简单的示例,展示了如何在Django视图中实现分页功能:

1
2
3
4
5
6
7
8
bookList = BookInfo.objects.all()
# Paginator(object_list ,per_page)
# object_list   结果集/列表
# per_page  每页多少条记录
p = Paginator(bookList, 2)
# 获取第几页的数据
bookListPage = p.page(2)
print("总记录数:", BookInfo.objects.count())

Django5高级查询匹配符

前面讲了开发中常用的数据查询方法,但有时需要设置不同的查询条件来满足多方面的查询要求。上述的查询条件 filter和 get是使用等值的方法来匹配结果。若想使用大于、不等于或模糊查询的匹配方法,则可在查询条件filter和 get里使用下表的匹配符实现。

匹配符 使用 说明
__exact filter(job__exact=’开发’) 精确等于,如SQL的like’开发’。
__iexact filter(job__iexact=’开发’) 精确等于并忽略大小写。
__contains filter(job__contains=’开发’) 模糊匹配,如SQL的like’%荣耀%’。
__icontains filter(job__icontains=’开发’) 模糊匹配,忽略大小写。
__gt filter(job__gt=5) 大于。
__gte filter(job__gte=5) 大于等于。
__lt filter(job__lt=5) 小于。
__lte filter(job__lte=5) 小于等于。
__in filter(job__in=[1,2,3]) 判断是否在列表内。
__startswith filter(job__startswith=’开发’) 以。。。开头。
__istartswith filter(job__istartswith=’开发’) 以。。。开头并忽略大小写。
__endswith filter(job__endswith=’开发’) 以。。。结尾。
__iendswith filter(job__iendswith=’开发’) 以。。。结尾并忽略大小写。
__range filter(job__range=’开发’) 在。。。范围内。
__year filter(job__year=’2018’) 日期字段的年份。
__month filter(job__month=’12’) 日期字段的月份。
__day filter(job__day=30) 日期字段的天数。
__isnull filter(job__isnull=True/False) 判断是否为空。

实例代码:

1
2
3
4
# 模糊查询图书名称含有"编程"的所有数据
# bookList = BookInfo.objects.filter(bookName__contains='编程')
# 查询图书价格大于等于50的所有数据
bookList = BookInfo.objects.filter(price__gte=50)

在查询数据时可以使用查询条件get或filter实现,但是两者的执行过程存在一定的差异,说明如下。

  • 查询条件get:查询字段必须是主键或者唯一约束的字段,并且查询的数据必须存在,如果查询的字段有重复值或者查询的数据不存在,程序就会抛出异常信息。
  • 查询条件filter:查询字段没有限制,只要该字段是数据表的某一字段即可。查询结果以列表形式返回,如果查询结果为空(查询的数据在数据表中找不到),就返回空列表。

Django5模型多表查询

我们在日常的开发中,常常需要对多张数据表同时进行数据查询。多表查询需要在数据表之间建立表关系才能够实现。一对多或一对一的表关系是通过外键实现关联的,而多表查询分为正向查询和反向查询。

以模型BookInfo和BokkTypeInfo为例,如果查询主题是BookInfo,通过外键bookType_id去查询BooKTypeInfo的关联数据,那么该查询称为正向查询;如果查询对象的主题是模型BookTypeInfo,要查询它与模型BookInfo的关联数据,那么该查询称为反向查询;

下面是一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def bookList2(request):
    """
    多表查询  正常查询 和反向查询
    :param request:
    :return:
    """
    # 正向查询
    book: BookInfo = BookInfo.objects.filter(id=2).first()
    print(book.bookType.bookTypeName)

    # 反向查询
    bookType = BookTypeInfo.objects.filter(id=1).first()
    print(bookType.bookinfo_set.first().bookName)
    print(bookType.bookinfo_set.all())

    content_value = {"title": "图书列表"}
    return render(request, 'book/list.html')

Django5模型数据新增

Django对数据库的数据进行增、删、改操作是借助内置ORM框架所提供的API方法实现的,简单来说,它在模型基础类 Model里定义数据操作方法,通过类继承将这些操作方法传给开发者自定义的模型对象,再由模型对象调用即可实现数据操作。

添加操作通过模型的save方法实现,添加下可以返回主键id值。

我们在前面实例的基础上,来实现这个例子。

因为添加页面是需要图书类别的数据,我们用下拉框实现。所以这里需要一个预处理操作。

先在views.py里定义一个添加预处理方法preAdd

1
2
3
4
5
6
7
8
9
10
def preAdd(request):
    """
    预处理,添加操作
    :param request:
    :return:
    """
    bookTypeList = BookTypeInfo.objects.all()
    print(bookTypeList)
    content_value = {"title": "图书添加", "bookTypeList": bookTypeList}
    return render(request, 'book/add.html', context=content_value)

urls.py里加下映射:

1
path('book/preAdd', helloWorld.views.preAdd),

原先的list.html,加上添加的链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<a href="/book/preAdd">添加</a><br/><br/>
<table border="1">
    <tr>
        <th>编号</th>
        <th>图书名称</th>
        <th>价格</th>
        <th>出版日期</th>
        <th>图书类别</th>
    </tr>
    {百分号 for book in bookList %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
        </tr>
    {百分号 endfor %}
</table>
</body>
</html>

再创建下图书添加页面add.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<form action="/book/add" method="post">
    {百分号 csrf_token %}
    <table>
        <tr>
            <td>图书名称:</td>
            <td>
                <input type="text" name="bookName">
            </td>
        </tr>
        <tr>
            <td>出版日期:</td>
            <td>
                <input type="text" name="publishDate">
            </td>
        </tr>
        <tr>
            <td>图书类别:</td>
            <td>
                <select name="bookType_id">
                    {百分号 for bookType in bookTypeList %}
                        <option value=""></option>
                    {百分号 endfor %}
                </select>
            </td>
        </tr>
        <tr>
            <td>图书价格:</td>
            <td>
                <input type="text" name="price">
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="提交">
            </td>
        </tr>
    </table>
</form>
</body>
</html>

最后在views.py里创建图书添加函数add:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def add(request):
    """
    图书添加
    :param request:
    :return:
    """
    # print(request.POST.get("bookName"))
    # print(request.POST.get("publishDate"))
    # print(request.POST.get("bookType_id"))
    # print(request.POST.get("price"))
    book = BookInfo()
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    book.save()
    # 数据添加后,获取新增数据的主键id
    print(book.id)
    return bookList(request)

运行测试:浏览器输入:http://127.0.0.1:8000/book/list

image-20240417214023951

点击添加链接,

image-20240417214111826

进入图书添加页面,输入图书信息,点提交:

image-20240417214143567

页面显示最新数据;

Django5模型数据修改

模型数据修改和添加都是用的save方法。

我们结合案例先实现下;

我们在views.py里先定义preUpdate方法,修改预处理,根据id获取图书信息,以及获取图书类别列表;

1
2
3
4
5
6
7
8
9
10
11
12
13
def preUpdate(request, id):
    """
    预处理,修改操作
    :param request:
    :return:
    """
    print("id:", id)
    book = BookInfo.objects.get(id=id)
    print(book)
    bookTypeList = BookTypeInfo.objects.all()
    print(bookTypeList)
    content_value = {"title": "图书修改", "bookTypeList": bookTypeList, "book": book}
    return render(request, 'book/edit.html', context=content_value)

urls.py里加下映射:

1
path('book/preUpdate/<int:id>', helloWorld.views.preUpdate),

book/list.html修改下,加下修改操作链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<a href="/book/preAdd">添加</a><br/><br/>
<table border="1">
    <tr>
        <th>编号</th>
        <th>图书名称</th>
        <th>价格</th>
        <th>出版日期</th>
        <th>图书类别</th>
        <th>操作</th>
    </tr>
    {百分号 for book in bookList %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td>
                <a href="/book/preUpdate/">修改</a>
            </td>
        </tr>
    {百分号 endfor %}
</table>
</body>
</html>

新建编辑页面edit.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<form action="/book/update" method="post">
    {百分号 csrf_token %}
    <table>
        <tr>
            <td>图书名称:</td>
            <td>
                <input type="text" name="bookName" value="">
            </td>
        </tr>
        <tr>
            <td>出版日期:</td>
            <td>
                <input type="text" name="publishDate" value="">
            </td>
        </tr>
        <tr>
            <td>图书类别:</td>
            <td>
                <select name="bookType_id">
                    {百分号 for bookType in bookTypeList %}
                        <option value=""
                                {百分号 if book.bookType.id == bookType.id %}selected{百分号 endif %}>
                            
                        </option>
                    {百分号 endfor %}
                </select>
            </td>
        </tr>
        <tr>
            <td>图书价格:</td>
            <td>
                <input type="text" name="price" value="">
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="hidden" name="id" value="">
                <input type="submit" value="提交">
            </td>
        </tr>
    </table>
</form>
</body>
</html>

再写一个update方法,保存图书信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def update(request):
    """
    图书修改
    :param request:
    :return:
    """
    book = BookInfo()
    book.id = request.POST.get("id")
    book.bookName = request.POST.get("bookName")
    book.publishDate = request.POST.get("publishDate")
    book.bookType_id = request.POST.get("bookType_id")
    book.price = request.POST.get("price")
    book.save()
    return bookList(request)

urls.py里再加下映射:

1
path('book/update', helloWorld.views.update),

我们来测试下,浏览器输入:http://127.0.0.1:8000/book/list

image-20240420161721347

我们点修改,修改id是5的图书,

image-20240420162225595

修改信息后,点提交;

image-20240420162256057

测试成功!

Django5模型数据删除

Django5 ORM框架提供了delete()方法来实现数据删除操作,下面是一些常用的方式,删除所有数据,删除指定id数据,根据filter条件删除删除。

1
2
3
4
5
6
# 删除所有数据
BookInfo.objects.all().delete()
# 删除指定id数据
BookInfo.objects.get(id=1).delete()
# 根据条件删除多条数据
BookInfo.objects.filter(price__gte=90).delete()

我们来完善下前面的实例:

views.py里先定义delete方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
def delete(request, id):
    """
    图书删除
    :param request:
    :return:
    """
    # 删除所有数据
    # BookInfo.objects.all().delete()
    # 删除指定id数据
    BookInfo.objects.get(id=id).delete()
    # 根据条件删除多条数据
    # BookInfo.objects.filter(price__gte=90).delete()
    return bookList(request)

urls.py里加下映射:

1
path('book/delete/<int:id>', helloWorld.views.delete),

book/list.html里加下删除操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<h3></h3>
<a href="/book/preAdd">添加</a><br/><br/>
<table border="1">
    <tr>
        <th>编号</th>
        <th>图书名称</th>
        <th>价格</th>
        <th>出版日期</th>
        <th>图书类别</th>
        <th>操作</th>
    </tr>
    {百分号 for book in bookList %}
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td>
                <a href="/book/preUpdate/">修改</a>
                <a href="/book/delete/">删除</a>
            </td>
        </tr>
    {百分号 endfor %}
</table>
</body>
</html>

我们测试下,浏览器输入 http://127.0.0.1:8000/book/list

image-20240422120831581

点击删除链接,

image-20240422120843649

数据直接被删除,说明测试成功!

Django5 ORM执行SQL语句

Django在查询数据时,大多数查询都能使用ORM提供的API方法,但对于一些复杂的查询可能难以使用ORM的API方法实现,因此Django引入了SQL语句的执行方法,有以下3种实现方法。

  • extra:结果集修改器,一种提供额外查询参数的机制。
  • raw:执行原始SQL并返回模型实例对象。
  • execute:直接执行自定义SQL。

extra适合用于ORM难以实现的查询条件,将查询条件使用原生SQL语法实现,此方法需要依靠模型对象,在某程度上可防止SQL注入。它一共定义了6个参数,每个参数说明如下:

  • select:添加新的查询字段,即新增并定义模型之外的字段。where:设置查询条件。
  • params:如果where设置了字符串格式化%s,那么该参数为where提供数值。tables:连接其他数据表,实现多表查询。
  • order_by:设置数据的排序方式。
  • select_params:如果select设置字符串格式化%s,那么该参数为select提供数值。

参考实例:

1
bookList = BookInfo.objects.extra(where=["price>%s"], params=[90])

raw只能实现数据查询操作,并且也要依靠模型对象,它一共定义了4个参数,每个参数说明如下:

  • raw_query: sQL语句。
  • params:如果raw_query设置字符串格式化%s,那么该参数为raw_query提供数值。
  • translations:为查询的字段设置别名。
  • using:数据库对象,即 Django 所连接的数据库。

参考实例:

1
bookList = BookInfo.objects.raw("select * from t_book where price>%s", params=[90])

execute的语法,它执行SQL语句无须经过Django的ORM框架。我们知道Django连接数据库需要借助第三方模块实现连接过程,如 MySQL的mysqlclient模块和SQLite 的sqlite3模块等,这些模块连接数据库之后,可通过游标的方式来执行SQL语句,而 execute就是使用这种方式执行SQL语句,实例如下:

1
2
3
cursor: CursorDebugWrapper = connection.cursor()
cursor.execute("select count(*) from t_book where price>90")
print(cursor.fetchone())

Django5 ORM数据库事务

事务是指作为单个逻辑执行的一系列操作,这些操作具有原子性,即这些操作要么完全执行,要么完全不执行。事务处理可以确保事务性单元内的所有操作都成功完成,否则不会执行数据操作。 事务应该具有4个属性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation),持久性(Durability),这4个属性通常称为ACID特性,说明如下。

  • 原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  • 一致性:事务必须使数据库从某个一致性状态变到另一个一致性状态,一致性与原子性是密切相关的。
  • 隔离性:一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他事务是隔离的,各个事务之间不能互相干扰。
  • 持久性:持久性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变应该是永久性的,其他操作或故障不应该对其有任何影响。

我们先来做一个用户转账的事例:

models.py下新建AccountInfo账户信息模型

1
2
3
4
5
6
7
class AccountInfo(models.Model):
    user = models.CharField(max_length=20)
    account = models.FloatField()

    class Meta:
        db_table = "t_account"
        verbose_name = "用户账户信息"  # 给模型取个直观的名字

然后我们执行: python manage.py makemigrations 生成数据库迁移文件,再执行:python manage.py migrate 执行迁移文件,同步到数据库中;生成了t_account表

image-20240429214452680

表里,我们加两个测试数据:

image-20240429214512475

views.py里,我们写一个测试转账测试实例:

1
2
3
4
5
6
7
8
9
10
11
def transfer2(request):
    """
    模拟转账
    :param request:
    :return:
    """
    a1 = AccountInfo.objects.filter(user='张三')
    a1.update(account=F('account') + 100)
    a2 = AccountInfo.objects.filter(user='李四')
    a2.update(account=F('account') - 100)
    return HttpResponse("OK")

urls.py里配置下映射:

1
path('transfer2/', helloWorld.views.transfer2),

测试,浏览器输入:http://127.0.0.1:8000/transfer2/,测试成功。

image-20240429215125253

这里涉及到2个数据库操作,假如第二个数据库操作失败,那张三就多了100,但是李四钱没少,这时候金融账务就对不上了,除了问题。

我们来模拟下吧,转出代码,除以0:

image-20240429220203045

再运行测试下,页面报错,

image-20240429220300096

李四的钱也没被扣,

image-20240429220320889

这时候,Django5提供的事务功能就派上用场了。

Django5主要有4个事务方法:

  • atomic():在视图函数或视图类里使用事务。
  • savepoint():开启事务。
  • savepoint_rollback():回滚事务。
  • savepoint_commit():提交事务。

我们用上事务,改下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@transaction.atomic
def transfer(request):
    """
    模拟转账
    :param request:
    :return:
    """
    # 开启事务
    sid = transaction.savepoint()
    try:
        a1 = AccountInfo.objects.filter(user='张三')
        a1.update(account=F('account') + 100)
        a2 = AccountInfo.objects.filter(user='李四')
        a2.update(account=F('account') - 100 / 0)
        # 提交事务 (如不设置,当程序执行完成后,会自动提交事务)
        transaction.savepoint_commit(sid)
    except Exception as e:
        print("异常信息:", e)
        # 事务回滚
        transaction.savepoint_rollback(sid)
    return HttpResponse("OK")

我们再测试下,发现回滚了,钱都没有变化;

Django5表单定义与使用

表单是搜集用户数据信息的各种表单元素的集合,其作用是实现网页上的数据交互,比如用户在网站输入数据信息,然后提交到网站服务器端进行处理(如数据录入和用户登录注册等)。 网页表单是Web开发的一项基本功能,Django5的表单功能由Form类实现,主要分为两种:django.forms.Form和 django.forms.ModelForm。前者是一个基础的表单功能,后者是在前者的基础上结合模型所生成的数据表单。

Django5 Form表单定义与使用

首先forms.py里定义下BookInfoForm类

1
2
3
4
5
6
7
8
9
10
11
12
class BookInfoForm(Form):
    """
    图书表单
    """
    bookName = forms.CharField(max_length=20, label="图书名称")
    price = forms.FloatField(label="图书价格")
    publishDate = forms.DateField(label="出版日期")
    # 获取图书类别列表
    bookTypeList = BookTypeInfo.objects.values()
    # 图书类别以下拉框形式显示,下拉框选项id是图书类别Id,下拉框选项文本是图书类别名称
    choices = [(v['id'], v['bookTypeName']) for v, v in enumerate(bookTypeList)]
    bookType_id = forms.ChoiceField(required=False, choices=choices, label="图书类别")

views.py下新建preAdd2方法

1
2
3
4
5
6
7
8
9
def preAdd2(request):
    """
    预处理,添加操作 使用form表单
    :param request:
    :return:
    """
    form = BookInfoForm()
    context_value = {"title": "图书添加", "form": form}
    return render(request, 'book/add2.html', context_value)

urls.py加下映射:

1
path('book/preAdd2', helloWorld.views.preAdd2),

book/list.html加一个新的添加链接:

1
<a href="/book/preAdd2">添加(使用form)</a><br/><br/>

我们测试下哈,http://127.0.0.1:8000/book/list,点下 添加(使用form)

image-20240502162122785

image-20240502162426534

输入表单信息,点击提交,测试成功。

表单Form的常用属性和方法如下:

  • data:默认值为None,以字典形式表示,字典的键为表单字段,代表将数据绑定到对应的表单字段。
  • auto_id:默认值为id_%s,以字符串格式化表示,若设置HTML元素控件的id属性,比如表单字段job,则元素控件id属性为id_job,%s 代表表单字段名称。
  • prefix:默认值为None,以字符串表示,设置表单的控件属性name和id的属性值,如果一个网页里使用多个相同的表单,那么设置该属性可以区分每个表单。
  • initial:默认值为None,以字典形式表示,在表单的实例化过程中设置初始化值。
  • label_suffix:若参数值为None,则默认为冒号,以表单字段job为例,其HTML控件含有label标签( ),其中 label标签里的冒号由参数label_suffix设置。field_order:默认值为None,则以表单字段定义的先后顺序进行排列,若要自定义排序,则将每个表单字段按先后顺序放置在列表里,并把列表作为该参数的值。
  • use_required_attribute:默认值为None(或为True ),为表单字段所对应的 HTML控件设置required属性,该控件为必填项,数据不能为空,若设为False,则HTML控件为可填项。errors():验证表单的数据是否存在异常,若存在异常,则获取异常信息,异常信息可设为字典或JSON格式。
  • is_valid():验证表单数据是否存在异常,若存在,则返回False,否则返回True。
  • as_table():将表单字段以HTML的<table>标签生成网页表单。
  • as_ul():将表单字段以HTML的<ul>标签生成网页表单。
  • as _p():将表单字段以HTML的<p>标签生成网页表单。
  • has_changed():对比用于提交的表单数据与表单初始化数据是否发生变化。

表单定义字段如如下类型:

  • CharField:文本框,参数max_length 和min_length分别设置文本长度。
  • IntegerField:数值框,参数max_value设置最大值,min_value设置最小值。
  • FloatField:数值框,继承IntegerField,验证数据是否为浮点数。
  • DecimalField:数值框,继承IntegerField,验证数值是否设有小数点,参数max_digits设置最大位数,参数decimal_places设置小数点最大位数。
  • DateField:文本框,继承BaseTemporalField,具有验证日期格式的功能,参数input_formats设置日期格式。
  • TimeField:文本框,继承BaseTemporalField,验证数据是否为datetime.time或特定时间格式的字符串。
  • DateTimeField:文本框,继承 BaseTemporalField,验证数据是否为datetime.datetime ,datetime.date或特定日期时间格式的字符串。
  • DurationField:文本框,验证数据是否为一个有效的时间段。
  • RegexField:文本框,继承CharField,验证数据是否与某个正则表达式匹配,参数regex设置正则表达式。
  • EmailField:文本框,继承CharField,验证数据是否为合法的邮箱地址。
  • FileField:文件上传框,参数max_length设置上传文件名的最大长度,参数allow_empty_file设置是否允许文件内容为空。
  • ImageField:文件上传控件,继承FileField,验证文件是否为Pillow库可识别的图像格式。FilePathField:文件选择控件,在特定的目录选择文件,参数 path是必需参数,参数值为目录的绝对路径;参数recursive、match、 allow_files和allow_folders为可选参数。 URLField:文本框,继承CharField,验证数据是否为有效的路由地址。
  • BooleanField:复选框,设有选项True和 False,如果字段带有required=True,复选框就默认为True。
  • NullBooleanField:复选框,继承BooleanField,设有3个选项,分别为None、True和 False。ChoiceField:下拉框,参数choices 以元组形式表示,用于设置下拉框的选项列表。
  • TypedChoiceField:下拉框,继承 ChoiceField,参数coerce 代表强制转换数据类型,参数empty_value表示空值,默认为空字符串。
  • MultipleChoiceField:下拉框,继承ChoiceField,验证数据是否在下拉框的选项列表。
  • TypedMultipleChoiceField:下拉框,继承MultipleChoiceField,验证数据是否在下拉框的选项列表,并且可强制转换数据类型,参数coerce代表强制转换数据类型,参数 empty_value表示空值,默认为空字符串。
  • ComboField:文本框,为表单字段设置验证功能,比如字段类型为CharField,为该字段添加EmailField,使字段具有邮箱验证功能。
  • MultiValueField:文本框,将多个表单字段合并成一个新的字段。
  • SplitDateTimeField:文本框,继承MultiValueField,验证数据是否为datetime.datetime或特定日期时间格式的字符串。
  • GenericIPAddressField:文本框,继承CharField,验证数据是否为有效的IP地址。
  • SlugField:文本框,继承CharField,验证数据是否只包括字母、数字、下画线及连字符。
  • UUIDField:文本框,继承CharField,验证数据是否为UUID格式。

每个表单有一些常用属性如下:

  • required:输入的数据是否为空,默认值为True。
  • widget:设置HTML控件的样式。
  • label:用于生成label标签的网页内容。initial:设置表单字段的初始值。
  • help_text:设置帮助提示信息。
  • error_messages:设置错误信息,以字典形式表示,比如{‘required”: ‘不能为空’, ‘invalid’: ‘格式错误}。
  • show_hidden_initial:参数值为True/False,是否在当前控件后面再加一个隐藏的且具有默认值的控件( 可用于检验两次输入的值是否一致)。
  • validators:自定义数据验证规则。以列表格式表示,列表元素为函数名。localize:参数值为True/False,设置本地化,不同时区自动显示当地时间。disabled:参数值为True/False,HTML控件是否可以编辑。
  • label_suffix:设置label 的后缀内容。

参数widget是一个forms.widgets对象,有4大类小部件:文本框类型,下拉框(复选框)类型,文件上传类型和复合框类型;

文本框类型:

  • TextInput,对应CharField字段,文本框内容设置为文本格式
  • NumberInput,对应IntegerField字段,文本框内容只允许输入数值
  • Emaillnput,对应 EmailField字段,验证输入值是否为邮箱地址格式
  • URLInput,对应URLField字段,验证输入值是否为路由地址格式
  • PasswordInput,对应CharField字段,输入值以“*”显示
  • HiddenInput,对应CharField字段,隐藏文本框,不显示在网页上
  • DateInput,对应DateField字段,验证输入值是否为日期格式
  • DateTimeInput,对应DateTimeField字段,验证输入值是否为日期时间格式
  • TimeInput,对应TimeField字段,验证输入值是否为时间格式
  • Textarea,对应CharField字段,将文本框设为Textarea格式

下拉框(复选框)类型:

  • CheckboxInput,对应 BooleanField字段,设置复选框,选项为True和 False
  • Select,对应 ChoiceField字段,设置下拉框
  • NullBooleanSelect,对应NullBooleanField,设置复选框,选项为None、True和 False
  • SelectMultiple,对应ChoiceField字段,与Select类似,允许选择多个值
  • RadioSelect,对应ChoiceField字段,将数据列表设置为单选按钮
  • CheckboxSelectMultiple,对应ChoiceField字段,与SelectMultiple类似,设置为复选框列表

文件上传类型:

  • FileInput,对应 FileField 或 ImageField字段
  • ClearableFileInput,对应 FileField 或ImageField字段,但多了复选框,允许清除上传的文件和图像

复合框类型:

  • MultipleHiddenInput,隐藏一个或多个HTML的控件
  • SplitDateTimeWidget,组合使用Datelnput和 Timelnput
  • SplitHiddenDateTimeWidget,与SplitDateTimeWidget类似,但将控件隐藏,不显示在网页上
  • SelectDateWidget,组合使用3个Select,分别生成年、月、日的下拉框

我们优化下前面表单实例,加一个字段验证,以及样式;

forms.py修改下BookInfoForm类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BookInfoForm(Form):
    """
    图书表单
    """
    bookName = forms.CharField(
        max_length=20,
        label="图书名称",
        required=True,
        widget=widgets.TextInput(attrs={"placeholder": "请输入用户名", "class": "inputCls"})
    )
    price = forms.FloatField(label="图书价格")
    publishDate = forms.DateField(label="出版日期")
    # 获取图书类别列表
    bookTypeList = BookTypeInfo.objects.values()
    # 图书类别以下拉框形式显示,下拉框选项id是图书类别Id,下拉框选项文本是图书类别名称
    choices = [(v['id'], v['bookTypeName']) for v, v in enumerate(bookTypeList)]
    bookType_id = forms.ChoiceField(required=False, choices=choices, label="图书类别")

add2.html加个样式:

1
2
3
4
5
    <style>
        .inputCls {
            width: 200px;
        }
    </style>

运行测试:

image-20240502184012437

image-20240502184019222

Django5 ModelForm表单定义与使用

我们知道Django的表单分为两种: django.forms.Form和 django.forms.ModelForm。前者是一个基础的表单功能,后者是在前者的基础上结合模型所生成的模型表单。模型表单是将模型字段转换成表单字段,由表单字段生成HTML控件,从而生成网页表单。

ModelForm有9个属性,说明如下:

  • model:必需属性,用于绑定Model对象。‘
  • fields:可选属性,设置模型内哪些字段转换成表单字段,默认值为None,代表所有的模型字段,也可以将属性值设为’all‘,同样表示所有的模型字段。若只需部分模型字段,则将模型字段写入一个列表或元组里,再把该列表或元组作为属性值。
  • exclude:可选属性,与fields 相反,禁止模型字段转换成表单字段。属性值以列表或元组表示,若设置了该属性,则属性fields 无须设置。
  • labels:可选属性,设置表单字段的参数label,属性值以字典表示,字典的键为模型字段。
  • widgets:可选属性,设置表单字段的参数 widget,属性值以字典表示,字典的键为模型字段。
  • localized_fields:可选参数,将模型字段设为本地化的表单字段,常用于日期类型的模型字段。
  • field_classes:可选属性,将模型字段重新定义,默认情况下,模型字段与表单字段遵从Django内置的转换规则。
  • help_texts:可选属性,设置表单字段的参数help_text。
  • error messages:可选属性,设置表单字段的参数error_messages.

模型字段转换表单字段遵从Django内置的规则进行转换,两者的转换规则如下:

模型字段类型 表单字段类型
AutoField 不能转换表单字段
BigAutoField 不能转换表单字段
BigIntegerField IntegerField
BinaryField CharField
BooleanField BooleanField或者NullBooleanField
CharField CharField
DateField DateField
DateTimeField DateTimeField
DecimalField DecimalField
EmailField EmailField
FileField FileField
FilePathField FilePathField
ForeignKey ModelChoiceField
ImageField ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericlPAddressField GenericIPAddressField
ManyToManyField ModelMultipleChoiceField
NullBooleanField NullBooleanField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmalllntegerField IntegerField
TextField CharField
TimeField TimeField
URLField URLField

我们用ModelForm改写下前面的图书添加实例:

forms.py里创建BookInfoModelForm类,继承ModelForm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class BookInfoModelForm(ModelForm):
    # 配置中心
    class Meta:
        model = BookInfo  # 导入model
        fields = '__all__'  # 代表所有字段
        # fields = ['bookName', 'price']  # 指定字段
        widgets = {  # 定义控件
            'bookName': forms.TextInput(attrs={"placeholder": "请输入用户名", 'id': 'bookName', 'class': 'inputCls'})
        }
        labels = {  # 指定标签
            'bookName': '图书名称',
            'price': '图书价格',
            'publishDate': '出版日期',
            'bookType': '图书类别'
        }
        help_texts = {
            'bookName': '请输入图书名称'
        }

views.py里定义preAdd3函数

1
2
3
4
5
6
7
8
9
def preAdd3(request):
    """
    预处理,添加操作 使用modelForm表单
    :param request:
    :return:
    """
    form = BookInfoModelForm()
    context_value = {"title": "图书添加", "form": form}
    return render(request, 'book/add2.html', context_value)

urls.py里定义下映射:

1
path('book/preAdd3', helloWorld.views.preAdd3),

book/list.html里加下新的添加链接

1
<a href="/book/preAdd3">添加(使用ModelForm)</a><br/><br/>

运行测试,浏览器输入:http://127.0.0.1:8000/book/list

image-20240504112032669

其他表单没问题,就这下拉框出现问题了;

image-20240504112339476

谷歌浏览器F12开发者工具,审查元素看下:

image-20240504112427491

我们看到value默认取的图书类型的主键id,但是选项文本取值就不对了。这时候我们可以通过魔法方法__str__来实现,默认打印对象输出图书类别名称;

1
2
3
4
5
6
7
8
9
10
class BookTypeInfo(models.Model):
    id = models.AutoField(primary_key=True)
    bookTypeName = models.CharField(max_length=20)

    class Meta:
        db_table = "t_bookType"
        verbose_name = "图书类别信息"  # 给模型取个直观的名字

    def __str__(self):
        return self.bookTypeName

再测试下,下拉框显示图书类别名称了

image-20240504122941561

这里要注意一个细节,默认转换的时候,下拉框的name是bookType,所以我们要改下views.py里的add方法,

image-20240504135712325

对应的改成bookType

我们最后测试下:

image-20240504135746801

测试成功:

image-20240504135800392

平时开发,建议大家还是用ModelForm,简单方便。

Django5内置Admin系统

Admin后台系统也称为网站后台管理系统,主要对网站的信息进行管理,如文字、图片、影音和其他日常使用的文件的发布、更新、删除等操作,也包括功能信息的统计和管理,如用户信息、订单信息和访客信息等。简单来说,它是对网站数据库和文件进行快速操作和管理的系统,以使网页内容能够及时得到更新和调整。

Django5内置Admin系统初体验

当一个网站上线之后,网站管理员通过网站后台系统对网站进行管理和维护。

Django 已内置Admin后台系统,在创建Django项目的时候,可以从配置文件settings.py中看到项目已默认启用Admin后台系统。

image-20240505174347908

urls.py里定义了Admin系统的首页地址:

image-20240505175239671

我们浏览器输入http://127.0.0.1:8000/admin/即可进入Admin系统首页,默认跳转到Admin系统登录页面。

image-20240505175502087

我们发现是英文,我们一般开发交付给客户,必须是本地化中文。我们可以加一个中文本地化的中间件即可实现;

settings.py里加下:

1
2
# 使用中文
'django.middleware.locale.LocaleMiddleware',

image-20240505175758064

注意下有顺序要求。

Admin系统用户,权限,认证相关的表有如下6个,其中auth_user是用来存后台管理员信息,默认里面是没有数据的。

image-20240506085511539

我们可以通过python内置的manage.py的createsuperuser命令来创建超级管理员的账号和密码:

image-20240506090038357

输入 createsuperuser命令,提示让我们输入用户名,再输入邮箱,以及密码和确认密码,最终我们可以强制输入y,确认。

这样auth_user数据库表有就有管理员数据了。

image-20240506090436557

我们回到Admin登录页面,输入刚才创建的用户名和密码:

image-20240506090529904

点击登录按钮,则进入系统管理主页;

image-20240506090607951

在Admin后台系统中可以看到,网页布局分为站点管理、认证和授权、用户和组,分别说明如下: (1)站点管理是整个Admin后台的主体页面,整个项目的App所定义的模型都会在此页面显示。 (2)认证和授权是Django内置的用户认证系统,包括用户信息、权限管理和用户组设置等功能。 (3)用户和组是认证和授权所定义的模型,分别对应数据表auth_user和 auth_user_groups。

Django5注册模型到Admin系统

我们开发业务系统的时候,会定义很多的业务模型,我们可以把模型注册到Admin系统,让Admin系统帮我们维护这些模型。也就是在Admin后台自动给模型实现增删改查功能。

注册模型到Admin系统有两个方式,我们都来演示下:

方式一,直接将模型注册到admin后台,以BookTypeInfo模型为例:

打开admin.py,

1
2
3
4
5
from helloWorld.models import BookTypeInfo

# Register your models here.
# 方法一,将模型直接注册到admin后台
admin.site.register(BookTypeInfo)

image-20240510152304669

这样admin后台就出现了图书类别信息的管理,我们可以点进去,

image-20240510152407737

image-20240510152426186

我们可以图书类别信息进行增删改查操作;

方式二:自定义类,继承ModelAdmin,以BookInfo为例

1
2
3
4
5
# 方法二,自定义类,继承ModelAdmin
@admin.register(BookInfo)
class BookInfoAdmin(admin.ModelAdmin):
    # 设置显示的字段
    list_display = ['id', 'bookName', 'price', 'publishDate', 'bookType']

我们可以点进ModelAdmin类里看下,我们可以对模型的增删改查操作做精细化的配置,包括显示字段,分页,可编辑字段,查询字段,排序等。

image-20240510160956077

Admin后台就多了图书信息

image-20240510160809366

点进去:

image-20240510160826010

image-20240510160842616

我们同样可以对图书信息做增删改查操作;

Django5内置Admin系统自定义设置

我们在使用Django5的内置Admin系统时会发现一些默认的设置,并不符合我们的业务需求,我们需要自定义设置下;

比如模块项目管理这块标题,默认用了模块项目名称,很不好:

image-20240519102655899

我们打开helloWorld项目的apps.py,配置类里加下 verbose_name = '网站图书管理'

image-20240519103019595

这样我们会发现,用户体验好多了;

image-20240519103037816

还有一个地方,网站标题和子标题,也不友好。

image-20240519103418806

我们可以打开admin.py,设置如下:

1
2
3
# 设置网站标题和应用标题
admin.site.site_title = '锋哥后台管理'
admin.site.index_title = '图书管理模块'

image-20240519103731524

这样就更友好了。

最后一个地方,最不能接受;

image-20240519104350947

image-20240519104406033

我们可以通过在admin.py设置 admin.site.site_header 实现;

1
admin.site.site_header = "python222网站管理系统"

image-20240519104617099

image-20240519104635649

这样一设置,客户会认为是我们专业定制开发一样的。

Django5内置Admin系统二次开发

前面我们体验了Admin系统,以及模型注册,自定义设置。但是依然满足不了我们实际的业务开发需求。接下来,我们来讲下更细致的Admin系统二次开发。

创建一个普通管理员账户

首先我们在Admin后台系统里新建一个普通管理员账号。

认证和授权的用户右侧点击“新增”

image-20240520111329663

输入用户名和密码后,点击“保存”

image-20240520111535202

勾选“职员状态”

image-20240520111856991

把所有系统权限都授权给fengge用户。

image-20240520114145871

最后点“保存”

这样我们就可以用fengge这个用户登录系统了。

image-20240520114330280

设置不可编辑字段 get_readonly_fields()

业务开发时,有时候一些敏感字段,我们不允许普通管理员修改。我们可以通过重写ModelAdmin的get_readonly_fields()方法实现;

1
2
3
4
5
6
7
    # 重写分类方法,设置只读字段
    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            self.readonly_fields = []
        else:
            self.readonly_fields = ['bookName']
        return self.readonly_fields

我们用普通管理员fengge登录,然后点击编辑,

image-20240527105444970

图书名称已经变成只读了。

这里我们同时也发现,字段label名称是英文BookName,原因是我们没有设置属性字段的verbose_name

我们可以在models.py里,加下verbose_name配置即可;

image-20240527105707956

再进入下系统,

image-20240527105739852

搞定,其他字段大家自行设置下。

用python222管理员登录的话,是所有字段都可以编辑的。

image-20240527105833399

当然还有很多细粒度设置的方法,如下

formfield_for_foreignkey() 设置外键下拉框过滤筛选

formfield_for_foreignkey() 重写外键下拉框数据,比如增加下拉选项。

save_model() 添加或者修改处理逻辑重写 ,可以增加一些日志等处理。

等等…

自定义Admin模版

Admin后台管理系统的模版文件和Django框架内置提供的,我们可以在源码里找到。

具体位置在django -> contrib -> admin -> templates 下

image-20240531124117384

很多时候我们需要修改默认的模版,包括程序功能,样式等,来达到业务需求。

我们可以直接修改源码里的模版,但是这种方式不好,如果一台机器有多个项目,会影响其他项目使用。

我们提倡在项目的模块项目的templates下,通过优先级来实现修改模版。

具体方式如下:

模块项目(比如我们这是helloWorld项目)的templates下,新建admin目录,然后admin目录下创建你需要覆盖的模版名称。

比如我们覆盖下修改的模版change_form.html。

image-20240531124816320

从django源码里复制一份源码,贴进去,然后根据需求我们改下。

我们先测试下

change_form.html官方模版源码是如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
{百分号 extends "admin/base_site.html" %}
{百分号 load i18n admin_urls static admin_modify %}

{百分号 block extrahead %}
<script src="{百分号 url 'admin:jsi18n' %}"></script>

{百分号 endblock %}

{百分号 block extrastyle %}<link rel="stylesheet" href="{百分号 static "admin/css/forms.css" %}">{百分号 endblock %}

{百分号 block coltype %}colM{百分号 endblock %}

{百分号 block bodyclass %} app- model- change-form{百分号 endblock %}

{百分号 if not is_popup %}
{百分号 block breadcrumbs %}
<div class="breadcrumbs">
<a href="{百分号 url 'admin:index' %}">{百分号 translate 'Home' %}</a>
&rsaquo; <a href="{百分号 url 'admin:app_list' app_label=opts.app_label %}"></a>
&rsaquo; {百分号 if has_view_permission %}<a href="{百分号 url opts|admin_urlname:'changelist' %}"></a>{百分号 else %}{百分号 endif %}
&rsaquo; {百分号 if add %}{百分号 blocktranslate with name=opts.verbose_name %}Add {百分号 endblocktranslate %}{百分号 else %}{百分号 endif %}
</div>
{百分号 endblock %}
{百分号 endif %}

{百分号 block content %}<div id="content-main">
{百分号 block object-tools %}
{百分号 if change and not is_popup %}
  <ul class="object-tools">
    {百分号 block object-tools-items %}
      {百分号 change_form_object_tools %}
    {百分号 endblock %}
  </ul>
{百分号 endif %}
{百分号 endblock %}
<form {百分号 if has_file_field %}enctype="multipart/form-data" {百分号 endif %}{百分号 if form_url %}action="" {百分号 endif %}method="post" id="_form" novalidate>{百分号 csrf_token %}{百分号 block form_top %}{百分号 endblock %}
<div>
{百分号 if is_popup %}<input type="hidden" name="" value="1">{百分号 endif %}
{百分号 if to_field %}<input type="hidden" name="" value="">{百分号 endif %}
{百分号 if save_on_top %}{百分号 block submit_buttons_top %}{百分号 submit_row %}{百分号 endblock %}{百分号 endif %}
{百分号 if errors %}
    <p class="errornote">
    {百分号 blocktranslate count counter=errors|length %}Please correct the error below.{百分号 plural %}Please correct the errors below.{百分号 endblocktranslate %}
    </p>
    
{百分号 endif %}

{百分号 block field_sets %}
{百分号 for fieldset in adminform %}
  {百分号 include "admin/includes/fieldset.html" %}
{百分号 endfor %}
{百分号 endblock %}

{百分号 block after_field_sets %}{百分号 endblock %}

{百分号 block inline_field_sets %}
{百分号 for inline_admin_formset in inline_admin_formsets %}
    {百分号 include inline_admin_formset.opts.template %}
{百分号 endfor %}
{百分号 endblock %}

{百分号 block after_related_objects %}{百分号 endblock %}

{百分号 block submit_buttons_bottom %}{百分号 submit_row %}{百分号 endblock %}

{百分号 block admin_change_form_document_ready %}
    <script id="django-admin-form-add-constants"
            src="{百分号 static 'admin/js/change_form.js' %}"
            {百分号 if adminform and add %}
                data-model-name=""
            {百分号 endif %}
            async>
    </script>
{百分号 endblock %}

{# JavaScript for prepopulated fields #}
{百分号 prepopulated_fields_js %}

</div>
</form></div>
{百分号 endblock %}

运行效果如下:

image-20240531125051355

我们从测试有效性的出发点来删除源码,改造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{百分号 extends "admin/base_site.html" %}
{百分号 load i18n admin_urls static admin_modify %}

{百分号 block extrahead %}
    <script src="{百分号 url 'admin:jsi18n' %}"></script>
    
{百分号 endblock %}

{百分号 block extrastyle %}
    <link rel="stylesheet" href="{百分号 static "admin/css/forms.css" %}">{百分号 endblock %}

{百分号 block coltype %}colM{百分号 endblock %}


再刷新页面看下:

image-20240531125211903

说明我们这种方式是有效的。

Django5内置Auth认证系统

我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。

Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统–auth,可以实现上述需求。它默认使用 auth_user 表来存储用户数据。

前面我们已经通过数据迁移生成了用户权限认证系统的物流表;里面包含系统用户表,权限表,用户组,以及用户组权限关联表,用户和组关联表,用户权限关联表。

image-20240604163124747

用户注册实现

我们实现Auth认证系统里的用户注册的话,用的是auth模版models.py里定义的User模型。

image-20240604205218102

通过auth内置的User,我们可以直接操作用户相关功能;

首先urls.py里定义下映射:

1
2
3
4
    # 跳转注册页面
    path('auth/toRegister', helloWorld.views.to_register),
    # 提交注册请求
    path('auth/register', helloWorld.views.register),

image-20240604205949628

templates下新建auth目录,再新建login.html和register.html两个页面;(用户注册后,跳转到登录页面)

register.html页面源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
<form action="/auth/register" method="post">
    {百分号 csrf_token %}
    <table>
        <tr>
            <th>用户注册</th>
        </tr>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username" value=""></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password" value=""></td>
        </tr>
        <tr>
            <td>
                <input type="submit" value="提交">
            </td>
            <td>
                <font color="red"></font>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

login.html页面源码(临时的):

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录页面
</body>
</html>

views.py实现to_register和register两个方法。新增用户用的是create_user,判断用户是否存在通过filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def to_register(request):
    """
    跳转注册页面
    :param request:
    :return:
    """
    return render(request, 'auth/register.html')


def register(request):
    """
    用户注册
    :param request:
    :return:
    """
    username = request.POST.get('username')
    password = request.POST.get('password')
    # 检验用户名是否存在
    result = User.objects.filter(username=username)
    if result:
        return render(request, 'auth/register.html',
                      context={"errorInfo": "该用户名已存在", "username": username, "password": password})
    User.objects.create_user(username=username, password=password)
    return render(request, "auth/login.html")

测试,浏览器输入 http://127.0.0.1:8000/auth/toRegister

image-20240604210638779

输入用户名和密码,点提交;

auth_user表,就会有用户数据:

image-20240604210719850

如果用户名重复,则报错提示:

image-20240604210749271

用户登录实现

用户登录功能,后端验证主要通过auth模块提供的authenticate校验方法,以及login登录方法实现。

首先urls.py里加下映射:

1
2
3
4
    # 跳转登录页面
    path('auth/toLogin', helloWorld.views.to_login),
    # 提交登录请求
    path('auth/login', helloWorld.views.login),

登录页面login.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<form action="/auth/login" method="post">
    {百分号 csrf_token %}
    <table>
        <tr>
            <th>用户登录</th>
        </tr>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username" value=""></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password" value=""></td>
        </tr>
        <tr>
            <td>
                <input type="submit" value="提交">
            </td>
            <td>
                <font color="red"></font>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

网站首页index.html,登录成功后跳转:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>网站首页</title>
</head>
<body>
网站首页,欢迎: 
</body>
</html>

views.py里实现to_login和login方法。

通过auth.authenticate校验用户是否已经存在。校验用户成功后,返回的是一个封装好的用户对象;校验错误则返回None

用户对象is_active方法判断用户是否激活。

通过调用auth.login,用户登录成功之后,返回给客户端登录的凭证或者说是令牌、随机字符串,则不需要我们去操作diango_session表,会自动创建session

当执行完auth.authenticateauth.login后,也就是登录成功后,我们就可以通过request.user直接获取到当前登录的用户对象数据

  • 登录成功的情况下,该方法获得的是登录用户对象
  • 登录不成功的情况下,该方法获得的是匿名对象AnonymousUser
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def to_login(request):
    """
    跳转登录页面
    :param request:
    :return:
    """

    return render(request, 'auth/login.html')


def login(request):
    """
    登录处理
    :param request:
    :return:
    """
    username = request.POST.get('username')
    password = request.POST.get('password')
    # 通过auth模块来检验加密后的密码 ,校验成功返回用户对象,否则返回None
    resUser: User = auth.authenticate(request, username=username, password=password)
    if resUser and resUser.is_active:
        print(resUser, type(resUser))
        # 用户登录成功之后(返回给客户端登录的凭证或者说是令牌、随机字符串)
        auth.login(request, resUser)
        return render(request, 'auth/index.html')
    else:
        return render(request, 'auth/login.html',
                      context={"errorInfo": "用户名或者密码错误", "username": username, "password": password})

image-20240605173910313

我们来测试下:浏览器输入: http://127.0.0.1:8000/auth/toLogin 进入登录页面:

image-20240605175154664

用户名密码输入错误,提示报错信息:

image-20240605175437174

用户名密码输入正确,则跳转到主页:

image-20240605175508638

用户修改密码实现

用户修改密码主要通过request.user对象的set_password实现,当然校验原密码用check_password,设置完后,需要保存,调用save()方法。

我们urls.py里加下映射;

1
2
    # 修改密码 get请求直接跳转页面,post请求执行处理
    path('auth/setPwd', helloWorld.views.setPwd),

新建setPwd.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>修改密码</title>
</head>
<body>
<form action="/auth/setPwd" method="post">
    {百分号 csrf_token %}
    <table>
        <tr>
            <th>修改密码</th>
        </tr>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username" value="" readonly></td>
        </tr>
        <tr>
            <td>原密码:</td>
            <td><input type="password" name="oldPwd" value=""></td>
        </tr>
        <tr>
            <td>新密码:</td>
            <td><input type="password" name="newPwd" value=""></td>
        </tr>
        <tr>
            <td>
                <input type="submit" value="提交">
            </td>
            <td>
                <font color="red"></font>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

views.py里实现setPwd函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def setPwd(request):
    """
    修改密码
    :param request:
    :return:
    """
    if request.method == "POST":
        oldPwd = request.POST.get("oldPwd")
        newPwd = request.POST.get("newPwd")
        # 1,校验用户密码 check_password
        isRight = request.user.check_password(oldPwd)
        if not isRight:
            return render(request, 'auth/setPwd.html',
                          context={"errorInfo": "原密码错误", "oldPwd": oldPwd, "newPwd": newPwd})
        # 2,设置新密码 set_password 实现加密
        request.user.set_password(newPwd)
        # 3,保存用户信息
        request.user.save()
        return render(request, 'auth/index.html')
    return render(request, "auth/setPwd.html")

我们测试下,先用户登录,然后浏览器输入: http://127.0.0.1:8000/auth/setPwd 进入修改密码页面;

image-20240606080817594

如果原密码输入错误,提示报错信息

image-20240606080838677

校验成功,跳转主页;

image-20240606080910733

系统用户表的密码也会被修改,同时是加密的后的密码;

image-20240606080937599

用户注销实现

用户注销通过auth.logout方法实现。我们来完善上前面的例子。用户登录后进入主页,显示注销功能;

如果用户没登录,则显示登录功能;

views.py里实现Logout方法:

1
2
3
4
5
6
7
8
def logout(request):
    """
    注销
    :param request:
    :return:
    """
    auth.logout(request)
    return render(request, 'auth/index.html')

再实现一个跳转主页的方法to_index:

1
2
3
4
5
6
7
def to_index(request):
    """
    跳转主页
    :param request:
    :return:
    """
    return render(request, 'auth/index.html')

urls.py里加映射:

1
2
3
4
5
# 跳转主页
    path('auth/index', helloWorld.views.to_index),

    # 用户注销
    path('auth/logout', helloWorld.views.logout),

主页index.html修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>网站首页</title>
</head>
<body>
网站首页
{百分号 if request.user.is_authenticated %}
    ,欢迎: <br/>
    <a href="/auth/logout">注销</a>
{百分号 else %}
    <a href="/auth/toLogin">登录</a>
{百分号 endif %}

</body>
</html>

通过is_authenticated()方法可以判断用户是否登录认证;

我们测试下:浏览器地址栏输入:http://127.0.0.1:8000/auth/index

image-20240608160442602

点击登录,用户登录后再次跳转主页:

image-20240608160656377

我们点击注销:

image-20240608160709371

Django5内置其他高级功能

Django5为开发者提供了常见的Web应用支持,如会话控制、缓存机制、CSRF 防护、消息框架、分页功能、国际化和本地化、和自定义中间件等,有效的提高了开发者的开发效率。

这些支持点前面课程基本都有涉及,部分没涉及的,比如缓存机制,我们一般用redis,这些我们后面实战可以安排。感谢大家支持。

后面计划安排两个Django5实战,一个是Vue3+Django5的通用权限系统,还有一个是Django5的博客系统,用来巩固和提升大家的python web开发技术。