今天休假最后一个工作日,突发奇想看了一下Flask,感觉很有意思。也将官方的demo跑了一遍,后来又找到如下的文章,感觉思路非常好,指引入门后到实战的一些内容。

但是整个文章比较混乱,代码中错误和缺少的部分较多,无法实际运行,评论中也有一些补充信息(有可运行的代码在:https://github.com/trtg/flask_assets_tutorial),请大家注意。转译一下,做个参考。

由于本人下午才看Flask,文章从晚上11:30翻到2:00,各种不通之处,请各位看官体谅一下,人艰不拆,不喜勿骂。

这篇文章中,作者将介绍Flask中的database设置,使用配置环境、管理静态文件和将app布置到生产环境中。

Flask第一步

强烈建议使用PIP安装Python模块(同样使用virtualenv)

pip install Flask

Flask有一个很好的入门教程 quickstart tutorial ,所以基础知识上我会一带而过.

作为一个框架, Flask类似Ruby的 Sinatra 和PHP的 Slim . 一个主应用对象(application object)被实例化并用了映射urls到函数上。

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
以上代码来自Flask的quickstart,app是我们的应用对象,并将url路径“/.”映射给hello_world()函数 .这种映射通过使用 装饰器 @app.route()decorator.
app.run() 在端口5000运行自带的web服务器.因此,第一个 Flask web app通过 http://localhost:5000打开. 通过下一行命令运行web服务器:
python app.py
如前所述,我建议你看一下 quickstart tutorial. 让我们进入下一个例子:
from flask import Flask, render_template, request, redirect, url_for, abort, session
app = Flask(__name__)
app.config['SECRET_KEY'] = 'F34TF$($e34D';
@app.route('/')
def home():
return render_template('index.html')
@app.route('/signup', methods=['POST'])
def signup():
session['username'] = request.form['username']
session['message'] = request.form['message']
return redirect(url_for('message'))
@app.route('/message')
def message():
if not username in session:
return abort(403)
return render_template('message.html', username=session['username'],
message=session['message'])
if __name__ == '__main__':
app.run()
在此例中,用户输入名字和想在第一个页面上看到的语句。数据将会存储在session 并且显示在 /message 页面上.
Flask中一个非常重要的概念是request context. Flask使用 thread-local 对象, 比如 request, session 和其他当前请求中的表现元素(represent elements of the current request). 这些对象仅当请求背景(request context)被初始化后才可用,通过接受到HTTP请求的Flask来完成.
一些注意事项:
app.config 是一个包含配置参数的字典.
@app.route() 默认为GET方法上. 可以通过 methods 的keyword arg来指定动作允许的HTTP方法.
url_for(route_name, **kwargs) 被用作产生要用来处理请求的urls。第一个参数为函数名,keyword args为函数所需的参数.
redirect(url) 产生一个带重定位代码和目的地的HTTP响应.
abort(http_code) 用来产生一个错误响应并停止执行函数.
在signup() 函数中, 请求的数据通过请求对象来获取。request.form是一个带有所有POST数据的 MultiDict (类似一个有多个部分的dict) ,request.args 是一个带有所有GET参数的MultiDict,request.values 是二者的组合.
Flask 本身集成了jinja2模板(译注:展开一下,Flask、jinjia、werkzeug都是一个团队开发,所以肥水不留外人田,不管你喜不喜欢jinjia,它都必须集成。Flask开发者的blog:http://lucumr.pocoo.org;开发团队的网站:http://www.pocoo.org)。
模板文件存储为.html文件到templates/ 文件夹中. 显而易见render_template(filename, **kwargs)函数是用作渲染模板文件。
index.html:
{% extends "layout.html" %}
{% block content %}
Say something
Username: 
Message: 
Send
{% endblock %}
message.html:
{% extends "layout.html" %}
{% block content %}
{{ username }} said:
{{ message }}
Say something else
{% endblock %}
layout.html:
Say somthing 
 
{% block content %}{% endblock %}
如你所见, 以上使用了jinja的 template inheritance 来为所有的模板文件添加layout.
同样也使用了url_for() 函数,通过静态路由的方法来获取位于static/ 文件夹中的文件的urls。
文件组织和脚本管理
在我们的例子中,应用为一个文件写成。你肯定会问,当项目越来越大时这种方式是否适用。
我这到一个好的方法来解决此问题,那就是将app作为python package来看待. package的名字就是app的名字,且初始化Flask对象在__init__.py 文件中完成.
templates/ 文件夹位于 package的目录中,与应用相关的其他文件同样放置 (eg. settings.py, models.py…).
example/
__init__.py
static/
favicon.ico
templates/
index.html
hello.html
注意,当应用越来越大时,应当使用Flask的 Blueprints来将你的代码整理为模块。 作者会在另一篇文章中讨论这个内容。will cover this in another tutorial.
作者常用Flask-Script扩展来通过命令行管理应用。 这个应用提供了自带的命令,同样也可以自定义命令。
$ pip install Flask-Assets
从位于模块外的manage.py文件中配置并运行扩展模块。
#!/usr/bin/env python
from flaskext.script import Manager, Shell, Server
from example import app
manager = Manager(app)
manager.add_command("runserver", Server())
manager.add_command("shell", Shell())
manager.run()
通过命令行来启动命令行程序:
$ ./manage.py runserver
使用数据库
Flask没有自带任何数据库,但对于有很多数据库模块的python来说毫无压力。最著名也是作者最喜欢用的是 SqlAlchemy.除了他非常出色的数据库工具套件外,它还有迷人的ORM机制 .
将SqlAlchemy集成到Flask中可能并不简单,多亏了有Flask-SqlAlchemy扩展模块。
$ pip install Flask-SqlAlchemy
如果你是第一次接触SqlAlchemy,建议阅读一下 ORM tutorial,以便更好的进行.
通常在models文件中,将扩展模块的初始化并配置模型。.
from flask_sqlalchemy import SQLAlchemy
from example import app
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String)
message = db.Column(db.String)
def __init__(self, username, message):
self.username = username
self.message = message
我们要添加一些数据库连接的参数到配置中。例如,如果使用 sqlite,那就在__init__.py 文件的app.config 改成如下代码:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://example.db'
现在,我们可以修改示例程序来使用model, 在 __init__.py:
from models import *
# ...
@app.route('/signup', methods=['POST'])
def signup():
user = User(request.form['username'], request.form['message'])
db.session.add(user)
db.session.commit()
return redirect(url_for('message', username=user.username))
@app.route('/message/')
def message(username):
user = User.query.filter_by(username=username).first_or_404()
return render_template('message.html', username=user.username,
message=user.message)
将session替换掉,现在我们创建了一个User对象, 并使用db.session.add()和db.session.commit()将它存储到数据库中 (参看 standard SqlAlchemy way of doing it).
在message() 函数中,添加了必须通过url链接给定的username参数. 接下来通过User.query来执行数据库查询. 注意,the first_or_404()函数要通过flask的扩展模块提供.
Configuration配置
如前面例子所见,可以通过app.configdict配置. 虽然这是最简单的方法,但相对于配置环境来说(configuration environments).实际上,app的配置在从开发环境到生产环境中,会有多次的不同。
我们将配置以python对象属性的方式存到settings.py文件中。当从系统环境变量中读取完当前环境后(例子中使用EXAMPLE_ENV),再从正确的配置对象中读取配置 .
settings.py文件内容如下:
class Config(object):
SECRET_KEY = 'secret key'
class ProdConfig(Config):
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/example'
class DevConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite://example.db'
SQLALCHEMY_ECHO = True
在package的 __init__.py中:
import os
# ...
env = os.environ.get('EXAMPLE_ENV', 'prod')
app.config.from_object('example.settings.%sConfig' % env.capitalize())
app.config['ENV'] = env
Assets
在CSS、JS等越来越丰富的前端工具使用情况下, assets管理称为网页app的一个重要方面。
我们再次使用一个牛X的扩展模块——Flask-Assets, 一个 webassets 的python实现.
$ pip install Flask-Assets
我将所需的assets文件存储到static/文件夹下, 分别放到css/, js/ and vendor/ 文件夹中.线面你可以看到我将jquery 和 bootstrap 放置到venor目录下.
example/
static/
css/
layout.less
js/
main.js
vendor/
bootstrap/
css/
bootstrap.css
js/
bootstrap.min.js
jquery/
jquery-1.7.2.min.js
对于 webassets 文件可以使用budles来分组。files are grouped as bundles. 每一个bundle有一个自定义的过滤器 (eg: transform less files to css). 作者在assets.py 文件中进行如下bundle:
from flask_assets import Bundle
common_css = Bundle(
'vendor/bootstrap/css/bootstrap.css',
Bundle(
'css/layout.less',
filters='less'
),
filters='cssmin', output='public/css/common.css')
common_js = Bundle(
'vendor/jquery/jquery-1.7.2.min.js',
'vendor/bootstrap/js/bootstrap.min.js',
Bundle(
'js/main.js',
filters='closure_js'
),
output='public/js/common.js')
这里定义了两个bundle, 一个用于css文件一个用于js文件。 同样对一些文件通使用嵌套bundles来使用指定的过滤器。
为了在page是中包含bundles,webassets 提供了一些jinjia2 helpers(用来添加到layout.html):
{% assets "common_css" %}
{% endassets %}
{% assets "common_js" %}
{% endassets %}
现在我们需要在__init__.py中配置webassets的环境:
from flask_assets import Environment
from webassets.loaders import PythonLoader as PythonAssetsLoader
import assets
# ...
assets_env = Environment(app)
assets_loader = PythonAssetsLoader(assets)
for name, bundle in assets_loader.load_bundles().iteritems():
assets_env.register(name, bundle)
如上所见,使用webassets的PythonLoader来加载assets模块中的bundles,并在环境中注册每一个bundle .
可以添加ASSETS_DEBUG=True 在DevConfig从而获得debugging的信息.其他一些配置参数列在这里listed here. 参数名应当加上前缀ASSETS_,并且大写(eg. Environment.versions becomes ASSETS_VERSIONS).
最后,Flask-Assets扩展模块提供了一些命令行工具,它们需要现在manage.py中注册一下:
from flask_assets import ManageAssets
from example import assets_env
# ...
manager.add_command("assets", ManageAssets(assets_env))
Available commands are listed in webassets documentation but the most important one isrebuild which regenerates all your bundle files:
$ ./manage.py assets rebuild
部署到生产环境中
现在我们有了一个完全的Flask应用,需要将它部署到生产环境中。我喜欢用 uWSGI + Nginx + Supervisor.
注意,本部分内容使用 Ubuntu 。
Nginx acts as the frontend web server and will serve static files. uWSGI acts as the WSGI server which runs our flask app. Finally, I use supervisor to manage processes. I like to use Supervisor instead of init.d scripts as I often have other processes to manage.
$ sudo apt-get install nginx supervisor
$ pip install uwsgi
Configure an uWSGI app in /etc/uwsgi.ini:
[uwsgi]
socket = 127.0.0.1:3031
chdir = /path/to/my/app
module = example:app
env = EXAMPLE_ENV=prod
Add a server entry in Nginx in /etc/nginx/sites-enabled/example.conf:
server {
listen 80;
server_name example.com;
root /path/to/my/app/example/static;
location / {
try_files $uri @uwsgi;
}
location @uwsgi {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}
}
Finally, configure Supervisor to manage the uWSGI process in/etc/supervisor/conf.d/example.conf:
[program:example]
command=/usr/local/bin/uwsgi --ini /etc/uwsgi.ini
autostart=true
autorestart=true
stopsignal=INT
And restart everything:
$ sudo /etc/init.d/nginx restart
$ sudo /etc/init.d/supervisor reload
Update: the next part in the series has been published: Getting bigger with Flask