规范结构维护代码

到本章为止,我们的 DEMO 程序功能已经日益强大,增删改查,用户登录,权限控制,数据库操作,功能已经有点复杂了,然后看看代码,发现也已经差不多 200 行了。这时,我们不禁要想,难道我们要在这一个 app.py 文件中继续编写下去吗?感觉每次添加新的功能好像都是在头(添加引用)在尾(添加逻辑)添加代码,难道这是正确的做法吗?

很显然,作为有洁癖的工程师,肯定不能容忍所有代码都这么一团塞在一个文件里面的,所以我们至少会想到拆分代码然后放到几个模块里面,例如什么 model.py, controller.py 啊之类的。但是,作为有深度洁癖的人,觉得把一大堆文件放在一个目录里面也是件糟心的事,所以,这里我要介绍一下我比较推荐的代码目录结构。

Flask 代码目录结构

虽然目录结构见仁见智,个人有个人的看法和习惯,但总的来说,经过很多人的实践和总结,还是有很多共同的意见和想法的,而我在查看他人的目录结构结合自身在工作中的使用经验,总结了一个个人认为比较恰当的目录结构供参考,而本书也是以这个目录结构为架构进行下去的。

我推荐的目录结构:

  1. .
  2. ├── README.md
  3. ├── application
  4. ├── __init__.py
  5. ├── controllers
  6. └── __init__.py
  7. ├── forms
  8. └── __init__.py
  9. ├── models
  10. └── __init__.py
  11. ├── services
  12. └── __init__.py
  13. ├── static
  14. └── __init__.py
  15. ├── templates
  16. └── __init__.py
  17. └── utils
  18. └── __init__.py
  19. ├── config
  20. ├── __init__.py
  21. ├── default.py
  22. ├── development.py
  23. ├── development_sample.py
  24. ├── production.py
  25. ├── production_sample.py
  26. └── testing.py
  27. ├── deploy
  28. ├── flask_env.sh
  29. ├── gunicorn.conf
  30. ├── nginx.conf
  31. └── supervisor.conf
  32. ├── manage.py
  33. ├── pylintrc
  34. ├── requirements.txt
  35. ├── tests
  36. └── __init__.py
  37. └── wsgi.py

这里稍作介绍,首先是第一级目录的话,主要分为两类,一类是目录,另一类是运行相关的文件;其中目录有:

  • application:项目所有逻辑代码都放这
  • config:项目的配置文件,按不同环境各占一份
  • deploy:部署相关的文件,后续将使用到
  • tests:单元测试代码所在的目录

文件的话分别有:

  • manage.py:Flask-Script 运行文件,后面介绍
  • pylintrc:静态分析代码使用的 pylint 标准
  • requirements.txt:项目依赖库的列表
  • wsgi.py:wsgi 运行的文件

规范代码到指定目录

既然我们已经规定好了目录结构,是时候将我们的意面分到各个盘子里了。首先从文件开始,因为我们还没有介绍 Flask-Script,静态检查和 wsgi,所以就忽略这些文件,那么就剩下 requirements.txt 文件了。这个文件的内容都在我们的 《本书概述》中列举了,直接放进去就好了。

  1. Flask==0.10.1
  2. flask-mongoengine==0.7.5
  3. Flask-Login==0.3.2
  4. Flask-Admin==1.4.0
  5. Flask-Redis==0.1.0
  6. Flask-WTF==0.12

然后是时候解耦代码了,我们没有表单,暂时没有 services,没有静态文件也没有页面模板,所以可以这样合并:

  • 将 route 代码放到 application/controllers 中
  • 将 model 代码放到 application/models 中
  • 将初始化绑定 app 的代码放到 application/init.py 中
  • 将 数据库等配置放到 config/development.py 中

最后就是编写 manager.py 文件了。这里概要得列举几个重要的文件,更多的文件大家可以从 github 上 clone 代码出来阅读。

合并后的文件

manager.py

  1. # coding: utf-8
  2. from flask.ext.script import Manager
  3. from application import create_app
  4. # Used by app debug & livereload
  5. PORT = 8080
  6. app = create_app()
  7. manager = Manager(app)
  8. @manager.command
  9. def run():
  10. """Run app."""
  11. app.run(port=PORT)
  12. if __name__ == "__main__":
  13. manager.run()

application/init.py

  1. # coding: utf-8
  2. import sys
  3. import os
  4. # Insert project root path to sys.path
  5. project_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
  6. if project_path not in sys.path:
  7. sys.path.insert(0, project_path)
  8. import logging
  9. from flask import Flask
  10. from flask_wtf.csrf import CsrfProtect
  11. from config import load_config
  12. from application.extensions import db, login_manager
  13. from application.models import User
  14. from application.controllers import user_bp
  15. # convert python's encoding to utf8
  16. try:
  17. reload(sys)
  18. sys.setdefaultencoding('utf8')
  19. except (AttributeError, NameError):
  20. pass
  21. def create_app():
  22. """Create Flask app."""
  23. config = load_config()
  24. print config
  25. app = Flask(__name__)
  26. app.config.from_object(config)
  27. if not hasattr(app, 'production'):
  28. app.production = not app.debug and not app.testing
  29. # CSRF protect
  30. CsrfProtect(app)
  31. if app.debug or app.testing:
  32. # Log errors to stderr in production mode
  33. app.logger.addHandler(logging.StreamHandler())
  34. app.logger.setLevel(logging.ERROR)
  35. # Register components
  36. register_extensions(app)
  37. register_blueprint(app)
  38. return app
  39. def register_extensions(app):
  40. """Register models."""
  41. db.init_app(app)
  42. login_manager.init_app(app)
  43. login_manager.login_view = 'login'
  44. @login_manager.user_loader
  45. def load_user(user_id):
  46. return User.objects(id=user_id).first()
  47. def register_blueprint(app):
  48. app.register_blueprint(user_bp)

application/controllers/init.py

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. import json
  4. from flask import Blueprint, request, jsonify
  5. from flask.ext.login import current_user, login_user, logout_user
  6. from application.models import User
  7. user_bp = Blueprint('user', __name__, url_prefix='')
  8. @user_bp.route('/login', methods=['POST'])
  9. def login():
  10. info = json.loads(request.data)
  11. username = info.get('username', 'guest')
  12. password = info.get('password', '')
  13. user = User.objects(name=username,
  14. password=password).first()
  15. if user:
  16. login_user(user)
  17. return jsonify(user.to_json())
  18. else:
  19. return jsonify({"status": 401,
  20. "reason": "Username or Password Error"})
  21. @user_bp.route('/logout', methods=['POST'])
  22. def logout():
  23. logout_user()
  24. return jsonify(**{'result': 200,
  25. 'data': {'message': 'logout success'}})
  26. @user_bp.route('/user_info', methods=['POST'])
  27. def user_info():
  28. if current_user.is_authenticated:
  29. resp = {"result": 200,
  30. "data": current_user.to_json()}
  31. else:
  32. resp = {"result": 401,
  33. "data": {"message": "user no login"}}
  34. return jsonify(**resp)

config/development.py

  1. # coding: utf-8
  2. import os
  3. class DevelopmentConfig(object):
  4. """Base config class."""
  5. # Flask app config
  6. DEBUG = False
  7. TESTING = False
  8. SECRET_KEY = "sample_key"
  9. # Root path of project
  10. PROJECT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
  11. # Site domain
  12. SITE_TITLE = "twtf"
  13. SITE_DOMAIN = "http://localhost:8080"
  14. # MongoEngine config
  15. MONGODB_SETTINGS = {
  16. 'db': 'the_way_to_flask',
  17. 'host': 'localhost',
  18. 'port': 27017
  19. }