目录
前文列表
用 Flask 来写个轻博客 (1) — 创建项目
用 Flask 来写个轻博客 (2) — Hello World!
用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
重构目录结构
现在项目的目录结构:
(env) [root@flask-dev opt]# tree JmilkFan-s-Blog/ -L 1
JmilkFan-s-Blog/
├── config.py
├── env
├── fake_data.py
├── main.py
├── manage.py
├── migrations
├── models.py
├── README.md
├── requirements.txt
├── static
├── templates
├── views.py
└── wt_forms.py
我们会一步一步的将整个项目模块化
创建存放整个模块的文件夹 jmilkfansblog
NOTE: 一般来说这个文件夹的名字应该跟项目的名字一模一样将文件 config.py/models.py 和目录 static/template move 到 jmilkfansblog 目录下
将文件 main.py move 到 jmilkfansblog 目录下并重命名为
__init__.py
,将wt_forms.py
move 到 jmilkfansblog 目录下并重命名为 forms.py创建 jmilkfansblog/controllers 目录,并创建控制器 jmilkfansblog/controllers/blog.py
将 views.py move 成为 jmilkfansblog/controllers/blog.py
现在整个目录结构应该是这样的:
(env) [root@flask-dev JmilkFan-s-Blog]# tree -L 3
.
├── fake_data.py
├── jmilkfansblog
│ ├── config.py
│ ├── controllers
│ │ ├── blog.py
│ │ └── __init__.py
│ ├── forms.py
│ ├── __init__.py
│ ├── models.py
│ ├── static
│ │ ├── css
│ │ ├── fonts
│ │ └── js
│ └── templates
│ ├── blog
│ └── __init__.py
├── manage.py
├── migrations
│ ├── alembic.ini
│ ├── env.py
│ ├── README
│ └── script.py.mako
│ └── versions
│ └── __init__.py
├── README.md
└── requirements.txt
NOET :在现在的目录结构中,存在这大量的 __init__.py
文件,这是当在一个目录结构中存在该文件的话,Python 就会将会该目录解析成为一个包。
重构代码
首先要将所有 move 到 blog 中的模板文件中的
url_for()
函数中的参数修改为url_for('blog.XXX')
或url_for('.XXX')
(当视图函数XXX是属于该模板目录所对应的蓝图时才能使用)。这是因为使用蓝图之后可能会出现多个同名的视图函数,所以 url_for() 函数中的参数必须是 ‘绝对路径’ 才能够正确的返回视图函数所对应的 URL。EGurl_for('blog.home', page=3)
所返回的 URL 为http://127.0.0.1:8089/blog/3
中的/blog/3
,而不是/3
NOTE : 在 blog.py 中的视图函数 home() 的路由装饰器为@blog_blueprint.route('/')
,而不是@blog_blueprint.route('/blog/')
,这是因为创建蓝图对象 blog_blueprint 时,传入 url_prefix 的实参为 ‘/blog’,所以所有装饰器@blog_blueprint.route
的路由 URL 定义会被自动的加入 ‘/blog’ 前缀。这样也便于我们编写同一个蓝图下视图函数的路由代码。将所有的 SQLAlchemy 代码都迁移到 models 模块中,其中包括了 import SQLAlchemy 的相关语句、Model class、tables object 等。
- models.py
from flask.ext.sqlalchemy import SQLAlchemy
# 在 jmilkfansblog/__init__.py 中再初始化 db 对象
db = SQLAlchemy()
posts_tags = db.Table('posts_tags',
db.Column('post_id', db.String(45), db.ForeignKey('posts.id')),
db.Column('tag_id', db.String(45), db.ForeignKey('tags.id')))
class User(db.Model):
"""Represents Proected users."""
...
NOTE : 数据库对象 db 的初始化不能再在 models 模块中进行,因为如果在该模块中导入 app 对象,很可能会造成将所有的的情况,所以我们将初始化 db 对象的代码实现在了 jmilkfansblog/__init__.py 中。
将所有的 WTForm 代码都迁移到 forms 模块中,其中包括了 所有的 Form class、Validation function 等。直接将 wt_forms.py 重命名为 forms.py 就可以了
将 jmilkfansblog/controllers/blog.py 定义成为一个表示博客展示页功能(表现层)组件的 Flask Blueprint,需要把 views 模块中所有的 路由函数、视图函数、sidebar_data() 等都迁移到 blog 模块中,实际上我们可以
mv views.py jmilkfansblog/controllers/blog.py
然后再做修改:- blog.py
from uuid import uuid4
from os import path
from datetime import datetime
from flask import render_template, Blueprint
from sqlalchemy import func
from jmilkfansblog.models import db, User, Post, Tag, Comment, posts_tags
from jmilkfansblog.forms import CommentForm
blog_blueprint = Blueprint(
'blog',
__name__,
# path.pardir ==> ..
template_folder=path.join(path.pardir, 'templates', 'blog'),
# Prefix of Route URL
url_prefix='/blog')
def sidebar_data():
"""Set the sidebar function."""
...
NOTE:现在当我们需要为这个 blog Application 添加一个新的蓝图时,我们只需要在 jmilkfansblog/controllers/ 目录下新建一个模块文件就可以实现。
再一个,我们需要多 jmilkfansblog/init.py 文件进行重构,该文件应该包含了 app 对象的创建、index()、注册蓝图 等代码。当然我们还要将初始化数据库对象 db
- jmilkfansblog/__init__.py
最后需要修改 manage.py 中的导入语句
from flask import Flask, redirect, url_for
from config import DevConfig
from models import db
from controllers import blog
app = Flask(__name__)
# Get the config from object of DecConfig
app.config.from_object(DevConfig)
# Will be load the SQLALCHEMY_DATABASE_URL from config.py to db object
db.init_app(app)
@app.route('/')
def index():
# Redirect the Request_url '/' to '/blog/'
return redirect(url_for('blog.home'))
# Register the Blueprint into app object
app.register_blueprint(blog.blog_blueprint)
if __name__ == '__main__':
app.run()
NOTE:以往我们还需要在 main.py 中导入视图模块 views 才能够使这些视图函数生效,现在因为我们将蓝图对象注册到了 app 对象中,而且 index()
路由函数还重定向到了 'blog/home()
中 。所以现在我们并不需要将 blog 模块导入到 jmilkfansblog/__init__.py 也能使这些视图函数生效了。
使用蓝图后的路由过程
我们假设现在有人使用浏览器访问 http://127.0.0.1:8089/
:
/
:被jmilkfansblog.__init__:index()
获取,并重定向到/blog/
/blog/
:被jmilkfansblog.controllers.blog.home()
获取,然后渲染并返回jmilkfansblog/templates/blog/home.html
总结
controllers 目录的意义:在 controllers/ 目录下,我们定义的 blog.py 蓝图模块(因为含有蓝图对象,并将视图函数注册到了该蓝图对象中),本质上就是以往的视图模块,主要还是用于控制 HTTP 请求的 URL 路由跳转。在以后的开发过程中,如果需要定义一个新的蓝图,那么我们只需要在该目录下创建一个新的蓝图模块,并将蓝图模块中的蓝图对象注册到应用程序入口 jmilkfansblog/init.py 的 app 对象中就可以了。
将视图函数注册到蓝图中:能够让我们在定义视图函数的路由 URL 的时候更加方便,而且自成一套命名空间,使得 Python 代码更加简洁。但是需要注意的是,我们一般会为一个蓝图准备独立出一个存放其模板文件和静态文件的目录,所以在编写模板文件的时候,一定要注意在
url_for
中使用 全路径 的引用方式。将蓝图注册到 app 中:蓝图是为了让整个项目更具模块化,引入了组件的思想,使得一个项目就像是由多个组件组合起来的一样。符合松耦合的设计思想,极大的提高了项目的扩展能力和便于合作开发。在这个项目中引入蓝图是为了以后添加后台管理组件、用户登陆组件等所做的准备。