2017 年总结

因为写书的事情忙的晕头转向,以至于这篇2017年总结到现在才写。2017年是个特别的一年,基本上全年都待在家里,全职写作《Flask Web开发实战》这本书。

编程

开源贡献

为多个开源项目贡献了代码,不过大部分都是在对文档进行修补和更新,有时间会写一篇文章来总结一下。

2017 Contributions 情况

2017 Contributions 情况

Web程序

花费较多时间编写了书里的几个示例程序,除了一堆简单的示例程序,相对完善的程序包括留言板、个人博客、图片社交网站、待办事项程序、聊天室(这几个程序的介绍可以在这篇文章看到)。

Flask扩展

随书衍生了几个扩展,除了作为扩展编写示例的Flask-Share,其他的扩展分别是:集成Dropzone.js(文件上传处理)的Flask-Dropzone、集成CKEditor(富文本编辑器)的Flask-CKEditor、集成Bootstrap并旨在替代缺乏维护的Flask-Bootstrap的Bootstrap-Flask以及提供各种头像处理功能的Flask-Avatars

写作

在Stack Overflow上答题

写了大约50个答案,Reputation由535上升到1300左右。

2017 Reputation情况

2017 Reputation情况

知乎专栏Hello, Flask!

知乎专栏Hello, Flask!只在年初写了几篇文章,2月后就没有更新,不过专栏关注人数由2016年的2500上升到6000。

写作《Flask Web开发实战》

从3月开始写作,原计划10月份结束的《Flask Web开发实战》,一直到2017年末都没有结束(仅仅完成了初稿)。

阅读和其他

这一年没有真正意义上的读书,只翻阅了几本写作过程中用到的技术书。

这一年的生活方式因为这本书的写作而完全改变,有时间会写一篇文章来总结这本书的写作过程。虽然没有进行时间统计,但这一年大部分时间都在进行Flask书的写作和相关编程工作。同样因为这本书的写作,大部分的2017年计划都直接移动到2018年计划中了。

《Flask Web开发实战》最新动态

《Flask Web开发实战:入门、进阶与原理解析》是我刚刚完成写作的一本技术书,涵盖了Flask Web开发学习的完整路径,而且包含大量的程序实例。你可以通过下面的文章了解这本书的更多信息:

本书动态:

  • 2017/3/1 开始写作
  • 2017/12/7 初稿完成
  • 2018/1/18 二稿完成
  • 2018/3/26 三稿完成
  • 2018/4/29 四稿完成
  • 2018/5/16 五稿完成
  • 2018/5/22 定稿(六稿)
  • 2018/6/5 确定最终修改,写作正式完结
  • 2018/6/20 完成封面设计初稿
  • 2018/6/22 完成封面文案初稿
  • 2018/6/22 确定英文书名为《Python Web Development with Flask》
  • 2018/8/20 下厂印刷
  • 2018/8/24 Kindle电子书上架(https://www.amazon.cn/dp/B07GST8Z8M
  • 2018/8/26 本书的豆瓣条目页面创建成功
  • 2018/8/28 电子书上架豆瓣阅读(read.douban.com/ebook/5
  • 2018/9/10 电商平台已经可以购买,亚马逊和京东自营预计9/15有货,访问本书主页查看购买链接

为Git仓库里的.idea文件夹正名

在网络上,我曾多次看到人们对于Git仓库中的.idea文件夹的偏见。最近的一次是在某个博客中技术专家对于志愿者提交的项目的点评,其中在“项目规范”中有一条加分项为“没有 .idea 这种不该上传的文件夹”;另一次是在知乎评价某程序员的问题下某个回答的评论中,有人发现该程序员的GitHub仓库里有.idea目录,就居高临下的将其作为理由讽刺该程序员,潜台词即“项目里有.idea = 水平低下”。想当然的,我没看到的类似情况会更多,而这些观点又会影响很多不熟悉具体事实的人,我想我应该尽一份力来改变这个错误的观点继续蔓延。

提示 尽管本文的标题使用了Git,本文的内容同样适用于其他VCS(Version Control System,版本控制系统)。

什么是.idea文件夹

当你使用JetBrains出品的IDE(Integrated Development Enviroment,集成开发环境)时,比如PyCharm、WebStorm或IntelliJ IDEA等,它们会在创建项目后在项目根目录创建一个.idea文件夹,其中保存了项目特定的配置文件。

至于为什么命名为.idea,则是因为IntelliJ IDEA是JetBrains最早推出的IDE(JetBrains一开始叫IntelliJ),因此使用IDEA作为配置文件夹的名称。按照这个SO回答里最高票答案的猜测,或许IntelliJ IDEA这个名字的含义是这样组成的:

  • Intelli ===> Intelligent
  • J ===> Java
  • Idea ===>IDE that is Advanced or Idea just means idea( I have a good idea …like this ) …

是否应该把.idea提交进Git仓库

这个问题没有唯一的答案,在Stack Overflow有很多类似的讨论。总的来说,开发者可以自由选择是否把IDE相关的配置文件(即.idea目录下的文件)提交到Git仓库中:

  • 如果你想让其他使用相同IDE的用户可以更方便(规范)的对项目进行开发,那么就把它提交到Git仓库中。
  • 如果你觉得Git仓库不应该包含和项目本身无关的文件,那么也可以不将它提交到Git仓库中。

正确的提交方法

当然,将.idea目录整个提交到Git仓库的行为并不可取。因为.idea目录下的文件中有包含隐私的内容,比如你的文件操作变动、用户词典、系统环境变量、数据库密码等等,这些文件对项目其他潜在的参与者没有用处,而且会泄露你的隐私。

按照JetBrains官方的建议,在使用VCS时我们应该遵循下面的原则:

分享下面的文件:

  • 除了workspace.xml、usage.statistics.xml和tasks.xml以外.idea目录下的所有文件
  • 所有可以被在不同模块目录下定位到的.iml模块文件(适用于IntelliJ IDEA)

谨慎分享下面的文件

  • Android artifacts that produce a signed build,因为它们包含keystore密码(前半句不理解,暂时保留原文)
  • 在IntelliJ IDEA 13 和之前的版本中的dataSources.ids和datasources.xml文件,它们包含数据库密码

避免分享下面的文件:

  • 对于使用Gradle或Maven的项目,避免分享.iml和.idea/modules.xml文件,因为它们会在导入时生成
  • gradle.xml文件
  • 用户字典(dictionaries文件夹)
  • .idea/libraries目录下的XML文件,因为它们会从Gradle或Maven项目中生成

另外,对于旧的项目格式(.ipr/.iml/.iws files)来说:

  • 分享项目.ipr文件和所有的.iml模块文件,不要分享.iws文件,因为它存储用户特定设置。

对于Git,你可以参考GitHub提供的JetBrains适用的.gitignore模板

我的新书中包含多个Flask项目,这些项目中的.gitignore文件则是通过gitignore.io来生成的。你可以在gitignore.io主页的输入框中输入你使用的操作系统、编程语言和IDE,它会快速为你来生成一份适用这些语言和平台的.gitignore规则,比如这个是输入Python+PyCharm生成的模板。你可以在这个模板的基础上添加自定义规则。

总结

如果你不想在Git仓库中提交IDE相关的配置文件,那么你可以忽略.idea文件夹;相反,你也可以有选择的把.idea目录下的文件提交进Git仓库。也就是说,项目Git仓库中是否包含.idea文件夹与程序员的开发水平并没有直接关系。

我加入了Pallets Projects开发团队

今天早晨起来检查收件箱,收到了David Lord(Flask核心维护者)的邮件,他同意了我的申请——前段时间邀请他为我的新书《Flask Web开发实战》写推荐语,同时申请加入Pallets Projects开发团队(Pallets Team)。

Pallets Projects是什么?

简单来说,Pallets Projects就是“Flask项目集合”,为了更好的管理Flask相关项目而由Pocoo分化而来。它同时也是一个GitHub Organization,包括了所有Flask相关的项目,比如Flask、Werkzeug、Jinja、Click等等。

这意味着什么?

这意味着我对于Flask项目拥有更多的操作权限,可以更好的向Flask贡献代码,并且可以帮助处理Issue和Pull Request。

 

一本更好的Flask书——《Flask Web开发实战》

如果你想了解这本书的最新动态,可以关注我的公众号Twitter豆瓣,也可以访问本书主页 http://helloflask.com/book

欢迎加入 HelloFlask 论坛QQ群(419980814)、微信群(备注“入群”)或 Telegram 群组讨论相关话题。

购买方式

欢迎访问本书的豆瓣图书页面撰写书评或短评。

电子书各平台的排版效果不一,购买前请先进行试读。

为什么说这是一本更好的Flask书

现有的几本Flask书包含下面这些问题:
 
  • 内容较旧,不论是Flask本身,还是其他扩展和Python库。
  • 包含误区或不完善的代码实现,比如在使用SQLAlchemy建立数据库关系时,出于性能的考虑,一般不会把加载关系记录的方式设为dynamic。
  • 示例程序过于简单,比如甚至没有删除帖子的功能。
  • 内容比较单一,仅包含入门知识。
相对的,这本《Flask Web开发实战:入门、进阶与原理解析》当然避免了以上问题:
 
  • Flask使用最新的1.0.2版本,使用Pipenv管理示例程序依赖,所有扩展和其他Python库均使用最新版本。不仅如此,项目中使用的前端框架Bootstrap(4.1)、Materialize(1.0)和Semantic-UI(2.3)也均使用最新版本。
  • 纠正了常见误区,并在相应位置给出提示。
  • 包含多个不同复杂程度的示例程序,尽可能的让程序贴近真实使用情况。
  • 如副书名所示,这本书除了基本的入门知识,还包含进阶内容和原理解析。
看到书名,你的第一想法也许是:为什么书名和Miguel Grinberg的书那么像?起书名并非由我一人决定,而且技术书的起名没有多大自由度,你不仅要考虑加入合适的关键词,还要考虑到如何凸显书的主要特点。“Flask Web开发”的确是简洁又突出重点的好名字,我们在后面添加了“实战”,用来着重体现这本书的最大特点。

3个推荐语

这本书内容翔实,推荐给想要系统学习Flask的人。不要漏掉书中的小知识点哦。
             —— Flask开发团队(Pallets)核心维护者 Hsiaoming Yang(lepture)
这本《Flask Web开发实战》非常有趣,是一本能够给读者带来帮助的书,作为一个Flask框架的爱好者,我也很期待这本书的出版。
—— 《Redis设计与实现》作者 黄健宏(huangz)
Grey Li is a really helpful contributor to Flask and the Python community. He is making it easier for more people to learn and use Flask.
—— Flask开发团队(Pallets)核心维护者 David Lord(davidism)

8个示例程序、5个项目实例、1个扩展

包含丰富的实例是这本书的最大特点,这也是为什么要把“实战”放到书名里。
 
书的第一部分(基础篇)共6章,每一章都包含一个示例程序,示例程序包含每章涉及的大部分代码,你可以实际运行程序来查看效果。以第5章《数据库》为例,除了一个简单的演示“增删查改”的笔记程序外,示例程序中还包含“一对多”、“多对一”、“一对一”、“多对多”、“一对多+双向关系”、“一对多+双向关系+使用backref简化关系定义”、“级联设置”“数据库时间监听函数”等15(2 * 7 + 1)个模型类定义和2个事件监听函数(两种实现方式)。
第1~6章以及第13章的示例程序统一包含在helloflask仓库中的demos目录下。
第二部分(实战篇)共5章,每一章都通过一个程序来组织起所有的知识点。这5个程序分别为:留言板SayHello、个人博客Bluelog、图片社交网站Albumy、待办事项程序Todoism和聊天室CatChat。你可以在下面的《章节概括》部分查看每一个示例程序所涉及的知识点。
和《Flask Web开发》中的做法类似,这部分的示例程序均使用Git标签来组织不同阶段的代码,方便你签出相应的版本或是进行对比。
第三部分(进阶篇)第13章《性能优化》通过两个实例程序来介绍使用Flask-Caching和Flask-Assets对Flask程序添加缓存和进行静态资源优化的前后变化对比。第15章《Flask扩展开发》则通过一个简单的为页面中添加社交分享组件的Flask-Share扩展来完整介绍从创建Git仓库到使用twine将包上传到PyPI的完整扩展开发流程。
你可以在文章《Flask Web开发实战》中的示例程序们一文中了解这些程序的具体信息,其中包含大量程序界面截图。
这几个项目的源码和在线Demo链接均可以在helloflask.com看到。

满足80%的读者提议

在文章《写一本Flask书》中,很多知友通过评论给出了希望看到的内容和建议。我做了简单的收集和处理,粗略的浏览,大概有80%的读者提议得到了满足。

内容丰富,大约700页

这本书在介绍基础知识外,还引入了许多进阶技巧。第一部分从第2章开始每一章都包含一个《进阶实践》章节,其中包含一些常见的技巧,比如“如何安全的跳转回上一页”,“使用WTForms表单内置的中文错误消息”,“数据库事件监听”等,具体可以参考下面的《目录》部分。
 
另外,这本书还介绍了一些被其他Flask书忽略,但又非常实用的知识,比如实现AJAX时Flask和JavaScript交互数据的方式、通过AJAX实现的资料弹窗、在弹窗中实现动态的关注收藏按钮、通过AJAX实现简单的单页程序、实时双向通讯、国际化和本地化、全文搜索、第三方登录、Markdown支持、代码语法高亮、头像裁剪、设置缓存等。
 
本书会尽量提供多个技术选型,比如第6章《电子邮件》中,除了介绍使用Flask-Mail实现发送电子邮件,还介绍了使用事务邮件服务SendGrid发送电子邮件的两种方式;在第14章《部署上线》中,我介绍了一个基本的服务器初始化过程,包括安装基本的而库、设置SSH密钥登录、设置防火墙等部署,接着才是Gunicorn、Nginx和Supervisor的使用介绍。除了Linux部署,还介绍了使用PythonAnywhere和Heroku的程序部署流程。
 
尽管如此,在多次修改的过程中,我还是删掉了大量内容(近8万字约240页):
 
  • 在Flask程序中使用Celery
  • Fabric自动化部署
  • 使用Tox自动化测试
  • 使用Sentry处理日志
  • 使用Docker开发和部署Flask程序
  • NoSQL数据库的使用介绍
  • 使用Frozen-Flask静态化处理程序
  • 静态部署
  • Flask-SSE的使用
  • Travis CI、Coverall的使用
  • 使用GitHub-Flask集成GitHub登录
这些内容或是偏离主题,或是有了更好的替代内容,删掉这些内容一方面可以保证书的内容贴近主题,同时也可以避免成书太厚。因为内容丰富,所以最终完稿共980页,成书大概会在700页左右。
对于这些删减掉的内容,我会抽取有价值的内容并进行完善后陆续发布在专栏中。

关于我

我是李辉,一个Python开发者,Flask开发团队(Pallets Team)的成员。我在GitHub上维护了几个Flask扩展项目,也尝试为多个Flask相关项目贡献代码和处理Issue;除了知乎,我偶尔会在Stack Overflow上回答一些Flask相关的问题。如果你想了解我的项目或书的最新动态,可以关注我的Twitter新浪微博账号。

章节概括

下面是本书的章节概括,简单的介绍了各章的内容,你可以通过下面的《目录》部分来了解详细的内容。本书由四部分组成,分别为基本篇、实战篇、进阶篇和附录,共16章。本书的章节安排经过精心的设计,力求让读者可以循序渐进的掌握Flask开发基础知识和技巧。
 
第一部分:基础篇 介绍Flask开发相关的基础知识。
 
  • 第1章:搭建开发环境,编写一个最小的Flask程序并运行它,了解Flask基本知识。
  • 第2章:介绍了Flask与HTTP的交互方式以及相关的Flask功能。
  • 第3章:Jinja2模板的使用和技巧。
  • 第4章:Web表单的创建和表单数据的验证。
  • 第5章:在Flask程序中使用数据库进行CRUD操作,各类数据库关系的建立。
  • 第6章:在Flask程序中发送电子邮件的几种方式,使用Flask-Mail通过SMTP服务器发送,或是通过事务邮件服务SendGrid(SMTP和Web API两种方式)。
第二部分:实战篇 通过几个示例程序来介绍Flask开发中的各类功能实现和技巧。
 
  • 第7章:通过一个简单的留言板程序SayHello介绍Web开发基本流程和基本的项目管理方式,对第一部分的基础知识进行简单的回顾,最后还介绍了Faker、Flask-Moment和Flask-DebugToolbar的使用。
  • 第8章 :通过个人博客程序Bluelog介绍CRUD操作、用户认证(Flask-Login)、文章评论和回复、后台管理等功能,其中还包括网站主题更换,渲染导航链接等小技巧。
  • 第9章:通过图片社交程序Albumy介绍用户注册和认证、用户权限管理、图片上传(Flask-Dropzone)与处理(Pillow)、用户头像、复杂的数据库关系、复杂的数据库查询、全文搜索(Flask-Whooshee)等内容。
  • 第10章:通过待办事项程序Todoism介绍单页应用、国际化与本地化(Flask-Babel)、Web API、OAuth服务器端实现等内容。
  • 第11章:通过聊天室程序CatChat介绍Websocket应用(实时双向通讯,通过Flask-SocketIO实现)、OAuth客户端实现(第三方登录,通过Flask-OAuthlib实现)、Markdown支持(markdown)、代码语法高亮(pygments)等内容。
第三部分:进阶篇 介绍Flask程序的部署流程:测试、性能优化、部署上线;介绍Flask开发的进阶话题:Flask扩展开发和Flask源码与机制分析。
 
  • 第12章:介绍Flask程序的自动化测试,包括单元测试和UI测试的编写、计算测试覆盖率和代码质量检查。
  • 第13章:对Flask程序进行性能优化的主要措施,包括函数与数据库查询的性能分析、缓存的使用(Flask-Caching)、静态文件优化(Flask-Assets)。
  • 第14章:介绍部署Flask程序前的准备,以及部署到Linux服务器和云平台Heroku、PythonAnywhere的完整流程。
  • 第15章:通过扩展Flask-Share来介绍编写Flask扩展的完整流程,从创建项目到上传到PyPI。
  • 第16章:介绍了Flask的一些设计理念,包括底层WSGI的相关实现,并对各个主要功能点进行源码分析。

书写完了

历时一年零三个月,Flask书的写作终于结束,目前已基本进入正式排版阶段,预计八月底可以上市。

这本书的写作时间线大致如下:

  • 2017/3/1 开始写作
  • 2017/12/7 初稿完成
  • 2018/1/18 二稿完成
  • 2018/3/26 三稿完成
  • 2018/4/29 四稿完成
  • 2018/5/16 五稿完成
  • 2018/5/22 定稿(六稿)
  • 2018/6/5 确定最终修改
  • 进入出版流程,开始排版

一本书的写作给我自己带来了很多变化,虽然有时感觉非常疲惫,但算得上是一个奇妙的体验。有时间会写一篇文章记录一下这一年的写作经历。

Pipenv:新一代Python项目依赖管理工具

UPDATE(2019/8/31):不要用 Pipenv

什么是Pipenv

Pipenv是Kenneth Reitz在2017年1月发布的Python依赖管理工具,现在由PyPA维护。你可以把它看做是pip和virtualenv的组合体,而它基于的Pipfile则用来替代旧的依赖记录方式(requirements.txt)。
 
在这篇文章里,我将会以旧的依赖管理工作流程作为对比来介绍Pipenv的基本用法,更详细的用法可以参考Pipenv文档,或是Kenneth Reitz在PyCon 2018的演讲《Pipenv: The Future of Python Dependency Management》
 
顺便说一句,我的还没想好名字的Flask书中所有示例程序都使用了Pipenv进行依赖管理。
提示 如果你对virtualenv的用法以及虚拟环境的概念不熟悉的话,可以通过专栏的旧文《Flask出发之旅》进行简单的认识。

为什么使用Pipenv

Pipenv会自动帮你管理虚拟环境和依赖文件,并且提供了一系列命令和选项来帮助你实现各种依赖和环境管理相关的操作。简而言之,它更方便、完善和安全。你可以通过Pipenv文档开头的介绍来了解它的详细特性。Pipenv的slogan是“Python Dev Workflow for Humans”,作为人类,当然应该尝试一下……

如何使用Pipenv

假设我们要编写一个博客程序,项目的依赖是Flask和Flask-WTF。顺便说一句,可以使用下面的命令安装Pipenv:
$ pip install pipenv
下面我会通过不同操作来给出所需命令的对比,OLD(旧)表示使用pip和virtualenv,NEW(新)表示使用Pipenv。
 

创建虚拟环境

  • OLD
$ virtualenv venv 
提示 这里的venv是虚拟环境目录的名称,你可以自由更改,这会在你的项目根目录创建一个venv文件夹,其中包含独立的Python解释器环境。
  • NEW
$ pipenv install
Pipenv会自动为你创建虚拟环境,自动生成一个随机的虚拟环境目录名。
 

激活虚拟环境

  • OLD
在Linux或macOS系统中:
$ . venv/bin/activate
Windows:
> venv\Scripts\activate
  • NEW
$ pipenv shell
此外,Pipenv还提供了一个pipenv run命令,在该命令后附加的参数会直接作为命令在虚拟环境中执行,这允许你不必显式的激活虚拟环境即可在虚拟环境中执行命令。比如,pipenv run python会启动虚拟环境中的Python解释器。
 

安装依赖到虚拟环境

  • OLD
$ . venv/bin/activate # 需要先激活虚拟环境
(venv)$ pip install flask flask-wtf 
  • NEW
使用Pipenv,不管你是否激活了虚拟环境,都可以通过pipenv install命令安装:
$ pipenv install flask flask-wtf 
事实上,对一个新项目来说,你不必手动使用pipenv install来创建虚拟环境。当使用pipenv install xxx直接安装依赖包时,如果当前目录不包含虚拟环境,Pipenv会自动创建一个。
 

记录依赖

  • OLD
(venv)$ pip freeze > requirements.txt
这个命令会把依赖列表写入requirements.txt文件。每当你安装或卸载了依赖包时,都需要手动更新这个文件。你必须保持谨慎,否则非常容易把依赖列表弄乱。
 
  • NEW
使用Pipenv时,什么都不必做,Pipenv会自动帮你管理依赖。Pipenv会在你创建虚拟环境时自动创建Pipfile和Pipfile.lock文件(如果不存在),并且会在你使用pipenv install和pipenv uninstall命令安装和卸载包时自动更新Pipfile和Pipfile.lock。
附注 Pipfile用来记录项目依赖包列表,而Pipfile.lock记录了固定版本的详细依赖包列表。

在部署环境安装依赖

  • OLD
当我们需要在一个新的环境,比如部署上线环境安装所有依赖时,我们需要重复上面的多条命令:
$ virtualenv venv # 创建虚拟环境
$ . venv/bin/activate # 激活虚拟环境
(venv)$ pip install -r requirements.txt # 安装requirement.txt中记录的依赖
  • NEW
使用Pipenv则只需要执行pipenv install,它会自动安装Pipfile中记录的依赖:
$ pipenv install

区分开发依赖

  • OLD
使用requirements.txt时,我们通过会单独创建一个requirements-dev.txt文件来手动加入开发依赖。比如项目开发时才会用到pytest,那么你需要手动创建这个文件,然后写入:
-r requirements.txt
pytest==1.2.3
在新的开发环境安装依赖时,你需要安装这个文件中的依赖:
(venv)$ pip install -r requirements-dev.txt
  • NEW
使用Pipenv时,你只需要在安装pytest时添加一个–dev选项,它会自动被分类为开发依赖(写入Pipfile的dev-packages一节中):
$ pipenv install pytest --dev
在新的开发环境安装依赖时,也只需要在pipenv install命令后添加–dev选项即可一并安装开发依赖:
$ pipenv install --dev

总结

为了让你更轻松的过渡,Pipenv甚至提供了读取和生成requirements.txt文件的功能(在使用pipenv install命令时它会自动读取requirements.txt文件安装依赖并生成Pipfile文件)。希望这篇文章可以让你更快的上手Pipenv。
 
– – – – –
 
 
 

新发布的Flask1.0带来了哪些新变化?

就在五个小时前,Flask终于发布了8年来的第一个主版本——1.0,这个版本带来了很多新的变化,让我们拥抱变化吧,请使用下面的命令来更新:

$ pip install -U flask

主要的变化

  • 从0.11开始,Flask引入了命令行支持,建议使用flask run命令来取代app.run()方法。1.0版本支持将Flask_APP环境变量设为包名称。Flask会自动在app.pywsgi.py寻找名称为appapplication的程序实例,同时会在传入的模块或包中寻找名称为create_app()make_app()的0参数的工厂函数。
  • 新添加了一个FLASK_ENV环境变量来设置Flask运行的环境,默认为production。在开发时需要设为development,这会自动开启调试器和重载器(即调试模式),避免直接使用FLASK_DEBUG
  • 当安装了python-dotenv时,flask命令(比如flask runflask shell等)会自动从项目根目录下的.flaskenv.env文件中导入环境变量,这样可以避免每次都手动设置环境变量。前者用来存储公开的Flask相关的环境变量,比如FLASK_APPFLASK_ENV等,后者用来存储包含敏感信息的环境变量,比如邮箱密码,API密钥等等,需要将文件名添加到.gitignore中。
  • 开发服务器默认开启多线程支持。
  • 日志系统被极大的简化,日志器总是命名为flask.app,它只会在没有日志处理器注册的情况下才添加处理器,而且不会移除已经存在的处理器。
  • 测试客户端(test_client)支持使用json参数来传入JSON数据,你可以使用这个特性来测试Web API。返回的响应对象添加了get_json方法来获取JSON数据。
  • 添加了一个 test_cli_runner() 方法,它可以用来触发使用Click注册的flask命令,我们可以使用它来测试自定义的flask命令。
  • 添加了一个flask routes命令,用来输出程序所有注册的路由。
  • 移除了旧的扩展导入代码flask.ext,还在使用旧的导入方式的扩展将无法使用。
  • 带来了一个重写后的Flaskr教程,包含了大量重构和改进,其他部分的文档也包含大量更新。
  • 和大多数项目一样,1.0版本不再支持Python2.6和3.3。

你可以访问Flask Changelog – Flask 1.0 documentation查看完整的Changelog,并了解每一个变化所对应的PR或Issue。

顺便说一句

正在写的Flask书将在五月初结束写作,不过距离上市还需要一小段时间(按照正常的出版流程,大概是40天~三个月区间内)。这本书的内容完全采用1.0版本的Flask,其他库也都使用最新版,包括Bootstrap等前端库。另外,专栏之前发布的文章相关的示例程序已经过时,请不要参考,可以阅读Flask文档中更新的Flaskr教程。如果不着急的话,可以等一等,新书会带来几个更完善的示例程序。

Flask-CKEditor:为Flask项目集成富文本编辑器

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

富文本编辑器即WYSIWYG(What You See Is What You Get)编辑器(所见即所得编辑器)。在Web程序中可用的开源富文本编辑器中,CKEditor是一个流行的选择。Flask-CKEditor简化了将CKEditor集成到Flask项目中的过程,可以让你方便的在Flask项目中添加富文本编辑器。它包含下面这些特性:

  • 提供WTForms/Flask-WTF集成支持
  • 支持图片上传与插入
  • 通过Flask配置来设置编辑器的语言、高度等参数
  • 支持代码块语法高亮

《Flask Web开发实战》中的第2个示例程序(博客程序Bluelog)使用了这个扩展。

基本用法

安装

首先使用pip或Pipenv等工具安装:

$ pip install flask-ckeditor

初始化扩展

一般情况下,你只需要导入并实例化CKEditor类,并传入程序实例即可:

from flask_ckeditor import CKEditor

app = Flask(__name__)
ckeditor = CKEditor(app)

如果你使用了工厂函数,那么也可以调用init_app()方法来进行初始化:

from flask_ckeditor import CKEditor

ckeditor = CKEditor()

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

引入CKEditor资源

为了使用CKEditor,我们首先要在模板中引入CKEditor的JavaScript等资源文件。推荐的做法是自己编写资源引用语句,你可以在CKEditor提供的Online Builder构建一个自定义的资源包,下载解压后放到项目的static目录下, 并引入资源包内的ckeditor.js文件,比如(实际路径按需调整):

<script src="{{ url_for('static', filename='ckeditor/ckeditor.js') }}"></script>

如果你不需要自定义,那么也可以从CDN加载:

<script src="//cdn.ckeditor.com/4.9.2/standard/ckeditor.js"></script>

最后,作为替代选项,你也可以使用Flask-CKEditor提供的ckeditor.load()方法来生成引用语句:

{{ ckeditor.load() }}

它默认从CDN加载资源,将配置变量CKEDITOR_SERVE_LOCAL设为True会使用扩展内置的本地资源。另外,你也可以使用custom_url参数来使用自定义资源包:

{{ ckeditor.load(custom_url=url_for('static', filename='ckeditor/ckeditor.js')) }}

创建CKEditor文本区域

Flask-CKEditor提供了两种方式来CKEditor文本区域:

1. 与WTForms/Flask-WTF集成

Flask-CKEditor提供了一个CKEditorField字段类,和你平时从WTForms导入的StringField、SubmitField用法相同。事实上,它就是对WTForms提供的TextAreaField进行了包装。

作为示例,我们可以创建一个写文章的表单类。这个表单类包含一个标题字段(StringField),一个正文字段(CKEditorField)和一个提交字段(SubmitField)。你会看到,其中的正文字段使用了CKEditorField。

from flask_wtf import FlaskForm
from flask_ckeditor import CKEditorField
from wtforms import StringField, SubmitField

class PostForm(FlaskForm):
    title = StringField('Title')
    body = CKEditorField('Body')
    submit = SubmitField('Submit')

 

在渲染文本编辑区域的模板中,我们可以像往常一样渲染表单:

<form method="post">
    {{ form.title.label }}{{ form.title() }}
    {{ form.body.label }}{{ form.body() }}
    {{ form.submit() }}
</form>

{{ ckeditor.load() }}
{{ ckeditor.config(name='body') }}

唯一需要注意的是,我们需要在资源引用语句后调用ckeditor.config()方法来让对CKEditor进行配置和初始化,并将name参数的值设为CKEditor字段的属性名,这里即body。

当表单提交后,你可以像其他字段一样通过form.attr.data属性来获取数据,这里的文本区域数据即form.body.data。

2. 手动创建

如果你不使用WTForms/Flask-WTF,那么可以直接使用Flask-CKEditor提供的ckeditor.create()方法在模板中创建文本编辑区域:

<form method="post">
    {{ ckeditor.create() }}
    <input type="submit">
</form>

{{ ckeditor.load() }}
{{ ckeditor.config() }}  <!-- 这时不用设置name参数 -->

在表单被提交后,你可以使用ckeditor作为键从表单数据中获取对应的值,即request.form.get('ckeditor')。

提示 完整的示例程序在examples/basic/examples/without-flask-wtf目录下。

配置变量

Flask-CKEditor提供了下面这些配置变量:

配置 默认值 说明
CKEDITOR_SERVE_LOCAL False 使用内置的ckeditor.load()方法时,设置是否使用本地资源,默认从CDN加载
CKEDITOR_PKG_TYPE 'standard' CKEditor资源包的类型,basicstandardfull中的一个
CKEDITOR_LANGUAGE None 设置CKEditor文本编辑器的语言,默认会自动探测用户浏览器语言,所以一般不需要设置。你也可以设置ISO 639格式的语言码,比如zhenjp等
CKEDITOR_HEIGHT CKEditor默认 编辑器高度,单位为px
CKEDITOR_WIDTH CKEditor默认 编辑器宽度,单位为px
CKEDITOR_FILE_UPLOADER None 处理上传文件的URL或端点
CKEDITOR_FILE_BROWSER None 处理文件浏览的URL或端点
CKEDITOR_ENABLE_MARKDOWN False 设置是否开启markdown插件,需要安装对应插件
CKEDITOR_ENABLE_CODESNIPPET False 设置是否开启codesnippet插件(插入代码块),需要安装对应插件
CKEDITOR_CODE_THEME 'monokai_sublime' 当使用codesnippet插件时,设置语法高亮的主题
CKEDITOR_EXTRA_PLUGINS [] 在CKEditor中开启的额外扩展列表,对应的扩展需要被安装

图片上传

在使用文本编辑器写文章时,上传图片是一个很常见的需求。在CKEditor中,图片上传可以通过File Browser插件实现。在服务器端的Flask程序中,你需要做三件事:

  1. 创建一个视图函数来处理并保存上传文件
  2. 创建一个视图函数来获取图片文件,类似Flask内置的static端点
  3. 将配置变量CKEDITOR_FILE_UPLOADER设为这个视图函数的URL或端点值

完整的代码示例如下所示:

from flask_ckeditor import upload_success, upload_fail

app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'

@app.route('/files/<path:filename>')
def uploaded_files(filename):
    path = '/the/uploaded/directory'
    return send_from_directory(path, filename)

@app.route('/upload', methods=['POST'])
def upload():
    f = request.files.get('upload')  # 获取上传图片文件对象
    # Add more validations here
    extension = f.filename.split('.')[1].lower()
    if extension not in ['jpg', 'gif', 'png', 'jpeg']:  # 验证文件类型示例
        return upload_fail(message='Image only!')  # 返回upload_fail调用
    f.save(os.path.join('/the/uploaded/directory', f.filename))
    url = url_for('uploaded_files', filename=f.filename)
    return upload_success(url=url) # 返回upload_success调用

注意 传入request.files.get()的键必须为'upload', 这是CKEditor定义的上传字段name值。

在处理上传文件的视图函数中,你必须返回upload_success()调用,每将url参数设置为获取上传文件的URL。通常情况下,除了保存文件,你还需要对上传的图片进行验证和处理(大小、格式、文件名处理等等,具体可以访问这篇《Flask文件上传(一):原生实现》了解),在验证未通过时,你需要返回upload_fail()调用,并使用message参数传入错误消息。

当设置了CKEDITOR_FILE_UPLOADER配置变量后,你可以在编辑区域点开图片按钮打开的弹窗中看到一个新的上传标签。另外,你也可以直接将图片文件拖拽到编辑区域进行上传,或复制文件并粘贴到文本区域进行上传(CKEditor >= 4.5)。

提示 对应的示例程序在examples/image-upload/目录下。

如果你使用的 CKEditor 版本小于 4.5,则使用下面的方式实现:

from flask import send_from_directory

app.config['CKEDITOR_FILE_UPLOADER'] = 'upload'  # this value can be endpoint or url

@app.route('/files/<filename>')
def uploaded_files(filename):
    path = '/the/uploaded/directory'
    return send_from_directory(path, filename)

@app.route('/upload', methods=['POST'])
@ckeditor.uploader
def upload():
    f = request.files.get('upload')
    f.save(os.path.join('/the/uploaded/directory', f.filename))
    url = url_for('uploaded_files', filename=f.filename)
    return url

为图片上传请求添加 CSRF 保护

如果你想为图片上传的请求添加 CSRF 保护,可以通过 CSRFProtect 实现(Flask-WTF 内置),首先安装 Flask-WTF:

$ pip install flask-wtf

然后初始化扩展:

from flask_wtf import CSRFProtect

csrf = CSRFProtect(app) 

Flask-CKEditor 0.4.3 版本内置了对 CSRFProtect 的支持,当使用 CSRFProtect 时,只需要把配置变量 `CKEDITOR_ENABLE_CSRF` 设为 `True` 即可开启 CSRF 保护:

app.config['CKEDITOR_ENABLE_CSRF'] = True

顺便说一句,在 Flask-CKEditor 内部需要把 CSRF 令牌放到上传图片的 AJAX 请求首部,这通过 CKEditor 4.9.0 版本新添加的一个配置选项 fileTools_requestHeaders 实现,这个配置可以用来想文件上传请求插入自定义的首部字段 。所以,如果想要实现 CSRF 保护,CKEditor 的版本需要大于或等于 4.9.0。

代码语法高亮

代码语法高亮可以通过Code Snippet插件实现(基于hightlight.js),你可以将配置变量CKEDITOR_ENABLE_CODESNIPPET设为Ture来开启。在此之前,你需要确保安装了这个插件(内置的资源包包含了这个插件)。

为了正确渲染代码块,你还需要引入对应的资源文件,最简单的方式是使用Flask-CKEditor提供的ckeditor.load_code_theme()方法:

<head>
 ...
 {{ ckeditor.load_code_theme() }}
</head>

你可以通过配置变量CKEDITOR_CODE_THEME来设置语法高亮的主题,默认为monokai_sublime,你可以在这个页面看到所有可用的主题对应的字符串。

提示 对应的示例程序在examples/codesnippet/目录下。

使用示例程序

项目仓库中提供了5个示例程序,分别展示基本用法、图片上传插入、代码语法高亮、Markdown模式和不使用Flask-WTF/WTForms。以基本示例程序为例,你可以通过下面的命令来获取并运行它:

$ git clone https://github.com/greyli/flask-ckeditor
$ cd flask-ckeditor/examples
$ pip install -r requirements.txt
$ python basic/app.py

然后在浏览器访问http://127.0.0.1:5000。

另外,helloflask仓库里在demos/form目录下的示例程序也包含一个Flask-CKEditor使用示例。

相关链接

 

我的第一个Chrome扩展:GhostPage(网页X光)

没有接触过Chrome扩展的时候,觉得它一定很复杂。crx的后缀也让我猜不出是用什么语言编写的,现在才发现不过就是HTML+CSS+JavaScript……

这个扩展本身很简单,实际只有一行代码。Google提供了一个非常好的入门指导,过一遍基本上就可以编写简单的扩展了。这个扩展作用也就是通过去掉页面样式来检查页面结构(参考这篇文章),可以用来解决下面的问题:

  • 元素超出边线,挤出的空白导致多余的平行滚动
  • 元素不对齐

使用这个扩展配合Chrome的检查工具可以快速的定位到出问题的元素。

Chrome商店的地址:
https://chrome.google.com/webstore/detail/ghostpage/hegpcollkgldlimbhkimijhhhoaicipp

crx文件下载(备用):https://chrome-extension-downloader.com/77577d1b5c39e945e597a9981f6e0ee6/hegpcollkgldlimbhkimijhhhoaicipp/GhostPage_v1.0.crx

附注:虽然扩展编写很简单,但为了交5美元的开发者认证费用却花了我一天时间。尝试了下面几种方式均失败了:

  • 全球付
  • Google Play礼品卡
  • 其他几个虚拟信用卡服务

最后花了两倍多的价格在淘宝上买了一个虚拟信用卡,第二天中午扣款成功了。

Flask-Dropzone:为你的Flask程序添加文件上传功能

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

某天在Stack Overflow上看到一个关于Dropzone.js的问题,就去研究了一下,写了一个回答,顺便写了一个小demo。我发现,如果有一个集成Dropzone.js到Flask,并且简化设置步骤的扩展,肯定要比其他上传方式简单的多——于是就有了Flask-Dropzone

Flask-Dropzone在模板中提供了一些方法来帮助你创建上传区域,引入相关资源。你只需要添加一些配置就可以实现上传类型的过滤,文件大小限制,上传后跳转等功能。当然,你还要自己编写视图函数来处理和保存文件,并进行服务器端的二次验证。

《Flask Web开发实战》中的第3个示例程序(图片社交程序Albumy)使用了这个扩展。

用法说明

安装

$ pip install flask-dropzone

初始化

和其他扩展类似,你可以通过实例化Dropzone类,并传入程序实例app进行初始化:

from flask_dropzone import Dropzone

app = Flask(__name__)
dropzone = Dropzone(app)

在使用工厂函数创建程序实例时,你也可以使用init_app()方法:

from flask_dropzone import Dropzone

dropzone = Dropzone()

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

引入Dropzone.js资源

你需要自己手动编写引入Dropzone.js的CSS和JavaScript资源的语句。在开发时,或对于玩具项目,你可以可以使用Flask-Dropzone提供的两个快捷方法:

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

创建并美化上传区域

如果你不需要对上传区域的样式有太多控制,那么你只需要在想要渲染上传区域的地方使用dropzone.create()方法:

{{ dropzone.create(action='处理上传文件的路由URL') }}

记得把action的值更改成你要处理文件上传的的URL。你可以使用style()方法为上传区域添加简单的自定义样式:

<head>
...  <!-- 在引入Dropzone.js的CSS文件后调用style()方法 -->
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%') }}
</head>

上传区域的截图示例如下所示:

Flask-Dropzone上传截图

Flask-Dropzone上传截图

在服务器端处理并保存上传文件

当文件被拖拽到上传区域,或是点击上传区域选择上传文件后,这些文件会以AJAX请求的形式发送到你在dropzone.create()方法中使用action参数传入的URL。我们需要在服务器端创建对应的视图函数来处理这些请求,下面是一个最基本的示例:

import os

from flask import Flask, request
from flask_dropzone import Dropzone

app = Flask(__name__)

dropzone = Dropzone(app)

@app.route('/uploads', methods=['GET', 'POST'])
def upload():

    if request.method == 'POST':  # 如果请求类型为POST,说明是文件上传请求
        f = request.files.get('file')  # 获取文件对象
        f.save(os.path.join('the/path/to/save', f.filename))  # 保存文件

    return 'upload template'  # 渲染上传页面

 

上传完成后重定向

这里需要注意的是,因为Dropzone.js通过AJAX请求提交文件,所以你没法在保存文件后将页面重定向。对于这个问题,你可以使用配置变量DROPZONE_REDIRECT_VIEW设置上传完成后跳转到的目标端点,或是添加一个按钮让用户自己点击进行跳转。

服务器端验证

尽管Dropzone.js可以在前端对用户提交的文件进行验证,但为了安全考虑,我们仍然需要在服务器端进行二次验证。在服务器端验证时,如果验证出错,我们不能像往常那样使用flash()函数“闪现”错误消息,因为AJAX请求接受到响应后并不会重载页面,所以不会显示通过flash()函数发送的消息。正确的做法是返回400错误响应,使用错误消息作为响应的主体。下面是一个简单的进行服务器端验证并返回错误消息得示例:

@app.route('/', methods=['POST', 'GET'])
def upload():
    if request.method == 'POST':
        f = request.files.get('file')
        if f.filename.split('.')[1] != 'png':
            return 'PNG only!', 400  # return the error message, with a proper 4XX code
        f.save(os.path.join('the/path/to/save', f.filename))
    return render_template('index.html')

在上面的代码中,我们验证图片是不是png格式,如果不是就返回一个错误提示,在服务器端会在图片下面看到我们返回的错误消息:

blank

 

 

完整的配置列表

Flask-Dropzone提供了丰富的配置变量,你可以使用它们对Dropzone.js进行各类配置。

Name Default Value Info
DROPZONE_SERVE_LOCAL   False       使用Flask-Dropzone提供的load_css()和load_js()方法时,这个变量设置是否使用内置的本地资源,默认从CND加载
DROPZONE_MAX_FILE_SIZE 3 允许的文件最大值,单位为MB
DROPZONE_INPUT_NAME file 上传字段的name值
DROPZONE_ALLOWED_FILE_CUSTOM False 是否使用自定义文件类型允许规则,具体见后面
DROPZONE_ALLOWED_FILE_TYPE 'default' 允许的文件类型,具体见后面
DROPZONE_MAX_FILES ‘null’ 一次可以上传的文件数量最大值
DROPZONE_DEFAULT_MESSAGE “Drop files here to upload” 上传区域显示的提示文字
DROPZONE_INVALID_FILE_TYPE “You can’t upload files of this type.” 文件类型错误的错误消息
DROPZONE_FILE_TOO_BIG “File is too big {{filesize}}. Max filesize: {{maxFilesize}}MiB.” 文件太大时显示的错误消息
DROPZONE_SERVER_ERROR “Server error: {{statusCode}}” 服务器错误的错误消息
DROPZONE_BROWSER_UNSUPPORTED “Your browser does not support drag’n’drop file uploads.” 浏览器不支持的错误消息
DROPZONE_MAX_FILE_EXCEED “Your can’t upload any more files.” 超过最大文件数量限制的错误消息
DROPZONE_UPLOAD_MULTIPLE False 是否在单个请求中发送多个文件,默认一个请求发送一个文件
DROPZONE_PARALLEL_UPLOADS 2 当DROPZONE_UPLOAD_MULTIPLE设为True时,设置单个请求包含的文件数量
DROPZONE_REDIRECT_VIEW None 上传完成后重定向的模板端点
DROPZONE_ENABLE_CSRF False 是否开启CSRF保护

这些配置的用法你可以参考Flask-Drozone的文档或是示例程序了解,这里我们仅简单介绍一下对文件类型进行过滤的设置方法。

设置文件类型过滤

Flask-Dropzone内置了一些文件类型(通过MIME定义),可选的值和对应的文件类型如下所示:

  • default:默认值,运行所有类型
  • image:图片
  • audio:音频
  • video:视频
  • text:文本
  • app:程序

你需要为DROPZONE_ALLOWED_FILE_TYPE设置对应的值,比如下面设置仅允许上传图片:

app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image'

如果你想要自己定义允许的文件类型列表,那么你需要将DROPZONE_ALLOWED_FILE_CUSTOM设置True,然后传入一个包含允许的文件后缀名列表组成的字符串给DROPZONE_ALLOWED_FILE_TYPE变量,使用逗号分隔多个后缀名,比如:

app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*, .pdf, .txt'

对于平行上传、CSRF保护等内容的具体实现方法你可以参考文档了解。不过,这个项目目前还没有创建完善的文档,暂时只是写到README里,如果你发现了英文语法或拼写错误,欢迎指正,同时也欢迎为项目贡献代码。

示例程序

Flask-Dropzone的Git仓库中的examples目录下包含4个示例程序,分别演示了基本用法、CSRF保护、平行上传和上传完成后跳转四个功能。

源码可以在这里找到:https://github.com/greyli/flask-dropzone/tree/master/examples

另外,helloflask仓库里在demos/form目录下的示例程序也包含一个Flask-Dropzone使用示例。

相关链接

 

编程名词辨析(5×10’)

在和很多同学的交流中,发现有些编程词语被误用了。今天我们来辨析一下。

我简化了大部分解释,如果需要了解更多,请点链接。如果发现有错,请指正。

 

包(package)和模块(module)

:一个包含__init__.py文件的文件夹就是一个包,这个文件夹里可以放多个Python模块。比如:

my_package
    - __init__.py
    - hello.py

模块:一个Python文件(.py)就是一个模块。

What’s the difference between a Python module and a Python package?

 

插件(plugin)和扩展(extension)

在Flask里,插件和扩展没有具体的区别,都指代增强Flask功能的Python包,但通常称作扩展。

 

参数(parameter)和参数(argument)

参数(形参):函数所期望你传入的参数(即name

def say_hello(name):
    print 'hello,' + name

参数(实参):传给函数的实际的参数(即‘kitty’

say_hello('kitty')

What’s the difference between an argument and a parameter?

函数(function)和方法(method)

函数:为了便于进行管理和重用而包装起来的代码块。

def meow():
    print 'meow meow meow'

方法:在类中定义的函数是方法。

class Cat(object):
    
    def meow(self):
        print 'meow meow meow'

Difference between functions and methods in Python

 

表达式(expression)和语句(statement)

表达式:能被打印和赋值的是表达式。比如:

1 + 1
[x for x in y]

语句:任何一行(或多行)Python代码就是语句(包括表达式),比如:

print 42
name = 'john'

What is the difference between an expression and a statement in Python?