Python全栈工程师(慕K)——Flask入门基础记录
1、flask安装
pip安装:pip install flask
源码安装:python steup.py install
验证:
cmd
python import flask flask.__version__
2、启动服务器
_1.设置环境变量
Windows:set FLASK_APP=app.py
Linux:export FLASK_APP=app.py
_2.flask run 启动内置web服务器
指定IP及端口:
flask run --host=0.0.0.0 --port=8001
或
flask run -h 0.0.0.0 -p 8001
3、开启调试模式(代码修改后服务器自动重启)
_1.设置环境变量(P.S.1:生产模式:production)
Windows:set FLASK_ENV=development
Linux:set FLASK_ENV=development
_2.flask run启动内置web服务器
4、Flask扩展
一搜、二看、三用
5、路由配置
_1.
方式一:使用装饰器
@app.route(url_name, methods)
e.g.:@app.route('/login, methods=['GET', 'POST'])
方式二:使用API配置
app.add_url_rule(url, url_name, view_name)
e.g.:app.add_url_rule('/home', 'home', hello_world)
_2.路由匹配规则
1)匹配整个文字:
@app.route('/login‘)
2)传递参数:
@app.route('/login/<username>')
3)指定参数类型(string、int、float、path、uuid):
@app.route('/post/<int:post_id>')
_3.查看URL路由规则列表
app.url_map
_4.URL逆向解析(根据名称解析成URL字符串)
1)url_for(url_name, **kwargs)
2)静态文件(js/css/图片)引用:url_for('static', filename='style.css')
_5.视图获取页面传值
1)URL中的值
@app.route('/user/<page>') def list_user(page):
return '您是第{}页用户'.format(page)
2)URL中的值为可选
@app.route('/user/')
@app.route('/user/<page>') def list_user(page=1): return '您是第{}页用户'.format(page)
6、上下文对象
_1.应用上下文对象
1)current_app:当前应用的实例
2)g:处理请求时的临时存储对象,每次请求都会重设这个变量
_2.请求上下文对象
1)request:请求对象,封装了客户端发出的HTTP请求中的内容
2)session:用户会话(dict),各请求之间的数据共享
7、请求报文
_1.请求报文常用参数
1)method:请求的类型(GET/POST/OPTIONS等)
2)form:POST请求数据dict
3)args:GET请求数据dict
4)values:POST请求和GET请求数据集合dict
5)files:上传文件数据dict
6)cookies:请求中的cookie dict
7)headers:HTTP请求头
@app.route('/test/req') def test_request(): """请求报文练习""" get_args = request.args print(get_args) # 页码一定是正整数 page = get_args.get('page', 1) print(page) # 服务器所在的主机地址 get_headers = request.headers print(get_headers) host = get_headers.get('Host') print('主机地址{}'.format(host)) print(request.host) # 获取ip地址 get_remote_addr = request.remote_addr print('远程IP地址{}'.format(get_remote_addr)) # 获取User-agent get_user_agent = request.user_agent print(get_user_agent) return 'request success'
_2.请求钩子
1)before_first_reqiest:服务器初始化后第一个请求到达前执行
2)before_request:每一个请求到达前执行
3)after_request:每次请求处理完成后执行,如果请求过程产生了异常,则不执行
4)teardown_request:每次请求处理完成后执行,如果请求过程产生了异常也执行
@app.before_first_request def first_request(): """请求钩子""" """服务器启动后第一个请求到达前""" print('before_first_request') @app.before_request def per_request(): """每一个请求到达前""" print('before_request')
8、响应报文
_1.响应字符串
_2.响应元组
1)response:响应内容
2)status:响应状态码
3)headers:响应头信息
_3.使用make_response代替
@app.route('/test/resp') def test_response(): """测试响应""" # return 'response success', 201, { # 'user': 'Jim', # } # 构造一个响应对象 resp = make_response('这是make_response响应对象', 403, { 'token': 'abc123' }) resp.headers['user_id'] = 'myid_123' resp.status_code = 200 # 响应HTML html = '<html><body><h2 style="color:red;">make_response-显示HTML</h2></body></html>' resp = make_response(html) return resp @app.route('/test/resp/html') def test_resp_html(): # 从文件中响应HTML ren_temp_html = render_template('index.html') # return ren_temp_html resp = make_response(ren_temp_html, 403, { 'token': '123' }) return resp
9、重定向等内部视图
_1.redirect():实现重定向
_2.abort():处理错误
@app.route('/') def hello_world(): # return 'Hello World!' # 访问“/”时从定向到“/index”页面 # return redirect('/index') ip_list = ['127.0.0.2'] ip = request.remote_addr if ip in ip_list: abort(403) return 'Hello' @app.errorhandler(403) def forbidden_page(err): print(err) return '您没有权限访问,请联系管理员开通权限'
10、全局函数
_1.url_for():URL解析函数
_2.get_flashed_messages():会话消息
{% for category, message in get_flashed_messages(with_categories=true, category_filter=['error']) %} <p>{{ category }} -- {{message }}</p> {% endfor %} ———————————— from flask import flash @app.route('/') def test(): flash('错误提示', 'error') flash('欢迎回来', 'success')
_3.range([start], stop, [step])
_4.dict(**items)
_5.cycler(*items):可用于css类名的循环
_6.joiner(sep=','):可用于字符串拼接
11、扩展
# 为模板引擎添加扩展,支持break/continue语法 app.jinja_env.add_extension('jinja2.ext.loopcontrols')
12、转义显示
_1.视为字符串
{{ '{{}} {%%}' }}
_2.使用raw标签
{% raw %} {% for key, value in data,items %} {{ key }}: {{ value }} {% endfor %} {% endraw %}
13、过滤器
_1.用管道符号(|)
_2.使用标签
{% filter upper %}
This text becomes uppercase
{% endfilter %}
_3.自定义过滤器
1)使用装饰器注册
@app.template_filter('reverse') def reverse_filter(s): return s[::-1]
2)调用函数注册
def reverse_filter(s): return s[::-1] app.jinja_env.filters['reverse'] = reverse_filter
14、模板中的宏
_1.html中使用宏
{% macro input(name, type='text', value='') %} <div> <input class="input-control" type="{{ type }}" name="{{ name }}" value="{{ value }}" /> </div> {% endmacro %} {{ input('username') }} {{ input('password', type='password') }}
_2.文件中宏的使用
1)将定义好格式的宏保存为forms.html
2)导入:
{% import 'forms.html' as forms %} {% from 'forms.html' import input %}
3)使用:
<p>{{ forms.input('username') }}</p>
15、flask-sqlalchemy安装及配置
_1.安装
pip安装:pip install -U Flask-SQLAlchemy
源码安装:python setup.py install
安装依赖:pip install mysqlclient
_2.配置
1)数据库URI:SQLALCHEMY_DATABASE_URI
# 配置数据库的连接参数 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:root@localhost/test_flask'
2)多个数据库支持
SQLALCHEMY_BINDS = { 'users': 'mysqldb://localhost/users', 'appmeta': 'sqlite:path/to/appmeta.db' }
16、数据库模型设计
_1.绑定到Flask对象
db = SQLAlchemy(app)
_2.ORM模型创建
class User(db.Model): id = db.Column(db.Integer, primary_key=True)
_3.指定表的名称
__tablename__='weibo_user'
_4.创建和删除表
1)创建表
db.create_all(bind='db1')
2)删除表
db.drop_all()
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:root@localhost/test_flask' db = SQLAlchemy(app) class User(db.Model): # 用户信息 __tablename__ = 'weibo_user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), nullable=False) password = db.Column(db.String(256), nullable=False) birth_day = db.Column(db.Date, nullable=True) age = db.Column(db.Integer, nullable=True) class UserAddress(db.Model): # 用户的地址 __tablename__ = 'weibo_user_addr' id = db.Column(db.Integer, primary_key=True) addr = db.Column(db.String(256), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('weibo_user.id'), nullable=False) user = db.relationship('User', backref=db.backref('address', lazy=True))
from app import db
db.drop_all()
db.create_all()
17、ORM插入,修改,删除,查询数据
_1.插入
1)构造ORM模型对象
user = User('admin', 'password')
2)添加到db.session(P.S.2:可添加多个对象)
db.session.add(user)
3)添加到数据库
db.session.commit()
_2.修改
user.username = 'root'
db.session.add(user)
db.session.commit()
_3.删除
user = User.query.filter_by(username='Jim').first()
db.session.delete(user)
db.session.commit()
_4.查询,返回结果集
1)查询所有数据
User.query.all()
2)按条件查询
User.query.filter_by(username='zhangsan')
User.query.filter(User.nickname.endswith('三')).all()
3)排序
User.query.order_by(User.username)
4)查询TOP10
User.query.limit(10).all()
5)根据pk查询
User.query.get(1)
6)获取第一条记录
User.query.first()
7)视图快捷函数:有则返回,无则返回404
first_or_404()
get_or_404()
8)多表关联查询
db.session.query(User).join(Address)
User.query.join(Address)
9)分页
-1>方式一:使用offset和limit
.offset(offset).limit(limit)
-2>方式二:paginate分页支持,返回Pagination的对象
.paginate(page=2, per_page=4)
has_prev/has_next——是否有上/下一页
items——当前页的数据列表
prev_num/next_num——上/下一页的页码
total——总记录数
pages——总页数
@app.route('/user/<int:page>/') def list_user(page): """ 用户分页 """ per_page = 10 # 每一页的数据大小 # 1.查询用户信息 sql = User.query # 2.准备分页的数据 user_page_data = sql.paginate(page, per_page=per_page) return render_template('list_user.html', user_page_data =user_page_data) ———————————————————————— html 总共有{{ user_page_data.total }}用户 当前在第{{ user_page_data.page }}页 总共有{{ user_page_data.pages }}页 {% for user in user_page_data.items %} {{ user.username }} {% endfor %} {% if user_page_data.has_prev %} {{ url_for('list_user', page=user_page_data.prev_num) }} {% endif %} {% if user_page_data.has_next %} {{ url_for('list_user', page=user_page_data.next_num) }} {% endif %}
18、wtf表单
_1.安装:
pip安装:pip install flask-wtf
源码安装:python setup.py install
_2.配置(CSRF保护)
WTF_CSRF_SECRET_KEY = 'a random string'
_3.表单模型
1)label:label标签
2)default:表单默认值
3)validators:表单验证规则
4)widget:定制界面显示方式
5)description:帮助文字
表单输入区域:{{ form.username }}
表单label:{{ form.username.label.text }}
from forms import LoginForm app.config['WTF_CSRF_SECRET_KEY'] = 'ABC123' app.config['SECRET_KEY'] = 'ABC123' @app.route('/form', methods=['GET', 'POST']) def page_form(): form = LoginForm() return render_template('page_form.html', form=form) —————————————————————————————— from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField class LoginForm(FlaskForm): username = StringField(label='用户名:') password = PasswordField(label='密码:') submit_btn = SubmitField(label='登录') —————————————————————————————— <form action="" method="post"> {{ form.username.label.text }} {{ form.username }} {{ form.password.label.text }} {{ form.password }} {{ form.submit_btn }} </form>
_4.通过表单保存数据
1)检测表单是否已经通过验证
form.validate_on_submit()
2)获取表单中传递过来的值
form.field_name.data
3)业务逻辑代码编写(可结合ORM)
4)CSRF表单保护
-1>默认开启CSRF保护
// 同步请求CSRF保护
{{ form.csrf_token }} or <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
// 异步请求CSRF保护 <!-- 第一步:模板中添加csrf_token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <script type="text/javascript"> let csrftoken = $("meta[name=csrf-token]").attr("content") or <script type="text/javascript"> let csrftoken = "{{ csrf_token() }}" <!-- 第二步:添加X-CSRFToken头 --> $.ajaxSetup({ beforeSend: function (xhr, settings) { if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken) } } }) </script>
-2>关闭单个表单CSRF保护
form = RegisterForm(csrf_enabled=False)
@app.route('/user/register', methods=['GET', 'POST']) def page_register(): form = RegisterForm(csrf_enabled=False) —————————————————————————— class RegisterForm(FlaskForm): def __init__(self, csrf_enabled, *args, **kwargs): super().__init__(csrf_enabled=csrf_enabled, *args, **kwargs)
-3>全局关闭
WTF_CSRF_ENABLED = False
class RegisterForm(FlaskForm): username = StringField(label='用户名:') password = PasswordField(label='密码:') re_password = PasswordField(label='确认密码:') birth_day = DateField(label='生日:') age = IntegerField(label='年龄:') submit_btn = SubmitField(label='注册') —————————————————————————— @app.route('/user/register', methods=['GET', 'POST']) def register_form(): form = RegisterForm() if form.validate_on_submit(): username = form.username.data password = form.password.data birth_day = form.birth_day.data age = form.age.data user = User(username=username, password=password, birth_day=birth_day, age=age) db.session.add(user) db.session.commit() return redirect(url_for('index')) else: print(form.errors) return render_template('page_register.html', form=form) —————————————————————————— <form action="{{ url_for('register_form') }}" method="post"> {{ form.csrf_token }} {{ form.username.label }} {{ form.username }} {{ form.password.label }} {{ form.password }} {{ form.re_password.label }} {{ form.re_password }} {{ form.birth_day.label }} {{ form.birth_day }} {{ form.age.label }} {{ form.age }} {{ form.submit_btn }} </form>
_5.表单验证
1)导入内置的表单验证器(或自定义)
from wtforms.validators import DataRequired
-1>内置表单验证器
DataRequired/InputRequired、Email/URL/UUID、Length(min=-1, max=-1, message=None)、EqualTo(fieldname, message=None)
2)配置到表单字段
username = StrinField('用户名', validators=[DataRequired('请输入用户名'), my_validator])
3)自定义表单验证
-1>只在当前表单使用
class RegisterForm(FlaskForm): username = StringField(label='用户名') def validate_username(self, field): username = field.data pattern = r'^1[0-9]{10}$' if not re.search(pattern, username): raise ValidationError('请输入手机号码') return field
——————————————————————————————————————————————
{% if form.username.errors %}
{% for err in form.username.errors %}
{{ err }}
{% endfor %}
{% endif %}
-2>在多个表单中使用
def phone_required(form, field): username = field.data pattern = r'1[0-9]{10}$' if not re.search(pattern, username): raise ValidationError('请输入手机号码') return field ———————————————————————— class LoginForm(FlaskForm): username = StringField(label='用户名', validators=[phone_required])
e.g.:
import re from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField, DateField, IntegerField, validators from wtforms.validators import ValidationError def phone_required(form, field): username = field.data pattern = r'^1[0-9]{10}$' if not re.search(pattern, username): raise ValidationError('请输入正确的手机号') return field class LoginForm(FlaskForm): username = StringField(label='用户名:', validators=[validators.DataRequired('手机号不能为空'), phone_required]) password = PasswordField(label='密码:') submit_btn = SubmitField(label='登录') class RegisterForm(FlaskForm): username = StringField(label='用户名:', validators=[validators.DataRequired()]) password = PasswordField(label='密码:', validators=[validators.DataRequired(), validators.EqualTo('re_password', message='两次密码不一致')]) re_password = PasswordField(label='确认密码:') birth_day = DateField(label='生日:') age = IntegerField(label='年龄:') submit_btn = SubmitField(label='注册') def validate_username(self, field): username = field.data pattern = r'^1[0-9]{10}$' if not re.search(pattern, username): raise ValidationError('请输入正确的手机号') return field ——————————————————————— from flask import Flask, render_template, redirect, url_for from flask_sqlalchemy import SQLAlchemy from forms import LoginForm, RegisterForm app = Flask(__name__) # 为模板引擎添加扩展,支持break/continue语法 app.jinja_env.add_extension('jinja2.ext.loopcontrols') app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:root@localhost/test_flask' app.config['WTF_CSRF_SECRET_KEY'] = '22' app.config['SECRET_KEY'] = '11' db = SQLAlchemy(app) @app.route('/form', methods=['GET', 'POST']) def login_form(): form = LoginForm() if form.validate_on_submit(): return redirect(url_for('index')) else: print(form.errors) return render_template('page_form.html', form=form) @app.route('/user/register', methods=['GET', 'POST']) def register_form(): form = RegisterForm() if form.validate_on_submit(): username = form.username.data password = form.password.data birth_day = form.birth_day.data age = form.age.data user = User(username=username, password=password, birth_day=birth_day, age=age) db.session.add(user) db.session.commit() return redirect(url_for('index')) else: print(form.errors) return render_template('page_register.html', form=form) ———————————————————————————————— <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户注册</title> </head> <body> <form action="" method="post"> {{ form.csrf_token }} {{ form.username.label }} {{ form.username }} {% if form.username.errors %} {% for err in form.username.errors %} {{ err }} {% endfor %} {% endif %} {{ form.password.label }} {{ form.password }} {{ form.re_password.label }} {{ form.re_password }} {% if form.password.errors %} {% for err in form.password.errors %} {{ err }} {% endfor %} {% endif %} {{ form.birth_day.label }} {{ form.birth_day }} {{ form.age.label }} {{ form.age }} {{ form.submit_btn }} </form> </body> </html> —————————————————————————————————— <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户登录</title> </head> <body> <form action="" method="post"> {{ form.csrf_token }} {{ form.username.label }} {{ form.username }} {% if form.username.errors %} {% for err in form.username.errors %} {{ err }} {% endfor %} {% endif %} {{ form.password.label }} {{ form.password }} {{ form.submit_btn }} </form> </body> </html>
_6.图片上传
1)不使用wtf
-1>设置<form>的enctype
enctype='multipart/form-data'
-2>在视图函数中获取文件对象
request.files
-3>保存文件
f.save(file_path)
2)文件名称格式化
werkzeug.utils.secure_filename
@app.route('/img/upload', methods=['GET', 'POST']) def img_upload(): """ 不使用wtf实现文件上传 """ file_path = os.path.join(os.path.dirname(__file__), 'medias') if request.method == 'POST': # 获取文件列表 files = request.files file1 = files.get('file1', None) if file1: # 保存文件 f_name = secure_filename(file1.filename) # 文件名 print('filename:', f_name) file_name = os.path.join(file_path, f_name) # 路径 file1.save(file_name) print('保存成功') return redirect(url_for('img_upload')) return render_template('img_upload.html') ———————————————————————————— <form action="{{ url_for('img_upload') }}" method="post" enctype="multipart/form-data"> <input type="file" name="file1"> <input type="file" name="file2"> <input type="submit" value="上传"> </form>
3)使用FileField并添加类型验证
-1>FileRequired文件上传必须验证
-2>FileAllowed文件类型验证
from werkzeug.utils import secure_filename app.config['WTF_CSRF_SECRET_KEY'] = '1111' app.config['SECRET_KEY'] = '1111' app.config['UPLOAD_PATH'] = os.path.join(os.path.dirname(__file__), 'medias') @app.route('/atatar/upload', methods=['GET', 'POST']) def avatar_upload(): """ 头像上传 """ form = UserAvatarForm() if form.validate_on_submit(): # 获取图片对象 img = form.avatar.data f_name = secure_filename(img.filename) file_name = os.path.join(app.config['UPLOAD_PATH'], f_name) img.save(file_name) print('保存成功') return redirect(url_for('index')) else: print(form.errors) return render_template('avatar_upload.html', form=form) ———————————————————————————— class UserAvatarForm(FlaskForm): """ 用户头像上传 """ avatar = FileField(label='上传头像', validators=[FileRequired('前选择头像文件'), FileAllowed(['png'], '仅支持PNG图片上传')]) ———————————————————————————— <form action="{{ url_for('avatar_upload') }}" method="post" enctype="multipart/form-data"> {{ form.csrf_token }} {{ form.avatar.label }} {{ form.avatar }} <input type="submit" value="提交"> </form>
4)扩展Flask-Uploads