[译]使用Flask实战开发Python web应用

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

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

(另一篇好的入门教程:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world

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

原文地址:http://maximebf.com/blog/2012/10/building-websites-in-python-with-flask/#.UfKgNhYldx8

这篇文章中,作者将介绍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 对象, 比如 requestsession 和其他当前请求中的表现元素(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 %}
    <h1>Say something</h1>
    <form method="post" action="{{ url_for('signup') }}">
        <p><label>Username:</label> <input type="text" name="username" required></p>
        <p><label>Message:</label> <textarea name="message"></textarea></p>
        <p><button type="submit">Send</button></p>
    </form>
{% endblock %}

message.html:

{% extends "layout.html" %}
{% block content %}
    <h1>{{ username }} said:</h1>
    <p>
        {{ message }}
    </p>
    <a href="{{ url_for('home') }}">Say something else</a>
{% endblock %}

layout.html:

<!doctype html>
<html lang="en">
    <head>
        <title>Say somthing</title>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>

如你所见, 以上使用了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/<username>')
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的配置在从开发环境到生产环境中,会有多次的不同。 

Flask的文档中有如下建议:nice way of handling these configuration environments.

我们将配置以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" %}
    <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}" />
{% endassets %}
{% assets "common_js" %}
    <script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% 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

posted @ 2013-07-27 02:25  sevntech  阅读(1555)  评论(0)    收藏  举报