{{ post['title'] }}
{{ post['body'] }}
通过官方文档学习
官方文档地址 (2.0.x版本) Welcome to Flask — Flask Documentation (2.0.x) (palletsprojects.com)

文件命名为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设置

将用户提供的所有值转化为转义字符,防止注入攻击
from markupsafe import escape  @app.route("") def hello_world(name):     return f"hello, {escape(name)}" 使用app的方法.route来进行路由,如@app.route("
一个url处理函数可有多个route装饰,即一个url处理函数,匹配多个url
可以对路由捕获的参数进行类型限定,如@app.route("
resource是文件夹名这种时,可以在url后加个/,当访问没加/,也会重定向到对应url
resource是文件时,url后一般不加/,此时如果访问时加了/,会报错,这防止了对resource重复进行重定向
该函数可用于建立url,用于将所有url汇总并做些自己的处理,但每个url都需要调用一次,函数传入第一个参数是route函数名,返回对应的url,后面可跟任意多关键字参数
可通过该函数,新建某个url,指向已有的url处理函数
注意,route是根据url找处理函数,url_for是根据函数名找url,可以用在的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 "hello world
"  with app.test_request_context():     url_for('static', filename='test.css')
可通过render_template函数渲染,需要提供模板名,模板需要放在项目的templates目录下

{% if name %} hello, {{ name }}
 {% else %} hello, world
 {% endif %}from flask import Flask from flask import url_for, render_template  app = Flask(__name__)  @app.route("/hello/") def hello_world(name):     return render_template("test.html", name=name)     #return "hello world
"  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函数处理

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对象设定响应头

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

密钥方法有很多,一个是import secret as s; s.token_hex()
flask以app方式运作,app是flask.Flask的实例,可以创全局app,也可以在函数创app,通过函数调用返回app。


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

g是全局对象,db操作也是全局的,不是每个request都会创db连接这种
flask会把user数据存在user表,post数据存在post表,先要创建这些表,框架不会自动创建

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

open_resource的文件是基于instance目录下的文件,这么写不需要显式指定文件目录
click.command定义命令行,会调用被装饰的函数
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 appblueprint可以组织view和其他相关代码,view需要注册给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此模板用作其他模板基础模板,其他模板会在此基础扩展
 {% block title %}{% endblock %} - Flaskr  { url_for('static', filename='style.css') }}">                            {% block header %}{% endblock %}                   {% for message in get_flashed_messages() %}         {{ message }}         {% endfor %}         {% block content %}{% endblock %}  全局对象g在模板也同样可以使用
注意,在视图用的flash方法,在视图里可以调用方法get_flashed_messages()方法获取flash信息
{% extends 'base.html' %}  {% block header %} {% block title %}Register{% endblock %}
 {% endblock %}  {% block content %}  {% endblock %}extend扩展了base模板
现在,让我们到注册界面,注册一个用户

如果不填信息会报错

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

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

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

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

类似auth blueprint,再开发一个blog
参考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@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 %} {% block title %}Posts{% endblock %}
 {% if g.user %} { url_for('blog.create') }}">New {% endif %} {% endblock %}  {% block content %} {% for post in posts %}                                                                                     {{ post['title'] }}                         
                                                          by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}                                                           {% if g.user['id'] == post['author_id'] %}                 { url_for('blog.update', id=post['id']) }}">                         Edit                                  {% endif %}                                    {{ post['body'] }}         
   {% if not loop.last %} 
 {% endif %} {% endfor %} {% endblock %}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 %}   {% block title %}New Post{% endblock %}
 {% endblock %}  {% block content %}    {% endblock %}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('//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 %}   {% block title %}Edit "{{ post['title'] }}"{% endblock %}
 {% endblock %}  {% block content %}      
    {% endblock %}没有视图,嵌在update里了
@bp.route('//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'))