对Flask感兴趣的,可以看下这个视频教程:
1. 第一个 flask 程序
# 从 flask 框架中导入 flask 类from flask import Flask# 用 flask() 初始化一个 flask 对象,并赋给 app# 需传递一个参数 __name__# 1. 方便 flask 框架去寻找资源# 2. 方便 flask 插件去定位问题app = Flask(__name__)# @app.route() 是一个装饰器,作用是对 url 与 视图函数进行映射# 将 `/` 映射到 hello_world() 函数上# 即用户访问 http://example:80/ 的时候,用 hello_world() 函数来响应@app.route('/')def hello_world(): return 'Hello World!'# 如果当前文件作为程序入口,那么就执行 app.run() if __name__ == '__main__': # app.run() 是启动一个应用服务器来响应用户请求并不断监听 app.run()
2. 使用 debug 模式
使用 debug 模式有很多好处:
- 将报错信息显示到浏览器上,而不需要进入编辑器中查看报错信息,方便开发者查看错误
- 当检测到程序代码(.py文件)发生改动,程序会自动加载而不需要手动重启服务器
对 flask 程序使用 debug 模式有 2 种方式,如下:
2.1 在 app.run() 中使用
在 app.run()
中直接传入一个关键字参数: app.run(debug=True)
。
2.2 在配置文件中使用
在相同目录下,新建一个
python
文件,建议命名为 config,并在里面指定该程序配置了DEBUG
模式,即 `config.py 文件的内容如下:config.py # encoding:utf-8 DEBUG = True # SECRET_KEY # SQLALCHEMY_DB # 数据库的一些参数配置
然后在主 app 文件中导入这个文件并配置到 app 中,主 app 文件内容如下:
First_Flask.py # encoding:utf-8 from flask import Flask import config # 导入 config 配置文件 app = Flask(__name__) app.config.from_object(config) # 将该配置文件的配置信息应用到 app 中 @app.route('/') def hello_world(): a = 3 b = 0 c = a/b return 'Hello,World.' if __name__ == '__main__': app.run()
config.py
文件的用处非常大,需要掌握这种配置方法,在后期的SECRET_KEY
和SQLALCHEMY_DB
(与数据库有关)都需要在这个文件中做配置.
3. URL 传参到视图
- 参数的作用:可以在相同的 URL 但是指定不同的参数时,来加载不同的数据
如:
http://localhost:8000/article/abc
和http://localhost:8000/article/def
中,两条 URL 的参数不同,我们可以获取这个参数并渲染后返回客户浏览器
如何在 flask 中使用参数?代码如下
@app.route('/article/
') def article(id): return u' 你请求的参数是:%s
' % id
- 参数需要放置在两个尖括号中
- 视图函数中需要放和 URL 参数同名的参数
4. URL 反转
正转指的是:在获取到用户输入的 URL 后将该 URL 映射到对应的视图函数中,让对应的视图函数去处理该用户的请求;
反转指的是:与正转相反,通过视图函数来查找对应的 URL。反转的作用是:1. 在页面重定向的时候会使用 URL 反转;2. 在模板中会使用 URL 反转实现反转的方法:
- 在 flask 框架中导入
url_for
模块 用
url_for('FunctionName')
反转First_Flask.py
源码如下:# encoding:utf-8 from flask import Flask,url_for import config app = Flask(__name__) app.config.from_object(config) @app.route('/') def hello_world(): print url_for('article',id='123') print url_for('my_list') return 'Hello,World.' @app.route('/article/
/') def article(id): return u' 你请求的参数是:%s
' % id @app.route('/list/') def my_list(): return '
list
' if __name__ == '__main__': app.run()
5. 页面跳转和重定向
作用:在用户访问某些需要登录的页面时,如果用户没登录,则可以让他重定向到登录页面
实现:
import redirect,url_for redirect(url_for('login'))
二、jinja2 模板
1. 模板渲染和参数
1.1 模板渲染
模板实际上就是一些被编写好的具有一定格式的 html
文件。
在 pycharm 左侧一栏,项目下有两个文件夹: static
和 template
,分别用于存放静态文件(如css,js,img文件)和模板文件(如html),所以我们的 html 文件应该放在 template
文件夹下。
如何在主程序中调用模板文件呢?
- 在
template
文件夹下新建一个 html 文件 - 在主程序中导入
render_template
模块 - 调用语法:
render_template('abc.html')
,注意不用写路径,flask 会自动去template
文件夹下查找abc.html
,但有文件夹除外
代码如下:
import render_templatereturn render_template('index.html')
1.2 参数
在 web 项目开发的大多数情况下,我们需要在 html 文件中从后台程序传入一些参数,然后将带有这些参数的 html 文件返回浏览器。这时候就需要在 html 文件中引用这些后台的参数,方法是 { { Params }}
用 2 个花括号括起来,同时还要在后台程序做一些传参的动作。具体如下:
后端传参:
render_template('index',username=u'蚂蚁有毒',gender=u'男',age=18)
前端引用:
用户名:{ { username }}
性别:{ { gender }}
年龄:{ { age }}
但是要是参数越来越多,则代码会变得很复杂,可读性差,管理难度大。那么我们可以用一个字典(DICT)来定义一组参数。如下:
user = { 'username':id, 'gender':u'男', 'age':18}# 调用时传入一个关键字参数即可return render_template('index.html',**user)
2. 模板中访问属性和字典
上面所演示的都是调用一些简单的参数,如果在更复杂的环境下,如调用类的属性呢?或者是调用字典中的字典的值呢?应该怎么做?
在主程序中可以先定义一个类并实例化:
class Person(object){ name = u'蚂蚁有毒' gender = u'男' age = 18 } p = Person()
再定义一个字典:
content = { 'person':p, 'websites':{ 'baidu':'www.baidu.com', 'google':'www.google.com' } }
然后传参时进行调用:
return render_template('index',**content)
最后在
index.html
中调用:姓名:{
{person.name}}性别:{
{person.gender}}年龄:{
{person.age}}
百度:{
{websites.baidu}}{
{websites.google}}
3. 模板中的 if 和 for
实际上,我们还可以在模板(html文件)中嵌入python的代码:{% code %}
,这是 jinja2
的语法,可以嵌入 if 语句和 for 语句来在 html 文件中执行相关的逻辑操作。
3.1 if 语句的操作
主程序代码:
# encoding:utf-8 from flask import Flaskrender_template app = Flask(__name__) app.config.from_object(config) @app.route('/
') def index(is_login): if is_login == '1': user = { 'username':u'蚂蚁有毒', 'age':18 } return render_template('index.html',user=user) else: return render_template('index.html') if __name__ == '__main__': app.run(debug=True) html 代码:
蚂蚁有毒的首页 {% if user and user.age > 18 %} { { user.username }} 注销 {% else %} 登录 注册 {% endif %}欢迎来到蚂蚁有毒的首页。
3.2 for 语句的操作
3.2.1 基本用法
for 循环的语法,在 html 中调用 for 语句和 if 语句的语法是一样的,都是在 {% %}
中写入 for 关键字。
主程序代码:
users = { 'username':u'蚂蚁有毒', 'gender':u'男', 'age':18 } websites = ['www.baidu.com','www.google.com','www.qq.com'] return render_template('index.html',user=users,website=websites)
html 代码:
{% for k,v in user.items() %}
{
{ k }}:{ { v }} {% endfor %}
{% for website in websites %}{
{ website }} {% endfor %}
完整代码参照上一节
3.2.2 练习
题目:渲染一个 四大名著 给前端,然后前端用一个表格展示出来。
先在主程序中定义一个变量用于存放四大名著的基本信息:
books = { u'三国演义':{ 'author':u'罗贯中', 'price':109 }, u'西游记':{ 'author':u'吴承恩', 'price':120 }, u'红楼梦':{ 'author':u'曹雪芹', 'price':113 }, u'水浒传':{ 'author':u'施耐庵', 'price':135 } }
再在主程序中将其传给前端 html 文件
return render_template('index.html',books = books)
最后在前端模板中调用
书名 作者 价格 { { k }} { { v.author }} { { v.price }}
4. 过滤器
过滤器可以理解为 Linux 中的管道符 |
,将获取的变量经过管道符后筛选出想要的内容。在 flask 中有很多过滤器,这里介绍 2 个比较常用的过滤器:default
和 length
。要注意的是,过滤器只能针对变量{ { params }}
使用。
4.1 default
default 过滤器的作用:如果当前变量不存在,可以使用指定的默认值
对于 default 过滤器,我们做一个实验:如果用户有头像则显示自己的头像,如果用户没有头像则显示默认头像。
在 html 文件中使用如下所示:该行代码表示: 如果后端主程序有传递 avatar 变量过来,那么就使用 avatar 变量的值; 如果后端主程序没有传递 avatar 变量过来,那么就使用 default 过滤器指定的内容对于本例而言,default 后面跟的图片地址应该是默认头像的地址,avatar 变量内保存的值应该是用户头像
4.2 length
可以统计有长度属性的变量的长度。语法与 default 过滤器一样,但不用在后面跟上指定的变量。对于 length 过滤器,我们做一个实验:统计评论的数量并显示评论内容。
主程序代码:
comments = [ { 'user':u'蚂蚁有毒', 'content':u'我不喜欢这个东西' }, { 'user':u'杨烺', 'content':u'有同感,我也是' } ] return render_template('index.html',comments = comments)
html 模板代码:
评论数:({
{ comments | length }})
{% for comment in comments %}- { { comment.user }}:
此外还有其他很多过滤器,可以自行查找资料。
- { { comment.content }}
5. 继承和使用 block
5.1 继承
继承的概念和面向对象编程的类的继承是一样的,只不过在这里继承的对象是模板。可以创建一个常用模板,并且定义相关接口,可以供其他模板所使用。继承的作用是:可以把一些公共的代码放在父模板中,避免编写重复的代码。
当创建好了一个模板后,在子模板中可以使用
{% extends 'base.html' %}
来继承该模板
5.2 使用 block
但是如果我要在子模板中编写自己所特有的内容,应该怎么办?这时候就需要在父模板中写一个接口,来让子模板实现:
- 在父模板需要的地方定义接口的方式是:
{% block abc %}{% endblock %}
- 在子模板中同样需要写上
{% block abc %} code {% endblock %}
,并且在code
处写子模板需要的代码。- 需要注意的是,子模板必须在父模板定义的接口中写代码,不然没有作用。如下所示:
1. 父模板(Base.html): {% block title %}Base {% endblock %} {% block content %}{% endblock %} 2. 子模板(index.html): {% extends 'base.html' %} {% block title %}首页 {% endblock %} {% block content %}蚂蚁有毒的首页
{% endblock %}
6. URL 链接和加载静态文件
6.1 URL 链接
我们如果想要实现:点击一个按钮就能跳转到另一个页面上。那么我们就要使用链接跳转技术,在 flask 中,可以通过 <a href={ { url_for('视图函数名') }}>哈哈</a>
来实现
6.2 加载静态文件
css,js,img 这 3 个文件都属于静态文件,都要放在项目的 static
文件夹下。如果我们要对模板中的一些内容进行渲染,如:对 <a>
标签的内容进行渲染,那么我们可以将原本写在 <head>
标签中的 <style>
标签的内容放到 css
文件中,再在模板中使用 url_for()
将 css
文件的渲染方式链接进来。步骤如下:
1. 加载 css 文件:
<link>
标签
- 在
static
文件夹下创建一个新的文件夹css
,再在css
文件夹下创建一个 css 文件,可随意命名(好记就行),如:head.css
。在使用了
<a></a>
标签的模板中,若想引用 css 文件的内容,可以用url_for()
链接进来,但格式与链接网址略有不同。正确链接静态文件的方式为:在模板 标签范文内: 注意!文件路径
2. 加载 img 文件
<img>
标签
- 第一步和加载 css 文件的第一步一样,创建文件夹和文件:
/static/img/index.jpg
第二步在模板中需要插入图片的地方使用如下代码:
3. 加载 js 文件
<script>
标签
- 第一步和加载 css 文件的第一步一样,创建文件夹和文件:
static/js/index.js
第二步在模板中需要插入脚本的地方使用如下代码:
<script src='{
{ url_for('static',filename='js/index.js') }}'>
一般静态文件也就这三样,使用方法如上所示。
三、数据库
1. 背景知识和软件安装
安装 MySQL
数据库我们使用的是
如果在安装过程中提示我们要安装 windows 插件,那就按照它提示的网址去下载,没有给网址的话就百度谷歌吧。MySQL
,下载地址:https://dev.mysql.com/downloads/mysql/
,下载社区版就行了。注意要下载安装包(.msi)而不是压缩包(.zip)安装 MySQL-python
MySQL-python 是一个驱动或者说是一个插件,如果我们想要通过 python 去操作 MySQL 就需要借助这个软件去做。在 Windows 下安装 MySQL-python 的步骤如下:进入 python 的 flask 虚拟环境并启动:
cd C:\Virtualenv\flask-env\Script\ active pip install mysql-python
- 安装的时候会报错,我们需要去
www.lfd.uci.edu/~gohlke/pythonlibs/#mysql-python
这个网站上下载一个非官方的插件MySQL_python-1.2.5-cp27-none-win_amd64.whl
,来使 mysql-python 支持 Windows 。可以下载到 Virtualenv 所在盘的任意目录。 安装这个插件:进入到该文件所在目录执行命令
pip install MySQL_python-1.2.5-cp27-none-win_amd64.whl
即可。
安装 Flask-SQLALchemy
- 介绍 Flask-SQLALchemy 是一套 ORM(Object Relationship Mapping,模型关系映射) 框架。好处:可以让我们操作数据库就和操作对象一样简单,非常方便。因为一个表就抽象成一个类,一条数据就抽象成该类的一个对象。这样一来,我们去操作数据库的时候,就不用去写什么 select 之类的数据库语句,而是可以直接用 python 操作实例对象就行了。
安装
cd C:\Virtualenv\flask-env\Script\ active pip install flask-sqlalchemy
2. SQLAlchemy 连接数据库
先创建一个数据库:
进入 MySQL 的命令行 -> 输入密码 ->
create database [db_demo1(数据库名)] charset utf8;
在主 app 程序中从
flask_sqlalchemy
导入SQLAlchemy
类并初始化from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) db = SQLAlchemy(app)
在
config.py
文件中配置数据库信息并连接DIALECT = 'mysql' DRIVER = 'mysqldb' USERNAME = 'root' PASSWORD = 'root' HOST = '127.0.0.1' PORT = '3306' DATABASE = 'db_demo1' SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
在主 app 文件中,添加配置文件
import config
app.config.from_objece(config)做测试,看有没有出现问题
db.create_all()
3. SQLAlchemy 模型与表映射
对于一个表,我们可以创建一个类(模型)来与之对应(一张表对应一个类),如下表:
先定义好表格是怎么样的
articel 表: create table article( id int primary key autoincrement, title varchar(100) not null, content text not null )
再创建出对应的(模型)类
class Article(db.Model): # 一定要继承自 db.Model __tablename__ = 'article' # 指定表名,默认为类的小写字符 id = db.Column(db.Integer,primary_key=True,autuincrement=True) # 表中的每个字段都要用 Column 创建,然后指定数据类型主键自增长等属性 title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
将所有创建的模型(类)都映射到数据库中成为一个个的表格
db.create_all()
连接数据库的总结:
- 模型(类)需继承自
db.Medel
,表格中的字段必须要用db.Column
映射数据类型:
db.Integer 代表 int, db.String(length) 代表 varchar,需要指定长度 db.Text 代表 text
其他属性:
primary_key=True 代表 主键 nullabel=True 代表 可为空,默认可以 autoincrement=True 代表 主键自增长将所有的模型(类)都在数据库中创建:
db.create_all()
4. 数据库的增删改查
对数据库的增删改查,一般都在视图函数里面写,这样在浏览器上对某个 URL 进行访问时,就会执行对应的视图函数里面的代码,从而达到操作数据库的目的。
flask 对数据库的增删改查,包括提交,都使用的是 db.session
操作。
增加
- 创建一个表中的数据,即实例化模型(类):
article1 = Article(title='Monday',content='something')
- 将 add 操作添加到事务中:
db.session.add(article1)
- 更新到数据库中:
db.session.commit()
- 创建一个表中的数据,即实例化模型(类):
删除
- 把要删除的数据查找出来:
article1 = Article.query.filter(Article.title=='Monday').firsr()
- 把找出来的数据删除:
db.session.delete(article1)
- 做事务的提交:
db.session.commit()
- 把要删除的数据查找出来:
修改
- 把要修改的数据查找出来:
article1 = Article.query.filter(Article.title=='Monday').firsr()
- 把找出来的数据做修改:
article1.title = 'Sunday'
- 做事务的提交:
db.session.commit()
- 把要修改的数据查找出来:
查询
result = Article.query.filter(Article.title=='Monday') # 查找是针对模型(类)的,使用 query 属性,该属性继承自 db.Model,并用 filter() 来过滤条件 article1 = result.first() # 实际上筛选出来的对象是放在一个 list 中,可用 .first() 取 list 中的第一个值 print 'title:%s' % article.title print 'content:%s' % article.content
5. SQLAlchemy 的外键约束
创建 2 张表:user
和 article
,其中 article
的 author_id
引用是 user
的 id
,即 2 者是外键关系。
数据库语句创建:
创建用户表: create table users( id int primary key autoincrement username varchar(20) not null ) 创建文章表: create table article( id int primary key autoincrement title varchar(100) not null content text not null author_id int, foreign key `author_id` reference `users.id` # 用外键相关联 )
flask-sqlalchemy 模型创建:
创建用户表: class Users(db.Model): __tablename__ = 'users' id = db.Column(db.Integer,primary_key=True,autoincrement=True) usernmae = db.Column(db.String(20),nullable=False) 创建文章表: class Article(db.Model): __tablename__ = 'article' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(100),nullabel=False) content = ab.Column(db.Text,nullabel=False) author_id = db.Column(db.Integer,db.ForeignKey('users.id')) # 外键关系
实例化模型(类)
因为
article
依赖与user.id
存在,所以先创建 user,在创建 article。user1 = User(username='user1') db.session.add(user1) db.session.commit() article = Article(title='aaa',content='bbb',author_id=1) db.session.add(article) db.session.commit()
需求:查询
title ='aaa'
的文章的作者4.1 传统方法
article = Article.query.filter(Article.title=='aaa').first() # 找出标题为 aaa 的文章 author = article.author_id # 找出文章的作者 user = User.query.filter(User.id==author).fitst() # 找出 id 为作者 id 的作者 print 'username:%s' % user.id # 打印作者名
当然了,这个方法实在是太过于繁琐,作为一个优秀的框架模型,SQLAlchemy 是可以用更先进的方法去找的,如下所示:
4.2 用 SQLAlchemy 语法
article = Article.query.filter(Article.title=='aaa') article.author.username # 找出标题为 aaa 的文章作者 user = User.query.filter(User.username=='user1') user1.articles # 找出作者 user1 写过的所有文章
当然了,这个只是理想中的方法,这样的方式更方便我们去获取需求,不过 SQLAlchemy 已经实现了这种方法,只需要做如下映射即可使用:
# 先在 Article 模型中添加属性 author = db.relationship('User',backref=db.backref('articles')) # 再查找某篇文章的作者 article = Article.query.filter(Article.title=='aaa').first() print 'username : %s' % article.author.username # 查找某个作者的文章 user = User.query.filter(User.id=='1').first() print u'%s 的文章:'% user.username for article in user.articles: print article.title
当对两个
table
的关系用了relationship
关联外键之后,那么article
的author
就不必在实例化的时候指定了,所以当你添加一篇文章,可以进行如下操作:article1 = Article(title='111',content='222') article1.author = User.query.filter(User.username=='myyd').first().id
6. flask_script 命令行操作
简介:Flask_Script
可以让程序通过命令行的形式来操作 Flask
,例如:通过命令跑一个开发版本的服务器、设置数据库,定时任务等等。要使用 Flask_Script
,可以在 Flask
虚拟环境中通过 pip install flask-script
安装最新版本。
6.1 编写 flask_script 脚本代码
新建一个
manage.py
文件,将代码写在该文件中,而不是写在主app
文件中。内容如下:# encoding:utf-8 from flask_script import Manager # 从 flask_script 导入 Manager from flask_script_demo1 import app # 从 flask_script_demo1 导入 app manage = Manager(app) # 初始化 app @manage.command # 装饰器 def runserver(): # 执行命令的程序写在这个函数下 print u'服务器跑起来了。' @manage.command # 装饰器 def stopserver(): # 执行命令的程序写在这个函数下 print u'服务器关闭了。' if __name__ == '__main__': manage.run()
命令行调用
manage.py
文件:在虚拟环境的命令行下,用
python manage.py command
执行manage.py
文件下的某个程序,如:python manage.py runserver
和python manage.py stopserver
分别会执行manage.py
文件中的runserver()
和stopserver()
方法。
6.2 从其他命令文件中调用命令
如果有一些关于数据库的操作,我们可以放在一个文件中执行。如
db_script.py
文件:# encoding: utf-8 from flask_script import Manager # 因为本文件不是作为主 app 文件,所以不需要写 if __name__ == '__main__' # 也不需要在初始化的时候传入 app 文件 DBManage = Manager() @DBManage.command def init(): print u'服务器初始化完成。' @DBManage.command def migrate(): print u'数据库迁移完成。'
这时候要想用上
db_script.py
里定义的命令,需要在主manage.py
文件中导入该文件并引用该文件的命令:from db_scripts import DBManage # 导入 db_script.py 文件 manage.add_command('db',DBManage) # 引用该文件 # 之后要是想使用 db_script.py 中的命令,命令行中就要通过 python manage.py db init 来调用 # 其中,db 是 manage.add_command() 中引号内的值,调用子命令的方法就是这种格式
总结:
如果直接在主
manage.py
文件中写命令,调用时只需要执行python manage.py command_name
即可如果将一些命令集中在另一个文件中,那么就需要输入一个父命令,比如
python manage.py db init
例子:两个文件的完整代码如下
1. manage.py # encoding:utf-8 from flask_script import Manager from flask_script_demo1 import app from db_scripts import DBManage manage = Manager(app) @manage.command def runserver(): print u'服务器跑起来了。' @manage.command def stopserver(): print u'服务器停止了。' manage.add_command('db',DBManage) if __name__ == '__main__': manage.run() 2. db_script.py # encoding: utf-8 from flask_script import Manager DBManage = Manager() @DBManage.command def init(): print u'服务器初始化完成。' @DBManage.command def migrate(): print u'数据库迁移完成。'
7. 分开 Models 和解决循环引用
之前我们都是将数据库的模型(类)放在主 app 文件中,但是随着项目越来越大,如果对于加入的新的模型,我们还放在主 app 文件中,就会使主 app 文件越来越大,同时也越来越不好管理。所以我们可以新建一个专门存放模型的文件。如 models.py
文件用来专门存放模型的文件。
将原本在主 app 文件中定义的模型(类)移动到
models.py
文件中,但是会提示错误,所以我们在models.py
文件中导入主 app 文件的 db:from models_sep.py import db
,两个文件的完整代码下所示:1. # models_sep.py 文件 from flask import Flask from flask_sqlalchemy import SQLAlchemy from models import Article app = Flask(__name__) db = SQLAlchemy(app) db.create_all() @app.route('/') def index(): return 'index!' if __name__ == '__main__': app.run() 2. # models.py 文件 from flask_script_demo1 import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincrement=True) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
执行以上文件,会报错。
报错提示:
ImportError: cannot import name Article
,出现此类报错,先排查路径和导入的内容是否有错,若保证没错,则很可能是出现循环引用。报错原因:循环引用,即
models_sep.py
引用了models.py
中的Article
,而models.py
又引用了models_sep.py
中的db
,从而造成循环引用。解决循环引用:
解决方法:将
db
放在另一个文件exts.py
中,然后models_sep.py
和models.py
都从exts.py
中引用db
变量,这样就可以打破引用的循环。三个文件的代码如下:
1. # exts.py 文件 from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() 2. # models_sep.py 文件 from flask import Flask from models import Article from exts import db import config app = Flask(__name__) app.config.from_object(config) db.init_app(app) @app.route('/') def index(): return 'index' if __name__ == '__main__': app.run() 3. # models.py 文件 from exts import db class Article(db.Model): __tablename__ = 'articles' id = db.Column(db.Integer,primary_key=True,autoincre) title = db.Column(db.String(100),nullable=False) content = db.Column(db.Text,nullable=False)
总结:
分开 models 的目的是:让代码更方便管理。
解决循环引用的方法:把 db 放在一个单独文件中如
exts.py
,让主 app 文件
和models 文件
都从exts.py
中引用。
8. flask-migrate 数据库迁移
在以上基础上,我们将模型(类)映射到数据库中,调用 db.create_all()
进行映射。然后运行,发现报错:No application found. Either work inside a view function or push an application context.
,是因为在 db 的上下文中,没有将 app 推入到栈中。
8.1 所谓上下文(flask独有的特性):
当用户访问了服务器,则服务器会将当前 app 并加载到 app 栈中;如果用户没有访问服务器,那么即使 app 已经创建,但是没有被服务器加载到 app 栈中。若这时候运行服务器,则 app 栈中没有加载 app,当 db.init_app(app)
去 app 栈中取 app 对象的时候没有成功获取,所以报错。
原因:
情况一:db = SQLAlchemy(app)
这句代码运行的时候,会将 app 推到app栈中;- 然后
db.create_all()
就可以通过 app 栈获取到栈顶元素 app 了。
- 使用分开 models 后,
db = SQLAlchemy()
时没有传入 app,所以没有将 app 推到栈中; - 那么在主 app 文件中通过
db.init_app(app)
将 app 与 db 绑定时,会去 app 栈中取 app,但是没有获取到,所以报错。
解决方法:将 app 手动推到 app 栈中:
# 将 db.create_all() 替换为以下代码 with app.app_context(): db.create_all()
8.2 migrate 数据库迁移:
这个时候如果我们的模型(类)要根据需求添加一个作者
字段,这时候我们需要去修改模型 Article
,修改完成我们需要再映射一遍。但是对于 flask-sqlalchemy
而言,当数据库中存在了某个模型(类)后,再次映射不会修改该模型的字段,即再次映射不会奏效。
传统解决办法:
在数据库中删除该模型对应的表格,再将带有新字段的模型重新进行映射。
很显然,这种方式明显很简单粗暴,非常不安全,因为在企业中一个数据库中的表格是含有大量数据的,如果删除可能会造成重大损失。所以我们需要一个可以动态修改模型字段的方法,使用 flask-migrate
。先安装:在虚拟环境下使用命令 pip install flask-migrate
即可。
8.3 使用 flask-migrate
动态修改模型字段
使用 flask-migrate
的最简单方法是:借助 flask-script
使用命令行来对 flask-migrate
进行操作。一共有好几个步骤,分别说明一下:
新建文件
manage.py
:新建
manage.py
文件后:导入相应的包并初始化
manager
from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app)
要使用
flask_migrate
必须绑定app
和db
migrate = Migrate(app,db)
把
MigrateCommand
命令添加到manager
中manager.add_command('db',MigrateCommand)
manage.py
文件代码如下:from flask_script import Manager from migrate_demo import app from flask_migrate import Migrate,MigrateCommand from exts import db manager = Manager(app) # 1. 要使用 flask_migrate 必须绑定 app 和 db migrate = Migrate(app,db) # 2. 把 MigrateCommand 命令添加到 manager 中 manager.add_command('db',MigrateCommand) if __name__ == '__main__': manager.run()
主 app 文件不需要再对模型进行映射,所以可以将以下语句给删除:
with app.app_context(): db.create_all() # 将模型映射到数据库中
在
manage.py
文件中,导入需要映射的模型(类):因为在主 app 文件中已经不再需要对模型进行映射,而对模型的操作是在
manage.py
文件中进行的,包括flask-migrate
动态映射,所以要导入需要映射的模型。from models import Article
完成以上步骤后,即可到命令行中更新数据库中的模型了:
python manage.py db init
,初始化flask-migrate
环境,仅在第一次执行的时候使用。python manage.py db migrate
,生成迁移文件python manage.py db upgrade
,将迁移文件映射到数据库的表格中
9. 使用 cookie 和 session
背景知识:HTTP 协议是无状态的,每次连接断开后再重新连接,服务器是不记得浏览器的历史状态的。也就是说:即使第一次浏览器访问服务器时登陆成功后,只要断开与服务器的连接,服务器就会忘记浏览器的信息,那么当浏览器与服务器再次成功连接时,服务器不知道浏览器的相关信息。
9.1 cookie 和 session 的介绍
cookie
而 cookie 的出现就是为了解决这个问题:当浏览器第一次在服务器上登录成功时,服务器会将一些数据(cookie)返回给浏览器,然后浏览器将 cookie 保存在本地,第二次访问服务器时浏览器会自动地将上次存储的 cookie 发送给服务器,服务器通过 cookie 判断浏览器的信息从而提供更好的服务(如自动记录用户上次浏览的位置等)。
服务器和浏览器交互 cookie 的过程对用户而言是透明的。
cookie 信息保存在用户的浏览器中。
session
session 的作用和 cookie 类似,都是为了保存用户的相关信息。不同的是:cookie 是将用户的信息保存在用户的浏览器本地,而 session 是将用户的信息保存在服务器上。相对而言,session 比 cookie 更加安全和稳定,但是会占用服务器的一些资源,不过影响不大。同时,session 还支持设置过期时间,也从另一方面保证了用户账号的安全。
9.2 flask 中 session 的工作机制
flaks 中 session 的工作机制是:服务器将用户的敏感信息加密后存入 session 中,然后把 session 放在 cookie 中,再将 cookie 返回给浏览器,下次浏览器访问的时候将 cookie 发送给服务器,服务器再根据浏览器发送的 cookie 取出里面的 session,再从 session 中取出用户的信息。
这样做可以节省服务器的一些资源。在安全问题上,因为用户的信息先经过加密再存入 session 中的,所以还是有一定的安全性能的。
9.3 操作 session
想要在 flask
中操作 session
,我们必须先导入 session
模块:from flask import session
。
当用户访问某一个 URL
的时候,相应的视图函数就会有相应的动作,我们可以在视图函数中进行 session
的操作。实际上操作 session
的方法和操作字典是一样的。
不过对 session
进行操作之前,要先设置一个 SECRET_KEY
,这个 SECRET_KEY
就是用来对用户的信息进行加密的密钥,加密后的数据再保存到 session
中。设置 SECRET_KEY
的方法有两种:
- 新建一个
config
文件,在config
文件中添加SECRET_KEY = 'xxx'
这条语句,再在主 app 文件中使用config
文件的配置app.config.from_object(config)
就行了。- 直接在主 app 文件中:
app.config['SECRET_KEY'] = 'xxx'
但实际上,SECRET_KEY
要求用 24 个字符来赋值,我们可以使用 os 模块,即 import os
。
session 添加数据:
session['username'] = 'myyd'
session 获取数据:
有 2 种方式,第一种和操作字典的方法一样:
return session['username]
;第二种是用session
中的 get() 方法:session.get('username)
。推荐使用第二种方式:
session.get('username')
,因为当username
不存在时,第一种方法会抛出异常,而第二种方法是返回 None。session 删除数据:
session.pop('username')
对于
session
的数据删除操作,我们可以设计一个小实验,即在删除前后分别打印session
中的数据,看看结果如何。session 清除所有数据:
session.clear()
同样可以设计一个和删除数据一样的小实验,来对清除操作进行验证。
源代码如下:
# encoding:utf-8 from flask import Flask,session import os # 该模块用于生成随机字符串 app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) # SECRET_KEY 为随机生成的字符串 @app.route('/') def index(): session['username'] = 'myyd' # 添加 session 数据 return 'index' @app.route('/get/') def get(): username2 = session.get('username') # 获取 session 数据 return username2 @app.route('/delete/') def delete(): print session.get('username') session.pop('username') # 删除 session 中指定的数据 print session.get('username') return 'success.' @app.route('/clear/') def clear(): print session.get('username') session.clear() # 清除 session 中所有的数据 print session.get('username') return 'success.' if __name__ == '__main__': app.run(debug=True)
以上是对 session 的一些基本操作。
同时还需要注意一点的是:
若服务器重启后,且浏览器没有重新获取 session 时,我们直接去获取浏览器的 session,会报错!
ValueError: View function did not return a response
,提示视图函数没有返回值。 这是因为:python 是脚本语言,运行时从头开始解释,则当服务器重启后,SECRET_KEY
会被重置,服务器想要读取浏览器发送过来的 session 中的数据时,会使用新的 SECRET_KEY
进行解密,而新的 SECRET_KEY
并不能够对 session 中的数据进行解密,所以服务器获取失败,不能返回值。
10. GET 、POST 和 钩子函数
10.1 概述
GET 和 POST 的区别:
使用场景
GET 请求:单纯从服务器获取资源,没有对服务器产生影响,即没有对服务器的数据进行修改。
POST 请求:从服务器获取资源,并且传给服务器一些需要处理的数据。服务器对浏览器传过来的数据进行相关逻辑判断或者处理后返回给浏览器。此时是对服务器产生了某些影响的,而不是单纯的访问浏览器某些资源。传参
GET 请求:请求的参数跟在 URL 后面并用
POST 请求:请求的参数不是放在 URL 后,而是将参数放在 Form 表单中。?
号连接。
10.2 使用 GET
通过一个小例子来帮助理解:
在
index.html
首页设置一个<a>
标签,跳转至search
的页面,同时在该<a>
标签中携带参数q=hello
,在服务器端获取浏览器请求的参数并返回给浏览器。先创建一个首页
index.html
并在主 app 程序中用render_template
返回首页:from flask import render_template # 导入该特性 return render_template('index.html') # 在视图函数 index 中返回首页
同时在首页用
<a>
标签跳转到search
页面,并携带参数传给服务器的search()
视图函数。点击查询
然后在主 app 程序的 search 视图函数中,用 request 对象获取用户提交的值,不过要先从 flask 中导入该特性
from flask import request # 导入 return 'the parameter you have submit is:%s' % request.args.get('q') # 获取并返回
注意这个 request 对象:
request 可以获取用户提交的请求参数,包括 GET 和 POST 提交的参数信息。reques 对象实际上一个字典,其获取的数据是以字典的格式保存下来的。如下所示:
# 如果提交的是这样的数据 request.args = { 'q':'hello' 'a':'world' } # 那么打印出来是下面这个样子的 print requesr.args ImmutableMultiDict([('q', u'hello'), ('a', u'world')])
所以我们可以向字典一样通过
request.args.get('q')
来获取用户提交的参数。参考代码如下:
# 主 app 文件 from flask import Flask,request,render_template @app.route('/') def index(): return render_template('index.html') @app.route('/search/') def search(): return u'你要查询的参数是:%s' % request.args.get('q') # index.html 文件 点击查询
10.3 使用 POST
POST 请求可以借助模拟登录的过程来做一个小实验:在 login.html
中创建一个表单,该表单要指定后台处理的视图函数,同时还需要指定提交请求的方式为 POST。还要在主 app 程序的视图函数中显式地标注该视图函数支持 POST 和 GET。
在
login.html
中创建表单:创建代码略,后面一起给出。
如果仅仅做了
render_template
的映射和url_for
的反转,还不够,因为视图函数login()
还未支持POST
请求方式,而默认的视图函数只能支持GET
请求。所以还需要进行如下配置:@app.route('/login/',method=['GET','POST'])
需要注意的是:
- 不能只写 POST,因为只写 POST 则该视图函数只支持 POST;
- 而我们去请求 login 网页的时候用的是 GET,向 login 提交数据的时候才用 POST,所以 login() 必须同时支持两种方法
我们可以在
login()
这个视图函数中根据用户的请求方式来判断用户是否登录,从而返回不同的内容。因为用户请求
login
这个页面的时候是用 GET,而在login
页面中进行登录所提交的请求是 POST。获取用户请求提交的方法,也是用 request 对象:request.methods
这里要注意,我们获取 POST 请求的参数时,虽然也是用 request 对象,但是这里应该用
request.form.get('username')
,而且在login.html
的表单中,需要为<input>
标签指定名字这样才可以在后台通过name
获取到内容。所以login()
视图函数应该这么写:@app.route('/login/,methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: return u'你好,%s' % request.form.get('username')
源代码:
1. 主 app 文件: # encoding:utf-8 from flask import Flask,render_template,request app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/search/') def search(): return u'你要查询的参数是:%s' % request.args.get('q') @app.route('/login/',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: return u'你好,%s' % request.form.get('username') if __name__ == '__main__': app.run(debug=True) 2. index.html 文件
index 点击查询 点击登录 3. login.html 文件login
10.4 总结 GET 和 POST
获取一切和请求相关的信息,使用
request
对象。如:获取请求方法:request.method 获取GET的参数:request.args 获取POST的参数:request.form
对于需要支持 POST 请求的视图函数,要在其装饰器上显式指定:
@app.route('/login/',methods=['GET','POST'])
并且要在模板里
<input>
标签中,用name
来标识value
的key
,方便后台获取。同时在写表单form
的时候要指定action
为用来处理的视图函数名。
10.5 钩子函数
钩子函数可以插入到执行过程之间,即在正常的程序执行过程中插入钩子函数的执行。举歌例子,原来是执行了 A 函数后就接着执行 B 函数,即执行过程为 A->B
;但是由于一个钩子函数 C 的介入,那么执行过程改变为 A->C->B
。这里介绍 2 个钩子函数 before_request
和 context_processor
。
10.5.1 before_request
before_request
顾名思义,就是在 request
执行之前执行,实际上就是在视图函数执行之前执行的。不过 before_request
和 route
一样,只是一个装饰器,它的作用就是把需要的在视图函数执行之前执行的动作放入一个函数并对该函数实现'钩子'
的功能。
在执行每个视图函数之前都先执行 before_request
定义的函数。
通过一个小实验,就能知道
before_request
的功能了:from flask import Flask app = Flask(__name__) @app.route('/') def index(): print 'index' return 'index' @app.before_request def My_before_request(): print 'hello,world' if __name__ == '__main__': app.run(debug=True)
可以在
pycharm
的控制台中看到:先打印了hello,world
,才打印的index
;说明了My_before_request()
是在index
之前执行的。这边扩展一下小案例。
需求:网站中有一个
思路:look
页面需要登录之后才能访问,利用before_request
来实现对用户判断用户是否登录。定义一个首页
index.html
,一个登录页面login.html
和一个查看页面look
以及他们对应的视图函数。在
index
视图函数中返回index.html
页面,并在index.html
中定义两个<a>
标签用以分别跳转至login.html
和look.html
。点击登录 点击查看
在
login
视图函数中对请求的方法做判断:GET
方法代表用户需要获取到这个页面,则返回login.html
页面让用户登录;POST
方法代表用户提交了登录的信息,则判断用户是否合法;若合法则发送 session,若非法则提示非法。@app.route('/login/',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: username = request.form.get('username') password = request.form.get('password') if username == 'MYYD' and password == '123456': session['username'] = username return u'登录成功!' else: return u'用户名或密码错误!'
在
before_request
中定义My_before_request
,然后取出session
的信息放入对象g
中。@app.before_request def My_before_request(): if session.get('username'): g.username = session.get('username')
在
look
是视图函数中,判断根据对象g
提供的信息判断该用户是否已经登录,若登录则返回look.html
页面;若没登录则重定向至login.html
页面。@app.route('/look/') def look(): if hasattr(g,'username'): return u'修改成功!' else: return redirect(url_for('login'))
完整代码如下:
# hook.py from flask import Flask,render_template,request,session,redirect,url_for,g import os app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) @app.route('/') def index(): return render_template('index.html') @app.route('/login/',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: username = request.form.get('username') password = request.form.get('password') if username == 'MYYD' and password == '123456': session['username'] = username return u'登录成功!' else: return u'用户名或密码错误!' @app.route('/look/') def look(): if hasattr(g,'username'): return u'修改成功!' else: return redirect(url_for('login')) @app.before_request def My_before_request(): if session.get('username'): g.username = session.get('username') if __name__ == '__main__': app.run(debug=True) # index.html
Index 点击登录 点击查看 # login.htmlLogin
10.5.2 context_processor
11. 实战练习-蚂蚁有毒问答平台
在首页的模板
index.html
中,先粘贴以下内容:在
v3.bootcss.com
中的组件下找自己想要的导航条