首发于Hello, Flask!

专栏2016~2018文章勘误

这篇文章会对从2016年11月开始到2018年年末的技术类文章中的错误、不合理的代码实现、Python库的新变化进行一个汇总。因为工作量太大,而且没有太多价值,原文章中仅会对错误进行更新。不论出于何种目的,我都推荐你通过《Flask Web开发实战》来学习Flask,而不是专栏的旧文章。最后,对文章中包含的错误表示抱歉。

《Flask介绍》

  • 第四小节介绍使用Flask网站时提及Pinterest是使用Flask最大的网站,证据在这个Quora回答里,答主表示Pinterest的API使用Flask构建:
Pinterest uses Flask for our API and has used it since late 2011, I'm not sure why Paul's answer (What is the technology stack behind Pinterest?) didn't include it as a core technology, but it's most likely due to the API being an ancillary thing during his tenure. Since early 2012, we've reinvested in the API, putting it on equal footing with the web and since late 2012 every Pinterest client (web, iOS or Android) hits it at some point. Presently, the API does over 12 billion requests per day, all in Flask.
  • 我又重新查证了一下,在StackShare提供的“使用Flask的公司”的数据中,似乎Reddit现在是使用Flask的最大的网站。

《Flask实践:猜数字》

  • WTForms已不推荐使用Required验证器(哪位知友知道deprecate更合理的翻译,请评论告诉我,谢谢!),所以你需要使用DataRequired替换它,导入语句和字段定义都需要更新,对应的源码仓库已经更新。
  • 文章中介绍了session不能用于存储敏感内容,比如密码,但是猜数字游戏的答案本身就属于敏感内容。用户可以通过浏览器获取到对应cookie的值(名称为session),然后通过非常简单的方式(base64解码)就可以获取到答案。最简单的,你可以在JWT.IO提供的解码工具进行测试,示例如下所示。左侧输入的是session的令牌值,右侧为解析后的结果,其中的number即答案:

《flash消息分类与美化》

  • Bootstrap 4支持了更多的消息(alert)样式类,完整的列表包括alert-primaryalert-secondaryalert-successalert-dangeralert-warningalert-infoalert-lightalert-dark,具体效果如下图所示。

《Flask-WTF:单个页面两个(多个)表单》

  • 对于单个页面多个表单的情况,有一个相对比较简洁的实现,使用多个视图来处理多个表单,示例如下:
...
@app.route('/')
def index():  # 渲染两个表单所在的模板
    register_form = RegisterForm()
    login_form = LoginForm()
    return render_template('index.html', register_form=register_form, login_form=login_form)

@app.route('/register', methods=['POST'])
def register():  # 处理注册表单
    register_form = RegisterForm()
    login_form = LoginForm()

    if register_form.validate_on_submit():
        ...  # handle the register form
    # render the same template to pass the error message
    # or pass `form.errors` with `flash()` or `session` then redirect to /
    return render_template('index.html', register_form=register_form, login_form=login_form)


@app.route('/login', methods=['POST'])
def login():  # 处理登录表单
    register_form = RegisterForm()
    login_form = LoginForm()

    if login_form.validate_on_submit():
        ...  # handle the login form
    # render the same template to pass the error message
    # or pass `form.errors` with `flash()` or `session` then redirect to /
    return render_template('index.html', register_form=register_form, login_form=login_form)

在模板中,你需要渲染这两个表单,并将表单的action属性值设置为对应的处理视图的URL,这样会把表单提交到对应的处理视图:

<h1>Register</h1>
<form action="{{ url_for('register') }}" method="post">
    {{ register_form.username }}
    {{ register_form.password }}
    {{ register_form.email }}
</form>

<h1>Login</h1>
<form action="{{ url_for('login') }}" method="post">
    {{ login_form.username }}
    {{ login_form.password }}
</form>

这个方法的缺点是在处理表单的视图中你必须再次渲染表单才能确保出现验证错误时将错误显示到模板中,替代的方法是通过flash()显示错误提示,或是存储到session中,这时可以使用重定向替代模板渲染。

《Flask表单:表单数据的验证与处理》

  • 在单独使用WTForms时,要在实例化表单类时手动传入表单数据。表单数据保存在请求对象的form属性中,所以我们要传入request.form,比如:
from flask import request
...
form = LoginForm(request.form)

《为你的Flask项目添加富文本编辑器》

  • 可以考虑使用扩展Flask-CKEditor来为项目集成CKEditor编辑器。

《Flask实践:待办事项(ToDo-List)》

  • 该项目已不再维护,将被新书《Flask Web开发实战》中的项目实例Todoism取代,过一段时间我会把Todoism的代码放到GitHub上。
  • 避免设置SQLALCHEMY_COMMIT_ON_TEARDOWN,在每次操作数据库时手动调用db.session.commit()方法来提交数据库会话。
  • 删除条目的实现不安全,应该使用表单并采用POST请求的方式来执行设计到数据库修改的操作,否则会导致XSS攻击。
  • Flask从0.11版本开始引入了命令行系统,现在可以替代掉扩展Flask-Script的使用。

《Flask实践:计算器》和《使用AJAX和jQuery动态加载数据》

  • 在JavaScript中手动使用字符串拼接URL不合理,应该在HTML中定义JavaScript变量来存储URL,URL本身则通过url_for()函数构建。

《Flask文件上传(一):原生实现》

  • 上传文件大小可以通过Flask内置的配置变量MAX_CONTENT_LENGTH进行限制,当请求主体大于这个限制值时,会抛出RequestEntityTooLarge异常,进而返回413错误。当使用开发服务器时可能会直接断开连接,属于正常现象,使用生产服务器时会正确返回413错误。

《Flask文件上传(二):使用扩展实现》

《Flask文件上传(四):文件管理与多文件上传》

import uuid
filename = uuid.uuid4().hex
  • 渲染多文件上传现在可以使用在WTForms2.2添加的MultipleFileField字段类,而不用手动设置multiple属性。不过在Flask-WTF中,还没有添加对多文件上传字段的相应支持和验证器。我在这个PR里添加了类似的MultipleFileField,FilesRequired,FilesAllowed,不过项目维护者想要一个更简洁的实现,所以这部分内容还是没能在书中确定下来。再等一段时间,我们就可以用到和单文件处理和验证类似的多文件处理接口。

《Flask文件上传(五):拖拽上传和进度条》

《Flask实践:图片墙生成器》

  • 在模板中生成URL时使用了下面的语句{{ url_for('static', filename='photos/') }}{{ image[0] }},为image[0]使用多余的变量标识符是多余的,可以简化为{{ url_for('static', filename='photos/%s' % image[0]) }}

《用Flask从零实现豆瓣相册》

  • 该项目已不再维护,将被新书《Flask Web开发实战》中的项目实例Albumy取代,过一段时间我会把Albumy的代码放到GitHub上。
  • 删除图片的实现不合理,应该使用POST方法通过表单来提交删除请求。
  • 获取还包含其他不合理实现,请谨慎参考。
  • 该系列文章停止更新,请见谅。

《用Flask实现豆瓣相册(一):相册与图片》

  • 对于图片,数据库中应该存储文件名,而不是URL,这会导致无法轻易更新图片的URL规则。
  • 定义关系函数时,对于简单的关系不应将加载方式设为dynamic,即lazy='dynamic',使用默认值即可。如果希望对某个用户的图片叠加查询,可以使用with_parent()查询方法,比如Photo.with_parent(user),这会返回包含该用户相关的图片记录的查询对象。

《用Flask实现豆瓣相册(二):图片上传与处理》

  • 在URL规则中,对于文件名应该使用path转换器,这可以支持包含斜线的文件名路径,即<path:filename>
  • Flask-WTF提供的FileField, FileAllowed,FileRequired只能处理单文件。现阶段要么手动处理多文件的保存和验证,要么等待Flask-WTF的下一个版本发布,具体参考上面《Flask文件上传(四):文件管理与多文件上传》中的第二条。

全局

  • 从Flask 0.11版本开始,app.run()就不推荐使用,你应该使用flask run命令来启动开发服务器。需要注意的是,Miguel Grinberg在这个PR里实现了将app.run()调用转发到命令flask run。如果这个PR被合并,那么可以继续使用app.run()。如果你觉得每次都要设置FLASK_APP来指定程序实例所在模块或包太麻烦,可以将它定义在.flaskenv文件中,然后安装python-dotenv,Flask会自动从这个文件中导入环境变量,具体参考Command Line Interface
  • 当使用包组织程序时,尽管使用相对导入很方便,不用写死包名称,而且可以避免包内导入和标准库模块产生冲突,但仍推荐使用绝对导入。主要的原因是绝对导入更容易理解,更符合直觉,尤其是对于新手来说。在包内使用绝对导入导入模块时,一律从包名称作为路径起点(谨慎命名包名称以避免冲突),这可以确保在不同的模块中导入某个模块时可以使用一致的导入语句,比如(假设我们的包名为myapp):
from myapp import model
from myapp.subpkg import foo 
from myapp.forms.admin import HelloForm
from myapp.blueprints.auth.views import bar

编辑于 2018-10-05

文章被以下专栏收录