Flask前言
Django是个大而全的框架,Flask是一个轻量级的框架
Dijango内部为我们提供了非常多的组件:orm / session / cookie / admin / form / modelform / 路由 / 视图 / 模板 / 中间件 / 分页 / auth / contenttype / 缓存 / 信号 / 多数据库连接
Flask框架本身没有太多的功能:路由 / 视图 / 模板(jinia2) / session / 中间件,第三方组件非常齐全
注意:Django的请求处理是逐一封装和传递;Flask的请求是利用上下文管理来实现的
依赖wsgi Werkzeug
wsgi:web服务网关接口
from werkzeug.serving import run_simple
def func(environ, start_response): print('request coming') pass
if __name__ == '__main__': run_simple('127.0.0.1', 5000, func)
|
from werkzeug.serving import run_simple
class Flask(object): def __call__(self, environ, start_response): return 'xx'
app = Flask()
if __name__ == '__main__': run_simple('127.0.0.1', 5000, app)
|
from werkzeug.serving import run_simple
class Flask(object): def __call__(self, environ, start_response): return 'xx'
def run(self): run_simple('127.0.0.1', 5000, self)
app = Flask()
if __name__ == '__main__': app.run()
|
快速使用Flask
from flask import Flask, request
app = Flask(__name__)
@app.route('/') def hello_world(): return 'Hello World!'
if __name__ == '__main__': app.run()
|
Flask路由
现代web应用都使用有意义的URL,这帮助用户记忆,网页会更得到用户青睐
@app.route('/index') def hello_world(): pass
|
路由参数
@app.route('/index', methods=['GET', 'POST'], endpoint='hw')
def hello_world(): pass
|
动态路由
@app.route('/index') def hello_world(): pass
@app.route('/index/<name>') def hello_world(name): pass
@app.route('/index/<int:nid>') def hello_world(nid): pass
|
获取提交数据
from flask import request
@app.route('/index') def login(): request.args request.form
|
返回数据
@app.route('/index') def login(): return render_template('模板文件') return jsonify() return redirect('/index') return '...'
|
Flask请求与响应
请求:request
http协议:
请求行: http://127.0.0.1:5000/index(请求方法为get和post)
请求头:key:value
请求体:
响应:response
响应行:状态码200 ok、404 not found、500 Internal Server Error、302 Status Code
响应头:key:value
响应体:响应标签html
GET与POST
- 传参不同,get将参数直接拼到url上,post用的是requestBody方式传参
- 编码不同,url用的是ASCII编码,所以get也是此编码,post没有编码限制
- url长度有限制,所以get的请求路径和参数不宜过长,post无限制
- get发送请求时的参数会被浏览器完整保留在历史记录里,post不会
- get请求会被浏览器缓存,post不会
- get的历史记录回退不会访问服务器,post是重新对服务器发送请求
项目配置
Debug模式
是否开启调试模式
Debug优势:
- 开启Debug模式后,只要修改代码保存(Ctrl+s),就会自动重新加载,不需要手动重启项目
- 在开发状态下,出现bug。开启Debug模式后,在浏览器上就可以看到出错信息
Hosh配置
项目运行使用的Host(访问项目的域名)
设置不同的ip地址号以达到不同效果,例如:0.0.0.0局域网访问,127.0.0.1本机
Port配置
项目运行监听的端口号
当某个端口(5000)被其他程序占用,可以通过修改port来监听其他端口以达到效果
URL
一般url分为http和https两种协议,http协议使用80端口,https协议使用443端口,所以一般不需要在域名后面输入端口
一般格式为:http[80]/https[443]://www.xxx.com/path
@app.route('/index') def hello_world(): pass
def hello_world(): pass app.add_url_rule('/index', view_func=hello_world)
|
定义无参数的URL
from flask import Flask
app = Flask(__name__)
@app.route('/blog') def blog(): return 'Blog Center!'
if __name__ == '__main__': app.run()
|
定义有参数的URL
from flask import Flask
app = Flask(__name__)
@app.route('/blog/<blog_id>') def blog_detail(blog_id): return '%s Blog Center!' % blog_id
if __name__ == '__main__': app.run()
|
可以使用/<int:blog_id>
定义输入的必须为整型的id值
获取不同约束下的内容
from flask import Flask, request
app = Flask(__name__)
@app.route('/book/list') def book_page(): page = request.args.get('page', default=1, type=int) return 'Get page %d content!' % page
if __name__ == '__main__': app.run()
|
当我们需要获取某一页图书的内容时/book/list?page=页码
当我们不输入需要获取的页码时,将返回默认值中的内容/book/list
url_for
在实际开发过程中,我们通常会在路由后面加入endpoint=路径名(匿名)
url_for可以直接解析路径名到实际路径
视图
response回应:
- 字符串,自动生成response对象
- dict,json
- response对象
- make_response()
- redirect()重定向
- render_template模板渲染
Jinja2
渲染模板
Jinja2是一款模板渲染插件,在安装flask同时会自动添加jinja2
在模板中获取view中传递的变量值
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/blog/<blog_id>') def blog_detail(blog_id): return render_template('blog_detail.html', blog_id=blog_id, username='xxx')
if __name__ == '__main__': app.run()
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p>博客ID:{{ blog_id }}</p> <h1>用户名:{{ username }}</h1> </body> </html>
|
模板访问不同对象
访问的对象是一个类对象时,可以在html文件中使用:对象名.对象中的参数名
class User: def __init__(self, username, email): self.username = username self.email = email
@app.route('/') def index(): user = User(username = 'Bob', email = 'xxx@gamil.com') return render_template('index.html', user=user)
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{ user.username } / { user.email }} </body> </html>
|
访问对象是一个字典对象时,可以在html文件中使用:对象名.对象中的参数名
@app.route('/') def index(): person = { 'username':'Bob', 'email':'xxx@gamil.com' } return render_template('index.html', person=person)
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div>{{ person['username'] }}</div> <div>{{ person.username }}</div> <div>{{ person.get(username) }}</div> </body> </html>
|
过滤器
变量可以通过过滤器修改。过滤器与变量用管道符号( |
)分割,并且也 可以用圆括号传递可选参数。多个过滤器可以链式调用,前一个过滤器的输出会被作为 后一个过滤器的输入
class User: def __init__(self, username, email): self.username = username self.email = email
@app.route('/filter') def filter(): user = User(username = 'Bob', email = 'xxx@gamil.com') return render_template('filter.html', user=user)
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{ user.uesename }}-{{ user.username|length }} </body> </html>
|
自定义过滤器
过滤器本质是python函数,他会把被过滤的第一个参数产给这个函数,函数经过一些逻辑处理后,再返回新的值。过滤器写好之后可以使用@app.template_filter
装饰器或app.add_template_filter
函数把自定义函数注册为jinja2过滤器
def datetime_formate(value, formate=%Y-%m-%d %H:%M): return value.strftime(formate) app.add_template_filter(datetime_fotmate, 'dformate')
|
def 函数名(变量名): ... return 变量名 app.add_template_filter(函数名, '自定义过滤器名')
@app.template_filter('自定义过滤器名') def 函数名(变量名): return 变量名
|
控制语句
if
@app.route('/control') def control(): age = 20 return render_template('control.html', age=age)
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% if age>18 %} <div>你已经满18岁了</div> {% elif age==18 %} <div>刚满18岁</div> {% else %} <div>不满18岁</div> {% endif %} </body> </html>
|
for
@app.route('/control') def control(): books = [{ 'name': '三国演义', 'author': '罗贯中' }, { 'name': '水浒传', 'author': '施耐庵' }] return render_template('control.html', books=books)
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% for book in books %} <div>图书名称:{{ book.name }}, 作者{{ book.author }}</div> {% endfor %} </body> </html>
|
jinja2中的for循环不存在break语句
模板继承
一个网页中大部分网页的模板都是重复的,例如顶部导航条,底部的注册信息。如果在每个页面都重复的去写这些代码,会让代码变得臃肿,提高后期维护成本。模板继承就可以把一些重复性的代码写在父模板当中,子模板继承父模板后再实现自身代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title%}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html>
|
{% extends '父模板文件名' %}
{% block title %} 子模板内容 {% endblock %}
{% block body %} 子模板内容 {% endblock %}
|
加载静态文件
一个网页中,除了HTML代码以外,还需要CSS、JavaScript和图片文件才能更加美观和实用。静态文件默认存放在static文件夹中,如果想要修改静态文件存放路径,可以在创建Flask对象时设置static_folder
在html文件中通过link导入外部静态样式"{{ url_for('static', filename='static下的文件名') }}"
html文件中的语法
变量: {{ 变量 }} {{ url_for('static', filename='') }}
块: {% if 条件 %}...{% endif %} {% for 条件 %}...{% endfor %} {% block 条件 %}...{% endblock %} {% macro 条件 %}...{% endmacro %}
{% include '' %}包含 {% import '' %}导入宏 {% extends '' %}继承
|
蓝图
Blueprint是一种组织一组相关视图及其他代码的方式。与把视图及其他代码直接注册到应用的方式不同,蓝图方式是把它们注册到蓝图,然后再工厂函数中吧蓝图注册到应用
Flask操作数据库
Flask连接MySQL数据库
在Flask中我们很少会使用功能pymysql直接写原生的SQL语句去操作数据库,更多的是通过SQLAlchemy提供的ORM技术,类似于操作普通Python对象一样实现数据库的增删改查操作
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'database_learn'
app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4'
db = SQLAlchemy(app)
with app.app_conntext(): with db.engine.connect() as conn: rs = conn.execute('select 1') print(rs.fetchone())
|
class Config(object): HOSTNAME = 'localhost' PORT = 3306 USERNAME = 'root' PASSWORD = 'root' DATABASE = 'database_learn'
app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4'
SQLALCHEMY_TRACK_MODIFICATIONS = True app.config['SQLALCHEMY_ECHO'] = True app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = False
app.config.from_object(Config)
db = SQLAlchemy(app)
|
ORM模型
对象关系映射(Object Relationship Mapping),是一种可以用Python面向对象的方式来操作关系型数据库的技术,具有可以映射到数据库表能力的Python类称为ORM模型
优点:
- 开发效率高:几乎不需要写原生的SQL语句,使用纯Python的方式来操作数据库
- 安全性高:ORM模型的底层代码对一些常见的安全问题做了防护
- 灵活性强:Flask-SQLAlchemy底层支持SQLite、MySQL、Oracle、PostgreSQL等关系型数据库,但是ORM模型代码几乎一样
数据库的CRUD操作
class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(100), nullable=False) password = db.Column(db.String(100), nullable=False)
|
create:
@app.route('/user/add') def add_user(): user = User(username='zhangsan', password='111111') db.session.add(user) db.session.commit() return '用户创建成功'
|
read:
@app.route('/user/query') def query_user(): user = User.query.get(1) print("user.id:%d\nuser.username:%s\nuser.password:%s" % (user.id, user.username, user.password)) users = User.query.filter_by(username='zhangsan') for user in users: print(user.username) return '数据查找成功'
|
update:
@app.route('/user/update') def update_user(): user = User.query.filter_by(username='zhangsan').first() user.password = '222222' db.session.commit() return '数据修改成功'
|
delete:
@app.route('/user/delete') def delete_user(): user = User.query.filter_by(username='zhangsan').first() db.session.delete(user) db.session.commit() return '数据删除成功'
|
表关系
关系型数据库一个强大的功能,就是多个表之间可以建立关系。比如文章表中,通常需要保存作者数据,但是我们不需要直接把作者数据放到文章表中,而是通过外键引用用户表。这种强大的表关系,可以存储非常复杂的数据,并且可以让查询非常迅速。在 Flask.SQLAlchemy 中,同样也支持表关系的建立。表关系建立的前提,是通过数据库层面的外键实现的。表关系总体来讲可以分为三种,分别是:一对多(多对一)、一对一、多对多
flask_migrate迁移ORM模型
from flask_migrate import Migrate ... migrate = Migrate(app, db)
|
ORM模型映射三部:终端输入
flask db init
:这步只需要一次,生成migrations的文件夹
flask db migrate
:识别ORM模型的改变,生成迁移脚本
flask db upgrade
:运行迁移脚本,同步到数据库中