《Flask web 开发:基于python》
Always believe that something wonderful is about to happen
第1章 安装
1.1virtualenv模块安装
#在shell窗口
easy_install virtualenv #即可
或者
pip isntall virtualenv
#查看版本
virtualenv --version
1.2创建虚拟环境venv 及激活
virtualenv venv#venv为名字,可随意改,但常用venv
#在目录下
venv\\scripts\\activate #注意是反斜杠,是地址
1.3在虚拟环境中作业
后面的所有安装模块及操作,都在虚拟环境中执行!虚拟环境就是一个独立的环境,将我们所有的文件都集中在一个世界里面,尤其是在有很多不同办法的应用、模块时,你会知道的虚拟环境的友好。
第2章 程序结构
2.1程序初始化
import flask import Flask
app=Flask(__name__)
#在Flask类中创建一个叫做app的实例,__name__是程序主模块或包的名字,在这里作为变量
2.2路由和视图函数
路由:一个处理url和函数之间映射关系的程序。
装饰器/修饰器:一个用来接受函数的函数,并且返回一个函数,如@app.route(),修饰器的惯用做法,就是将函数注册为事件的处理程序。
@app.route("/") #此处装饰器就是将index()函数绑定一个url
def index():
return"Hello world!"
#把index()函数注册为根目录的处理程序
@app.route("/user/<name>")
def index():
return"hello,%s" %name
#“动态路由”
2.3启动程序
实例的.run()方法来启动程序,调试模式的话则设置debug=True:
if __name__="main":
app.run(debug=True)
#app.debug=True
#app.run()
#调试模式下,浏览器在脚本修改代码后并保存的同时,自动重新载入,并且在发生错误的时候,提供一个调节器
以上三步骤就可以构造一个功能最简单的flask程序了,重点是理解修饰器的实质含义
2.5请求响应循环
介绍flask的设计理念
1.程序和请求上下文:flask使用上下文临时将某些对象变成全局可访问——一个线程中的全局访问,不会干扰其他线程的。
from flask import request
@app.route(‘/‘)
def index():
user_agent = request.headers.get(‘User-Agent‘)
return ‘<p>Your browser is %s</p>‘ % user_agent
#request变量,这是一个请求上下文。只有当请求被推送之后,request才会有意义,才可以使用request,否则就会报错,因为缺少上下文。
程序上下文和请求上下文:有种“环境”意味,解释见上面。
2.请求调度:就是找到某个请求的视图函数。
3.请求钩子:为了避开视图函数使用重复代码,加一个“钩子”,这个钩子关联到一个函数,在请求发送到视图函数之前、之后调用
4.响应:响应就是flask调用视图函数后,返回的一个值或者一个页面,也有在重定向响应中,返回的是一个新的地址链接。
第3章 模板
3.1 jinja2模板引擎
在安装jinja2后,将其应用到程序中,jinja2有一套自己的模板和语法,其主要作用是控制html响应、显示效果以及程序和页面之间信息交互。
1渲染模板:在通用骨架(即模板)中,传入我们个性的信息,最终展现出来。
from flask import Flask,render_template#导入render_template函数
#...
@app.route("/user/<name>")
def user(name):
return render_template("user.html",name=name)
#传入name变量实际值到user.html中
#render_template()函数第一个变量第模板名字,第二个是键值对
2变量:
模块中占位符{{name}}
表示一个变量,告诉模板引擎,这个位置的值从渲染模板时,使用的数据中获取并替代。
3控制结构:即是jinja2提供的,用来改变渲染流程,有以下几种。
1条件控制语句
{% if user %}
Hello,{{user}}!
{% else %}
Hello,stranger!
{% endif %}
2将多次重复的代码写成一个单独的文件中,例如common.html,使用包含命令,传到其他文件中,避免重复
{% include "common.html"%}
#...
3继承,也是重复使用代码,先建立基模板,再建立衍生模板,基模板的某些元素可以在衍生模板中更改。
#基模板base.html
#只有block标签定义的元素才可在衍生模板总更改
</html>
<head>
{% block head %}
<title>{% block title %}{% endblock%} - my application</title>
{% endblock%}
</head>
<body>
{% block body%}
{% endblock%}
</body>
</html>
#衍生模板
{% extends "base.html"%}#继承基模板
{% block title %}Index{% endblock %}#直接加入需要更改的元素
{% block head %}
{{super()}} #head元素中仅仅改了title,所以剩下的还是照旧,就super()来获取原来的内容
{% endblock%}
{% block body %}
Hello,World!
{% endblock %}
从以上三种控制方法,得出以下结论
- 控制结构基本形式{% xxx %},条件控制,则是比常见的if从句多一个结尾{% endif %}
- 控制结构语句都是有始有终,不管是{% endif %}还是{% endblock%}
继承方法中,block标签是更改元素的关键。
3.2使用flask-bootstrap集成Twitter-BootStrap
BootStrap是一个Twitter开发的开源框架,为了在程序中集成BootStrap,我们使用一个叫做flask-bootstap的扩展。
通过初始化化bootstrap=BootStrap(app)
,即可将基模板为我所用,利用模板继承{% extends "bootstrap/base.html"%}
,就可以定义出统一页面了。
3.3自定义错误页面
常见的错误有路由错误404和未处理的异常500,flask允许我们基于模板自定义错误页面:
@app.errorhandler(404)
def page_not_found(e):
return render_template("404.html"),404
@app.errorhandler(500)
deg internal_serner_error(e):
return render_template("500.html"),500
Thinking
这里代码与前几章定义index.html代码进行对比,有显著的差别,index.html使用@app.route("/")修饰器注册,而这里是好像是一个“类”,@app.errorhandler(404),代表是访问的路由不存在这类情况,即可响应404.html,不涉及到"路由"功能。
3.4链接
用的最多就是导航条了,链接到的有相对地址和绝对地址,常用的是url_for()函数,最简单的用法是将函数名(不带后缀类型)作为参数,生成对应的url。还有,用此函数生成动态地址,将动态部分作为关键字参数传入。
url_for("index")#注意不是index.html,这里不需要后缀
url_for("index",_external=True)#返回绝对地址
url_for("user",name=name)#生成动态url链接
3.5 静态文件
常见静态文件有HTML中使用的图片、javascript源代码文件和CSS。
{#定义收藏夹图标#}
{% block head %}
{{ super() }}{#保存定义块的原始内容#}
<link rel="shortcut icon " href="{{ url_for("static",filename = "favicon.ico")}}" type="image/x-icon">
<link rel="icon" href="{{ url_for("static",filename = "favicon.ico")}}" type="image/x-icon">
{% endblock %}
3.6使用flask-moment本地化时间和日期
网络上有个统一的时间,UTC,universal time coordinated协调世界时间,我们将之转为本机时间。flask-moment依靠着moment.js和jquery.js库来实现功能。二者都是在HTML引入这两个库。
要实现显示时间功能,1引入时间变量datetime,2在基模板中引入moment.js库,3在衍生模板中渲染时间戳。时间渲染详见momentjs介绍
步骤
#安装模块及初始化
pip install flask-moment#安装模块
from flask.ext.moment import Moment
moment=Moment(app)#初始化flask-moment
#template/base.html,引入moment.js库
{# 本地化时间 之引入moment.js,jquery在bootstrap中自动引入了 #}
% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
#hello.py 加入datetime变量
from datetime import datetime
@app.route()
def index():
return render_template("index.html",current_time=datetime.utcnow())
#template/index.html,使用flask-moment渲染时间戳
<p>The local data and time is {{ moment(current_time).format("LLLL") }}.</p>{#时间显示#}
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
<p>{{ moment.lang("ch") }}</p>#涉及到语言
第4章 web表单
什么叫做表单?每个web表单都由一个继承自Form的类表示。通俗来说,一个表单经过渲染为HTML后,就是我们看到网页上有交互功能、能键入信息的框框。
flask-wtf 扩展非常适用于表单处理
4.1表单类
新建一个表单,WTForms支持的HTML标准字段及內建的验证函数种类很多,见书本列表。
from flask_wtf import Form
from wtforms import StringField,SubmitField,PasswordField
from wtforms.validators import Required,Length,Email#验证函数
def NameForm(Form):
name=StringField("your name:",validators=[Required])#名字非空,这是一个字段
password=PasswordField("your password",validators=[Required(),Length(6)])#非空、长度不少于6
submit=SubmitField("Submit")
4.2把表单渲染为HTML格式
利用Bootstrap中预先定义好的表单样式渲染整个Flask-WTF表单
{% import "bootstrap/wtf.html" as wtf %}{#此处是导入模块来渲染表单,就是将表单渲染为HTML样式#}
{% wtf.quick_form(form)%}
4.3在视图函数中处理表单
表单中的数据,传递给视图函数,显示具有个性的消息
@app.route("/",methods=["GET","POST"])
def index():
name=None #自定义变量name,通过赋值None来获得
form=NameFlaskForm()#form是对象、实例
if form.valitate_on_submit(): #form.valitate_on_submit()要是填写正确,返回True.
name=form.name.data #name在前面自定义时,是在if外面
form.name.data=""
return render_template("index.html",name=name,form=form)#index.html中的变量name,form接受传入的参数值,name=name,前面的name是变量。
4.4重定向和会话
重定向就是为了避免提交表单刷洗,会提醒再次确认的现象发生
会话session主要是用在重定向中,在请求之间,记住数据,session["name"]=form.name.data
,name=session.get("name")
可以看出来,session好比一个字典,用来储存我们的信息。
4.5Flash消息
请求完成后,有时候需要让用户知道了状态的变化,比如显示确认、警告或者错误from flask import flash
。书中例子是在视图函数总添加提醒出来的条件,在模板文件中设置提醒样式即可。
第5章 数据库
使用Flask-SQLAlchemy管理数据库,在学习之余结合《SQL必知必会》终于理解了这一章节,这一张内容大致如下:
- 关系型数据库的特点,创建模型、关系
- 在shell中使用flask-SQLAlchemy管理数据库的基本操作,创建表、行及删除行、查询
- 进阶为在视图函数中操作数据库,将视图函数填入的form.name.data加入数据库,进而针对不对访问对象有不同的欢迎信息
- 对数据库的迁移migrate、更新和维护等。
5.1常用语法
初始化及简单配置一个SQLite数据库:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)
basedir=os.path.dirname(os.path.abspath(__file__))
#获得当前文件的绝对目录,想一下file为啥不能改为name?_file__ 是用来获得模块所在的路径,固有的“属性”吧
app.config["SQLALCHEMY_DATABASE_URI"]="sqlite:///"+os.path.join(basedir,"shiyanku.sqlite")
#config[]字典配置URI,而不是URL,os.path.join(a,b)用来组合路径的,文件名为shiyanku.sqlite
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"]=True
#自动提交数据库
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]=True
#书中无本此语句,自己根据运行bug添加
db=SQLAlchemy(app)
#SQLAlchemy实例
5.2定义模型:模型就是持久化实体,模型一般是python的类,类中的属性就是数据表的列。
class Role(db.Model):
__table__="roles" #模型中数据表的名称
id=db.Column (db.Integer,primary_key=True) #id列,为主键
name=db.Column(db.String(64),unique=True)#name列,有唯一性
def __repr__(self): #定义一个方法,返回具有可读性的字符串表示模型,具体解释详见下面链接
return "Role:%r" %self.name
__repr__
不管有没有print 在前面,都会显示我们想要的内容,而不是内存地- 注意列Colume(类型,选项)的类型和选项来定义列属性,比如整数/字符串,主键/唯一性等等
5.3关系
关系型数据库就是利用关系将不同表中的行联系起来,为我所用。
“一个角色role可以多个用户user,但是一个用户只能是一个角色”一对多的关系如下:
class Role(db.Model):
#...
users = db.relationship("User",backref="role",lazy="dynamic")#lazy指明如何加载相关记录
#backref反向引用,这一语句意思:一个users列表,是一个相同role角色的列表。
class User(db.Model):
#...
role_id=db.Column(db.Integer,db.ForeignKey("roles.id"))、
#建立role_id外键列,用来“一个用户只能一个角色”来映射roles的id列
5.4数据库操作
上面讲数据库完成了配置,下面就是在Pythonshell中操作数据库
创建表
在pythonshell中输入:
>>>from hello import db #hello为hello.py
>>>db.create_all()
#这个时候就在目录下新建一个xxx.sqlite文件,xxx是在配置中指定的。
#删除表格:db.drop_all()
新建行
>>>from hello import Role,User
>>>admin_role=Role(name="admin")
>>>user_role=Role(name="user")
>>>user_lengqian=User(username="lengqian",role="user_role")
>#...
#role="user_role",role在关系中赋予User模型role属性(backref="role")(小九九:role这个统一赋值的话,在后期就可以容易的列出同一个角色的用户列表...)
#注意,这些数据只是存在python中还未写入数据库
>>>db.session.add(admin_role)
>>>db.session.add(user_lengqian)
#或者简写如下:
>>>db.session.add_all([admin_role,user_lengqian])
再次确认提交:
>>>db.session.commit()
修改、删除行
#修改为直接修改重命名
>>>admin_role.name="adminstrator"
>>>db.session.add(admin_role)
>>>db.session.commit()
#删除行
>>>db.session.delete(admin_role)
>>>db.sessiom.commit()
查询行:
Flask-SQLAlchemy为每个模型类都提供了query对象?(方法吧)。配合使用过滤器进行精确筛选
常见过滤器,有filter_by(条件),filter(),order_by(条件)按条件进行排序返回一个新查询
常见查询执行函数,有all(),first(),count(),get()等
User.query.all()
#返回的是一个列表[<User u"lengqian">,<...>]
User.query.filter_by(username="lengqian").all()
#返回的是一个列表[<User u"lengqian">]
#filter为等值过滤器,username="lengqian"就是匹配条件
5.5在视图函数中操作数据库
现在是简单的在视图函数中操作数据库,比如将用户submit的用户名与已有的数据库模型User表users进行对比,最后展示具有个性的页面。
借助书本例子,关系到数据库的操作有添加查询、添加,重点在于理解session["known"],个人认为,session作为会话,权当做一个类,known是一个实例,在这里是给known赋值,session["known"]=True,只是在这么一个视图函数的特殊环境中,才有session参与进来
#书本例子
@app.route("/",methods=["GET","POST"])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username=form.name.data)
db.session.add(user)
session["known"]=False
else:
session["known"]=True
session["name"]=form.name.data
form.name.data="" #无此行代码时,submit后刷新总是有提醒,个人理解:post提交一次后,form.name.data就被清空了
return redirect(url_for("index"))#redirect函数,重定向响应
return render_template("index.html",form=form,current_time=datetime.utcnow(),name=session.get("name"),known=session.get("known",False))
5.6集成python shell
就是将数据库的数据自动提交给shell中,利用的是flask-script的Shell命令,这个导入只是将我们程序当中的数据库实例、模型注册到shell中,这样的话,就不用我们一个个手动在shell中添加进去了。
以上只是书本描述的一部分作用,更重要的是,将下面文件运行python c:/..../manage.py shell
后,会出现如下代码。这些待选项就是我们用来操作的方法:
============== RESTART: C:\\Users\\Administrator\\flasky\\hello.py ==============
usage: hello.py [-?] {shell,db,runserver} ...
positional arguments:
{shell,db,runserver}
shell Runs a Python shell inside Flask application context.
db Perform database migrations
runserver Runs the Flask development server i.e. app.run()
optional arguments:
-?, --help show this help message and exit
有以下几种操作:
python c:/../../manage.py shell #启动python shell
python c:/../../manage.py db init #数据库的初始化,可建造xxx.sqlite
python c:/..../manage.py db upgrade #更新数据库
python c:/..../manage.py runserver #启动运行
讲了这么多操作,注意点在于加入路径操作才会有满意的结果,否则出现了not package或者toplevel等等各种错误
若想要把对象添加到列表中,可以为shell命令注册一个make_context函数,
from flask_script import Shell
def make_shell_context():
return dict(app=app,db=db,User=User,Role=Role)
manager.add_command("shell",Shell(make_context=make_shell_context))#以后再解释。
if __name__== "__main__"
manager.run() #集成python shell和原来程序有点不同,就是将app.run()改为manager.run()这时候运行如书中一样,运行之后出现一个类似普通shell的界面
如上面所示,make_shell_context()函数注册了程序、数据库实例和模型到shell中,可以如下查看:
>>>python hello.py shell
>>>app
<Flask "app">
>>>python hello.py db
>>>...#数据库库的路径
若没有这样配置的话,要自己手动导入对象才能往下操作
>>>hello
>>>from hello import app
>>>app
>>><Flask "app">
5.7使用Flask-Migrate实现数据库迁移
能够跟踪数据库模式的变化,增量式地 把变化应用到数据中
hello.py:配置flask-migrate
from flask_migrate import Migrate,MigrateCommand
#...
migrate=Migrate(app,db)
manager.add_command("db",MigrateCommand)
步骤:暂时放下来,后期再在项目中补充,挺难的,尤其是脚本的创立(之所以这样,是在前面的pythonshell中失败了)
更新:将上述代码加入程序当中即可
- 在shell中,利用init子命令建立迁移仓库
(venv)...python hello.py db init
即可创建迁移仓库 - 实际操作,更新数据库
(venv)...python hello.py db upgrade
即可
第6章 电子邮件
6.1测试邮件
本章内容见电子邮件QQ邮箱.md(9/17 2017)
单一的邮件测试成功,详见电子邮件QQ邮箱.md和qq.py,问题主要是在于变量的设置和POP3/SMTP授权码的最新属性。
6.2在程序中集成发送邮件
在程序中集中发送、异步发送电子邮件失败。详见hello2.py,暂时放一放吧#0908
更新:上述错误主要在于授权码没有更新,更新后测试成功单独见“QQ邮箱.md”
6.3异步发送电子邮件
第7章 大型程序的结构
本章节最大的问题是“单个文件hello.py转为为大型程序”的描述不足,在结合git checkout
的条件下,将作者代码和自己的对比,一点点转化过来。大概能理解和正常运行了。
7.1项目结构
在这里,使用包和模块组织大型程序的方式,其最大作用是,将功能分开,有利于后期的维护等。
7.2配置选项
在实例生产中,我们需要程序在不同的环境中运行,比如测试、开发和生产,这个时候就需要不同的配置了,于是config.py产生了。config字典中,注册不同的配置环境,一般情况还要注册一个默认的配置,如:"default":DevelopmentConfig
7.3程序包
- 使用工厂函数:就是为了创建,在不同配置环境下的程序实例,用来测试程序运行效果。比如,在开发、生产和测试中。
- 在蓝本中实现程序功能:让路由先休眠来定义路由简单,使拆分开的可以嵌套一起并运行-使得各个模块可以单独编辑维护
7.4x
- 启动脚本:由manage.py来启动脚本
- 需求文件:需求文件生成,requirements.txt用来记录所有依赖包及其版本号在shell
pip freeze >requirements.txt
;新生成虚拟环境的完全副本:pip install -r requirements.txt
即可。文件名自己定义,一般是常用requirements.txt - 单元测试:就是实实在在的将程序可能出现的错误写出来,进行测试。常用标准库中的unittest包编写。
8.4使用flak-login认证用户
第九章 用户角色
用户角色,代表着用户的权限,比如发表文章、评论、关注等,在这里使用的是权限的组合,再根据用户的角色分配不同的权限。具有通用性。10/26/2017 11:24:29 PM
9.1角色在数据库中的表示
在角色模型中,添加permissions字段,其值是一个整数,表示位标志。各操作对应一个位位置,能操作就是1;增加的default列(db.Boolean),则是用来区分是否为管理员。
文中的《程序的权限》表格,并不是flask的设计理念,而是我们的自己添加的属性。我们构建一个8位的二进制数字,是位标志,不同的值代表着不同的权限(数字和权限附加到一起了)。代表不同权限的值相加(权限的相加)结果,就可以代表着不同的用户了,例如,普通用户和管理员肯定不同的。而后,我们构建函数来实现数字和角色的演变。
实际操作:
在models.py中建立"权限常量"???
class Permission:
FOLLOEW = 0x01
...
第12章 关注者
12.1 再论数据库关系
- 数据库使用关系建立记录之间的联系。一对多关系是最常见的关系类型,实现这种关系时,我们要在"多"这一侧键入一个外键,指向"一"这一侧联结的记录(用户是多,角色才是一,"多个用户配一个角色",每个用户通过外键和唯一的角色联系起来)。
大部分的其他关系类型都可以从一对多类型中衍生。一对一关系类型是简化版的一对多关系--限制"多"这一侧最多只能有一个记录即可。
12.1.1 多对多关系
- 需要添加第三张表-关联表。多对多关系就可以分解成原表和关联表之间的两个一对多关系。例如,在student和class两个模型之间,我们建立一个关联表,表中的每一行都表示一个学生注册的一个课程。学生和关联表是一对多关系(一个学生多个课程选择),课程也是如此(一个课程多个学生选择)。
- 于是,我们在多,也就是关联表中建立外键,产生联系。注意关联表只是一个table,不是模型。SQLAlchemy会自动接管这个表。
- 多对多关系仍旧使用定义一对多关系的db.relationship()方法定义,不同的是,必须把secondary参数设为关联表。多对多关系可以在任何一个类中定义,backref参数会处理好关系的另一端。注意其中的回引backref=db.backref("studetns",lazy=‘dynamic‘)。
12.1.2 自引用关系
问题汇总
1.程序启动问题
程序启动有以下三种方法:
- 在shell中交互式输入一条条命令(-Shell就是一个命令行解释器,它的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive))
- 在shell中批处理直接执行脚本(批处理(Batch),用户事先写一 个Shell脚本(Script),其中有很多条命令)
- 在python自带的IDLE中执行脚本
2.程序小问题
- 第九章《用户角色》中,在model.py中更改了角色内容,每次在manage.py shell之后,都要调用Role.insert_roles来更新数据库10/30/2017 3:20:34 PM
- 同样的,在很多章节中,修改数据库项目后,都要执行python managy.py db upgrade,进行数据更新
问题扩展:
1.一个脚本文件hello.py在IDLE中执行,报错no module named flask_stript
,在shell中输入C:\\Python27\\Flask\\hello.py
还是报错(输入地址麻烦的话可以直接将文件丢进shell中执行),报错一样,在shell中键入python hell.py shell
成功运行。
2.书本中多次涉及到“命名空间”,例如flask-bootstrap从flask.ext命名空间导入。
结合之前
3.网络请求只是还需要补充GET,POST等
4.蓝本的理解flask蓝本
5.工厂函数的理解:个人理解,工程函数def current_app(config_name):
,主要是用来测试程序在不同配置环境下的运行效果。若是普通函数的话,程序实际在运行第一次的时候就随之建立了,但是在工厂函数中,先没有创建实例,而是在配置后,再创建实例了,多个配置config,就可以创建很多程序实例用来测试了。0912
6.current_app以及在异步发送邮件一章中的current_app.get_current_boject()辨识链接1
[email protected]和classmethod的区别:链接2
阿里云部署flask:阿里云部署flask+wsgi+nginx
flask GitHub优秀作品:刘志辉
问题汇总
关于pip模块的安装:
- 电脑系统更换SSD系统也全新安装之后,进行开发环境的安装,python2.7.13
- 第一步是安装virtualenv模块,在CMDshell窗口中使用
pip isntall xx
和easy_install xx
,提示没有pip和easy_install命令 - 在python/scripts安装包中发现有pip和easy应用程序,将该目录添加至path即可使用。
- 提示,我百度的时候,大部分做法是下载pip.exe文件安装后再使用。
使用
pip install -r requirements/common.txt
安装依赖包,仅部分安装了,重试还是如此。后面将common.txt中明显不需要的模块删除后放至根目录下,安装成功。这算是一个bug吗?- 基本步骤
- python安装及path配置,注意pip程序路径的配置 # 安装python时自带pip和easy_install应用程序,注意也需要添加至path
- 安装virtualenv模块 #使用pip install进行安装
- 安装虚拟环境并激活
- 在虚拟环境中安装所需要的模块
pip install -r requirements.txt
即可。 - 开发运行环境配置完毕。3/8/2018 10:58:11 PM