11dimension / SukieTalk

6 stars 0 forks source link

基于 Flask 的快速 Web 开发入门 #5

Open couldtt opened 7 years ago

couldtt commented 7 years ago

flask-logo

一、前言

Flask 是基于 Werkzeug 的一个小型框架,小到都不应该把 Flask 叫框架(Framework),因为它没有像常见框架那样集成 ORM,只有薄薄的请求处理和模板渲染功能。

但我们不能因为 Flask 小,就轻视它,认为它是一个玩具框架,不能用于生产实践。相反,正是因为它结构松散,没有绑定多余的东西,因此它足够灵活,你可以根据自己的需要,去集成任何你想要的东西。

Python 庞大的生态,加上Flask丰富的扩展,在用 Flask 做 Web 开发的过过程中,几乎可以轻易地找到大部分想要的“轮子”,从而可以让开发人员专注于业务功能,实现产品的快速开发。

本文会用一个简单的 CMS 系统为例,为大家分享如何在一个小时之内,利用 Flask 框架快速地搭建起一个带管理后台和 API 的简单 CMS 系统。

另外,本文是入门教程,主旨是分享 Flask 框架的使用以及其丰富的周边生态,让大家对 Flask 开发 Web 的方便和快捷有个基本的了解,因此会更注重于看得见摸得着的开发实践,而不会对框架的源码做详细深入的介绍。所以,暂时略去了关于 WSGI 和 Werkzeug 的介绍,有兴趣的可以看下官方文档。

二、快速开始

要想构建一个CMS(内容管理系统),最重要的东西是什么呢?毫无疑问,就是文章文章分类,那我们就以这两点为基础,进行我们的开发构建。

2.1 基础环境搭建

因为是 CMS 系统,所以数据库是免不了的,这里使用 SQLAlchemy 作为数据库的 ORM 层。具体安装非常简单,一句话就可以搞定:

pip install Flask Flask-SQLAlchemy

2.2 Quick Demo

from flask import Flask
app = Flask(__name__)

@app.route('/')
def main():
    return 'hello world'

if __name__ == '__main__':
    app.run(debug=True, port=5012)

以上是每一个框架所必备的 Hello World 示例。可以看到,使用 Flask 的入门成本是极低的,没有冗余繁琐的配置和需要事先了解的一大堆的范式,只有简单的路由定义和一目了然的响应方式,让我们可以用极快的速度上手开发。

2.3 表模型创建

下面我们直接进入正题,开始真正的 CMS 的开发。

使用 SQLAlchemy 来定义表模型是十分简单的,SQLAlchemy 抽象除了各种数据结构映射到对应的数据库的各种类型的字段。为了演示方便,这里是用 SQLite 数据库,声明数据库的地址直接在 Flask 实例化的 app 里进行配置即可:

from flask_sqlalchemy import SQLAlchemy
DB = SQLAlchemy(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'SQLite://test.db'

下面是我们要定义的表模型:

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(48), nullable=False, doc='分类名')
    pid = db.Column(db.Integer, nullable=False, index=True, doc='父ID')

    def __repr__(self):
        return '{} <{}>'.format(self.name, self.id)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128), index=True, nullable=False, doc='标题')
    category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False, doc='分类ID')
    content = db.Column(db.Text)

    category = db.relationship('Category', backref='post')

然后是利用 Flask 自带的 hook,在首次响应的时候,自动创建数据库表模型:

@app.before_first_request
def create_db():
    db.create_all()

在我们访问了一次 http://127.0.0.1:5012 以后,会自动在当下目录生成 test.db 文件,这个就是我们的数据库了。

三、 快速构建管理控制后台

有了数据库表模型以后,我们想的第一件事情应该就是往数据库里塞一点测试数据,然后供我们继续的开发测试用了。这个时候,有的同学就会找一个数据库客户端,然后打开我们刚才生成的 test.db 文件,然后找到我们刚刚创建的 category 和 post 这两张表,往里面插入数据了。

接下来我是不是要说这个呢?

看我们的小标题就知道了,肯定不是!

我们要做的是在创建完表模型以后,快速得构建出一个管理后台,让我们能够通过这个管理后台去做生成和修改测试数据的事情。但是,天天写增删改查这种逻辑任谁也受不了,所以我们需要一个比较无痛的方法省掉我们那些增删改查的代码,把我们从重复中解放出来。

OK,下面祭出我们的 Flask 界的后台生成神器:Flask-Admin

这是一款前端基于 bootstrap 的后台管理工具,安装同上,依然是很简单的 pip install Flask-Admin 就可以搞定。

在代码中的引入和实例化也和 SQLAlchemy 类似

from flask_admin import Admin
admin = Admin(app, name='Baixing Simple CMS', template_mode='bootstrap3')

细心的同学看到这个地方就会发现,Flask的扩展的命名和引入方式大多都是一样的,先引入扩展,然后实例化的时候传入 app 实例,使之与Flask 绑定。这里有个注意点,就是 Flask-Admin 使用的时候需要我们去配置 SECRET_KEY 这个参数,如果不定义的话,是没办法在后台进行表单提交的。配置方法很简单,在上面配置 SQLALchemy 的地方,再补充一句配置就可以了,示例如下:

app.config['SECRET_KEY'] = '!!!-----www.baixing.com-----!!!!'

既然是自动生成后台的工具了,那么必然有一个生成的依据,这个依据就是我们刚刚定义的 SQLAlchemy 模型,在 Flask-Admin 中,我们可以直接使用由 SQLAlchemy 定义的模型来生成增删改查的页面。

先引入适配 SQLAlchemy 的 ModelView

from flask_admin.contrib.sqla import ModelView

最后一步,创建我们需要的管理页面:

admin.add_view(ModelView(Category, db.session))
admin.add_view(ModelView(Post, db.session))

接下来访问http://127.0.0.1:5012/admin/看下效果

后台列表页截图 flask-admin-listing

后台编辑页截图 flask-admin-create

Awesome!!

一个带着增删改查全功能的后台就这么出来了,而且从上图可以看到,在创建 Post 的时候,Flask-Admin 可以自动识别外键,直接将 category 和 我们的 category 模型进行关联,而显示的内容,正是我们定义 Category 这个模型的时候,定义在__repr__这个魔术方法中的返回值。

上面的例子里,我们代码基本都是通过配置的方式生成的,虽然很快速,但有的同学一定会有这样的疑惑,就是我们用的Flask-Admin,会不会因为自动生成的缘故,导致了难以实现定制,从而降低了应用的可扩展性。

可以肯定的说,自动生成必然会带来可扩展性的降低,但是 Flask-Admin 给我们预留的扩展的口子还是很多的。从模板层面来讲,因为是基于 Bootstrap 的,所以你几乎可以无痛地将模板更换层任何你想用的 Bootstrap 模板,笔者就曾经换了一套 AdminLTE2 模板 (https://github.com/couldtt/flask-admin) 用在公司一个项目的开发上。

另外,在使用 Flask-Admin 的时候,我们可以不局限于 ModelView 这种方式,把 Flask-Admin 当做一种页面布局的方式。通过 ModelView 和 CustomView 混合布局的方式,适合直接用模型生成的就直接生成,不合适的就略做调整,通过重定义各类 form 操作的方法来实现部分的自定义,如果还是不合适,那就直接重写界面,Flask-Admin 提供了一个 expose 方法,用途和 Flask 中的 route 方法类似,可以用来定义 Admin 的路由。

最后,顺便提一句,Airbnb 开源的数据流管理工具 airflow (https://github.com/apache/incubator-airflow) 也是基于Flask-Admin 开发的喔~

四、 快速构建 Restful 风格的 API

通过上面的内容,我们已经构建出了一个 CMS 的雏形,但是还缺了最重要的一层——展示层。 这里我们使用当下流行的前后端分离的方式去做,输出 Restful 风格的 API 供前端使用。

Restful 风格的 API 说白了其实就是针对一个 Resource 用 HTTP 协议里已经有了定义的 Method 来进行增删改查。比如我们的 Post 接口,我们需要能够看到 Post 列表,能够看到 Category 列表,能够看到 Post 的详细内容。但是我们不能删除这些内容,因为我们的 API 是给客户端用的,而我们的观众是没有这个权限干这个事情的。

所以,到这里明确了我们要做的事情,我们需要以下几个 API:

对于 Listing 类型的接口,我们通常需要额外增加一个翻页的功能,以供内容足够多的时候进行分块的显示。

既然说了是快速构建,那这里肯定也不是手写一堆查询的逻辑和分页的逻辑了,依然是用配置为主少量自定义为辅的方式进行。

这里再引入一个 Flask 里很好用的扩展:Flask-Restless

安装同上 pip install Flask-Restless

使用上也和 Flask-Admin 雷同

from flask_restless import APIManager
api_manager = APIManager(app, flask_sqlalchemy_db=db)

api_manager.create_api(Post, methods=['GET'])
api_manager.create_api(Category, methods=['GET'])

至此,我们需要的四个查询接口,都已经被生成了。访问方式是 URL 后面加个 api ,然后接上我们的 post 或者 category 即可。

关于 Post Listing, Post Detail, Category Listing 这三个接口的返回,因为很简单,这里不再赘述,着重说一下 Post Listing by category 这个接口。

看 URL 就可以猜出,这个接口的目的在于返回某个分类下的文章,而我们什么都没做,只是定义了 Post 和 Category 这两个 Resource 而已,直接访问/api/category/categoryId/post 就可以获取下面的结果,而这其中的奥秘就在于我们上面定义 Post 这个模型的时候,定义的 Relationship category = db.relationship('Category', backref='post'), backref 的全称是 “back reference”,反向引用的意思。Flask-Restless 可以直接读取这个引用关系,直接应用到我们创建的 API 上面去,可谓十分方便。

Post Listing by category 的返回结果:

{
num_results: 2,
objects: [
    {
        category: {
            id: 1,
            name: "未命名",
            pid: 0
        },
        category_id: 1,
        content: "212121",
        id: 1,
        title: "1212"
    },
    {
        category: {
            id: 1,
            name: "未命名",
            pid: 0
        },
        category_id: 1,
        content: "",
        id: 3,
        title: "3241234"
    }
],
page: 1,
total_pages: 1
}

从上面的返回结果中看,分页已经被处理了,我们在使用 GET 语义的接口获取 Listing 相关的内容时,是不需要去关心 total 以及 offset 等分页的逻辑的,拿来就用即可。

另外,Flask-Restless 的功能远远不止这些, Flask-Restless 在每一个 HTTP Verb 上,都定义了对应的 preprocessor 和 postprocessor,你可以很轻松得使用面向切面编程的方式去插入各种中间件,针对特定的业务需求引入自己的业务逻辑。

五、后记

作为一个 CMS , 上面的例子还很原始,它还缺少很多基本的功能,比如作者管理,授权等等。但是从这个例子出发,稍微扩展一下,加一加模型,在熟悉 Flask 开发的套路以后,将其补充成一个功能完备的 CMS 系统并不困难。

Flask、Flask-SQLAlchemy、Flask-Admin 和 Flask-Restless 进行配合,可以很轻松的实现我们日常开发中一些常见的 xxx 系统,尤其是对于性能等要求不高的内部系统,尤为合适。对于初创产品的构建,使用这个组合也可以很好地满足需求,既能实现快速开发的目标,同时又保留了扩展的空间,可以方便我们后期对项目进行重构和迁移。

此外,本文使用的 Python 环境为 Python3.4,文中代码可以访问如下地址获取:

https://gist.github.com/couldtt/22583f6f4cb303a29c2b004484afe9ab

六、参考资料

zpbx commented 7 years ago

审核结果