标签归档:Flask

《Flask Web开发实战》勘误、源码等资源索引

这篇文章列出了本书的勘误、源码等相关资源。衷心的希望这本书能够帮助到你,同时也为书中包含的错误为你带来的不便说声抱歉。

勘误 & FAQ & 可改进实现 & 版本更新记录

在本书的Meta仓库helloflask(https://github.com/greyli/helloflask)中,你可以找到下列资源:

实战项目源码

本书第7~11章的5个实战项目的源码地址如下:
这些项目的介绍以及在线Demo链接可以在本书主页找到。

其他相关资源

本书附带的其他相关资源如下:

HelloFlask.com

helloflask.com是本书的衍生站点,以上的资源信息都可以在本书的主页http://helloflask.com/book找到。

问题与错误反馈

如果你发现了书中的错误和不足,或是有和书相关的问题和反馈,根据类别,你可以分别选择下面的途径告诉我:
  • 书中包含的笔误、错误和不足的反馈:在helloflask仓库创建issue或PR
  • 各个项目的bug:在各个项目所在的仓库创建issue或PR
  • 其他问题、建议和批评:直接发邮件给我(withlihui@gmail.com),或是发送知乎私信、新浪微博私信、Twitter私信给我(邮件可以被我最快看到)

Powered by This Book

如果你通过阅读整本书编写了自己的小项目,欢迎通过各种方式告诉我,我会把你的项目添加到本书主页中的“Powered by This Book”栏目。
 
最后,欢迎大家在新浪微博Twitter上关注我,以便及时获取本书的最新动态。

《Flask Web开发实战》第二部分项目Demo和源码上线

很抱歉,因为电子书突然提前上架,一些进度被拖延了,现在终于把所有项目的源码都推送到GitHub了(如果你不方便访问GitHub,本书主页上提供了这些项目的源码合集文件下载)。

下面是这些项目的源码和Demo链接。关于这些项目的截图和功能介绍参见《Flask Web开发实战》中的示例程序们或本书主页(helloflask.com/book)。

第1~6章、13章:HelloFlask

第7章:留言板 – SayHello

Say hello to the world.

第8章:个人博客 – Bluelog

A blue blog.

第9章:图片社交网站 – Albumy

Capture and share every wonderful moment.

第10章:待办事项程序 – Todoism

We are todoist, we use todoism.

第11章:在线聊天室 – CatChat

Chatroom for coders, not cats.

提示 在线Demo部署在DigitalOcean的廉价主机上,某些地区或在特定时段可能会无法访问,请尝试使用VPN。另外,在线Demo关闭了部分功能。建议根据书中第二部分每章开始的操作指导在本地运行程序。

特别说明(给使用Windows系统的Python2.7用户)

Werkzeug当前版本(14.2)存在一个Bug,当在Windows系统下使用Python2开启调试模式时,重载器会因为环境变量FLASK_ENV的编码问题而出现TypeError异常。这个Bug已在master分支修复(话说定位这个Bug花了我很长时间),预计在纸书正式发售前会发布Werkzeug 0.15版本。

目前,临时的解决方案有修改Werkzeug源码、修改python-dotenv源码、从GitHub上的master分支更新Werkzeug等,但这些方法都太麻烦。我建议你临时不开启调试模式来避免这个异常出现,也就是在.flaskenv文件中将FLASK_ENV定义那一行注释掉(使用#号),比如:

# FLASK_ENV=development

等到Werkzeug 0.15发布后,我会发一篇文章通知大家更新本地依赖,并给出具体的更新方法。

致购买《Flask Web开发实战》电子版的读者

按照编辑的说法,电子书是要和纸书一起发售的。没想到的是,纸书还在印刷的时候,电子书突然就在24号上架了……这个“突袭”带来了一些问题,这篇文章汇总了这些问题及对应的解决方法。很抱歉这些问题为你带来了不便!

电子书亚马逊链接:https://www.amazon.cn/dp/B07GST8Z8M

豆瓣阅读链接:https://read.douban.com/ebook/56335667/

排版错误

或许是因为排版人员不够专心,又或者是软件问题,电子书存在一些排版问题:

  • 文本中所有的半角括号被转换为全角括号
  • 类似“python -m”命令中的空格被去掉
  • 部分代码缩进错乱
  • 部分字符缺失

我这几天会大致的过一遍电子书,汇总出所有的排版问题,然后尽快推送更新。

你可以访问勘误文件查看完整的勘误列表。

运行程序时出现TypeError异常(针对在Windows系统使用Python2的用户)

更新 python-dotenv 到最新版本即可:

$ pip install -U python-dotenv

Flask 0.12.2版本发现安全漏洞,请考虑升级

因为Flask-CKEditor的示例程序目录下包含一个旧的requirements.txt文件,其中Flask版本被固定为0.12.2,推动代码到GitHub时,触发了内置的依赖安全提示,进而了解了一下这个关于Flask 0.12.2版本的漏洞。

漏洞描述

这个漏洞(CVE-2018-1000656)四天前(8月20号)被发布在NVD(National Vulnerability Database,国家漏洞数据库)上,漏洞描述如下:
The Pallets Project flask version Before 0.12.3 contains a CWE-20: Improper Input Validation vulnerability in flask that can result in Large amount of memory usage possibly leading to denial of service. This attack appear to be exploitable via Attacker provides JSON data in incorrect encoding. This vulnerability appears to have been fixed in 0.12.3.
大致的翻译如下:
Pallets项目组开发的Flask 0.12.3及以下版本包含CWE-20类型的漏洞:不合适的输入验证漏洞。这个漏洞将会导致大量内存占用,可能会导致拒绝服务。攻击者可以通过提供使用了错误编码的JSON数据来进行攻击。这个漏洞已经在0.12.3版本中修复(#2691)。

应对措施

对于这个漏洞,你可以通过升级来进行防范。如果你打算使用最新版本(Flask 1.0.2),可以使用下面的命令更新(参见这篇文章了解Flask 1.0版本包含哪些主要变化):
$ pip install -U flask
如果你使用Pipenv,则可以使用下面的命令:
$ pipenv update flask
如果你还没有准备好使用最新版本,可以升级到0.12.3版本
$ pip install flask==0.12.3
然后更新requirements.txt:
flask ~> 0.12.3
如果使用Pipenv,则使用下面的命令:
$ pipenv install flask==0.12.3

附注

《Flask Web开发实战》签名版开始预售

《Flask Web开发实战》即将下厂印刷,如果想要购买作者签名版,可以访问http://helloflask.com/book/signed。本书定价129,电商平台预计价格为99,签名版为109。

注意,此商品为预售,无现货,具体发货时间取决于本书正式发售日期。更多信息请访问预售页面查看。

Flask-Origin:Flask 0.1版本源码注解

本项目是《Flask Web开发实战》的衍生品。在本书第16章的前半部分,为了让读者快速对Flask的源码结构建立一个初步的认识(以便阅读后面的内容),推荐读者阅读0.1版本的源码。

本项目对0.1版本Flask源码(项目根目录下的flask.py脚本)中的注释和文档字符串进行了翻译,并在必要的地方添加了一些额外的注解,以便于阅读和理解。

项目地址:https://github.com/greyli/flask-origin

欢迎fork项目进行补充和纠错。

阅读前的准备

为了更容易理解Flask的实现原理,你需要对WSGI协议以及HTTP协议有一些了解,建议先简单浏览下面的基本知识:

进一步阅读

Flask内部实现大量依赖于Werkzeug,包括请求和响应对象,路由匹配,URL生成等等,你可以阅读Werkzeug的文档来深入了解这些内容的具体实现。另外,如果你对模板渲染部分的内容感兴趣,也可以考虑阅读Jinja2文档:

注意:新版本的Werkzeug和Jinja2已经发生很大的变化,0.1版本的Flask对应的Werkzeug源码版本为0.6.1,对应的Jinja2源码版本为2.4。上述文档链接分别为0.14和2.9版本,请谨慎参考。

新版本的Flask中如何启动开发服务器和开启调试模式

从Flask 0.11版本开始,官方就建议使用flask run命令来取代app.run()方法运行开发服务器。尽管如此,两年多过去了,仍然有大量新发布的文章和教程在示例中使用app.run()方法启动程序。类似的,虽然内置的命令行支持已经非常完善,但还有很多人在使用Flask-Script。

Added flask and the flask.cli module to start the local debug server through the click CLI system. This is recommended over the old flask.run() method as it works faster and more reliable due to a different design and also replaces Flask-Script.

Flask Changelog 0.11

不得不承认,在某些特殊场景下,app.run()更加方便,比如创建Flask命令在附加Werkzeug提供的性能分析中间件后启动程序,这时通过app.run()可以直接在脚本内启动程序。但是在大多数情况下,flask run更能胜任启动开发服务器的工作。而且,在大型项目中,使用app.run()需要你在项目根目录单独创建一个启动脚本,flask run则没有这个要求;在单脚本程序中,使用flask run也可以省掉脚本末尾的两行代码。

注意 这两种方法都只是用来启动内置(Werkzeug提供)的开发服务器,仅适用于开发用途。在生产环境下,应该使用性能更好,更加完善的开发服务器,比如Gunicorn、uWSGI等。

不同组织形式的程序的启动方式

下面我们来了解一下使用flask run启动开发服务器时在几种方式。

简单的单脚本程序

如果脚本命名为app.pywsgi.py,那么在包含程序脚本的目录下直接调用flask run即可:

$ flask run

Flask会自动探测找到脚本中的程序实例并启动。如果脚本命名为其他名称,比如hello.py,那么需要将脚本名写入环境变量FLASK_APP,然后再调用flask run命令:

$ export FLASK_APP=hello
$ flask run

提示 在Windows系统下,你需要使用set命令来设置环境变量,比如 > set FLASK_APP=hello,后面的命令亦同。

使用包组织的程序

这种情况下,可以将包含程序实例的对应模块的路径写入FLASK_APP

$ export FLASK_APP=my_pkg.app
$ flask run

通常情况下,我们会在包内的__init__.py文件中创建程序实例,所以这时可以直接将包名称写入FLASK_APP

$ export FLASK_APP=my_pkg
$ flask run

使用工厂函数创建程序实例的程序

因为Flask会自动探测程序实例,所以使用工厂函数创建程序实例时不需要进行额外设置。具体来说,Flask会在FLASK_APP变量存储的对应模块/包构造文件中寻找名为create_appmake_app的函数,并调用这个函数来创建一个程序实例。

为了让你的程序能够被探测到,工厂函数的名称需要命名为create_appmake_app,而且要确保工厂函数接受默认值参数。这时启动开发服务器的方式仍然不变:

$ export FLASK_APP=my_pkg
$ flask run

如果你的工厂函数接受的参数不是默认参数,或者你想详细定义调用工厂函数的方式,那么也可以通过FLASK_APP环境变量来定义:

$ export FLASK_APP="my_pkg:create_app('development')" 
$ flask run

提示 Flask的FLASK_APP还接受其他形式的输入值,你可以参考文末给出的文档相关部分链接了解完整内容。

如何避免重复设置FLASK_APP环境变量

在上面的几种方式中,除了包含程序实例的程序脚本命名为app.pywsgi.py的情况外,都需要设置FLASK_APP环境变量。有没有办法避免重启电脑或是新打开命令行会话时重复输入FLASK_APP呢?当然。Flask提供了对一个常用的Python虚拟环境管理工具python-dotenv的支持,我们需要先安装它:

$ pip install python-dotenv

当python-dotenv安装后,执行flask run命令会首先将项目根目录下的.env.flaskenv文件中的环境变量写入。所以,你可以将FLASK_APP写在这两个文件中。按照约定,.env存储包含敏感数据的环境变量,这个文件需要加入到.gitignore中以避免提交到Git仓库中;而.flaskenv时Flask特别支持的文件,这个文件则用来存储和Flask相关的环境变量,比如FLASK_ENVFLASK_DEBUG等,所以我们可以把FLASK_APP写到这个文件中:

FLASK_APP=my_pkg

现在,我们可以仅通过一个命令来启动开发服务器:

$ flask run

使用flask run时如何开启调试模式?

在使用app.run()方法时,我们会通过将debug参数设为True来开启调试模式。而当使用flask run时,则需要通过FLASK_ENV环境变量来设置调试模式。默认情况下,FLASK_ENV的值为production,在开发时我们可以将其设为development来开启调试模式。

同样的,为了避免重复写入这个环境变量,我们也将其写到.flaskenv中:

FLASK_ENV=development

提示 目前已不推荐使用FLASK_DEBUG来开启调试模式,当FLASK_ENV的值为development时调试模式会自动开启。

使用flask run时如何自定义主机和端口

在通过flask run启动开发服务器时,你可以通过命令行选项来自定义监听的主机和端口,示例如下:

$ flask run --port 5001

下面的示例同时指定了端口和主机:

$ flask run --host 0.0.0.0 --port 5001

另外,Flask还支持通过环境变量来定义命令选项,支持的环境变量名称模式为“FLASK_命令_选项”。比如,如果你想设置端口,那么可以定义FLASK_RUN_PORT环境变量,作用和传入--port选项相同。

启动包含程序上下文的Python Shell

你可以通过flask shell命令来启动一个激活了程序上下文的Python Shell,而不是使用python命令:

$ flask shell

app.run()的未来

从0.11版本到现在的1.0.2版本,app.run()始终处于不建议使用状态,而且Flask的命令行系统、flask run命令的程序探测都在逐渐完善,我觉得未来也许会正式”deprecate“这个app.run()方法。不过,因为某些特殊用途仍然需要使用app.run(),未来的变化还不好说。而且,Miguel Grinberg提交了1个PRapp.run()间接调用flask run,如果这个PR被合并,也许app.run()将会重回正轨。

就目前来说,flask run要远比app.run()更加方便、好用、简洁、直观,准备好了吗?穿上新衣服吧。

相关链接

Flask test_client()测试客户端为勾选框传递布尔值数据

今天写单元测试发现了一个常见的问题,即测试时发送POST请求时如何传入布尔值数据(勾选框字段值)?答案是:你没法直接传递布尔值。其实这个答案相当显而易见,客户端当然没法向服务器端发送Python类型的数据,数据的转换是在接受到请求数据后在服务器端进行的。之前在不借助Flask-WTF/WTForms,手动编写表单并处理时就已经注意到了这个问题,不过在测试中不太容易想到。

首先,我们需要了解一下勾选框(<input type="checkbox">)提交的行为:

  • 如果没有勾选,那么勾选框字段的值为空值,而且这个字段不会被序列化到请求中;在服务器端,WTForms会将其转换为False
  • 如果勾选框被勾选,那么传入服务器端的数据会是该字段value属性的值,如果value属性的值为空,那么则提交字符串"on";在服务器端,WTForms会将其转换为True

也就是说,勾选框的数据只要不为空,WTForms就会将其转换为True。所以,在测试中,如果你想让勾选框的值最终转换为True,那么就传入任意字符串;反之则传递空字符串或直接不加入该字段。下面是传入空字符串的示例:

def test_privacy_setting(self):
    self.login()
    response = self.client.post(url_for('user.privacy_setting'), data=dict(
        public_collections='',  # <--
    ), follow_redirects=True)

    user = User.query.get(1)
    self.assertEqual(user.public_collections, False)

顺便说一句,基于勾选框的提交行为,如果没有使用Flask-WTF/WTForms,那么在手动处理提交数据的时候也要进行相应的处理:没有在request.form中获取到勾选框字段(比如,request.form.get('remember')会是None),即表示没有勾选,那么就转换为False;勾选框字段一旦出现,那么就表示勾选,转换为True

Flask Web开发实战番外

《Flask Web开发实战》删减下近8万字的内容,有时间我会把其中有价值的内容整理成文章发布出来。

使用Flask-Avatars在Flask项目中设置头像

Flask-Avatars

大多数Web程序中都会涉及到头像的实现。不同类型的项目,对于头像的需求不同,有些项目可以直接使用Gravatar提供的头像服务,而有的项目则需要提供自定义头像设置。扩展Flask-Avatars几乎满足了所有常见的头像需求:

  • 默认头像
  • Gravatar头像
  • Robohash头像
  • 社交网站头像
  • 生成随机图案头像Identicon
  • 图片裁剪头像

Flask-Avatars

GitHub主页:https://github.com/greyli/flask-avatars

安装与初始化

使用pip安装:

$ pip install flask-avatars

像其他扩展类似,你需要实例化从flask_avatars导入的Avatars类,并传入app实例:

from flask_avatars import Avatars

app = Flask(__name__)
avatars = Avatars(app)

如果你使用工厂函数创建程序实例,那么这里也可以不传入程序实例app,在工厂函数中对这个avatars对象调用init_app()方法时再传入app实例。

默认头像

Flask-Avatars内置了几个默认头像,可以用来作为用户注册后的初始头像,或是作为博客评论者的头像。在模板中调用avatars.default()即可获取URL:

<img src="{{ avatars.default() }}">

你可以通过size参数来设置尺寸,默认为m,其他可选值有l和s。实际的调用示例如下所示:default avatar

 

Gravatar

在模板中调用avatars.gravatar()方法并传入Email散列值即可获取Gravatar(gravatar.com)的头像URL:

<img src="{{ avatars.gravatar(email_hash) }}">

Email散列值可以通过下面的方式获取:

import hashlib

avatar_hash = hashlib.md5(my_email.lower().encode('utf-8')).hexdigest()

实际的调用示例如下所示:gravatar

Robohash

Robohash(robohash.org)是一个生成随机机器人头像的服务(目前Gravatar的默认头像中已经支持这一类型,可以通过将default参数设为robohash获取)。在模板中调用avatars.robohash()并传入随机文本作为参数即可获取Robohash的头像URL:

<img src="{{ avatars.robohash(some_text) }}">

实际的调用示例如下所示:

robohash

社交网站头像

Flask-Avatars通过Avatars.io提供了社交头像获取服务,目前支持Facebook、Twitter、Instagram和Gravatar。通过在模板中调用avatars.social_media()方法并传入用户名(username)即可获取对应的头像URL,通过size参数可以设置尺寸,可选值为small、medium和large:

<img src="{{ avatars.social_media(username) }}">

通过platform参数可以设置平台,默认为twitter,可选值为twitter、facebook、instagram和gravatar:

<img src="{{ avatars.social_media(username, platform='facebook') }}">

实际的调用示例如下所示:

avatars.io

生成随机图案头像Identicon

Gravatar服务可能会有不稳定的情况,这种情况下,你可以在本地为用户生成头像(identicon),下面是一个简单的示例:

app.config['AVATARS_SAVE_PATH '] = 'the/path/to/save/avatar'

avatar = Identicon()
filenames = avatar.generate(text=‘一串唯一字符’)

avatar.generate()会根据传入的随机字符创建三个尺寸(可以通过配置变量AVATARS_SIZE_TUPLE自定义)的头像,保存到指定的位置,并返回三个头像文件名。你可以将这个文件名保存到数据库中,并创建一个视图函数来提供头像文件:

from flask import send_form_directory, current_app

@app.route('/avatars/<path:filename>')
def get_avatar(filename):
    return send_from_directory(current_app.config['AVATARS_SAVE_PATH'], filename)

实际生成的头像示例如下所示:identicon

裁剪头像

Flask-Avatars基于Jcrop提供了头像裁剪功能,具体步骤可以参考文档中的相关部分。下面是示例程序中的裁剪页面:裁剪

 

裁剪后的结果:裁剪完成

配置

Flask-Avatars支持的配置列表如下所示:

配置 默认值 说明
AVATARS_GRAVATAR_DEFAULT identicon Gravatar默认头像类型
AVATARS_SAVE_PATH None 头像保存路径
AVATARS_SIZE_TUPLE (30, 60, 150) 头像尺寸三元素元组,格式为 (small, medium, large),用于生成identicon头像和裁剪头像
AVATARS_IDENTICON_COLS 7 Identicon头像一行的色块数量
AVATARS_IDENTICON_ROWS 7 Identicon头像一列的色块数量
AVATARS_IDENTICON_BG None Identicaon头像的背景颜色,传入RGB元组,比如(125, 125, 125)。默认使用随机颜色
AVATARS_CROP_BASE_WIDTH 500 裁剪图片的显示宽度
AVATARS_CROP_INIT_POS (0, 0) 裁剪框起始位置,两元素元组(x, y),默认为左上角
AVATARS_CROP_INIT_SIZE None  裁剪框的尺寸,默认为尺寸元组中设置的l尺寸大小,即AVATARS_SIZE_TUPLE[0]
AVATARS_CROP_MIN_SIZE None 裁剪框的限制最小尺寸,默认无限制
AVATARS_CROP_PREVIEW_SIZE None 预览窗口的尺寸,默认为尺寸元组中设置的m尺寸大小,即AVATARS_SIZE_TUPLE[1]
AVATARS_SERVE_LOCAL False 是否从本地加载Jcrop资源,默认从CDN加载

示例程序

Flask-Avatars的Git仓库中包含三个实例程序,也就是文中的截图对应的程序:

  • examples/basic —— 基本示例
  • examples/identicon —— Identicon示例
  • examples/crop —— 裁剪示例

你可以通过下面的方式来运行实例程序:

$ git clone https://github.com/greyli/flask-avatars.git
$ cd flask-avatars/examples
$ pip install flask flask-avatars
$ cd basic  # 切换到identicon和crop目录可以运行对应的示例程序
$ flask run

这篇文章属于“Flask常用扩展介绍系列”,这个系列的文章目录索引可以在《Flask常用扩展介绍系列文章索引》看到。

相关链接

使用Flask-SQLAlchemy调用create_all()创建数据库表前是否需要导入模型类?

当继承db.Model基类的子类被声明创建时,根据db.Model基类继承的元类中设置的行为,类声明后会将表信息注册到db.Model.metadata.tables属性中。

create_all()方法被调用时正是通过这个属性来获取表信息。因此,当我们调用create_all()前,需要确保模型类被声明创建。如果模型类存储在单独的模块中,不导入该模块就不会执行其中的代码,模型类便不会被创建,进而便无法注册表信息到db.Model.metadata.tables中,所以这时需要导入相应的模块。

因为我们的目的是让模型类被创建,所以不论是导入整个模块还是导入其中某个模型类都可以,并不需要导入全部模型类。

一般情况下,我们不用关心这个问题。在单脚本的Flask程序中自不必说,在使用包组织的Flask程序中,创建程序实例时必然需要导入视图函数所在的模块,或是蓝本所在的模块,而这些模块会导入模型类。

下面我们会通过解析源码简单了解模型类对应的表信息是如何注册到db.Model.metadata.tables中的。

不论是单独使用SQLAlchemy时创建的Base基类,还是使用Flask-SQLAlchemy时创建db对象后的db.Model基类,都是通过declarative_base()函数创建,具体源码如下:

def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
                     name='Base', constructor=_declarative_constructor,
                     class_registry=None,
                     metaclass=DeclarativeMeta):

    lcl_metadata = metadata or MetaData()
    if bind:
        lcl_metadata.bind = bind

    if class_registry is None:
        class_registry = weakref.WeakValueDictionary()

    bases = not isinstance(cls, tuple) and (cls,) or cls
    class_dict = dict(_decl_class_registry=class_registry,
                      metadata=lcl_metadata)

    if isinstance(cls, type):
        class_dict['__doc__'] = cls.__doc__

    if constructor:
        class_dict['__init__'] = constructor
    if mapper:
        class_dict['__mapper_cls__'] = mapper

    return metaclass(name, bases, class_dict)

源码位置:github.com/zzzeek/sqlal

这个函数返回一个元类实例,对应的元类为DeclarativeMeta,这个元类定义了一些特殊行为:

class DeclarativeMeta(type):
    def __init__(cls, classname, bases, dict_):
        if '_decl_class_registry' not in cls.__dict__:
            _as_declarative(cls, classname, cls.__dict__)
        type.__init__(cls, classname, bases, dict_)

    def __setattr__(cls, key, value):
        _add_attribute(cls, key, value)

    def __delattr__(cls, key):
        _del_attribute(cls, key)

源码位置:github.com/zzzeek/sqlal

_as_declarative()函数以及附加的其他多层调用为这个类进行了更多设置,比如添加__table__属性为存储表信息的Table对象,设置metadata属性等等。

其中魔法方法__setattr__()中调用了_add_attribute()函数,这个函数执行了一系列模型类属性的注册操作,其中的一个操作便是向基类 __table__属性指向的Table对象调用append_column()方法添加表字段信息:

def _add_attribute(cls, key, value):
    if '__mapper__' in cls.__dict__:
        if isinstance(value, Column):
            _undefer_column_name(key, value)
            cls.__table__.append_column(value)
            cls.__mapper__.add_property(key, value)
        ...

源码位置:github.com/zzzeek/sqlal

经过这一系列注册操作,表信息就被添加到db.Model.metadata.tables属性中,这个属性返回包含所有表信息的字典(表名称与Table实例的映射),下面是一个示例:

immutabledict({'comment': Table('comment', MetaData(bind=None), Column('id'
, Integer(), table=<comment>, primary_key=True, nullable=False), Column('bo
dy', Text(), table=<comment>), Column('timestamp', DateTime(), table=<comme
nt>, default=ColumnDefault(<function utcnow at 0x03D2E3F0>)), Column('flag'
, Integer(), table=<comment>, default=ColumnDefault(0)), Column('author_id
', Integer(), ForeignKey('user.id'), table=<comment>), Column('photo_id', I
nteger(), ForeignKey('photo.id'), table=<comment>), schema=None)})             

db.Model.metadata存储MetaData类实例,MetaData类实例存储Table类实例的集合,而Table类实例存储模型类对应的数据库表字段信息,可以通过模型类的__table__属性获取。create_all()方法实际调用的是db.Model.metadata.create_all 方法,这个方法会将Table类实例存储的信息转换为数据库模式(通过sqlclchemy.sql.ddl.SchemaGenerator)。

顺便说一句,因为表信息存储在特定的基类中,所以为了正确创建数据库表,你需要对模型类继承的基类调用create_all()方法,即db.create_all(),或是Base.metadata.create_all(engine)。如果你在测试时新创建一个db对象或是Base基类,那么它是不会包含表信息的。

这篇文章来自我在知乎写的这个回答,作为备份。

使用Bootstrap-Flask在Flask项目中集成Bootstrap

Bootstrap-Flask是一个简化在Flask项目中集成前端开源框架Bootstrap过程的Flask扩展。使用Bootstrap可以快速的创建简洁、美观又功能全面的页面,而Bootstrap-Flask让这一过程更加简单和高效。尤其重要的是,Bootstrap-Flask支持最新版本的Bootstrap 4版本。

Bootstrap-Flask logo

Bootstrap-Flask logo

GitHub项目主页:https://github.com/greyli/bootstrap-flask

和Flask-Bootstrap的区别

简单来说,Bootstrap-Flask的出现是为了替代不够灵活且缺乏维护的Flask-Bootstrap。它的主要设计参考了Flask-Bootstrap,其中渲染表单和分页部件的宏基于Flask-Bootstrap中的相关代码修改实现。和Flask-Bootstrap相比,前者有下面这些优点:

  • 去掉了内置的基模板,换来更大的灵活性,提供了资源引用代码生成函数
  • 支持Bootstrap 4
  • 标准化的Jinja2语法
  • 提供了更多方便的宏,比如简单的分页导航部件、导航链接等
  • 宏的功能更加丰富,比如分页导航支持传入URL片段
  • 统一的宏命名,即“render_*”,更符合直觉

安装与初始化

你如果使用过Flask-Bootstrap,那么除了安装时的名称外,这个过程基本没有不同。

安装:

$ pip install bootstrap-flask

初始化:

from flask_bootstrap import Bootstrap
from flask import Flask

app = Flask(__name__)

bootstrap = Bootstrap(app)

如果你使用工厂函数创建程序实例,那么可以使用下面的方式初始化扩展:

from flask_bootstrap import Bootstrap
from flask import Flask

bootstrap = Bootstrap()

def create_app():
    app = Flask(__name__)
    bootstrap.init_app(app)
    
    return app

Bootstrap-Flask提供了哪些功能

2个资源加载函数

在简单的示例程序中,或是在开发时,你可以使用它提供的两个快捷方法来生成Bootstrap资源引用代码,如下所示:

<head>
{{ bootstrap.load_css() }}
</head>
<body>
...
{{ bootstrap.load_js() }}
</body>

7个快捷渲染宏

目前,Bootstrap-Flask一共提供了7个宏,分别用来快捷渲染各类Bootstrap页面组件,并提供了对扩展Flask-WTF、Flask-SQLAlchemy的支持。

模板路径 说明
render_field() bootstrap/form.html 渲染一个WTForms表单字段
render_form() bootstrap/form.html 渲染一个WTForms表单类
render_pager() bootstrap/pagination.html 渲染一个简单分页导航,包含上一页和下一页按钮
render_pagination() bootstrap/pagination.html 渲染一个标准分页导航部件
render_nav_item() bootstrap/nav.html 渲染一个导航条目
render_breadcrumb_item() bootstrap/nav.html 渲染一个面包屑条目
render_static() bootstrap/utils.html 渲染一个资源引用语句,即 <link><script>标签语句

使用方法相当简单,以渲染Flask-WTF(WTForms)的表单类的render_form()宏为例,你只需要从对应的模板路径导入宏,然后调用即可并传入必要的参数:

{% from 'bootstrap/form.html' import render_form %}

{{ render_form(form) }}

你可以在项目仓库的examples目录下找到一个完整的示例程序,示例程序的运行方式如下:

$ git clone https://github.com/greyli/bootstrap-flask.git
$ pip install flask flask-wtf flask-sqlalchemy bootstrap-flask
$ cd bootstrap-flask/examples
$ flask run

现在访问http://localhost:5000即可看到示例程序主页。示例程序包含所有宏的实际调用示例,其中分页宏示例页面如下图所示:

分页宏示例

分页宏示例

欢迎贡献代码

这个项目还刚刚起步,各方面都有需要完善的地方,近期我会为它编写一份完善的文档,欢迎有兴趣的朋友贡献代码