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必备的包

基本流程

配置项可以参考 官方 文档

  • 创建flaskAPP
  • 定义视图函数
  • 对视图函数配置路由
  • 使用装饰器,配置路由
  • 调用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()

工厂模式

如果在一个函数中 创建对象,那么就可以创建多个实例。

那么这样做有什么用呢?

  1. 用于测试。可以针对不同的情况使用不同的配置来测试应用。
  2. 用于多实例,如果你需要运行同一个应用的不同版本的话。当然你可以在服务器上 使用不同配置运行多个相同应用,但是如果使用应用工厂,那么你可以只使用一个 应用进程而得到多个应用实例,这样更容易操控。

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)

请求体参数-表单数据

POSTPUT 可以携带请求体参数, 请求体参数包括: 表单数据、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>