Django 之 MVT 模式
1 MVC 模式
MVC
, 全名 Model View Controller
, 是软件工程中的一种软件设计模式 (常用于架构层面),把软件系统分为三个基本部分:模型 (Model)
, 视图 (View)
和 控制器 (Controller)
。分层的好处很明显,把一团软件代码分成职责分明的层次,每一层有其专注的领域层与层之间也有明确的接口,实现上可以分层实现,哪层实现的不好重构之对整体也影响不大 。具有耦合性低,重用性高、生命周期成本低等优点。
- Model(模型): 简而言之即 数据模型 。模型不是数据本身(比如数据库里的数据),而是抽象的描述数据的构成和逻辑关系 。通常模型包括了数据表的各个字段(比如人的年龄和出生日期)和相互关系(单对单,单对多关系等),以及一些数据层面的业务逻辑。Web 开发框架会根据模型的定义来自动生成数据表。
- Controller (控制器):应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据(比如增加或更新数据表)。
- View (视图): 主要用于显示数据,用来展示用户可以看到的内容或提供用户可以输入或操作的界面。数据来源于哪里?用户输入的数据给又谁?为了降低系统的耦合程序,中间就会抽像出控制器一层做为两者的桥接。
MVC 最大的优点是实现了软件或网络应用开发过程中数据、业务逻辑和用户界面的分离,使软件开发更清晰,也是维护变得更容易。这与静态网页设计中使用 html 和 css 实现了内容和样式的分离是同一个道理。
而 Django
的 MVT
设计模式由 Model (模型) , View (视图) 和 Template (模板) 三部分组成,分别对应单个 app 目录下的 models.py
, views.py
和 templates
文件夹。它们看似与 MVC 设计模式不太一致,其实本质是相同的。MVT 模式其实是一种特殊的 MVC 模式的实现。
2 MVT 模式
Django 的 MVT 设计模式与经典的 MVC 对应关系如下。
- Django Model (模型): 这个与经典 MVC 模式下的模型 Model 差不多。是数据模型及业务的抽象。
- Django View (视图): 这个与 MVC 下的控制器 Controller 更像。视图不仅负责根据用户请求从数据库读取数据、指定向用户展示数据的方式 (网页或 json 数据), 还可以指定渲染模板并处理用户提交的数据。
- Django Template (模板): 这个与经典 MVC 模式下的视图 View 一致。模板用来呈现 Django view 传来的数据,也决定了用户界面的外观。Template 里面也包含了表单,可以用来搜集用户的输入内容。
Django MVT 设计模式中最重要的是视图 (view), 因为它同时与模型 (model) 和模板 (templates) 进行交互。当用户发来一个请求 (request) 时,Django 会对请求头信息进行解析,解析出用户需要访问的 url 地址,然后根据路由 urls.py 中的定义的对应关系把请求转发到相应的视图处理。视图会从数据库读取需要的数据,指定渲染模板,最后返回响应数据。
3 Django 中的 mtv 实践
3.1 Django 基于 app 的模块化
我们看回在第一章中建立的 schoolSystem
项目
1 | . |
Django 默认就是采用的是模块化的设计,与项目同名文件夹放的是工程的主项目。主项目下是按工程职责分好的相关配置文件,比如 settings.py
为基本配置文件,urls.py
为路由配置文件。
而我们写的具体模块在 Django 中被称为 app
,比如我们现在要创建一个 《教务管理系统》
中的学生管理模块 – 就叫 student
。
我们使用命令创建 student 模块(从现在开始,我们还是坚持使用 django 语言吧 – “学生管理 app”!)
1 | python manage.py startapp student |
命令执行之后,我们可看到 Django 自动在项目根目录帮我们创建了 student 模块:
1 | └── student |
3.2 创建 app 的 mtv 部分
入乡随俗,我们后文中统一将 “student 模块” 称之为 app
。创建好 app 的目录结构后,我们开始编写项目的主要 mtv 部分。
在构建一个系统前,整理并明确系统的需求是最重要的。我们要做的是一个《学生管理模块》,管理员可以往系统里添加学生,而普通用户可以查看系统中的学生。
所以我们这版要做的基础功能:
- 【添加学生】管理员往系统添加学生;
- 【浏览学生信息】普通用户查看系统中的学生;
开发一个系统或模块,在确定好需求之后,首要就是创建数据模型。在当前的学生管理模块中,当然最重要是建立学生的数据模型。
3.3 创建模型
在 django 中,每个模型被表示为 django.db.models.Model
类的子类。每个模型中有许多类变量,它们都表示模型里的一个数据库字段。
- ORM 思想
ORM(Object Relational Mapping)
是通过使用描述对象和数据库之间映射的元数据。 django 方便之处就在于其内置了一套 ORM
框架。这类框架一般使用面向对象的语言抽象出数据库结构,一般类名就表名,类属性即表字段,其中也有一些规则可用于描述表之间的关系。
django 模型统一在 app
根目录下的 models.py
中创建。
每个字段都是 Field
类的实例 - 比如,字符字段被表示为 CharField
,日期时间字段被表示为 DateTimeField
。这将告诉 Django 每个字段要处理的数据类型。比如下面代码示例,我们描述了 student 模型:
1 | from datetime import datetime |
从上面示例可以看出,我们基于 django.db.models.Model
派生出类 Student
,这就是我们要抽象出来的数据模型,框架会自动其结构抽象出对应的 sql 及模型操作。
模型 Student
类有许多属性,这些将成为 Student
表的字段,这些字段由 models.xxField
修饰,定义其类型与 options。(各类型会在后面详细介绍模型层的章节中详细介绍)。
我们使用 ForeignKey 定义了一个关系 creator
。这将告诉 Django,每个学生记录都关联一个 User 记录,表示添加这个学生的管理员。Django 支持所有常用的数据库关系:多对一、多对多和一对一。
创建好数据模型后,我们可以针对模型使用 django 框架生成对应的数据库脚本,这对于 django 来说很 easy ,只要运行一个命令则可以,但在之前,需要先在 settings.py 中配置好 app。
3.4 app 配置与路由
app 配置与路由涉到到项目根目录的几个重要组件 settings.py
& urls.py
:
3.4.1 root/settings.py
root/settings.py
文件中包含了 Django
项目设置的 Python
模块。其中包含与项目相关的各种主要设置:
- 数据库设置(没设置的话 Django 默认内置 SQLite)
- 时区设置:
TIME_ZONE
- 模块配置:
INSTALLED_APPS
1 | # 初始项目已包含了基础的 app 与 middleware |
注意,这里注册 app student
时 使用的是 student.apps.StudentConfig
,其在 student/apps.py
这个配置文件中,是此 app 的配置文件。
3.4.2 root/urls.py
路由的配置在前一章已有说明了,这里再简单介绍下:
1 | from django.contrib import admin |
函数 include()
允许引用其它 URLconfs。每当 Django 遇到 include()
时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送到 URLconf 以供进一步处理。
在这里,相当于主模块配置了模块到子 app 的路由,而具体 app 中的路由在 app 目录中定义。
我们现在看看 app 中如何定义路由 urls.py
1 | from django.urls import path |
在 student
模块中,我们现在比较简单,只将根目录的请求发向 index
方法。
3.5 初始化数据表
3.5.1 migration 数据模型的版本管理
默认开启的某些应用需要至少一个数据表,所以,在使用他们之前需要在数据库中创建一些表。请执行以下命令:
1 | python manage.py makemigrations student |
makemigrations
命令会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 migration(迁移)
。
所谓的 make migration
是 Django 对于模型定义(也就是你的数据库结构)的变化的储存形式,其会对应在 app 目录下生成 migrations
文件夹,如上面命令执行后的输出所示,命令运行后的 “迁移” 被存到 student/migrations/0001_initial.py
这个文件中,我们的打开来看一下,形如:
1 | class Migration(migrations.Migration): |
这个文件其实就是来自于我们刚写的 models.py
,他将我们继承自 models
的数据模型转换为框架内的语言。当我们每次修改 models.py
中定义的数据模型,他会基于每次生成的 migration
文件迭代地生成对应的数据模型增量文件。
比如我们现在给 student 加一个属性 age 年龄:
1 | stu_age = models.SmallIntegerField(null=True) |
然后再运行模型 makemigrations 命令:
1 | python manage.py makemigrations student |
此时 django 帮我们自动生成了 student/migrations/0002_student_stu_age.py
,打开看看:
1 | from django.db import migrations, models |
可见,django 自动帮我们管理了数据库变更及版本管理,依靠的就是自动生成的 migration 版本文件
。但是,生成了版本文件是否就够了呢,那不是的,django 还需要一个命令根据生成好的 migration 版本文件
到数据库建立对应的数据表。
3.5.2 migrate 自动生成数据库表
生成了 migration 版本文件
后,我们就需要在数据库里创建新定义的模型的数据表了,这一步也是可以自动完成的,只要运行 migrate
命令.
这个 migrate
命令查看 INSTALLED_APPS
配置,并根据 mysite/settings.py
文件中的数据库配置和随应用提供的数据库迁移文件,创建任何必要的数据库表。
1 | $ python manage.py migrate |
这个命令会选中所有还没有执行过的 migration 版本文件
并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。
我们可以从命令的输出日志中看到, migrate
命令根据所有的 migration 文件成生了各系统内的数据表,同时还根据 student.0001_initial
,Applying student.0002_student_stu_age
生成了最终符合我们数据模型的 student 表。
你只需要记住,改变模型需要这三步:
编辑 models.py 文件,改变模型。
运行 python manage.py makemigrations 为模型的改变生成迁移文件。
运行 python manage.py migrate 来应用数据库迁移。
sqlmigrate
使用 sqlmigrate
命令可以查看迁移的具体 sql:
1 | python manage.py sqlmigrate student 0001 |
3.5.3 sqlmigrate 数据模型转 SQL 语句
我们还可以使用 sqlmigrate
查看数据库 migration 文件是如何将模型转为数据库 SQL 语言的,运行如下命令:
1 | manage.py sqlmigrate student 0001 |
命令的输出是一段 SQL 脚本,由 django 框架自动根据 migration 文件生成,django 就是这样完成模型与数据库转换的。如果你是数据库管理员,需要写脚本来批量处理数据库时会很有用。
3.6 基于自带 admin 模块管理员功能
基于上面的代码,我们已完成学生模型的创建,现在可以利用 django 内置的 admin 管理员功能将模型注册到自带的管理系统中去。修改 app 下 admin.py
如下:
1 | from django.contrib import admin |
注册之后,./manage.py runserver
运行项目,登录 localhost:8008/admin
这个路径进入管理员页面(这里省去了创建管理员账号部分,有不了解的可以查看 [上一章](../py-django-1overview#2-5-3 - 运行项目))。在管理员页面 (如下图) 中,你可以管理内置提供的用户组、用户(给系统添加用户、增加权限管理),还可以管理我们刚注册进系统的学生模型。
通过上面的界面就可以实现学生的管理啦,就这么轻松!
3.7 视图层 - View
Django 中的视图的概念是 “一类具有相同功能和模板的网页的集合”。比如,在一个博客应用中,你可能会创建如下几个视图:
- 博客首页 —— 展示最近的几项内容。
- 内容 “详情” 页 —— 详细展示某项内容。
- ..
上面是 django 官网对视图的定义。从这里可以看出,其和我们常用的 sever 端 controller
还是略有不同的,django 的 view 真的是更偏向于页面,这和他的定位也不无关系,因为 django 本身就是定位为类似博客系统类似的 CMS
,其天然就是面向页面的,从我们上面配置路由可以看出:
1 | urlpatterns = [ |
这里表示 /student
这个模块的主页路径 https://xx/student
,指向的是 views.index
这个处理函数,其主要作用为返回模块首页。而我们常使用的 server 框架中 router 一般指的是一个 Controller 中的 restful 接口
。
这样我们就可知道,Django 中的视图层,更偏向于针对 urls 模型
指向的路由,返回特定的页面。那我们现在就基于此思想来规划下咱《学生管理系统》中的几个基础页面:
我们现在做普通用户查看学生信息的功能,这里只做两个简单功能:
- 普通用户可查看学生列表;
- 从学生列表点进去后,可以查看简单的指定的学生信息;
从上面我们抽象出系统两个 api:
1 | ┌────────┐ |
root/student/
–>students.html
-> 学生列表页root/student/<id>
–>student.html
-> 学生详情页
3.7.1 配置 url 协议
这块经过前面介绍,我们应该已经驾轻就熟了。
1 | from django.urls import path |
这里 django 的路由配置,有几个特性我们要注意下:
- 支持正则与常用的 id 匹配;(比如
<int:stu_id>
这个配置,后面接收到student/5
这样的请求,可以将id
匹配出来传给视图层的def detail (request, stu_id)
方法处理 ); - 支持给路由取名(这个后面分发时会用到,可以简化代码);
- URL命名空间
app_name = 'student'
;
URL命名空间可以在使用路由别名
时解决同名冲突问题。比如现在有学生详情页还有老师详情页,在定义URL时,都使用”detail”,但在跳转路由时,可带上路由别名找到指定的视图处理函数,如student:detail
这样,就定位到了此学生管理视图器的 detail
函数。
配置好路由之后,我们就可以开始编写视图层的代码了。
3.7.2 编写 View
View 代码如下,主要两个方法 index
& detail
,这里先是简单的实现。
1 | # 学生管理首页(展示学生信息列表) |
如上面的示例代码,现在我们运行项目,用管理员账号添加一些学生后,访问 http://localhost:8080/student/
已可看到基本的学生列表了,输入 http://localhost:8080/student/1
这样也可以查看相应的学生数据。
但是这样的页面太简陋了,我们需要创建相应的页面。
3.8 模板层 - Template
使用 django 配置模板非常容易,框架层面早就提前帮咱考虑好了:
1 | TEMPLATES = [ |
这点有点像我们以前用 java
写 jsp
,项目的 TEMPLATES
配置项描述了 Django 如何载入和渲染模板(DjangoTemplates 后端模板支持)。
而 'APP_DIRS': True,
这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS
文件夹中寻找 "templates"
子目录,从而加载正确的模板。
我们首先在 templates
模板目录下新建目录 student
,用于存放学生管理模块的所有模板。为什么我们不直接把模板 html 文件放到 templates
根目录中呢。因为 django 模板支持会根据 views 中指定的模板名加载第一个匹配的模板文件,而 DjangoTemplates
引擎会扫所有 INSTALLED_APPS
下的 templates
模板目录,这样如果存在同名模板则无法做区分。所以在 templates
模板目录再建立 app 模板自身的目录结构会比较规范。
我们在 student
目录下建 3 个模板文件:
templates/student/base.html
: 导航与框架页面templates/student/students.html
: 学生列表templates/student/student.html
: 学生详情页
1 | <h1> 学生管理系统 </h1> |
学生列表页 students.html。此页传入学生列表对象 student_list
,并遍历之,通过 .
点语法取出对象中的属性。我们可以看出,其表达式语法如下类似于 JSP:
1 | {% 表达式 %} |
同时也有些 if
, for
这样的模板语法:
1 | {% extends 'student/base.html' %} |
上面学生列表这里可能有点要解释下的就是 {% url %}
这个语法,用到的我们设置路由时的“路由别名”,直接带上 student.id 则可(注意,这里跳路由时使用了我们上面说的命名空间)。
1 | {% extends 'student/base.html' %} |
3.9 修改视图
写完模板,我们现在该回过头来修改下视图了。还记得我们之前是直接显示取出的模型数据吗,现在要将之填充入模板展示,我们先上 views.py 完整代码:
1 | from django.http import HttpResponse |
我们先聚焦 index
函数:
1 | def index(request): |
这个很好理解,我们先从模型层取数,取出学生对象列表 studentList
。然后使用 django.template.loader
的 get_template(模板地址)
解析模板。页面模板被解析为template
对象,往其render
方法传递一个上下文(context,是一个字典,key 为模板中取对象的名称,值为我们要传递的对象)。最后使用HttpResponse
将渲染好的模板输出。
此时访问http://localhost:8080/student/
,即可访问学生列表页面。
当然, django 为我们提供了更简洁的渲染方法,参考detail
方法:
1 | def detail(request, stu_id): |
这里使用 get_object_or_404
方法,取对象失败直接抛 404错误。渲染时,也可直接调 django.shortcuts.render()
方法,在 render()
中指定模板路径,context 上下文。
到此为止,我们基于 django 的 mvt 模式,非常快速地制作完成简易的学生管理系统:
- 使用 admin 账号登录后可对我们定义好的
Student
做增删查改; - 普通账号访问
student
路径可以查看所有学生列表;点击相应的学生可进入详情页查看详情;
以前要开发这样一套系统,MVC 各种代码要写一卡车,现在只要少数代码就能形成一个健壮的系统,省了许多喝咖啡的时间。
4 小结
- MVT 概述
MVC
是软件工程中的一种软件设计模式 (常用于架构层面),具有耦合性低,重用性高、生命周期成本低等优点。主要由 模型 (Model)
, 视图 (View)
和 控制器 (Controller)
组成。
而 Django 的 MVT
设计模式由 Model (模型), View (视图) 和 Template (模板) 三部分组成,分别对应单个 app 目录下的 models.py, views.py 和 templates 文件夹。其本质也是 MVC 设计模式。
只是 Django 将其用 MVT
这个命名区分开是突出了其 Template
模板层的实现,这一层将通用的数据展示与用户输入封装成已成型的模板,大大加快了开发的速度与规范性。而 Django 的 View
层与传统 restful 的控制器也稍有区别,其控制器的处理函数更偏向于接口(一般一个函数对应于一个restful接口),而 Django 的 View 更偏向于页面,其一个处理函数一般对应一个页面(由模板加指定的 Context 上下文数据渲染而成)。
- Django 的模块化思想
Django 默认就是采用的是模块化的设计,与项目同名文件夹放的是工程的主项目,而我们写的具体模块在 Django 中被称为 app,在项目根目录使用 python manage.py startapp myApp
创建 app(也就是项目的模块)。
- 模型层
首先是数据建模,基于 django 自带的 django.db.Model
ORM框架:
1 | class ModelName(models.Model): |
然后就是基于数据模型创建与建数据库表,Django 中创建与修改模型需要这三步:
编辑
models.py
文件,创建与修改模型。运行
python manage.py makemigrations [MODEL_NAME]
为模型的改变生成迁移文件。注意,MODEL_NAME
也可不填。运行
python manage.py migrate
来应用数据库迁移,生成数据库表。视图层
Django 中的视图的概念是 “一类具有相同功能和模板的网页的集合”。如介绍 MVT
中所言,Django 的 View 更偏向于页面,其一个处理函数一般对应一个页面。Django 提供了非常方便的抽象,使视图处理函数可以非常简单完成一个接口处理,一般只需要3步:
- 从模型层取数;
- 根据业务对数据进行处理;
- 加载模板,并将数据渲染至模板再返回给客户端;
- 模板层
模型层可以就是普通的HTML页,由 {% 模板命令 表达式 %}
这样的标记语法将数据渲染上去。我们一般会在 templates
目录中基于模块建子目录及使用命名空间的方法来增加模板的通用性。