Python Web 开发-Flask 框架
目录
安装
安装 pip 包管理器和 Python 虚拟环境
pip install virtualenv
pip 安装包速度过慢可以使用豆瓣镜像源
安装 Flask 两种方法
pip install -r requeirements.txt #在文件中写入要安装的扩展,pip 读取这个文件的内容。
pip install flask #直接安装
路由
路由即路径,这里给一段URL http://baidu.com/user 其中 user 就是路由。
flask_router1.py
"""
路由即路径
"""
from flask import Flask
app = Flask(__name__)
# 最简易版的路由,当访问根(/)就返回Hello ,I love Flask.。
# 参考文档:https://dormousehole.readthedocs.io/en/latest/quickstart.html#id7
@app.route('/')
def hell():
return 'Hello ,I love Flask.'
# 这是稍微进阶一点的路由,可以接收参数。
# 通过接收路径/my后面的参数,以关键字参数的方式将它传入my()。
@app.route('/my/<username>')
def my(username):
return f'my page:{username}'
if __name__ == '__main__':
# host参数默认为127.0.0.1,port参数默认为5000。
# 开启了debug模式修改代码后保存会自动重启服务。
app.run(port=80, debug=True)
路由不常用一种定义方式
add_url_rule和@app.route作用一致,@app.route底层还是调用add_url_rule方法。
from flask import Flask
app = Flask(__name__)
# @app.route('/')
def hell():
return 'Hello ,I love Flask.'
# @app.route('/my/<username>')
def my(username): # 会
return f'my page:{username}'
app.add_url_rule(rule='/', view_func= hell)
app.add_url_rule(rule='/my/<username>', view_func= my)
if __name__ == '__main__':
app.run(port=80, debug=True)
蓝图
其中有一种路由是在项目中使用的最多的,叫蓝图,它的一个好处在于模块化并且减少定义路由时的重复代码量。
比如你编写一个模块在定义路由时/user/是所有关于用户的操作,此时有两个功能,更改密码&查看用户信息,那在定义路由时就得写/user/info表示查看用户信息,/user/forget_password来表示更改密码,使用蓝图可以将/user提取出来作为前缀,在注册路由时就可省略前缀减少代码。
模块化是将一个个功能抽离出来,作为模块存在,使用一个入口文件调用就行,你可能会想路由不也可以抽离出来作为模块嘛,是可以抽离出来,但你在入口文件还得一个个导入方法,蓝图就直接一行代码就能全部导入,比路由方便。
使用过程很简单,定义蓝图 --> 使用蓝图 --> 注册蓝图,下面是一个最基本的示例。
flask_router_blueprint.py
"""
Blueprint用来定义蓝图
"""
from flask import Flask, Blueprint
app = Flask(__name__)
# 定义蓝图,第一个参数为蓝图名字,第二个参数为表示蓝图属于那个包下的模块。
index_page = Blueprint('index_page', __name__)
# 使用蓝图
@index_page.route('/')
def index_page_index():
return 'use blueprint'
# 注册蓝图(必须所有内容写完了才能注册,内容敲定了才能板上钉钉嘛)
app.register_blueprint(index_page, url_prefix='/i')
# 普通路由规则
@app.route('/')
def hell():
return 'Hello Flask router'
if __name__ == "__main__":
app.run(debug=True)
在实际使用过程中一般分为入口文件和若干个功能模块,下面为示例。
flask_router_control.py
from flask import Flask, Blueprint
app = Flask(__name__)
# 定义首页蓝图
index_page = Blueprint('index_page', __name__)
# 定义用户功能蓝图
users_ops = Blueprint('users', __name__)
# 使用蓝图定义首页
@index_page.route('/')
def index_page_index():
return 'this is index, use blueprint'
@index_page.route('/opt')
def options():
return '使用蓝图自动带上路径前缀'
# 蓝图定义用户功能
@users_ops.route('') # 默认就是根,这样就不用写/uesr,方便啊hhh
def user_index():
return '用户首页'
@users_ops.route('forgetPwd')
def forget_password():
return '修改密码页面'
@users_ops.route('getName')
def get_name():
return '获取用户名'
flask_router_index.py
from flask import Flask
from flask_router_control import index_page, users_ops
app = Flask(__name__)
# 注册蓝图
app.register_blueprint(index_page, url_prefix='/') # 所有内容依照这个前缀来访问
app.register_blueprint(users_ops, url_prefix='/user')
if __name__ == "__main__":
app.run(port=80, debug=True)
加载配置文件
配置文件路径config/base_setting.py,内容是DEBUG = True,下面通过几种不同的方式加载它。
flask_config_file.py
from flask import Flask
app = Flask(__name__)
# 通过Python文件的方式加载配置文件(常用!)
app.config.from_pyfile('config/base_setting.py')
@app.route('/')
def hello():
return 'hello web application word'
if __name__ == '__main__':
app.run(port=80)
flask_config_modle.py
from flask import Flask
app = Flask(__name__)
# 通过模块的方式加载配置文件(常用)
app.config.from_object('config.base_setting')
@app.route('/')
def hello():
return 'hello web application word'
if __name__ == '__main__':
app.run(port=80)
flask_config_sysenv.py
from flask import Flask
app = Flask(__name__)
# 通过系统环境变量的方式加载配置文件
# Linux使用export ops_config=./base_setting.py
# Windows使用set
app.config.from_envvar('ops_config')
@app.route('/')
def hello():
return 'hello web application word'
if __name__ == '__main__':
app.run(port=80)
flask_config_var.py
from flask import Flask
app = Flask(__name__)
# 通过变量的方式加载配置文件(用得少)
app.config['DEBUG'] = True
@app.route('/')
def hello():
return 'hello web application word'
if __name__ == '__main__':
app.run(port=80)
Request对象
flask_request.py
"""
request对象可以用来获取请求
"""
from flask import Flask, Blueprint, request
app = Flask(__name__)
index_page = Blueprint('index_page', __name__)
@index_page.route('/')
def index_page_index():
return 'index'
# 默认接收GET请求
@index_page.route('/get')
def get():
# 获取参数a的值,当参数a不存在时可以设置默认值。
# 当定义为GET请求时就不能以POST请求传输消息否则报错(405)。
var_a = request.args.get('a', 'i love imooc')
return f'<h2>ok, method:{request.method} params:{request.args} var_a:{var_a}</<h2>>'
# 启用POST需单独设定
@index_page.route('/post', methods=['post'])
def post():
# 获取参数a的值,POST不支持默认值,当你不传递a这个值就会报错,所以给它做个判断。
# curl -d 'a=1&b=2' 127.0.0.1:5000/post
# 除了curl还可使用postman这个API调试工具
var_a = request.form['a'] if 'a' in request.form else ''
return f'method:{request.method},params:{request.form},var_a:{var_a}'
@index_page.route('/mix_get')
def mix_get():
# request.values可以接收GET和POST请求,无需单独记request.args.get()和request.form。
var_a = request.values['a'] if 'a' in request.values else 'i love imooc'
return f'method:{request.method}, params:{request.args}, var_a:{var_a}'
@index_page.route('/mix_post', methods=['POST'])
def mix_post():
var_a = request.values['a'] if 'a' in request.values else ''
return f'method:{request.method}, params:{request.values}, var_a:{var_a}'
# 获取上传的文件具体信息
@index_page.route('/upload', methods=['POST'])
def upload():
# 获取具体文件对象 request.files['file']
# curl -F file=@./log.xml 127.0.0.1:5000/upload
f = request.files['file'] if 'file' in request.files else '' # 判断一下用户到底传了文件没有,没传则会报错。
return f'method:{request.method}, params:{request.files}, file_obj:{f}'
# 作业:现有一个POST请求,在URL中请求a=bget,并且POST请求参数也有a=bpost,此时用request.values对a的取值是?
# 盲答:已经使用POST就不能再以GET方式传输参数,否则返回405,此时values取值一定是bpost。
# 实际测验:
# curl -d a=bpost 127.0.0.1:5000/home_work?a=bget
# method:POST, params:CombinedMultiDict([ImmutableMultiDict([('a', 'bget')]), ImmutableMultiDict([('a', 'bpost')])]), var_a:bget
# 结论:POST请求传同名GET参数和POST参数,会先获取GET参数。
@index_page.route('/home_work', methods=['POST'])
def home_work():
var_a = request.values['a'] if 'a' in request.values else ''
return f'method:{request.method}, params:{request.values}, var_a:{var_a}'
app.register_blueprint(index_page, url_prefix='/')
if __name__ == '__main__':
app.run(port=80, debug=True)
Response对象
flask_response.py
"""
make_response 返回相应对象,可以设置响应头信息。
jsonify 在Flask中以更简单操作将对象转换为 JSON 并设置相关响应头信息。
render_template 返回模板文件。
"""
from flask import Flask, Blueprint, make_response, jsonify, render_template
app = Flask(__name__)
index_page = Blueprint('index_page', __name__)
@index_page.route('/response_html')
def index_page_html():
# 默认返回状态码200,也可返回其他状态。
return '默认是返回HTML文档的', 200
@index_page.route('response_same_html')
def index_page_same_html():
# 第二个值依然可以设置状态码。
response = make_response('使用make_response返回HTML文档', 200)
return response
@index_page.route('json')
def resp_json():
# 使用json.dumps将字典序列化为JSON格式,再设置内容类型的值为JSON。
# JSON Viewer插件可以美化JSON格式
import json
data = {"blog_address": "https://www.raingray.com"}
response = make_response(json.dumps(data))
response.headers['Content-Type'] = 'application/json'
return response
@index_page.route('json_same')
def resp_json_same():
# jsonify会调用json.dumps来序列化对象,最后帮我们加上JSON对应的headres。
data = {"blog_address": "https://www.raingray.com"}
response = make_response(jsonify(data))
return response
@index_page.route('template')
def resp_template():
# 模板文件会到当前目录下的templates目录内寻找。
# 参考文档:https://dormousehole.readthedocs.io/en/latest/quickstart.html#id10
return render_template('index.html')
app.register_blueprint(index_page, url_prefix='/i')
# 普通路由规则
@app.route('/')
def hell():
return 'Hello Flask Router'
if __name__ == "__main__":
app.run(port=80, debug=True)
Jinja2模板语法
文件结构
jina2_template
templates
common
layout.html
index.html
template.html
control.py
index.py定义视图返回模板,还可以将一些表达式传入模板,在模板将值打印出来。
control.py
from flask import Flask, Blueprint, render_template, make_response
index_page = Blueprint('index', __name__)
@index_page.route('/')
def template():
name = 'gbb'
age = 18
dict_data = {'name': 'gbb', 'phone': [13412341234, 13422223333]}
# 将变量的值输出在模板中,
# render_template第二个值为可变参数,可接收解包后的字典或者是关键字参数,并将这个值传到模板文件中。
return render_template('index.html', **dict_data, age=age)
@index_page.route('/template')
def sub_template():
return render_template('template.html')
Jinja2 模板语法和 Python 类似,很简单。
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板</title>
</head>
<body>
<h1>模板加载成功</h1>
<h2>输出表达式:</h2>
语法:<code>{{'{{ Expressions }}'}}</code><br />
疑问:怎么能够同时输出两个表达式?<code>{{'{{ Expressions, Expressions }}'}}</code>这样写会报错
<p>{{ name }}</p>
<h2>循环</h2>
用法和Python循环一致<br />
语法:
<pre>
{{'{{% for elements in obj %}}'}}
{{'{{ elements }}'}}
{{'{{% endfor %}}'}}
</pre>
{% for str in name %}
{{ str }}
{% endfor %}
<h2>条件判断</h2>
用法和Python条件语句一致<br />
语法:
<pre>
{{'{{% if obj %}}'}}
{{'{{% endif %}}'}}
</pre>
{% if phone %}
{{ phone }}<br />
输出方法1:{{ phone.1 }}<br />
输出方法2:{{ phone[1] }}<br />
参考文档:<a href="http://docs.jinkan.org/docs/jinja2/templates.html#variables">变量</a>
{% endif %}
</body>
</html>作为入口文件注册蓝图并启动 Flask。
index.py
from flask import Flask
from control import index_page
app = Flask(__name__)
app.register_blueprint(index_page)
if __name__ == '__main__':
app.run(port=80, debug=True)
模板继承
模板继承就是一个不变框架,往里面儿填充内容,下面使用的语法是{% block content %}{% endblock %},那你在子模板中也必须使用相同的语法来填充内容。
templates/common/layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>公共布局</title>
</head>
<body>
<h1>公共布局文件</h1>
{% block content %}{% endblock %}
<p>使用<code>{{ '{{ self.content() }}' }}</code>重复引用相同内容:{{ self.content() }}</p>
</body>
</html>template.html
{# 必须使用extends先继承模板 #}
{% extends "common/layout.html" %}
{# 下面的内容是子模板可以填充的值,这个block语法要和继承的模板保持一致 #}
{% block content %}这是子模板填充的内容{% endblock %}连接MySQL
安装扩展
pip install flask_sqlalchemy mysqlclient设置好数据库配置信息,这个 application.py 就专门来初始化应用。
application.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 定义数据库配置信息
# scheme://username:pasword@host/database_name
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:@localhost/mysql'
# 初始化对象
db = SQLAlchemy(app)
所有路由相关的定义就放在 control.py 中。
control.py
from flask import Flask, Blueprint, render_template, make_response
from sqlalchemy import text
from application import db
index_page = Blueprint('index', __name__)
@index_page.route('/')
def template():
name = 'gbb'
age = 18
dict_data = {'name': 'gbb', 'phone': [13412341234, 13422223333]}
# 查询mysql数据库内user表所有字段内容,这样可能造成语句拼接导致SQL注入。
sql = text('select * from `user`')
result = db.engine.execute(sql)
return render_template('index.html', **dict_data, age=age, sql_result=result)
数据库查询内容将在模板文件数据
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板</title>
</head>
<body>
<h2>SQL查询结果</h2>
<p>原始数据:{{ sql_result }}<p />
{% for result in sql_result %}
{% for r in result %}
{{ r }}
{% endfor %}
{% endfor %}
</body>
</html>www.py 用来注册蓝图的。
www.py
from control import index_page
from application import app
app.register_blueprint(index_page)
启动文件
from application import app
from www import *
if __name__ == '__main__':
app.run(port=80, debug=True)
使用 model 查询内容
使用 ORM 可以不用输入 SQL 语句就能查询。
在前面的基础上创建与 templates 同级文件common/model/user.py。
这个文件就是model。
from application import db
# 会把类名字当做表名并把类变量当做字段查询,验证方法是把类名改为一个不存在的表,在模板文件输出结果就可以看到报错结果。
# SQL: SELECT user.`Host` AS `user_Host`, user.`User` AS `user_User`, user.`Password` AS `user_Password` FROM user
class User(db.Model):
# 我发现必须要设置主键不然报错,而且只有设置主键的内容才会显示出来...
Host = db.Column(db.String(60), primary_key=True)
User = db.Column(db.String(80), primary_key=True)
Password = db.Column(db.String(41), primary_key=True)
在 control.py 使用query.all()查询数据
from flask import Flask, Blueprint, render_template, make_response
from sqlalchemy import text
from application import db
from common.model.user import User
index_page = Blueprint('index', __name__)
@index_page.route('/')
def template():
name = 'gbb'
age = 18
dict_data = {'name': 'gbb', 'phone': [13412341234, 13422223333]}
# 查询mysql数据库内user表所有字段内容,这样可能造成语句拼接导致SQL注入。
# sql = text('select * from user')
# result = db.engine.execute(sql)
# 用Model查询
result = User.query.all()
# 将变量的值输出在模板中,
# render_template第二个值为可变参数,可接收解包后的字典或者是关键字参数,并将这个值传到模板文件中。
return render_template('index.html', **dict_data, age=age, sql_result=result)
由于 query.all() 返回是列表,但模板中我们使用了嵌套循环,这样会报错,修改后内容如下。
{% for result in sql_result %}
{{ result }}
{% endfor %}使用sqlacodgen生成model
数据库结构设计完成后,在开发时不需要手写,可直接用工具生成。
所有操作依靠此插件进行。
pip install flask-sqlacodegen运行次命令将会在 common/model 目录下产生名为 user.py 的 model 文件。
flask-sqlacodegen "mysql://root:@localhost/mysql" --tables user --outfile "common/model/user.py" --flask运行没报错,删除 user.py 中自动导入的内容(不知道为什么不删除在运行时会报错,也许是用不到这些功能吧,具体课程里没讲)
from sqlalchemy import Column, Integer, LargeBinary, Numeric, String, Text
from sqlalchemy.schema import FetchedValue
from sqlalchemy.dialects.mysql.enumerated import ENUM接着将代码内的 db 对象改为本项目中与 Flask 关键的 db,而不使用自动生成 db,因为它没有指向任何一个 Flask。
通过model建立表
千万不要忘了指定你要在那个数据库下创建表。
# 定义数据库配置信息
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123@localhost/tmp_test'在导入于 Flask 关联的 db 对象后使用此方法创建表结构。
db.create_all()MVC
参考链接
最近更新:
发布时间: