1 虚拟环境
书上介绍了
virtualenv
,每个venv都会拷贝一份packages到项目 /venv目录。
比较了一下conda管理环境,可能conda更胜一筹。
或者用 virtualenvwrapper
.gitignore:指定哪些文件或目录不作同步,比如 ./venv/,*.pyc,数据库文件.sqlite3, .mysql
推荐IDE:
PyCharm
导入已有的virtualenv: File -> Setting -> Project Interprater -> 选择项目目录下的/venv/Python
特点:
-> new Flask Project
-> jump between View funcion and Templates
-> Git
2 基本结构
初始化:
Flask类的构造函数只有一个必须指定的参数,即程序主模块或包的名字。在大多数程序中,Python的__name__变量就是所需的值。Flask用这个参数
决定程序的根目录
,以便稍后能够找到相对于程序根目录的资源文件位置
路由
(route)和
视图函数
(view function):
定义路由的最简便方式,是使用程序实例提供的app.route修饰器,把修饰的函数注册为路由
修饰器是Python语言的标准特性,可以使用不同的方式修改函数的行为。惯常用法是使用修饰器把函数注册为事件的处理程序。
动态路由:地址中可以包含可变部分,Flask支持在路由中使用int、float和path类型。path类型也是字符串,但不把斜线视作分隔符
默认端口是5000,可以改成其它的(flask_script.Manager也有此功能)
python manage.py runserver -p 7777
# 有些端口不能用,查询已占用的端口:netstat -ano;netstat -aon|findstr "6000";tasklist|findstr "
";taskkill /f /t /im XXX.exe
-
公认端口(Well Known Ports):从0到1023,紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务协议。80端口实际上总是HTTP通讯。
-
注册端口(Registered Ports):从1024到49151。它们松散地绑定于一些服务。这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。
-
动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。
请求-响应循环
Context 上下文全局变量:
-
current_app
程序上下文 当前激活程序的程序实例
-
g
程序上下文 处理请求时用作临时存储的对象。每次请求都会重设这个变量
-
request
请求上下文 请求对象,封装了客户端发出的HTTP请求中的内容
-
session
请求上下文 用户会话,用于存储请求之间需要“记住”的值的词典
URL映射
是URL和视图函数之间的对应关系。Flask使用app.route修饰器或者非修饰器形式的app.add_url_rule()生成映射。
HEAD、Options、GET是请求方法,由路由进行处理。Flask为每个路由都指定了请求方法,这样不同的请求方法发送到相同的URL上时,会使用不同的视图函数进行处理。HEAD和OPTIONS方法由Flask自动处理
请求 Hook
: 在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量g
-
before_first_request • :注册一个函数,在处理第一个请求之前运行。
-
before_request • :注册一个函数,在每次请求之前运行。
-
after_request • :注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。
-
teardown_request • :注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行
响应(
视图函数返回)
Flask扩展
原书更正: Importing flask.ext.script is deprecated, use
flask_script
instead.
3 模板 template
业务逻辑和表现逻辑 要分开
按功能分(模板不需要重用时),或按Division分(大部分模板需要重用时)
Jinja2模板引擎
模板是一个包含响应文本的文件,其中包含用
占位变量{{...}}
表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为
渲染。
模板变量
Jinja2能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象
可以使用
过滤器
修改变量。千万别在不可信的值上使用safe过滤器,例如用户在表单中输入的文本
-
Hello, {{ name|capitalize }
完整的过滤器列表
-
safe
渲染值时不转义。默认情况下,出于安全考虑,Jinja2会转义所有变量
-
capitalize 把值的首字母转换成大写,其他字母转换成小写
-
lower
把值转换成小写形式
-
upper
把值转换成大写形式
-
title
把值中每个单词的首字母都转换成大写
-
trim
把值的首尾空格去掉
-
striptags
渲染之前把值中所有的HTML标签都删掉
控制结构{%...%}
,可用来改变模板的渲染流程 if, for, macro, import, include
需要在多处重复使用的模板代码片段可以写入单独的文件,再包含 {%include 'common.html' %} 在所有模板中
另一种重复使用代码的强大方式是
模板继承
,block标签定义的元素可在衍生模板中修改
extends指令声明这个模板衍生自base.html。在
extends
指令之后,基模板中的3个块被重新定义,模板引擎会将其插入适当的位置。注意新定义的head块,在基模板中其内容不是空的,所以使用
super()
获取原来的内容(向已经有内容的块中添加新内容)。
使用Flask-Bootstrap
Bootstrap是客户端框架,因此不会直接涉及服务器。服务器需要做的只是提供引用了Bootstrap层 叠样式表(CSS)和JavaScript文 件的HTML响 应,并在HTML、CSS和JavaScript代码中实例化所需组件。这些操作最理想的执行场所就是模板。
Flask-Bootstrap基模板中定义的块:doc, head, title, styles, body, navbar, content, scripts
Bootstrap官方文档
CDN本地加速:
修改 Base.html,引用本地的css 文件,里面元素跟Bootstrap 重名的,则会覆盖官方里相同元素
以上本地加速证明是有误的!会重复下载本地和官方Bootstrap 的 css/js
解决:
/app/__init__.py
from flask_bootstrap import Bootstrap, WebCDN, ConditionalCDN, BOOTSTRAP_VERSION, JQUERY_VERSION, HTML5SHIV_VERSION, RESPONDJS_VERSION
def create_app(config_name):
另
外:本地加速 moment.js
这个文件也在国外服务器,访问很慢,而且 size=160KB
/app/templates/base.html
需要单独下载 https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/
moment-with-locales.min.js
到本地目录 /app/static/
自定义错误页面
url_for() 链接辅助函数
使用url_for()生成动态地址时,将动态部分作为关键字参数传入。例如,
url_for('user', name='john', _external=True)的返回结果是http://localhost:5000/user/john
使用
Flask-Moment本地化日期和时间
。
4 Web Form表单
app.config
字典可用来存储框架、扩展和程序本身的配置变量。使用标准的字典句法就能
把配置值添加到
app.config
对象中。这个对象还提供了一些方法,可以从文件或环境中导
入配置值。
Form
基类由
Flask-WTF
扩展定义,所以从
flask.ext.wtf
中导入。字段和验
证函数 可以直接从
WTForms
包中导入。
WTForms
支持的HTML
标准字段:StringField,
TextAreaField,PasswordField。。。
WTForms
内建的验证函数:
Email,DataRequired...
Placeholder提示:
重定向和用户会话
刷新页面后会再次提交表单。大多数情况下,这并
不是理想的处理方式。
很多用户都不理解浏览器发出的这个警告。 基于这个原因,最好别让
Web
程序把
POST
请
求作为浏览器发送的最后一个请求。
这个技
巧称为
Post/
重定向
/Get
模式
。
使用
get()
获取字典中键对应的值以避免未找
到键的异常情况,因为对于不存在的键,
get()
会返回默认值
None
。
Flash
消息
仅调用
flash()
函数并不能把消息显示出来,程序使用的模板要渲染这些消息。最好在base.html
中渲染
Flash
消息,因为这样所有页面都能使用这些消息。
Flask
把
get_flashed_
messages()
函数开放给模板,用来获取并渲染消
5 数据库
使用SQL还是NoSQL
SQL 数据库擅于用高效且紧凑的形式存储结构化数据。这种数据库需要花费大量精力保证数据的一致性。NoSQL 数据库放宽了对这种一致性的要求,从而获得性能上的优势。
MySQL Q&A:
安装MySQL in Windows 报错:需要预先把Windows Defender 打开,或者configure mysql server时,不要勾选“Windows Firewall”
可能要先安装:
Microsoft Visual C++ Compiler for Python 2.7
本地安装MySQLdb: pip install mysql-python
Window7 64位下安装可能还会报 cl.exe错
workaround: 先conda install mysql-python,再手动复制以下目录及文件到 venv\Lib\Site-packages下:
Anaconda2\Lib\site-packages\MySQLdb
Anaconda2\Lib\site-packages\MySQL_python-1.2.5.dist-info
Anaconda2\Lib\site-packages\_mysql*
MySQL创建connection之后,还需要创建“schema” --> 对应SQLAlchemy里的“database”
MySQLdb 中文乱码的处理:
conn = MySQLdb.connect(host='localhost', user='root', passwd='XXX', db='app_englishgo',
charset = 'utf8'
)
显示:title.encode('gbk')
接收输入:
unicode
(request.form['title'])
SQLAlchemy 和MongoEngine:数据库抽象层代码包(ORM、ODM),你可以使用这些抽象包直接处理高等级的 Python 对象,而不用处理如表、文档或查询语言此类的数据库实体。
使用Flask-SQLAlchemy管理数据库
MySQL
mysql://username:password@hostname:port/database
SQLite(Windows)
sqlite:///c:/absolute/path/to/database
Relationship 关系型数据库
SQLAlchemy 完整的命令、过滤器、查询执行 列表参见 SQLAlchemy 文档
SQLAlchemy engine设置编码,防止中文乱码:
集成Python shell
每次启动 shell 会话都要导入数据库实例和模型。为避免重复导入,我们可以做些配置,让 Flask-Script 的 shell 命令自动导入特定的对象。为 shell 命令注册一个 make_context 回调函数
新数据库迁移 flask-migrate
由于模型中经常会新加一行或几行column (比如用来保存账户的确认状态),此时要修改 models.py,并执行一次新数据库迁移
MySQL Workbench 自动产生EER,可以清楚地看到各个表格之间关系:一对多 Foreign_Key、Index等
1) config.py:
2) python manage.py deploy
3) MySQL Workbench: Database -> Reverse Engineer -> 选择 connection -> database -> 一路 Next
6 E-mail
使用Flask-Mail提供电子邮件支持
千万不要把账户密令直接写入脚本,特别是当你计划开源自己的作品时。让脚本
从本机环境中导入敏感
信息
Windows 用户可按照下面的方式设定环境变量:
所有的在cmd命令行下对环境变量的修改只对
当前窗口
有效,不是永久性的修改。也就是说当关闭此cmd命令行窗口后,将不再起作用。永久性修改环境变量的方法有两种:一种是直接修改注册表(overkill python script),另一种是通过我的电脑-〉属性-〉高级,来设置系统的环境变量
异步发送电子邮件
为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程(Threading)中
很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文。Flask-Mail 中的 send() 函数使用 current_app ,因此必须激活程序上下文。不过,在不同线程中执行 mail.send() 函数时,程序上下文要使用 app.app_context() 人工创建。
7 大型程序的结构
项目结构
requirements.txt 文件,用于记录所有依赖包及其精确的版本号。以便要在另一台电脑上重新生成虚拟环境
创建:(venv) $ pip freeze > requirements.txt
恢复:
(venv) $ pip install -r requirements.txt
重组后的程序和单脚本版本使用不同的数据库,可使用如下命令创建数据表或者升级到最新修订版本:(venv) $ python manage.py db upgrade
8 用户认证
Flask的认证扩展
Flask-Login:管理已登录用户的用户会话。
Werkzeug:计算密码散列值并进行核对。
itsdangerous:生成并核对加密安全令牌。
创建认证蓝本
对于不同的程序功能,我们要使用不同的蓝本(main, auth),这是保持代码整齐有序的好方法
因为 Flask 认为模板的路径是相对于程序模板文件夹而言的。为避免与 main 蓝本和后续添加的蓝本发生模板命名冲突,可以把蓝本使用的模板保存在单独的文件夹中
使用Flask-Login
认证用户
LoginManager 对象的 session_protection 属性可以设为 None 、 'basic' 或 'strong' ,以提供不同的安全等级防止用户会话遭篡改。设为 'strong' 时,Flask-Login 会记录客户端 IP地址和浏览器的用户代理信息,如果发现异动就登出用户。
为了保护路由只让认证用户访问,Flask-Login 提供了一个 login_required 修饰器
current_user 由 Flask-Login 定义,且在视图函数和模板中自动可用
模板中加入用户登录后的信息和提示效果 base.html:
按照第 4 章介绍的“Post/ 重定向 /Get 模式”,Login的 POST 请求最后也做了重定向,不过目标 URL 有两种可能。用户访问未授权的 URL 时会显示登录表单,Flask-Login 会把原地址保存在查询字符串的
next
参数中,这个参数可从 request.args 字典中读取。如果查询字符串中没有 next 参数,则重定向到首页
app/auth/views.py
用户注册表单
app/auth/forms.py
这个表单使用 WTForms 提供的 Regexp 验证函数,确保 username 字段只包含字母、数字、下划线和点号。
密码要输入两次。此时要验证两个密码字段中的值是否一致,这种验证可使用WTForms 提供的另一验证函数实现,即 EqualTo
如果表单类中定义了以
validate_
开头且后面跟着字段名的方法,这个方法就和常规的验证函数一起调用
发送确认邮件
使用itsdangerous生成确认令牌
对蓝本来说, before_request 钩子只能应用到属于蓝本的请求上。若想在蓝本中使用针对程序全局请求的钩子,必须使用
before_app_request
修饰器
9 User Role 角色
角色在数据库中的表示
赋予角色
角色验证
10 User Profile 资料
用户资料页面
资料编辑器
用户头像
国内 gravatar.com被墙,改用其它方法:静态jpg头像
目录:c:\git\flasky\app\static\avatar\001.jpg ~ XXX.jpg
base.html:
models.py:
user.html:
_posts.html:
_commments.html:
效果:
扩展TODO:加入性别、用户自选头像。。。
11 Blog articles 博客文章
实现
功能,即允许用户阅读、撰写博客文章。本章新技术:重用模板、分页显示长列表以及处理富文本。
生成虚拟信
息用于测试,其中功能相对完善的是
ForgeryPy
添
加分页导航
paginate()
方法的返回值是一个 Pagination
类对象,这个类在 Flask-SQLAlchemy 中定义。这个对象包含很多属性, 用于在模板中生成分页链接
使用
Markdown
和
Flask-PageDown
支持富文本文章
• PageDown
: 使用
JavaScript
实现的客户端
Markdown
到
HTML
的转换程序。
• Flask-PageDown
: 为
Flask
包装的
PageDown
,把
PageDown
集成到
Flask-WTF
表单中。
• Markdown
: 使用
Python
实现的服务器端
Markdown
到
HTML
的转换程序。
• Bleach
: 使用
Python
实现的
HTML清理器。
博客文章的固定链接
博客文章编辑器
12 followers 关注者
再论数据库关系
一对多关系:是最常用的
关系类型,它把一个记录和一组相关的记录联系在一起。实现这种关系时,要在“多”这一
侧加入一个外键,指向“一”这一侧联接的记录。
多对多关系:
这种问题的解决方法是添加第三张表, 这个表称为
关联表
。多对多关系可以分解成
原表和关联表之间的两个一对多关系
若想显示所关注用户发布的所有文章,第一步显然先要获取这些用户,然后获取各用户的
文章,再按一定顺序排列,写入单独列表。可是这种方式的伸缩性不好,随着数据库不断
变大,生成这个列表的工作量也不断增长,而且分页等操作也无法高效率完成。获取博客
文章的高效方式是只用一次查询。
完成这个操作的数据库操作称为
联结
。联结操作用到两个或更多的数据表, 在其中查找满
足指定条件的记录组合, 再把记录组合插入一个临时表中,这个临时表就是联结查询的结
果。
创建函数更新数据库这一技术经常用来更新已部署的程序,因为运行脚本更新比手动更新
数据库更少出错。
13 User comments 评论
评论属于某篇博客文章,因此定义了一个从
posts
表到
comments
表的一对多关系。使用这
个关系可以获取某篇特定博客文章的评论列表。
comments
表还和
users
表之间有一对多关系。通过这个关系可以获取用户发表的所有评
论,还能间接知道用户发表了多少篇评论