flask学习小结

avatar
作者
猴君
阅读量:0

背景

通过官方文档学习

官方文档地址 (2.0.x版本) Welcome to Flask — Flask Documentation (2.0.x) (palletsprojects.com)

quickstart

第一个app

代码

文件命名为hello.py

windows执行set FLASK_APP=hello,linux执行export FLASK_APP=hello,然后执行flask run即可运行app

默认运行的app是127.0.0.1:5000,可通过-h -p参数改ip地址和端口。其中,127.0.0.1只能本电脑访问,如果希望其他局域网也可访问,可将host设为0.0.0.0。如果在linux,需要放通对应的端口才能访问:iptables -A INPUT -p tcp --dport 8089 -j ACCEPT; iptables -A INPUT -p udp --dport 8089 -j ACCEPT;

部署时如果是测试环境会有提示,页面也会显示详细报错,可通过环境变量FLASK_ENV设置

http转译

将用户提供的所有值转化为转义字符,防止注入攻击

from markupsafe import escape  @app.route("<name>") def hello_world(name):     return f"hello, {escape(name)}"

<name>可捕获url的参数作为变量传给url处理函数

路由

使用app的方法.route来进行路由,如@app.route("<test_var>")

一个url处理函数可有多个route装饰,即一个url处理函数,匹配多个url

路由变量类型

可以对路由捕获的参数进行类型限定,如@app.route("<int:test_var>")

关于url末尾的斜杠

resource是文件夹名这种时,可以在url后加个/,当访问没加/,也会重定向到对应url

resource是文件时,url后一般不加/,此时如果访问时加了/,会报错,这防止了对resource重复进行重定向

url_for函数

该函数可用于建立url,用于将所有url汇总并做些自己的处理,但每个url都需要调用一次,函数传入第一个参数是route函数名,返回对应的url,后面可跟任意多关键字参数

可通过该函数,新建某个url,指向已有的url处理函数

注意,route是根据url找处理函数,url_for是根据函数名找url,可以用在<a>的href

请求方法

可通过@app.route("/test", methods=['GET', 'POST'])指定url允许的请求方法,如果不带method参数,默认只支持get方法,但如果显式规定了GET方法,也会自动支持HEAD和OPTIONS方法

静态文件

当需要js,css等文件,可通过静态文件部署

可通过执行url_for('static', filename='test.css')实现静态文件对应的访问endpoint,在这之前需要先在目录下手动创建static文件夹,filename对应的文件应该也放在目录下

from flask import Flask from flask import url_for  app = Flask(__name__)  @app.route("/hello") def hello_world():     return "<p>hello world</p>"  with app.test_request_context():     url_for('static', filename='test.css')

模板渲染

可通过render_template函数渲染,需要提供模板名,模板需要放在项目的templates目录下

{% if name %} <p>hello, {{ name }}</p> {% else %} <p>hello, world</p> {% endif %}
from flask import Flask from flask import url_for, render_template  app = Flask(__name__)  @app.route("/hello/<name>") def hello_world(name):     return render_template("test.html", name=name)     #return "<p>hello world</p>"  with app.test_request_context():     url_for('static', filename='test.css')

访问请求数据

可通过访问request对象对请求数据进行访问

request.form 表单数据

request.method 请求方法

request.args.get('key_name', '') 访问url里的参数,如果get报错会,服务器最终会返回400

文件上传

上传文件时不要忘了enctype="multipart/form-data"就行,否则浏览器不会上传你的文件

可执行request.files访问文件,文件对象和python内置file对象差不多,多了个save方法

可执行request.files['filename'].filename获取文件原始文件名,一般不可以新人原始文件名,如果要信任,需要使用secure_filename函数处理

cookie

request.cookies访问cookie,是字典形式。建议使用session带的cookie而不是直接使用request.cookie,因为更安全

注意用法,make_response(render_template(**kwargs))

重定向与抛出错误

可以使用flask.redirect, flask.abort进行重定向与返回错误代码

error还可以使用app的errorhandler设定错误码

关于响应对象

函数返回的值会被自动转化成response对象,转化规则如下:1如果返回类型是response类型直接返回 2如果是字符串则作为入参给response 3如果返回字典,最后直接返回调jsonify处理的结果,而不是resp对象 4如果返回是元组,元组里的status code会覆盖 5如果以上都不是,flask会认为返回值是个合法的,直接入参给response

make_response可以封装返回值和状态码,可对make_response对象设定响应头

session对象

除了request对象还有session对象,session可以针对某个特定用户存储一些信息等功能

密钥方法有很多,一个是import secret as s; s.token_hex()

tutorial

app setup

flask以app方式运作,app是flask.Flask的实例,可以创全局app,也可以在函数创app,通过函数调用返回app。

数据库配置

对接sqlite3,缺点是不支持并发,对于并发请求只能串行写入

连接db

g是全局对象,db操作也是全局的,不是每个request都会创db连接这种

创建表

flask会把user数据存在user表,post数据存在post表,先要创建这些表,框架不会自动创建

再去写个函数调用这个schema.sql

open_resource的文件是基于instance目录下的文件,这么写不需要显式指定文件目录

click.command定义命令行,会调用被装饰的函数

注册app

close_db和init_db_command函数需要给app注册,否则app不会用到这两个函数

teardown_appcontext是返回响应给client前需要做的事情

add_command添加了可以让flask命令调用的命令

此函数需要在app工厂函数里调用

初始化数据库

至此,可在flaskr上级目录下调用flask init-db,即可执行上述db初始化动作

# flaskr/db.py import sqlite3  import click from flask import current_app, g from flask.cli import with_appcontext  def get_db():     if 'db' not in g:         g.db = sqlite3.connect(current_app.config['DATABASE'], detect_types=sqlite3.PARSE_DECLTYPES)         g.db.row_factory = sqlite3.Row     return g.db  def close_db(e=None):     db = g.pop('db', None)     if db is not None:         db.close()  def init_db():     db = get_db()     with current_app.open_resource('schema.sql') as f:         db.executescript(f.read().decode('utf-8'))  @click.command('init-db') @with_appcontext def init_db_command():     init_db()     click.echo('Initialized the database')  def init_app(app):     app.teardown_appcontext(close_db)     app.cli.add_command(init_db_command)
# flaskr/schema.sql DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS post;  CREATE TABLE user (   id INTEGER PRIMARY KEY AUTOINCREMENT,   username TEXT UNIQUE NOT NULL,   password TEXT NOT NULL );  CREATE TABLE post(   id INTEGER PRIMARY KEY AUTOINCREMENT,   author_id INTEGER NOT NULL,   created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,   title TEXT NOT NULL,   body TEXT NOT NULL,   FOREIGN KEY (author_id) REFERENCES user (id) );
# flask/__init__.py import os  from flask import Flask  def create_app(test_config=None):     app = Flask(__name__, instance_relative_config=True)     app.config.from_mapping(SECRET_KEY='dev',DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'))      if test_config is None:         app.config.from_pyfile('config.py', silent=True)     else:         app.config.from_mapping(test_config)      try:         os.makedirs(app.instance_path)     except OSError:         pass      @app.route('/hello')     def hello():         return 'hello, world'      from . import db     db.init_app(app)      return app

view和blueprints

blueprint可以组织view和其他相关代码,view需要注册给blueprint

创建blueprint

__name__表示blueprint的相对未知,url_prefix用来关联url

蓝图可以在工厂函数中通过调用app.register_blueprint()函数来注册并使用

第一个视图 注册

写一个注册视图

import functools  from flask import Blueprint, flash, g, redirect, render_template, request, session, url_for from werkzeug import check_password_hash, generate_password_hash from flaskr.db import get_db  bp = Blueprint('auth', __name__, url_prefix='/auth')  @bp.route('/register', methods=('GET', 'POST')) def register():     if request.method == 'POST':         username = request.form['username']         password = request.form['password']         db = get_db         error = None          if not username:             error = 'username is required'         elif not password:             error = 'password is required'          if error is None:             try:                 db.execute("INSERT INFO user (username, password) VALUES (?, ?)", (username, generate_password_hash(password)))                 db.commit()             except db.IntegrityError:                 error = f'User {username} is already registerd'             else:                 return redirect(url_for('auth.login'))          flash(error)      return render_template('auth/register.html)

bp.route将url注册关联到auth blueprint下

request.form是个dict类型的数据

注意数据库插入数据语句采用?作为占位符

pwd直接存数据库不安全,用generate_password_hash生成加密pwd

如果username已存在会报IntegrityError

注册完成后,调用redirect可重定向到登录界面

flash方法可存储error信息,在模板渲染时可拿来用

登录视图

@bp.route('/login', methods=('GET', 'POST')) def login():     if request.method == 'POST':         username = request.form['username']         password = request.form['password']         db = get_db         error = None         user = db.execute('SELECT * from user WHERE username = ?', (username, )).fetchone()          if user is None:             error = 'incorrect username'         elif not check_password_hash(user['password'], password):             error = 'incorrect password'          if error is None:             session.clear()             session['user_id'] = user['id']             return redirect(url_for('index'))          flash(error)      return render_template('auth/login.html')

fetchone返回查询结果的一个,如果查询为空则返回None

check_password_hash将密码加密后和已存储的密码对比是否一致

注意登录成功时会清除当前session,然后将登入用户id存在session,后续请求可通过判断session有无用户id,直接用此session,从而省去后续登录,这需要再写一个逻辑用来在开始视图函数前判断是否已登录

@bp.before_app_request def load_logged_in_user():     user_id = session.get('user_id')     if user_id is None:         g.user = None     else:         g.user = get_db().execute('SELECT * FROM user WHERE id = ?', (user_id, )).fetchone()

注销视图

@bp.route('/logout') def logout():     session.clear()     return redirect(url_for('index'))

其他视图的认证

其他视图的增删改查都需要用户登入,在auth写一个检测用户登入信息的函数,当装饰器用

def login_required(view):     @functools.wrap(view)     def wrapped_view(**kwargs):         if g.user is None:             return redirect(url_for('auth.login'))         return view(**kwargs)     return wrapped_view

模板

基本模板

此模板用作其他模板基础模板,其他模板会在此基础扩展

<!doctype html> <title>{% block title %}{% endblock %} - Flaskr</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <nav>         <h1>Flaskr</h1>         <ul>                 {% if g.user %}                 <li><span>{{ g.user['username'] }}</span></li>                 <li><a href="{{ url_for('auth.logout') }}">Log Out</a></li>                 {% else %}                 <li><a href="{{ url_for('auth.register') }}">Register</a></li>                 <li><a href="{{ url_for('auth.login') }}">Register</a></li>                 {% endif %}         </ul> </nav> <section>         <header>                 {% block header %}{% endblock %}         </header>         {% for message in get_flashed_messages() %}         <div class="flash">{{ message }}</div>         {% endfor %}         {% block content %}{% endblock %} </section>

全局对象g在模板也同样可以使用

注意,在视图用的flash方法,在视图里可以调用方法get_flashed_messages()方法获取flash信息

注册模板

{% extends 'base.html' %}  {% block header %} <h1>{% block title %}Register{% endblock %}</h1> {% endblock %}  {% block content %} <form>         <label for="username">Username</label>         <input name="username" id="username" required />         <label for="password">Password</label>         <input type="password" name="password" id="password" required />         <input type="submit" value="Register" /> </form> {% endblock %}

extend扩展了base模板

登录模板

注册用户

现在,让我们到注册界面,注册一个用户

如果不填信息会报错

两个input去掉required的话,不填信息再次点击注册,报错

用已注册的名字再注册提示已注册

登录报错,查看后台日志,因为还没实现index

静态文件

在base.py模板中已经放好样式表,通过url_for('static', filename='style.css')访问,看看效果

blog blueprint

类似auth blueprint,再开发一个blog

blueprint

参考auth blueprint写一个blog的blueprint注意,blog的没有url_prefix,也就是说直接在根路径访问

注意这么写了以后ur_for('index') url_for('blog.index')效果都是一样的

from flask import url_for, Blueprint, flash, g, redirect, render_template, request from werkzeug.exceptions import abort from flaskr.auth import login_required from flaskr.db import get_db  bp = Blueprint('blog', __name__)
    # init db     from . import db     db.init_app(app)      # init blueprint auth     from . import auth     app.register_blueprint(auth.bp)      # init blueprint blog     from . impor blog     app.register_blueprint(blog.bp)     app.add_url_rule('/', endpoint='index')      return app

index

@bp.route('/') def index():     db = get_db()     posts = db.execute('SELECT p.id, title, body, created, author_id, username FROM post p JOIN user u ON p.author_id = u.id ORDER BY created DESC').fetchall()     return render_template('blog/index.html', posts=posts)
{% extends 'base.html' %}  {% block header %} <h1>{% block title %}Posts{% endblock %}</h1> {% if g.user %} <a class="action" href="{{ url_for('blog.create') }}">New</a> {% endif %} {% endblock %}  {% block content %} {% for post in posts %} <article>         <header>                 <div>                         <h1>                                 {{ post['title'] }}                         </h1>                         <div class="about">                                 by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}                         </div>                 </div>                 {% if g.user['id'] == post['author_id'] %}                 <a class="action" href="{{ url_for('blog.update', id=post['id']) }}">                         Edit                 </a>                 {% endif %}         </header>         <p class='body'>                 {{ post['body'] }}         </p> </article> {% if not loop.last %} <hr> {% endif %} {% endfor %} {% endblock %}

create

login_required是之前检查g.user的,若为空则跳转登录界面

@bp.route('/create', methods=('GET', 'POST')) @login_required def create():     if request.method == 'POST':         title = request.form['title']         body = request.form['body']         error = None          if not title:             error = 'Titie is required'         else:             db = get_db()             db.execute('INSERT INTO post (title, body, author_id) VALUES (?, ?, ?)', (title, body, g.user['id']))             db.commit()             return redirect(url_for('blog.index'))     return render_template('blog/create.html')
{% extends 'base.html' %}  {% block header %}   <h1>{% block title %}New Post{% endblock %}</h1> {% endblock %}  {% block content %}   <form method="post">     <label for="title">Title</label>     <input name="title" id="title" value="{{ request.form['title'] }}" required>     <label for="body">Body</label>     <textarea name="body" id="body">{{ request.form['body'] }}</textarea>     <input type="submit" value="Save">   </form> {% endblock %}

update

def get_post(id, check_author=True):     post = get_db().execute('SELECT p.id, title, body, create, author_id, username FROM post p JOIN user u ON p.author_id = u.id WHERE p.id = ?', (id, )).fetchone()     if post is None:         abort(404, f'Post id {id} doesn\'t exist.')      if check_author and post['author_id'] != g.user['id']:         abort(403)      return post  @bp.route('/<int:id>/update', methods=('GET', 'POST')) @login_required def update(id):     post = get_post(id)      if request.method == 'POST':         title = request.form['title']         body = request.form['body']         error = None          if not title:             error = 'Title is required.'          if error is not None:             flash(error)         else:             db = get_db()             db.execute(                 'UPDATE post SET title = ?, body = ?'                 ' WHERE id = ?',                 (title, body, id)             )             db.commit()             return redirect(url_for('blog.index'))      return render_template('blog/update.html', post=post)

注意,对于route有参数的url,用url_for时要加参数,比如url_for('blog.update', id=post['id'])

{% extends 'base.html' %}  {% block header %}   <h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1> {% endblock %}  {% block content %}   <form method="post">     <label for="title">Title</label>     <input name="title" id="title"       value="{{ request.form['title'] or post['title'] }}" required>     <label for="body">Body</label>     <textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>     <input type="submit" value="Save">   </form>   <hr>   <form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">     <input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">   </form> {% endblock %}

delete

没有视图,嵌在update里了

@bp.route('/<int:id>/delete', methods=('POST',)) @login_required def delete(id):     get_post(id)     db = get_db()     db.execute('DELETE FROM post WHERE id = ?', (id,))     db.commit()     return redirect(url_for('blog.index'))

安装app

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!