flask的基本操作

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>

 

posted @ 2021-06-01 20:40  GTMWu  阅读(483)  评论(0)    收藏  举报