flask学习小结

背景

通过官方文档学习

官方文档地址 (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

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值