Flask
是一个使用 Python 语言编写的 Web 框架,它可以让你高效的编写 Web 程序。我最近用flask+vue
搭建一个简单的todolist
项目示例来学习,主要是参考flask-tutorial ,感兴趣的可以看看,示例代码
前言 这个项目比较简单,就是用户登录,然后有一个todolist
列表,可以简单的增删改,前端界面使用vue
框架,所以关于flask template
涉及不多,感兴趣的可以自己去学习下。本系列基于python3
版本,所以命令跟python2
可能有些许出入。
准备工作 基础软件 安装python
,
编辑软件,一般文本软件软件即可,这个看个人习惯,我这边是用vscode
安装git
,window用户推荐使用git bash
内置了很多linux命令
创建目录 1 2 3 mkdir todo-list cd todo-list mkdir app serve
我这边项目里面创建两个目录app
(vue前端页面,这个这里不多做介绍),serve
(flask后端服务),
虚拟环境 1 2 3 4 5 6 7 8 9 cd serve # 安装虚拟环境 py -3 -m venv env # Windows or python3 -m venv env # Linux 和 macOS # 激活虚拟环境 env\Scripts\activate # Windows . env/bin/activate # Linux 或 macOS # 安装flask pip3 install flask
env
这个名字不固定,你也可以使用venv
,记得.gitignore
忽略掉这个目录。
在激活虚拟环境后,无论操作系统和 Python 版本,都可以统一使用 python
和 pip
命令来调用当前虚拟环境内的 Python 和 pip 程序/二进制文件。此时执行 python
或 pip
命令指向的程序和激活脚本在同一个目录下,在 Windows 下所在目录为 env\Scripts\
,Linux 和 macOS 下所在目录为 env/bin/
Hello Flask serve
根目录下创建app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 # 创建app.py vim app.py # app.py内容 from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Welcome to My Watchlist!' # 启动 flask run
现在打开浏览器,访问 http://localhost:5000 即可访问我们的程序主页
程序发现机制 如果你把上面的程序保存成其他的名字,比如 hello.py,接着执行 flask run
命令会返回一个错误提示。这是因为 Flask 默认会假设你把程序存储在名为 app.py
或 wsgi.py
的文件中。如果你使用了其他名称,就要设置系统环境变量 FLASK_APP
来告诉 Flask 你要启动哪个程序。
1 2 3 export FLASK_APP=hello.py # Linux 或 macOS $ Env:FLASK_APP=hello.py set FLASK_APP=hello.py # Window CMD
管理环境变量 为了不用每次打开新的终端会话都要设置环境变量,我们安装用来管理系统环境变量的 python-dotenv
1 pip3 install python-dotenv
当 python-dotenv
安装后,Flask
会从项目根目录的 .flaskenv
和 .env
文件读取环境变量并设置。
.flaskenv
用来存储 Flask 命令行系统相关的公开环境变量;
.env
则用来存储敏感数据,不应该提交进Git仓库,我们把文件名 .env
添加到 .gitignore
文件的结尾(新建一行)来让 Git 忽略它。
开启调试模式:
1 2 vim .flaskenv FLASK_ENV=development
Routing Web的核心功能,这个后面经常用,有个印象即可。
Variable Rules 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @app.route('/user/<username>' ) def show_user_profile (username ): return 'User %s' % escape(username) @app.route('/post/<int:post_id>' ) def show_post (post_id ): return 'Post %d' % post_id @app.route('/path/<path:subpath>' ) def show_subpath (subpath ): return 'Subpath %s' % escape(subpath)
注意用户输入可能包含恶意代码,所以最好用escape
进行转义处理
URL Building 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @app.route('/' ) def index (): return 'index' @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): return 'login' @app.route('/user/<username>' ) def profile (username ): return '{}\'s profile' .format (escape(username)) with app.test_request_context(): print (url_for('index' )) print (url_for('login' )) print (url_for('login' , next ='/' )) print (url_for('profile' , username='John Doe' ))
1 2 3 4 / /login /login?next=/ /user/John% 20Doe
Redirects and Errors 1 2 3 4 5 6 7 8 9 10 from flask import abort, redirect, url_for@app.route('/' ) def index (): return redirect(url_for('login' )) @app.route('/login' ) def login (): abort(401 ) this_is_never_executed()
1 2 3 4 5 from flask import render_template@app.errorhandler(404 ) def page_not_found (error ): return render_template('page_not_found.html' ), 404
Request 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from flask import request@app.route('/login' , methods=['POST' , 'GET' ] ) def login (): error = None if request.method == 'POST' : if valid_login(request.form['username' ], request.form['password' ]): return log_the_user_in(request.form['username' ]) else : error = 'Invalid username/password' return render_template('login.html' , error=error)
1 searchword = request.args.get('key' , '' )
1 2 3 4 5 @app.route('/upload' , methods=['GET' , 'POST' ] ) def upload_file (): if request.method == 'POST' : f = request.files['the_file' ] f.save('/var/www/uploads/' + secure_filename(f.filename))
Response return string
rerun html 1 return '<hr>Hello World</hr>'
render template 1 2 3 4 5 6 return render_template('error.html' ), 404 resp = make_response(render_template('error.html' ), 404 ) resp.headers['X-Something' ] = 'A value' return resp
return json 1 2 3 4 5 6 7 8 @app.route("/me" ) def me_api (): user = get_current_user() return { "username" : user.username, "theme" : user.theme, "image" : url_for("user_image" , filename=user.image), }
1 2 3 4 @app.route("/users" ) def users_api (): users = get_all_users() return jsonify([user.to_json() for user in users])
数据库 这边为了简单,使用Sqlite
flask-sqlalchemy 为了简化数据库操作,我们将使用 SQLAlchemy ——一个 Python 数据库工具(ORM,即对象关系映射)。借助 SQLAlchemy
,你可以通过定义 Python 类来表示数据库里的一张表(类属性表示表中的字段 / 列),通过对这个类进行各种操作来代替写 SQL 语句。
Flask 有大量的第三方扩展,这些扩展可以简化和第三方库的集成工作。我们下面将使用一个叫做 Flask-SQLAlchemy 的官方扩展来集成 SQLAlchemy。
1 pip3 install flask-sqlalchemy
大部分扩展都需要执行一个“初始化”操作。你需要导入扩展类,实例化并传入 Flask 程序实例:
1 2 3 from flask_sqlalchemy import SQLAlchemy # 导入扩展类 app = Flask(__name__) db = SQLAlchemy(app) # 初始化扩展,传入程序实例 app
数据库URL 为了设置 Flask、扩展或是我们程序本身的一些行为,我们需要设置和定义一些配置变量
Flask文档配置页面
Flask-SQLAlchemy 文档的配置页面
sqlite:////tmp/test.db
mysql://username:password@server/db
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import osimport sysfrom flask import Flaskfrom flask_sqlalchemy import SQLAlchemyWIN = sys.platform.startswith('win' ) if WIN: prefix = 'sqlite:///' else : prefix = 'sqlite:////' app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI' ] = prefix + os.path.join(app.root_path, 'data.db' ) app.config['SQLALCHEMY_TRACK_MODIFICATIONS' ] = False db = SQLAlchemy(app)
创建模型 我们这边就只有两个模型,一个用户模型,一个代办项目模型
1 2 3 4 5 6 7 8 9 10 class User (db.Model, UserMixin ): id = db.Column(db.Integer, primary_key=True ) username = db.Column(db.String(20 )) password_hash = db.Column(db.String(128 )) class TodoItem (db.Model ): __tablename__ = 'todo_item' id = db.Column(db.Integer, primary_key=True ) title = db.Column(db.String(32 )) descs = db.Column(db.String(256 ))
自定义命令 注册命令,便于我们通过CLI 对程序进行一些额外的数据处理,比如数据库建表,脚本初始化等
1 2 3 4 5 6 7 8 9 10 import click@app.cli.command() @click.option('--drop' , is_flag=True , help ='Create after drop.' ) def initdb (drop ): """Initialize the database.""" if drop: db.drop_all() db.create_all() click.echo('Initialized database.' )
1 2 flask initdb # 创建数据库 flask initdb --drop # 清空数据库
CURD 创建 1 2 3 4 5 6 7 8 from app import User, TodoItem >>> user = User(username='wjc' ) >>> ti1 = TodoItem(title='study' ) >>> ti2 = TodoItem(title='game' ) >>> db.session.add(user) >>> db.session.add(ti1)>>> db.session.add(ti2)>>> db.session.commit()
读取 1 <模型类>.query.<过滤方法(可选)>.<查询方法>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 >>> from app import TodoItem >>> todoItem = TodoItem.query.first() >>> todoItem.title 'study' >>> TodoItem.query.all () [<TodoItem 1 >, <TodoItem 2 >] >>> TodoItem.query.count() 2 >>> TodoItem.query.get(1 ) <TodoItem 1 > >>> TodoItem.query.filter_by(title='game' ).first() <TodoItem 2 > >>> TodoItem.query.filter (TodoItem.title=='game' ).first() <TodoItem 2 >
sqlalchemy query
更新 1 2 3 4 >>> todoItem = TodoItem.query.get(2 )>>> todoItem.title = 'play ball' >>> todoItem.descs = '玩球' >>> db.session.commit()
删除 1 2 3 >>> todoItem = TodoItem.query.get(1 )>>> db.session.delete(todoItem) >>> db.session.commit()
Flask Marshmallow 因为sqlalchemy
查询出来的数据不能直接序列化,所以一般要转成dict
,这边引入flask-marshmallow 来处理
1 2 pip3 install marshmallow-sqlalchemy pip3 install flask-marshmallow
引入
1 2 3 4 5 from flask import Flaskfrom flask_marshmallow import Marshmallowapp = Flask(__name__) ma = Marshmallow(app)
定义
1 2 3 class TodoItemSchema (ma.SQLAlchemyAutoSchema ): class Meta : model = TodoItem
使用
1 2 3 4 5 @app.route('/todoItems' , methods=['GET' ] ) def all (): todoItems = TodoItem.query.all () todoitems_schema = TodoItemSchema() return jsonify(result = todoitems_schema.dump(todoItems, many=True ))
用户认证 密码存储 Flask
的依赖 Werkzeug
内置了用于生成和验证密码散列值的函数,werkzeug.security.generate_password_hash()
用来为给定的密码生成密码散列值,而 werkzeug.security.check_password_hash()
则用来检查给定的散列值和密码是否对应
1 2 3 4 5 6 7 8 9 10 11 12 13 from werkzeug.security import generate_password_hash, check_password_hashclass User (db.Model ): id = db.Column(db.Integer, primary_key=True ) name = db.Column(db.String(20 )) username = db.Column(db.String(20 )) password_hash = db.Column(db.String(128 )) def set_password (self, password ): self.password_hash = generate_password_hash(password) def validate_password (self, password ): return check_password_hash(self.password_hash, password)
flask-jwt-extended flask-jwt-extended
1 pip3 install flask-jwt-extended
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from flask_jwt_extended import ( create_access_token, jwt_required, get_jwt_identity ) access_token = create_access_token(identity=username) current_user_name = get_jwt_identity() @app.route('/todoItem' , methods=['POST' ] ) @jwt_required def add (): todoItem = TodoItem(title = request.form['title' ], descs = request.form['descs' ]) db.session.add(todoItem) db.session.commit() return utils.result(msg= 'Item added.' )
代码结构 代码结构调整,待定。。。
打包部署 待定
相关链接 flask-tutorial
flask 1.1