lask
django
是大而全,提供所有常用的功能flask
是小而精,只提供核心功能
环境配置
为了防止 django和 flask环境相互冲突,可以使用 虚拟环境分割开
pip install virtualenv virtualenvwrapper-win # 安装虚拟环境基本的依赖包
mkvirtualenv flask # 创建一个叫 flask的虚拟环境
deactivate # 退出当前虚拟环境
rmvirtualenv flask # 删除名叫 flask的虚拟环境
workon flask # 进入虚拟环境flask
pip install -r requirements.txt # 在虚拟环境中安装flask必备的包
基本流程
配置项可以参考 官方 文档
- 创建
flask
APP - 定义视图函数
- 对视图函数配置路由
- 使用装饰器,配置路由
- 调用app的方法
add_url_rule
对视图函数配置路由
- 直接运行APP
from flask import Flask
# __name__ 当前 文件的名字
# __file__ 当前 文件的路径
# 1. 创建flask的app
app = Flask(__name__)
# 2.1 创建视图函数,利用装饰器可以配置路由
@app.route('/')
def hello():
return "hello Flask"
# 2.2.1 创建视图函数
def two():
return 'two page'
# 2.2.2 利用app的方法对视图函数添加路由
app.add_url_rule('/two', view_func=two)
if __name__ == '__main__':
# 3. 运行APP
app.run()
项目配置
配置文件
通过专门的配置文件,读取配置项,适用于配置项较多
settings.py
class Config(object):
DEBUG = True
主模块
import os
from flask import Flask
from settings import Config
app = Flask(__name__)
# app.config.from_object('settings.Config')
app.config.from_object(Config)
@app.route('/')
def hello():
return "hello Flask"
if __name__ == '__main__':
app.run()
工厂模式
如果在一个函数中 创建对象,那么就可以创建多个实例。
那么这样做有什么用呢?
- 用于测试。可以针对不同的情况使用不同的配置来测试应用。
- 用于多实例,如果你需要运行同一个应用的不同版本的话。当然你可以在服务器上 使用不同配置运行多个相同应用,但是如果使用应用工厂,那么你可以只使用一个 应用进程而得到多个应用实例,这样更容易操控。
app/settings.py
class Config(object):
# 配置项在flask文档中已经规定好
DEBUG = True
app/__init__.py
from flask import Flask
from .settings import Config
def create_app():
# 1. 创建app对象
app = Flask(__name__)
# 2. 导入配置类
app.config.from_object(Config)
# 3. 返回 flask对象
return app
主模块
main.py
# 1. 导入工厂函数
from app import create_app
# 2. 生成APP
app = create_app()
@app.route('/')
def hello():
return "hello Flask"
if __name__ == '__main__':
# 3. 启动flask服务
app.run(host='0.0.0.0', port=7000, debug=True)
小结
想要创建一个flask项目,都需要手动创建
- 创建app包
settings.py
: 项目 的配置文件__init__.py
: 定义工厂函数,生成 flask app
- 如果项目功能模块很多,可以按照模块划分,例如: 商品模块, 创建
goods
包
__init__.py
: 创建蓝图对象, 负责管理当前模块中的所有代码models.py
: 实现 商品模块中的 所有 商品模型类views.py
: 实现 商品模块中的所有视图- 每个蓝图 代码搞定之后,需要在 主app中 注册 app, 类似于 django 的注册 app
请求参数
视图处理过程中,都是接收请求,处理逻辑,返回响应
flask中
request
对象 不是 局部对象,也不属于某个视图函数,而是在 全局中 拥有同一个 请求对象
路径参数
GET student/1/
int
: 获取整数string
: 也是默认类型,得到字符串
# GET student/1/
@school_bp.route('/student/<int:pk>/')
def student_detail(pk):
return {
'id': pk
}
查询字符串参数
GET student/list/?page=3&size=5&a=3&a=9
: 一般是 GET请求时,传递 查询字符串参数
from flask import request
@student_bp.route('student/list/')
def student_list():
# 1. 获取查询字符串参数
page = request.args.get('page') # 根据参数的键获取具体的值
size = request.args.get('size') # 获取单个值
a = request.args.getlist('a') # 根据参数的键,获取对应的多个值,格式为列表
return '查询第{}页学生数据,每页{}条'.format(page, size)
请求体参数-表单数据
POST
、PUT
可以携带请求体参数, 请求体参数包括: 表单数据、json数据flask视图处理过程中,默认只 支持
GET
请求,想要支持其他请求方式,应该指明 请求方式
@school_bp.route('/student/form/', methods=['POST'])
def student_create():
name = request.form.get('name')
age = request.form.get('age')
gender = request.form.get('gender')
return {
'id': 1,
'name': name,
'age': age,
'gender': gender
}
请求体参数-json
@school_bp.route('/student/json/', methods=['POST'])
def student_json_create():
name = request.json.get('name')
age = request.json.get('age')
gender = request.json.get('gender')
return {
'name': name,
'age': age,
'gender': gender
}, 201
小结
将 查询和添加 放到同一个视图中,判断不同的请求方式,从而执行不同的处理逻辑
@school_bp.route('/student/', methods=['GET', 'POST'])
def student():
# 如果请求方式是 GET, 返回学生列表数据
if request.method == 'GET':
page = request.args.get('page', 1)
size = request.args.get('size', 5)
return {
'page': page,
'size': size,
'student': [{'id': 1, 'name': "小米"}]
}
# 如果请求方式是POST, 获取请求体参数,保存学生数据,返回响应
else:
name = request.form.get('name')
age = request.form.get('age')
gender = request.form.get('gender')
if not all([name, age, gender]):
return {'msg': '缺少必要参数'}, 400
return {
'id': 1,
'name': name,
'age': age,
'gender': gender
}
项目构造
python包
flask项目,一切功能围绕 核心app 来完成, 这个包就用来构造 核心APP
app/settings.py
: 项目的配置文件
class Config(object):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'mysql://root:mysql@localhost:3306/flask'
# mysql://用户名:密码@IP地址:端口号/数据库名
SQLALCHEMY_TRACK_MODIFICATIONS = False
app/extensions.py
: 项目所使用的第三方扩展插件
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_cors import CORS
# 环境中,使用 mysqlclient 链接mysql,就可以正常使用
# 如果使用的是pymysql链接 mysql, 就需要加一些配置项
# import pymysql
# pymysql.install_as_MySQLdb()
db = SQLAlchemy()
migrate = Migrate()
cors = CORS()
def config_extensions(app):
db.init_app(app)
migrate.init_app(app, db=db)
cors.init_app(app)
app/__init__.py
: 定义工厂函数,生成核心flask APP
from flask import Flask
from .settings import Config
from .extensions import config_extensions
def create_app():
# 1. 创建APP
app = Flask(__name__)
# 2. 导入配置参数
app.config.from_object(Config)
# 3. 导入 第三方的插件
config_extensions(app)
# 4. 返回APP
return app
项目的管理文件
在项目中,创建
manage.py
manage.py
from flask_script import Manager, Server
from flask_migrate import MigrateCommand
from app import create_app
# 1. 利用工厂函数生成app
app = create_app()
# 2. 创建管理对象,管理项目
manage = Manager(app)
# 3. 构建命令
manage.add_command('runserver', Server(host='0.0.0.0', port=7000))
manage.add_command('db', MigrateCommand)
if __name__ == '__main__':
# 利用管理器启动服务
manage.run()
命令
在项目的终端可以使用如下命令,操作项目
python manage.py runserver # 启动flask 服务
python manage.py db init # 只有第一次迁移时,才会执行 该命令, 执行完,会生成迁移文件
python manage.py db migrate # 生成迁移文件,出路第一次之外,任何修改模型类,都需要 执行该命令, 生成迁移文件
python manage.py db upgrade # 根据迁移文件,生成表
学校子应用
school/__init__.py
: 创建蓝图对象, 创建完成,需要 注册蓝图
from flask.blueprints import Blueprint
school_bp = Blueprint('school', __name__)
# 为了让蓝图,管理你的 模型类和视图,因此需要导入
from .models import *
from .views import *
school/models.py
: 创建模型类
from app.extensions import db
# 学生: id、姓名、年龄、性别
class Student(db.Model):
__tablename__ = 'tb_student' # 指定表名
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(20))
age = db.Column(db.Integer)
gender = db.Column(db.String(5))
需要在 蓝图,导入模型类,以便项目识别 模型类
- 生成迁移文件:
python manage.py db migrate
- 生成表:
python manage.py db upgrade
视图
school/views.py
: 实现 逻辑处理
from flask import request
from school import school_bp
from .models import Student
from app.extensions import db
from sqlalchemy.exc import SQLAlchemyError
@school_bp.route('/student/', methods=['POST', 'GET'])
def student():
# 添加学生 POST student/ 表单参数
if request.method == 'POST':
# 1. 接收参数
name = request.form.get('name')
age = request.form.get('age')
gender = request.form.get('gender')
# 2. 校验参数
if not all([name, age, gender]):
return {'mgs': '缺少必要参数'}, 400
# 3. 存入数据库
# 3.1 创建学生对象成功,得到学生对象
stu = Student(name=name, age=age, gender=gender)
# 3.2 添加到 db 事务中
db.session.add(stu)
# 3.3 提交 事务, 因为,不提交事务之前,数据并没有保存到数据库,不会发生错误
try:
db.session.commit()
except SQLAlchemyError as e:
# print(e)
return {
'msg': '添加失败'
}, 500
# 4. 返回响应
return {
'id': stu.id,
'name': stu.name,
'age': stu.age,
'gender': stu.gender
}, 201
else:
# 1. 查询所有学生,得到查询集
students = Student.query.all()
# 2. 循环遍历查询集,解析数据
data = []
for s in students:
data.append({
'id': s.id,
'name': s.name,
'age': s.age,
'gender': s.gender,
})
# 3. 返回响应
return {'students': data}, 200
基本操作
增、查询全部
from flask_restful import Resource, reqparse, marshal_with
from sqlalchemy.exc import SQLAlchemyError
from .fields import book_page_fields
from .models import *
class BookView(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('title', type=str, location=['form', 'json'], required=True, help='图书标题不能缺少')
parser.add_argument('bread', type=int, location=['form', 'json'], default=0, help='输入合法的阅读量')
# 1. 获取参数
title = parser.parse_args().get('title')
bread = parser.parse_args().get('bread')
# 2. 创建图书对象
book = Book(title=title, bread=bread)
# 3. 添加图书对象到事务
db.session.add(book)
# 5. 提交事务
try:
db.session.commit()
except:
return {'msg': '添加失败'}, 500
# 6. 正常返回
return {
'id': book.id,
'title': book.title,
'bread': book.bread
}, 201
@marshal_with(book_page_fields)
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('page', type=int, location=['args'], default=1, help='输入合法的页码')
page = parser.parse_args().get('page')
# 1. 先利用模型类查询,并分页, 得到分页器对象
pagination = Book.query.paginate(page=page, per_page=2)
# 2. 返回响应
return {
'books': pagination.items,
'pages': pagination.pages,
'total': pagination.total
}
详情、修改、删除
class BookDetailView(Resource):
def get(self, pk):
# 1. 通过主键id,查询 图书对象, id对应的数据不存在,得到结果为None
book = Book.query.get(pk)
# 2. 如果book图书不存在
if not book:
return {
'msg': '图书不存在'
}, 404
# 3. 返回对象
return {
'id': book.id,
'title': book.title,
'bread': book.bread
}
def delete(self, pk):
Book.query.filter_by(id=pk).delete()
db.session.commit()
return None, 204
def put(self, pk):
parser = reqparse.RequestParser()
# 1. 根据主键,查询到需要修改的 图书对象
book = Book.query.get(pk)
# 2. 判断该图书 是否 存在
if not book:
return {
'msg': '找不到'
}, 404
# 3. 解析参数,如果没有传递参数,设置 图书的 原属性 为默认值
parser.add_argument('title', type=str, location=['json', 'form'], default=book.title)
parser.add_argument('bread', type=int, location=['json', 'form'], default=book.bread)
# 4. 获取参数,得到字典
args = parser.parse_args()
# 5.将对象的值,赋值为 传递的新值
book.title = args.get('title')
book.bread = args.get('bread')
# 6.修改完成,需要提交事务,让修改生效
db.session.commit()
return {
'msg': '修改成功'
}, 201
小结
模型类.query.filter_by(字段=值)
, 结果是一个 查询集模型类.query.get(主键的值)
, 结果是一个 对象,不存在,结果为None
模型类.query.get_or_404(主键的值)
, 结果是一个对象, 不存在,抛出 404异常模型类.query.all()
, 结果是查询集pagination = 模型类.query.paginate(page=xxx, per_page=xxx)
, 结果是分页器对象
pagination.items
: 当前页的数据,是一个查询集pagination.pages
: 总页码pagination.total
: 总的记录数量
Vue
添加数据
<template>
<div>
<!-- 1. 获取表单数据 -->
标题: <input type="text" v-model="title">
阅读量: <input type="text" v-model="bread">
<!-- 2. 对按钮的点击事件,绑定方法 -->
<button @click="postBook">添加</button>
</div>
</template>
<script>
export default {
name: "Book",
data() {
return {
title: '',
bread: 0
}
},
methods: {
postBook() {
// 3. 使用axios发送数据
this.$axios.post('/book', {'title': this.title, 'bread': this.bread})
.then(resp => {
alert('添加成功')
})
.catch(err => {
alert('添加失败')
})
}
}
}
</script>
列表分页展示
<template>
<div>
<!-- 3 循环展示所有图书以及页码 -->
<table>
<tr v-for="b in books">
<td>{{ b.id }}</td>
<td>{{ b.title }}</td>
<td>{{ b.bread }}</td>
</tr>
</table>
<!-- 4. 点击页码按钮,传递新的页码给对应的方法 -->
<button @click="pageChange(page-1)">上一页</button>
<button v-for="p in pages" @click="pageChange(p)">{{ p }}</button>
<button @click="pageChange(page+1)">下一页</button>
</div>
</template>
<script>
export default {
name: "BookList",
data() {
return {
page: 1, // 当前页码
books: [], // 当前页的所有数据
pages: 1, // 总的页码
}
},
methods: {
// 1. 定义方法,根据页码请求图书数据
getBooks() {
this.$axios.get('/book?page=' + this.page)
.then(resp => {
this.books = resp.data.books
this.pages = resp.data.pages
})
},
// 5. 接收传递的新页码,并且判断是否合法,如果不合法,不做任何处理,如果合法,根据新页码,请求新数据
pageChange(n) {
if (n > this.pages) {
alert('到底啦')
} else if (n < 1) {
alert('已经是第一页啦')
} else if (n === this.page) {
} else {
// 判断过不是上面的任何一种情况,说明 n 是 合法的 新页码
this.page = n
// 根据新页码,重新请求数据
this.getBooks();
}
}
},
mounted() {
// 2. 挂载请求数据的方法
this.getBooks()
}
}
</script>
修改页面
<template>
<div>
<!-- 4. 将原始数据和表单进行双向绑定 -->
标题:<input type="text" v-model="book.title">
阅读量: <input type="text" v-model="book.bread">
<!-- 5. 对修改按钮,绑定方法 -->
<button @click="updateBook">更新</button>
</div>
</template>
<script>
export default {
name: "BookUpdate",
data() {
return {
id: this.$route.query.id,
book: {
// 1. 从路由对象中获取传递的id
title: '',
bread: ''
}
}
},
methods: {
// 2. 根据id,查询图书的原始数据
getBook() {
this.$axios.get('book/' + this.id)
.then(resp => {
this.book.title = resp.data.title
this.book.bread = resp.data.bread
})
},
// 6. 定义修改方法
updateBook() {
this.$axios.put(`book/${this.id}`, this.book)
.then(resp => {
this.$router.push('/')
})
.catch(err => {
alert('修改失败')
})
}
},
mounted() {
// 3. 挂载请求数据的方法
this.getBook();
}
}
</script>