0. 概述 一心只为能用,只总结要点 主要目的是一些简单的展示功能,更深的内容以后用到了再看(反正到时候也差不多都忘了) 细致的笔记可以直接看第一个参考资料
参考资料:Flask 入门笔记 Flask 快速入门 Flask 教程
1. 项目结构 假设我们编辑完了代码,在项目里执行flask run
即可运行调试服务器 flask 默认app.py
为代码入口,默认为普通模式,这两个设置与两个环境变量有关:FLASK_APP
和FLASK_ENV
1.1 管理环境变量 可以通过一个小工具管理环境变量:python-dotenv
1 pip install python-dotenv
python-dotenv 会自动读取两个文件中的设置
.env
:存放系统相关的敏感数据,一般写进.gitignore
文件
.flaskenv
:存放与 flask 相关的环境变量
1 2 3 # .flaskenv 文件 # FLASK_APP = app.py FLASK_ENV=development
1.2 基本结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flaskapp = Flask(__name__) @app.route('/' ) @app.route('/hello' ) def hello (): return 'Welcome to My Watchlist!' @app.errorhandler(404 ) def page_not_found (e ): user = User.query.first() return render_template('404.html' , user=user), 404
2. 模板 可以通过渲染模板获得最终的 HTML 文件,模板文件通常放在项目根目录中的templates
文件夹中
2.1 模板语法
语法
作用
{{变量}}
标记变量
{%语句%}
标记语句,如 if,for
{#注释#}
注释
{{var | filter}}
过滤器
各种内置过滤器
.
操作符可以读取元素的子属性,过滤器则相当于一个函数
主页模板例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="utf-8" /> <title > {{ name }}'s Watchlist</title > </head > <body > <h2 > {{ name }}'s Watchlist</h2 > {# 使用 length 过滤器获取 movies 变量的长度 #} <p > {{ movies|length }} Titles</p > <ul > {% for movie in movies %} {# 迭代 movies 变量 #} <li > {{ movie.title }} - {{ movie.year }}</li > {# 等同于 movie['title'] #} {% endfor %} {# 使用 endfor 标签结束 for 语句 #} </ul > <footer > <small >© 2018 <a href ="http://helloflask.com/tutori al" > HelloFlask</a > </small > </footer > </body > </html >
2.2 渲染模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from flask import Flask, render_templateapp = Flask(__name__) name = 'Grey Li' movies = [ {'title' : 'My Neighbor Totoro' , 'year' : '1988' }, {'title' : 'Dead Poets Society' , 'year' : '1989' }, {'title' : 'A Perfect World' , 'year' : '1993' }, {'title' : 'Leon' , 'year' : '1994' }, {'title' : 'Mahjong' , 'year' : '1996' }, {'title' : 'Swallowtail Butterfly' , 'year' : '1996' }, {'title' : 'King of Comedy' , 'year' : '1999' }, {'title' : 'Devils on the Doorstep' , 'year' : '1999' }, {'title' : 'WALL-E' , 'year' : '2008' }, {'title' : 'The Pork of Music' , 'year' : '2012' }, ] @app.route('/' ) def index (): return render_template('index.html' , name=name, movies=movies) @app.context_processor def inject_user (): user = User.query.first() return dict (user=user)
2.3 模板继承机制 父模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <!DOCTYPE html > <html lang ="en" > <head > {% block head %} <meta charset ="utf-8" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" /> <title > {{ user.name }}'s Watchlist</title > <link rel ="icon" href ="{{ url_for('static', filename='favicon.ico') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css" /> {% endblock %} </head > <body > <h2 > <img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}" /> {{ user.name }}'s Watchlist </h2 > <nav > <ul > <li > <a href ="{{ url_for('index') }}" > Home</a > </li > </ul > </nav > {% block content %}{% endblock %} <footer > <small >© 2018 <a href ="http://helloflask.com/tutorial" > HelloFlask</a > </small > </footer > </body > </html >
子模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 {% extends 'base.html' %} {% block content %} <p > {{ movies|length }} Titles</p > <ul class ="movie-list" > {% for movie in movies %} <li > {{ movie.title }} - {{ movie.year }} <span class ="float-right" > <a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb" >IMDb</a > </span > </li > {% endfor %} </ul > <img alt="Walking Totoro" class="totoro" src="{{ url_for('static', filename='images/totoro.gif') }}" title="to~to~ro~" /> {% endblock %}
3. 静态文件 在模板文件中可以通过 url_for 函数生成静态文件的 url:
1 2 <img src ="{{ url_for('static', filename='foo.jpg') }}" />
静态文件主要是 css 文件,图片文件,js 文件之类的 可以通过一些前端框架搞这些东西:bootstrap Semantic-UI Foundation
Bootstrap-Flask 可以简化 Bootstrap 的使用
4. 数据库 参考资料:Flask 入门教程:数据库 Flask-SQLAlchemy 用户指南
4.1 sqlalchemy 首先安装 flask-sqlalchemy 包以在 flask 中使用 sqlalchemy
1 pip install flask-sqlalchemy
然后在程序入口根据程序实例初始化数据库
1 2 3 4 5 6 from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) db = SQLAlchemy(app)
然后对数据库进行配置
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)
4.2 数据模型 1 2 3 4 5 6 7 8 class User (db.Model ): id = db.Column(db.Integer, primary_key=True ) name = db.Column(db.String(20 )) class Movie (db.Model ): id = db.Column(db.Integer, primary_key=True ) title = db.Column(db.String(60 )) year = db.Column(db.String(4 ))
4.3 命令行操作 可以使用如下方式创建和删除数据库
1 2 3 4 (env) $ flask shell > >> from app import db > >> db.create_all() > >> db.drop_all()
也可以自己创建一些命令
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
4.4 数据库操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from app import User, Movieuser = User(name='Grey Li' ) db.session.add(user) db.session.commit() movie = Movie.query.first() Movie.query.filter_by(title='Mahjong' ).first() movie = Movie.query.get(2 ) movie.title = 'WALL-E' db.session.commit() movie = Movie.query.get(1 ) db.session.delete(movie) db.session.commit()
5. 表单 首先需要在模板中定义表单
1 2 3 4 5 6 7 8 9 10 11 <form method ="post" > <label for ="name" > 名字</label > <input type ="text" name ="name" id ="name" /> <br /> <label for ="occupation" > 职业</label > <input type ="text" name ="occupation" id ="occupation" /> <br /> <input type ="submit" name ="submit" value ="登录" /> </form >
然后写处理表单的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from flask import request, url_for, redirect, flash@app.route('/' , methods=['GET' , 'POST' ] ) def index (): if request.method == 'POST' : title = request.form.get('title' ) year = request.form.get('year' ) if not title or not year or len (year) > 4 or len (title) > 60 : flash('Invalid input.' ) return redirect(url_for('index' )) movie = Movie(title=title, year=year) db.session.add(movie) db.session.commit() flash('Item created.' ) return redirect(url_for('index' )) movies = Movie.query.all () return render_template('index.html' , movies=movies)
接受视图函数中传来的 flash 信息
1 2 3 4 5 6 {% for message in get_flashed_messages() %} <div class ="alert" > {{ message }}</div > {% endfor %} <h2 > ...</h2 >
6. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import unittestfrom module_foo import sayhelloclass SayHelloTestCase (unittest.TestCase ): def setUp (self ): pass def tearDown (self ): pass def test_sayhello (self ): rv = sayhello() self.assertEqual(rv, 'Hello!' ) def test_sayhello_to_somebody (self ): rv = sayhello(to='Grey' ) self.assertEqual(rv, 'Hello, Grey!' ) if __name__ == '__main__' : unittest.main()
常用来判断结果的断言
assertEqual(a, b)
assertNotEqual(a, b)
assertTrue(x)
assertFalse(x)
assertIs(a, b)
assertIsNot(a, b)
assertIsNone(x)
assertIsNotNone(x)
assertIn(a, b)
assertNotIn(a, b)
常用的测试方法可以参考:测试方法
7. 组织代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ├── .flaskenv # Flask 环境变量 ├── test_watchlist.py # 单元测试(也可以做成一个包) └── watchlist # 主程序包(需要在flaskenv中修改环境变量) ├── __init__.py # 入口程序 ├── commands.py # 命令行操作 ├── errors.py # 错误处理(404) ├── models.py # 数据库模型 ├── views.py # 视图函数 ├── static # 静态资源 │ ├── favicon.ico │ ├── images │ │ ├── avatar.png │ │ └── totoro.gif │ └── style.css └── templates # 模板文件 ├── base.html ├── edit.html ├── errors │ ├── 400.html │ ├── 404.html │ └── 500.html ├── index.html ├── login.html └── settings.html
8. 部署上线 创建虚拟环境 部署上线