FlaskBB阅读笔记(一)
开篇
FlaskBB是用Flask框架实现的一个轻量级的论坛社区软件,代码托管在GitHub上。本系列文章通过阅读FlaskBB的源代码来深入学习Flask框架以及在一个产品级的Flask应用里的一些最佳实践规则。本文是这系列文章的第一遍。
本文分析FlaskBB的主程序app.py
的源码。我们从create_app()
函数入手,分析FlaskBB的软件结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
def create_app(config=None): """ Creates the app. """ # Initialize the app app = Flask("flaskbb") # Use the default config and override it afterwards app.config.from_object('flaskbb.configs.default.DefaultConfig') # Update the config app.config.from_object(config) # try to update the config via the environment variable app.config.from_envvar("FLASKBB_SETTINGS", silent=True) configure_blueprints(app) configure_extensions(app) configure_template_filters(app) configure_context_processors(app) configure_before_handlers(app) configure_errorhandlers(app) configure_logging(app) return app |
配置
FlaskBB使用下面典型的配置代码了加载应用程序的配置信息。
1 2 3 4 5 6 |
# Use the default config and override it afterwards app.config.from_object('flaskbb.configs.default.DefaultConfig') # Update the config app.config.from_object(config) # try to update the config via the environment variable app.config.from_envvar("FLASKBB_SETTINGS", silent=True) |
Flask.config
类是专门用来处理应用程序全局配置信息的。它类似python的dict
类,增加了一些导入配置的函数而已。其中Flask.config.from_object()
用来从一个python类里导入配置信息,需要注意的是,这个函数只导入大写的类成员变量,小写的类成员函数是不导入的。
Flask.config.from_envvar()
用来从环境变量指定的文件中导入设置信息,比如上例中,可以设置FLASKBB_SETTINGS
的环境变量指向/path/to/python/config/file.py,这样程序就会从这个文件里导入配置信息。
需要注意的是,最后导入的配置信息可覆盖前面导入的信息。所以,一般会有三个层次:一是默认配置;二是应用程序创建时传入的参数;最后再从环境变量里导入。
FlaskBB提供了不少配置信息,比如SEND_LOGS
表示是不是要把错误信息发送给网站管理员邮箱;SQLALCHEMY_DATABASE_URI
表示ORM数据库路径等等。具体参阅flaskbb.configs.default.DefaultConfig
类。
Blueprint
Blueprint是Flask提供的模块化设计组件。FlaskBB通过configure_blueprints()
来初始化Blueprint
1 2 3 4 5 |
def configure_blueprints(app): app.register_blueprint(forum, url_prefix=app.config["FORUM_URL_PREFIX"]) app.register_blueprint(user, url_prefix=app.config["USER_URL_PREFIX"]) app.register_blueprint(auth, url_prefix=app.config["AUTH_URL_PREFIX"]) app.register_blueprint(management, url_prefix=app.config["ADMIN_URL_PREFIX"]) |
主要由四个Blueprint组成:
- forum
论坛主模块,由flaskbb.forum.views.forum
Blueprint实现,默认其挂载的URL是/
- user
用户管理模块,由flaskbb.user.views.user
Blueprint实现,默认其挂载的URL是/user
- auth
鉴权模块,由flaskbb.auth.views.auth
Blueprint实现,默认其挂载的URL是/auth
- management
后台管理模块,由flaskbb.management.views.management
Blueprint实现,默认其挂载的URL是/admin
FlaskBB用到的Flask扩展
有大量的第三方开发者为Flask构架开发扩展,在这里可以找到官方收录的所有Flask扩展。FlaskBB通过configure_extensions()
函数来初始化用到的扩展。这些扩展的简要信息汇总如下:
- Flask-SQLAlchemy
这是sqlalchemy的Flask扩展,提供SQL数据库和ORM访问。 - Flask-Login
这个扩展提供了用户会话管理。实现了一些通用的会话管理任务,如登录,登出,以及记录用户会话期间的状态数据等。 - Flask-Mail
这个扩展让Flask应用很容易地发送电子邮件,而且支持单元测试。 - Flask-Cache
给Flask程序提供Cache支持。 - Flask-DebugToolbar
从Django移植过来的适用于Flask的调试器。主要用来调试Flask程序及性能。 - Flask-Redis
给Flask程序添加Redis的扩展。Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 - Flask-Migrate
给Flask用的SQLAlchemy数据库迁移工具。比如,Flask应用的1.3版本在1.2版本的数据库的某个表里添加了一个字段,那么使用这个工具可以自动生成数据库迁移脚本,帮助使用1.2版本的用户把数据库从1.2版本升级到1.3版本。 - Flask-Theme2
给Flask应用添加主题支持。 - Flask-Plugins
和FlaskBB的作者是同一个人。提供更简单的插件管理。 - Flask-WhooshAlchemy
这个扩展帮助Flask程序实现基于SQLAlchemy数据库内容的文本搜索和索引服务。正如扩展的名字,它是使用whoosh和SQLAlchemy的ORM结合起来实现广西内容的搜索和索引功能。 - Flask-WTF
当你必须处理浏览器提交的表单数据时,视图代码很快会变得难以阅读。有一些库可以简化这个工作,其中之一便是WTForms。这个扩展让Flask使用更简单地集成WTForms,同时处理了CSRF(Cross-Site Rrequest Forgery,跨站请求伪造),提供更好的安全性。还提供文件上传等功能。 - Flask-Script
给Flask应用程序提供外部脚本支持。比如运行开发服务器,初始化数据库,等命令行相关的任务。对FlaskBB而言,python manage.py runserver
和python manage.py createall
等命令就是通过这个扩展实现的。
自定义的Jinja2过滤器
Jinja2提供了自定义过滤器的功能,可以在Jinja2模板里灵活使用。FlaskBB通过函数configure_template_filters()
定义了一系列过滤器,其中is_online
过滤器是这样定义的:
app.jinja_env.filters['is_online'] = is_online
过滤器就是一个简单的python函数,is_online()
函数定义在flaskbb/utils/helper.py里:
1 2 3 4 5 6 7 |
def is_online(user): """A simple check to see if the user was online within a specified time range :param user: The user who needs to be checked """ return user.lastseen >= time_diff() |
这样,在Jinaja2模板flaskbb/templates/user/profile.html里,就可以使用下面的代码来判断用户是否在线:
{% if user|is_online %} <tr><td><span class="label label-success">Online</span></td></tr> {% else %} <tr><td><span class="label label-default">Offline</span></td></tr> {% endif %}
上面Jinja2模板里user|is_online
脚本会导致is_online(user)
被调用来判断用户是否在线。
向模板注入设置信息
FlaskBB通过调用configure_context_processors()
向模板注入设置信息。代码如下:
1 2 3 4 5 6 7 8 9 10 |
def configure_context_processors(app): """ Configures the context processors """ @app.context_processor def inject_flaskbb_config(): """ Injects the ``flaskbb_config`` config variable into the templates. """ return dict(flaskbb_config=flaskbb_config) |
使用context_processor
装饰器来装饰inject_flaskbb_config()
函数,这样这个函数会被Flask记录起来,每次要渲染模板时,会先调用这个函数更新一下模板上下文信息。这样,在模板里就可以访问这里注入的上下文信息。上下文处理器函数必须返回一个dict
实例。Flask官方文档对context_processor有详细的描述。
例如,返回的dict
里包含’name: kamidox’这样的值,则在Jinja2模板里可以直接用{% name %}
来访问name
变量,其值为kamidox
。
更新用户在线信息
FlaskBB通过configure_before_handlers()
函数来注册每个请求之前的动作,以记录用户的在线信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def configure_before_handlers(app): """ Configures the before request handlers """ @app.before_request def update_lastseen(): """ Updates `lastseen` before every reguest if the user is authenticated """ if current_user.is_authenticated(): current_user.lastseen = datetime.datetime.utcnow() db.session.add(current_user) db.session.commit() |
这里,before_request
装饰器将把update_lastseen()
函数注册进Flask,Flask在处理请求之前都会调用这个函数。FlaskBB使用这个机制来记录最后一次看到用户的时间。结合会话的超时机制,就可以判断用户是否在线。
自定义错误处理
通过configure_errorhandlers()
来实现自定义错误处理。其中,HTTP 403, 404及500的错误处理定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def configure_errorhandlers(app): """ Configures the error handlers """ @app.errorhandler(403) def forbidden_page(error): return render_template("errors/forbidden_page.html"), 403 @app.errorhandler(404) def page_not_found(error): return render_template("errors/page_not_found.html"), 404 @app.errorhandler(500) def server_error_page(error): return render_template("errors/server_error.html"), 500 |
这样当发生这些http错误时,错误网页将从服务器返回自定义的错误网页,而不是浏览器客户端默认的错误页面。
LOG配置
通过configure_logging()
来配置系统的LOG。FlaskBB的LOG主要保存在应用程序根目录下的logs目录里,分两个文件保存,一个是INFO级别的LOG,另外一个是ERROR级别的LOG。同时还支持把ERROR级别的LOG通过邮件的方式发送给网站管理员。
下面代码配置了INFO级别的LOG:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
logs_folder = os.path.join(app.root_path, os.pardir, "logs") formatter = logging.Formatter( '%(asctime)s %(levelname)s: %(message)s ' '[in %(pathname)s:%(lineno)d]') info_log = os.path.join(logs_folder, app.config['INFO_LOG']) info_file_handler = logging.handlers.RotatingFileHandler( info_log, maxBytes=100000, backupCount=10 ) info_file_handler.setLevel(logging.INFO) info_file_handler.setFormatter(formatter) app.logger.addHandler(info_file_handler) |
下面代码配置了通过邮件发送错误LOG给网站管理员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from logging.handlers import SMTPHandler if app.config["SEND_LOGS"]: mail_handler = \ SMTPHandler(app.config['MAIL_SERVER'], app.config['MAIL_DEFAULT_SENDER'], app.config['ADMINS'], 'application error, no admins specified', ( app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'], )) mail_handler.setLevel(logging.ERROR) mail_handler.setFormatter(formatter) app.logger.addHandler(mail_handler) |
结束语
通过对主程序app.py
的代码分析,我们基本上知道了FlaskBB的主体框架,模块划分,用到的外部扩展等信息。下一篇将分模块深入阅读各个Blueprint模块的实现以及数据库设计方面的内容。