蓝图

在前面,我们都是把代码写在单一的文件里面,虽然看起来很方便,但也只是供学习的时候用用而已,真正在一个实际项目中,是不应该这样做的,为什么呢?

我们还是从 hello world 开始讲起,新建一个脚本文件,比如 hello.py。

1. $ cat hello.py

3. # -*- coding: utf-8 -*-

5. from flask import Flask

7. app = Flask(__name__)

9. @app.route("/")
10. def index():
11. return "Hello World!"

13. if __name__ == "__main__":
14. app.run(host='127.0.0.1', port=5200, debug=True)

运行该脚本,在浏览器输入链接 http://localhost:5200/,可以看到 Hello World! 的字样。

OK,现在我们需要添加一个『读书』频道,允许查看并添加书籍。这不很简单吗,将上面的代码修改一下,如下:

1. $ cat hello.py

3. # -*- coding: utf-8 -*-

5. from flask import Flask, request, redirect, url_for

7. app = Flask(__name__)
8. app.secret_key = 'some secret key'

10. books = ['the first book', 'the second book', 'the third book']

12. @app.route("/")
13. def index():
14. render_string = '<ul>'

16. for book in books:
17. render_string += '<li>' + book + '</li>'

19. render_string += '</ul>'

21. return render_string

23. @app.route("/book", methods=['POST', 'GET'])
24. def book():
25. _form = request.form

27. if request.method == 'POST':
28. title = _form["title"]
29. books.append(title)
30. return redirect(url_for('index'))

32. return '''
33. <form name="book" action="/book" method="post">
34. <input id="title" name="title" type="text" placeholder="add book">
35. <button type="submit">Submit</button>
36. </form>
37. '''

39. if __name__ == "__main__":
40. app.run(host='127.0.0.1', port=5200, debug=True)

OK,上面的脚本实现了最基本的查看并添加书籍的功能。

接着,我们还想添加『电影』、『音乐』、『美食』和『旅游』等频道,我们是继续往这个文件添加功能吗?当然不是,是时候把这些功能进行拆分了,将我们的应用模块化,把一个大应用分解成若干个小应用。你可能会想:把这些功能分别写到多个文件,比如 movie.py,music.py 等等,可是这样的话,我们的应用是跑不起来的。为什么呢?因为 app 对象只有一个。

那怎么办呢?

事实上,Flask 提供了 Blueprint (蓝图) 的功能,让我们可以实现模块化的应用。使用它主要有以下好处:

  • 将一个复杂的大型应用分解成若干蓝图的集合,也就是若干个子应用或者说模块,每个蓝图都包含了可以作为独立模块的视图、模板和静态文件等;
  • 制作通用的组件,使开发者更易复用组件;

不过目前 Flask 蓝图的注册是静态的,不支持可插拔。

现在,让我们将上面的代码进行改写,加入蓝图的功能。

首先,我们对程序的结构做一些调整,如下:

1. ├── app.py            -- 启动程序
2. ├── book              -- book 模块
3. │   ├── __init__.py
4. │   └── book.py
5. ├── movie             -- movie 模块
6. │   ├── __init__.py
7. │   └── movie.py
8. └── templates         -- 模板
9. ├── 404.html
10. ├── book.html
11. ├── layout.html
12. └── movie.html

其中,movie 模块跟 book 模块是独立的,它们有各自的业务逻辑,互不影响。我们这里只分析 book 模块,book.py 的代码如下:

1. # -*- coding: utf-8 -*-

3. from flask import Blueprint, url_for, render_template, request, flash, redirect

5. # 创建一个蓝图对象
6. book_bp = Blueprint(
7. 'book',
8. __name__,
9. template_folder='../templates',
10. )

12. books = ['The Name of the Rose', 'The Historian', 'Rebecca']

14. @book_bp.route('/', methods=['GET'])
15. def index():
16. return '<h1>Hello World!</h1>'

18. @book_bp.route('/book', methods=['GET', 'POST'])
19. def handle_book():
20. _form = request.form

22. if request.method == 'POST':
23. title = _form["title"]
24. books.append(title)
25. flash("add book successfully!")
26. return redirect(url_for('book.handle_book'))

28. return render_template(
29. 'book.html',
30. books=books
31. )

33. @book_bp.route('/book/<name>')
34. def get_book_info(name):
35. book = [name]
36. if name not in books:
37. book = []

39. return render_template(
40. 'book.html',
41. books=book
42. )

注意到,我们使用了下面的代码创建一个蓝图对象:

1. book_bp = Blueprint('book', __name__, template_folder='../templates')

Blueprint 要求至少传入两个参数,第一个参数是蓝图的名称,第二个参数是蓝图所在的包或模块,其他参数是可选的,比如template_folder,url_prefix和static_folder等。

在蓝图中使用路由是这样的:

1. # book_bp 就是我们创建的蓝图对象
2. @book_bp.route('/book/<name>')

创建好了蓝图之后,我们还需要注册它,否则不能使用。我们在app.py中注册:

1. # -*- coding: utf-8 -*-

3. from flask import Flask, render_template
4. from book import book_bp
5. from movie import movie_bp

7. app = Flask(__name__)
8. app.secret_key = 'The quick brown fox jumps over the lazy dog'

10. # 注册蓝图
11. app.register_blueprint(book_bp)
12. app.register_blueprint(movie_bp)

14. @app.errorhandler(404)
15. def page_not_found(error):
16. return render_template('404.html'), 404

19. if __name__ == '__main__':
20. app.run(host='127.0.0.1', port=5200, debug=True)

本文完整的代码在这里。

在命令行中输入$ python app.py,我们就可以访问该应用了,如下:

Imgur

Imgur

总结

  • 使用蓝图对我们的应用进行模块化
  • 通过添加蓝图扩展我们的应用
  • 蓝图定义完之后,还需要在应用中注册
  • 可以给蓝图中的所有路由定义一个 URL 前缀 (url_prefix)

更多阅读