《Flask Web开发:基于Python的Web应用开发实战》学习笔记(一)

转载于:http://pdf.us/2017/10/03/451.html,感谢这位大神

《Flask Web开发:基于Python的Web应用开发实战》学习笔记

这里是第一部分的学习笔记。第一部分:Flask简介

准备工作Git

git clone https://github.com/miguelgrinberg/flasky.git

git checkout 1a  #签出到某个版本

git reset --hard  #若修改代码,强行还原到签出状态

git fetch --all

git fetch --tags

git reset --hard origin/master

fetch从远程仓库更新本地仓库的提交历史和标签,但并不改动原文件;git reset才真正写入。

git diff 2a 2b  #查看版本间的区别

git tag  #查看所有标签

git log --oneline --graph  #查看标签历史

第一章 安装

flask官网:http://flask.pocoo.org/

使用虚拟环境

pip install virtualenv    (15.1.0)

yum install python-virtualenv  (1.10.1)

virtualenv venv  #创建虚拟环境

. venv/bin/activate  #进入虚拟环境

deactivate  #退出虚拟环境

pip install flask  #安装flask

第二章 程序的基本结构

初始化

from flask import Flask
app = Flask(__name__)

__name__参数用于决定程序的根目录,以便找到相对根目录的其它资源

路由和视图函数

路由,处理URL与函数之间的关系,常使用app.route修饰器

视图函数的返回值是响应

动态类型:
默认,字符串;int,整数,如<int:id>;float,浮点数;path,包含斜线/的字符串

启动服务器

if __name__='__main__':
app.run(debug=True)      #**调试器和重载程序

一个完整的程序

hello.py:

请求-响应循环

上下文在线程级别全局可访问

user_agent=request.headers.get('User-Agent')   #该方法获取User-Agent

Flask上下文全局变量:

变量名 上下文 说明
current_app 程序上下文 当前**程序的程序实例
g 程序上下文 处理请求时用作临时存储,每次请求均会重设
request 请求上下文 请求对象,封装客户端请求内容
session 请求上下文 用户会话,存储请求间需要记住的内容

app.app_context()可以获得程序上下文,之后再.push()推送上下文,然后才可以使用current_app;收回程序上下文则是pop()。

form hello import app
form flask import current_app
app_ctx=app.app_context()
app_ctx.push()
current_app.name
app_ctx.pop()

请求调度

生成映射的方法:1、app.route修饰器;2、app.add_url_rule()。app.add_url_rule('/ma/','index',hello.index)

查看url映射:app.url_map。  请求方法中,HEAD,OPTIONS由Flask自动处理,不需要单独写方法。

请求钩子

请求钩子用于在请求处理之前或之后执行,使用修饰器实现。

1、before_first_request:在处理第一个请求这前执行;
2、before_request:在每次请求前执行;
3、after_request:若无未处理的异常,在每次请求后执行;
4、teardown_request:即便有未处理的异常,也在每次请求后执行

在请求钩子函数和视图函数之前共享数据,一般使用g

响应

响应可指定状态码,如:return '<h1>Hello,World!</h1>',400
响应还可带第三个参数,由首部(header)组成的字典。响应也可以返回给Response对象,该对象也包含三个参数。与前面的相对应。response设置cookie的例子:

重定向302

from flask import redirect
...
return redirect('http://pdf.us/')

abort函数,用于处理错误,但不会将控制权还给调用它的函数,而是抛出异常将控制权交给Web服务器

from flask import abort
...
user=load_user(id)
if not user:
abort(404)
return '<h1>Hello,%s</h1>' % user
Flask扩展

Flask-Script扩展,支持命令行选项,安装:pip install flask-script

在程序中注册扩展的方法

from flask.ext.script import Manager    #最新的写法是from flask_script import Manager
manager=Manager(app)
#...
if __name=='__main__':
manager.run()

专门为Flask开发的扩展都暴露在falsk.ext命令空间下。

python hello.py runserver -h 0.0.0.0 -p 80 -d -r      #启动开发服务器的正确姿势

第三章 模板

业务逻辑与表现逻辑

其中,index.html和user.html在templates目录下。user.html内容为:<h1>Hello,{{name}}</h1>

模板中的变量

形如{{ mydict['key'] }}  {{ mylist[3] }}  {{ myobj.somemethod() }} 都支持

过滤器

{{ <h1>Hello</h1>|safe }}

过滤器 说明
safe 渲染值时不转义
capitalize 首字母大写
lower 小写
upper 大写
title 每个单词首字母大写
trim 渲染时去掉首尾空格
striptags 渲染之前把值中所有HTML标签都删掉
控制结构

条件控制  if...else...endif

for循环

宏,类似于函数

宏也可以导入使用

能重复使用的代码片也可以使用导入

模板的继承

基模板base.html

衍生模板index.html

注意head块,因为不是空的,所以使用super()获取原来的内容。

使用Flask-Bootstrap

pip install flask-bootstrap

初始化
from flask.ext.bootstrap import Bootstrap #from flask_bootstrap import Bootstrap
#...
bootstrap = Bootstrap(app)

使用{% extends "bootstrap/base.html" %},然后可以定义各种块。在定义styles和scripts块时,需要特别注意,要使用{{super()}},否则基模板中的相应块中的内容会丢失。

自定义错误页面

采用模板继承机制,简化模板制作

链接

url_for('index') 得到 '/'  这样引用:{{ url_for('index') }}

url_for('index',_external=True) 得到绝对地址

生成动态地址时,将动态部分作为关键字参数传入:url_for('user',name='john',_external=True)

动态地址还可以传入额外参数:url_for('index',page=2) 生成:/?page=2

静态文件

通常存放地static文件夹下面,可以有子文件夹

url_for('static',filename='css/style.css',_external=True)将返回.../static/css/style.css

Flask-Moment本地化日期时间

pip install flask-moment

初始化
from flask.ext.moment import Moment  #from flask_moment import Moment
moment=Moment(app)

使用moment时,还需要引入jquery.js库【该库bootstrap已经引用】和moment.js库

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}

使用:

视频函数
from datetime import datetime
#...
@app.route('/')
def index():
return render_template('index.html',current_time=datetime.utcnow())模板:
{{ moment(current).format('LLL') }}  #L~LLLL代表不同复杂度,LTS
{{ moment(current).fromNow(refresh=True) }}  #相对时间,a minute ago

Flask-Moment实现了moment.js中的format(),fromNow(),fromTime(),calendar(),valueOf(),unix()方法,具体查看文档http://momentjs.com/docs/#/displaying/

设置语言:{{ moment.lang('es') }}

第四章 Web表单

request.form能获取POST请求中提交的表单数据

或者使用Flask-WTF扩展

pip install flask-wtf

WTF需要设置一个**,用于验证表单数据真伪,设置方法:

app.config['SECRET_KEY'] = 'yourkey'

app.config字典用于存储框架、扩展和程序本身的配置变量。

表单类

每个Web表单都继承自Form类,这个类定义表单中的字段,每个字段都用对象表示,字段对象可附属验证函数。表单的字段都定义为类变量,类变量的值是相应字段类型的对象。

定义表单类

其中,from flask.ext.wtf import Form可用from flask_wtf import Form替换

字段类型 说明
StringField 文本字段
TextField 多行文本
PasswordField 密码文本
HiddenField 隐藏文本
DateField 文本,值为datetime.date格式
DatetimeField 文本,值为datetime.datetime格式
IntegerField 文本,值为整数
DecimalField 文本,值为decimal.Decimal
FloatField 文本,值为浮点
BooleanField 复选框,值为True或False
RadioField 一线单选框
SelectField 下拉列表
SelectMultipleField 下接列表,可多选
FileField 文件上传
SubmitField 提交按钮
FormField 嵌套表单
FieldList 一组指定类型的字段
验证函数 说明
Email 电子邮件地址
EqualTo 比较两个字段的值,常用于二次密码验证
IPAddress IPv4地址
Length 字符串上度
NumberRange 数字范围
Optional 无输入值时跳过其它验证函数
Required 不为NULL
Regexp 使用正则表达式验证
URL URL
AnyOf 确保输入值在可选值列表中
NoneOf 确认输入值不在可选值列表中
渲染表单

表单字段可调用,在模板中调用后会渲染成HTML。

例如视图函数中将表单通过参数form传入模板,则在模板中这样渲染:

更好的方式是使用bootstrap:

{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
在视图函数中处理表单

form.validate_on_submit方法在所有字段通过验证函数,且点击提交后,为True,否则为False.

重定向和用户会话

最好不要将POST请求作为浏览器发送的最后一个请求!

Post/重定向/Get模式

默认情况下,用户会话session保存在客户端cookie中,使用SECRET_KEY进行加密签名,如果篡改,签名就会失效

Flash消息

确认消息、警告消息、错误消息

Flask开放函数get_flashed_messages()给模板,用于模板获取并渲染消息

{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}

第五章 数据库

对象关系映射——ORM——SQL

对象文档映射——ODM——NoSQL

Flask-SQLAlchemy

pip install flask-sqlalchemy

数据库引擎 URI
MySQL mysql://username:[email protected]/database
Postgres postgresql://username:[email protected]/database
SQLite sqlite:////absolute/path/to/database
SQLite sqlite:///c:/absolute/path/to/database

使用mysql时,pip install mysql-python 安装MySQLdb模块,然后手动新建数据库database

初始化数据库

定义模型

模型是一个类,模型一般对应一张表,类属性对应数据库表中的列

常用列类型及对应的Python类型

类型名 Python类型 说明
Integer int 普通整数,32位
SmallInteger int 整数,16位
BigInteger int/long 不限制精度整数
Float float 浮点数
Numeric decimal.Decimal 定点数
String str 变长字符串
Text str 变长字符串,较长
Unicode unicode 变长Unicode字符串
UnicodeText unicode 变长Unicode字符串,较长
Boolean bool 布尔值
Date datetime.date 日期
Time datetime.time 时间
DateTime datetime.datetime 日期和时间
Interval datetime.timedelta 时间间隔
Enum str 一组字符串
PickleType 任何Python对象 自动使用Pickle序列化
LargeBinary str 二进制文件
选项名 说明
primary_key 主键
unique 不允许出现重复值
index 索引
nullable 允许使用空值
default 为该列定认默认值
关系

一对多关系:Role(一),User(多)

关于一对多关系的进一步说明:在多的一方添加外键,外键指明了对应另外一方的哪一列。在一的一方的users属性代表这个关系的面向对象视角,对Role类的实例(行)可以通过users属性返回关联列表,relationship第一个参数指明了关系的另一端,backref参数向User模型添加role属性,从而定义反向关系。

选项名 说明
backref 在关系的另一个模型中添加反向引用
primaryjoin 明确指定两个模型之间使用的联结条件。只在模棱两可的关系中指定
lazy 指定如何加载相关记录
select(首次访问按需加载)
immediate(源对象加载后就加载)
joined(加载记录,但使用联结)
subquery(立即加载但使用子查询)
noload(永不加载)
dynamic(不加载记录,但提供加载记录的查询)
uselist 若设为False,不使用列表,而使用标量值
order_by 指定排序方式
secondary 指定多对多关系中关系表的名字
secondaryjoin SQLAlchemy无法自行决定时,指定多对多关系中的二级联结条件

一对一关系:使用一对多关系,但调用db.relationship()时,把uselist设为False,把多变成一;

多对一关系:使用一对多关系,对调两个表;或者把外键和db.relationship()都放到多这一侧;

多对多关系:使用关系表

数据库操作
创建表

根据模型类创建数据库:db.create_all()

如果数据库表已经存在于数据库,那么create_all将不会重新创建或更新这个表。如果修改数模型后,要将修改应用到数据库,则只有先删除再重新创建[会清空数据]:

db.drop_all()
db.create_all()
插入行
>>> from hello import Role,User,db
>>> admin_role=Role(name='Admin')
>>> mod_role=Role(name='Moderator')
>>> user_role=Role(name='User')
>>> user_john=User(username='john',role=admin_role)
>>> user_susan=User(username='susan',role=user_role)
>>> user_david=User(username='david',role=user_role)
>>> user_dong=User(username='dong',role_id=1)
>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)
>>> db.session.add(user_dong)
###  db.session.add_all([admin_role,mod_role,user_role,user_joho,user_susan,user_david,user_dong])
>>> db.session.commit()
###  db.session.rollback()   回滚会话
因为id在提交前不会生成,主键由SQLAlchemy自己处理,不需要设置
修改行
>>> admin_role.name='Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
删除行
>>> db.session.delete(mod_role)
>>> db.session.commit()
查询行
### 查询所有记录
>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
### 使用过滤器
>>> User.query.filter_by(role=user_role).all()
[<User u'susan'>, <User u'david'>]
### 查询原生SQL
>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id \nFROM users \nWHERE %s = users.role_id'

常用的查询过滤器:

过滤器 说明
filter() 把过滤器添加到原查询上,返回一个新查询
filter_by() 把等值过滤器添加到原查询上,返回一个新查询
limit() 限制原查询返回结果数量,返回一个新查询
offset() 偏移原查询返回结果,返回一个新查询
order_by() 对原查询结果进行排序,返回一个新查询
group_by() 对原查询进行分组,返回一个新查询

查询执行函数

方法 说明
all() 以列表形式返回所有结果
first() 返回查询第一个结果,若无,返回None
first_or_404() 返回查询第一个结果,若无,中止,返回404
get() 返回指定主键对应的行,若无,返回None
get_or_404() 返回指定主键对应的行,若无,中止,返回404
count() 返回查询结果数量
paginate() 返回Paginate对象,包含指定范围的结果

一对多关系的查询
>>> users=user_role.users
>>> list(users)
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>
>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2L

在视图函数中操作数据库

注意,这里并没有db.session.commit(),
因为app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

集成Python shell

使用shell时,每次需要导入app,db,User,Role,为简化操作,可以为shell添加上下文:

数据库迁移

不丢失数据的更新数据库

创建迁移仓库

pip install flask-migrate

初始化(注意,需要传入db)
from flask.ext.migrate import Migrate,MigrateCommand
#from flask_migrate import Migrate,MigrateCommand
#...
migrate=Migrate(app,db)
manager.add_command('db',MigrateCommand)

维护数据库前,先创建迁移仓库:

python hello.py db init

会生成migrations目录

创建迁移脚本

upgrade()把改动应用到数据库

downgrade()将改动删除

自动创建的迁移不一定总是正确,需要认真检查!!

python hello.py db migrate -m "inital migration"

更新数据库

检查并修正好脚本后(位于migtarions/versions目录)后,使用db upgrade迁移:

python hello.py db upgrade

第六章 电子邮件

pip install flask-mail

Flask-Mail的默认参数
MAIL_SERVER:localhost
MAIL_PORT:25
MAIL_USE_TLS:False
MAIL_USE_SSL:False
MAIL_USERNAME:None
MAIL_PASSWORD:None初始化:
from flask_mail import Mailapp.config['MAIL_SERVER']='smtp.126.com'
app.config['MAIL_PORT']=465
app.config['MAIL_USE_SSL']=True
app.config['MAIL_USERNAME']=os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD']=os.environ.get('MAIL_PASSWORD')
#export MAIL_USERNAME='mymailname'
#export MAIL_PASSWORD='mymailpassword'mail=Mail(app)
命令行测试发送邮件
>>> from flask_mail import Message
>>> from hello import mail
>>> msg=Message('test',sender='[email protected]',recipients=['[email protected]'])
### 奇怪的是只能自己给自己发,给别人发会报535错误,被当成垃圾邮件啦?
### msg.body='text body'
### msg.html='<h1>test mail</h1>
>>> with app.app_context():
...              mail.send(msg)
在程序中添加发送邮件功能

为配合,需要templates目录下新建mail子目录,里面存放邮件的模板。模板示例:

User <b>{{ user.username }}</b> has joined.

异步发送邮件

第七章 大型程序的结构

项目结构:

《Flask Web开发:基于Python的Web应用开发实战》学习笔记(一)

 

配置选项

使用配置类。公共配置放于Config类,开发,测试,生产分别使用继承自Config类的类,最后生成一个config字典。调用字典时:config['DevelopmentConfig']()生成所有配置。

配置条目相应要做修改:
app.config['SECRET_KEY'] = os.environ.get('Key') or 'hard to guess string'
改为:
SECRET_KEY= os.environ.get('Key') or 'hard to guess string'

SECRET_KEY= os.environ.get('Key') or 'hard to guess string'
如果环境中有,则使用环境中的值,环境中无,则使用配置中的值

程序包

使用程序工厂函数

延迟创建程序实例,创建时使用工厂函数。工厂函数在app包的构造文件中定义__init__.py

在蓝本中实现程序功能

蓝本中定义的路由处于休眠状态,只有蓝本注册到程序上后,路由才成为程序的一部分。

app/main/__init__.py创建蓝本

 

注册蓝本到程序,app/__init__.py中

蓝本中的错误处理程序:app/main/errors.py

蓝本中的路由:app/main/views.py

Flask会为蓝本全部端点加上命名空间,空间名就是蓝本名,所以这里的url_for函数参数为main.index可简写为.index。同一蓝本中重定向可以简写,但是跨蓝本的重定向必须使用有命名空间的端点名。

启动脚本

manage.py

需求文件

requirements.txt用于记录所有依赖包及精确的版本号

生成:pip freeze >requirements.txt

使用:pip install -r requirements.txt

单元测试

test/test_basics.py

setUp()和tearDown()分别在测试前后运行,名字以test_开头的函数都作为测试执行。

test/__init__.py可为空,因为unittest会扫描所有模块并查找测试。

为方便测试,可以添加启动命令:

测试:python manager.py test

创建数据库

python manager.py db upgrade