笔记-falsk-入门-1

笔记-falsk-入门-1

1.      前言

有几个概念需要解释下,WSGI,JINJA2,WERKZEUG

Flask是典型的微框架,作为Web框架来说,它仅保留了核心功能:请求响应处理和模板渲染。

这两类功能分别由Werkzeug(WSGI工具库)完成和Jinja(模板渲染库)完成,因为Flask包装了这两个依赖,暂时不用深入了解它们。

2.      开始-最简单的网站

from flask import Flask,session, g, render_template
app = Flask(__name__)

# 最简路由
@app.route('/')
def hello():
    return '<h1>hello tol </h1><div>first div</div>'

很简单,声明一个对象,绑定一个响应函数。

@app.route()起了路由的功能

路由上可以玩一些花样,比如为多个地址绑定一个处理函数

另外,注意http://localhost:5000/会返回hello而非hello1,很好理解,找到即返回。

同时绑定多个响应函数并不会报错。。。。

# 多个路由
@app.route('/')
@app.route('/index')
@app.route('/home')
def hello1():
    return 'Welcome to My Watchlist!'

有时需要传递一些参数,访问url为http://localhost:5000/user/user_test

# 传递参数
@app.route('/user/<name>')
def user_page(name):
    return 'User: %s' % name

结果是这样的:

3.      模板

上面展示了最简单的web交互,但网页可能会非常复杂,不可能每次返回一段简单的字符串,需要把网页定义独立出来并抽象化,结果就是模板。

模板是一段包含变量、运算逻辑、格式信息的文本。

执行变量替换,逻辑运算及格式化的过程叫做渲染,jinja2就是flask的渲染引擎。

默认flask会从同级目录下的templates目录中寻找模板,所以需要创建templates目录,并在其中添加模板。

# templates/index.html:主页模板

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="utf-8">

    <title>{{ name }}'s Watchlist</title>

</head>

<body>

    <h2>{{ name }}'s Watchlist</h2>

    {# 使用 length 过滤器获取 movies 变量的长度 #}

    <p>{{ movies|length }} Titles</p>

    <ul>

        {% for movie in movies %}  {# 迭代 movies 变量 #}

        <li>{{ movie.title }} - {{ movie.year }}</li>  {# 等同于 movie['title'] #}

        {% endfor %}  {# 使用 endfor 标签结束 for 语句 #}

    </ul>

    <footer>

        <small>&copy; 2018 <a href="http://helloflask.com/tutorial">HelloFlask</a></small>

       </footer>

</body>

</html>

另外,还需要提供变量数据

app.py:定义虚拟数据

name = 'Grey Li'

movies = [

    {'title': 'My Neighbor Totoro', 'year': '1988'},

    {'title': 'Dead Poets Society', 'year': '1989'},

    {'title': 'A Perfect World', 'year': '1993'},

]

添加路由:

@app.route('/index_t')
def index():
    return render_template('index.html', name=name, movies=movies)

结果展示:

发生了什么?

第一步,定义了变量name,movies

第二步,使用render_template() 函数渲染模板并返回结果

第三步,结果由server发送给用户

4.      静态文件

静态文件(static files)和我们的模板概念相反,指的是内容不需要动态生成的文件。比如图片、CSS文件和JavaScript脚本等。

flask默认会到static目录下寻找静态文件,创建static目录。

接下来会为网页添加一个图片,位于标题上方,比较小;

百度下载一张图片到static目录下,timg.jpg

在html文件中添加引用

<h2>

    <img alt="Avatar" src="{{ url_for('static', filename='images/avatar.png') }}">

    {{ name }}'s Watchlist

</h2>

呃,图片会按原尺寸显示,非常大,需要调整一下显示格式。

在static中添加一个style.css文件,部分内容如下:

body {
    margin:auto;
    max-width:580px;
    font-size:14px;
    font-family: Helvetica, Arial, sans-serif;
}

/* 头像 */
.avatar {
    width: 40px;
}

在html文件中引用并将格式应用到图片元素上

# 引用css

<head>
    <meta charset="utf-8">
    <title>{{ name }}'s Watchlist</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
</head>

格式应用

<img alt="Avatar" class="avatar" src="{{ url_for('static', filename='timg.jpg') }}">

刷新页面,结果如下:

5.      数据库

更复杂的数据管理需要数据库,这里使用的是python自带的数据库SQLite

pip install flask-sqlalchemy

sqlite没怎么用过,也不想关心底层的操作,为了简化数据库操作,将使用SQLAlchemy,Python数据库工具(ORM,即对象关系映射)。借助SQLAlchemy,可以通过定义Python类来表示数据库里的一张表(类属性表示表中的字段/列),通过对这个类进行各种操作来代替写SQL语句。这个类我们称之为模型类,类中的属性我们将称之为字段。

Flask有大量的第三方扩展,这些扩展可以简化和第三方库的集成工作。我们下面将使用一个叫做Flask-SQLAlchemy的官方扩展来集成SQLAlchemy。

首先使用Pipenv安装它:

$ pipenv install flask-sqlalchemy

5.1.    初始化声明代码:

import os

import sys

from flask import Flask

WIN = sys.platform.startswith('win')

if WIN:  # 如果是 Windows 系统,使用三个斜线

    prefix = 'sqlite:///'

else# 否则使用四个斜线

    prefix = 'sqlite:////'

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, 'data.db')

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 关闭对模型修改的监控

5.2.    创建数据库模型:

class User(db.Model):  # 表名将会是 user(自动生成,小写处理)

    id = db.Column(db.Integer, primary_key=True)  # 主键

    name = db.Column(db.String(20))  # 名字

class Movie(db.Model):  # 表名将会是 movie

    id = db.Column(db.Integer, primary_key=True)  # 主键

    title = db.Column(db.String(60))  # 电影标题

    year = db.Column(db.String(4))  # 电影年份

模型类的编写有一些语法需要注意:

模型类要声明继承db.Model。

每一个类属性(字段)要实例化db.Column,传入的参数为字段的类型,下面的表格列出了常用的字段类。

在db.Column()中添加额外的选项(参数)可以对字段进行设置。比如,primary_key设置当前字段是否为主键。除此之外,常用的选项还有nullable(布尔值,是否允许为空值)、index(布尔值,是否设置索引)、unique(布尔值,是否允许重复值)、default(设置默认值)等。

常用的字段类型如下表所示:

db.Integer整型

db.String(size)字符串,size为最大长度,比如db.String(20)

db.Text长文本

db.DateTime时间日期,Pythondatetime对象

db.Float浮点数

db.Boolean布尔值

5.3.    创建数据库表

只需要在第一次运行时创建数据库,有两种选择,一是加上逻辑判断,如果数据库表已存在则不创建;二是单独使用语句创建数据库表;

这里使用的是第二种方法。

新建db_create_test.py文件

from flask_website.app import db
from flask_website.app import User, Movie

# db create
RUN = 0
if RUN:
    from flask_website.app import db,app
    #print(app.root_path)
   
db.create_all()

执行完成后会在项目目录下发现data.db文件。

如果你改动了模型类,想重新生成表模式,那么需要先使用db.drop_all()删除表,然后重新创建:

>>>db.drop_all()

>>>db.create_all()

创建完成后,需要对此进行验证并为数据库添加虚拟数据。

代码如下:


# db test

# RUN变量控制代码块是否执行
# 添加记录测试
RUN = 0
if RUN:
    from flask_website.app import User, Movie  # 导入user = User(name='Grey Li')
    # 创建一个 User 记录
   
user = User(name='Grey Li'# 创建一个 User 记录
   
m1 = Movie(title='Leon', year='1994'# 创建一个 Movie 记录
   
m2 = Movie(title='Mahjong', year='1996'# 再创建一个 Movie 记录
   
db.session.add(user)  # 把新创建的记录添加到数据库会话
   
db.session.add(m1)
    db.session.add(m2)
    db.session.commit()


# 添加虚拟数据
RUN = 0
if RUN:
    movies = [
        {'title': 'My Neighbor Totoro', 'year': '1988'},
        {'title': 'Dead Poets Society', 'year': '1989'},
        {'title': 'A Perfect World', 'year': '1993'},
        {'title': 'Leon', 'year': '1994'},
        {'title': 'Mahjong', 'year': '1996'},
        {'title': 'Swallowtail Butterfly', 'year': '1996'},
        {'title': 'King of Comedy', 'year': '1999'},
        {'title': 'Devils on the Doorstep', 'year': '1999'},
        {'title': 'WALL-E', 'year': '2008'},
        {'title': 'The Pork of Music', 'year': '2012'},
    ]

    for m in movies:
        movie = Movie(title=m['title'], year=m['year'])
        db.session.add(movie)

    db.session.commit()

# 输出数据
RUN = 0
if RUN:
    from flask_website.app import Movie
    movie = Movie.query.all()
    for _ in movie:
       print(_.title)

5.4.    在页面视图中展示数据库记录

设置了数据库后,页面视图 可以从数据库里读取真实的数据:

@app.route('/db_test')
def db_test():
    user = User.query.first()
    movies = Movie.query.all()
    return render_template('index.html', user=user, movies=movies)

6.      模板

前面展示的简单的模板使用,按照开发惯例模板应该被视为一个对象,它应该也必需还有以下功能:

  1. 异常处理:例如访问不存在的页面时返回404;
  2. 模板抽象及继承:谁也不想每个模板都复制一遍代码,改通用代码时会疯掉的;
  3. 上下文处理:模板运行环境,模板渲染;

下面是一些案例:

异常处理:404页面

@app.errorhandler(404)  # 传入要处理的错误代码

def page_not_found(e):  # 接受异常对象作为参数

    user = User.query.first()

    return render_template('404.html', user=user), 404  # 返回模板和状态码

上下文处理:

注意,添加上下文处理之后的代码需要做一些改动。

@app.context_processor
def inject_user():
    user = User.query.first()
    return dict(user=user)


# error functions
@app.errorhandler(404)
def page_not_found(e):
    #user = User.query.first()
    #return render_template('404.html', user=user)
   
return render_template('404.html')

6.1.    模板继承

对于模板内容重复的问题,Jinja2提供了模板继承的支持。这个机制和Python类继承非常类似:我们可以定义一个父模板,一般会称之为基模板(basetemplate)。基模板中包含完整的HTML结构和导航栏、页首、页脚都通用部分。在子模板里,我们可以使用extends标签来声明继承自某个基模板。

基模板中需要在实际的子模板中追加或重写的部分则可以定义成块(block)。块使用block标签创建,{%block块名称%}作为开始标记,{%endblock%}或{%endblock块名称%}作为结束标记。通过在子模板里定义一个同样名称的块,你可以向基模板的对应块位置追加或重写内容。

基础模板:

base.html基模板

<!DOCTYPE html>

<html >

<head>

    <meta charset="UTF-8">

     {% if title %}

        <title>{{ title }} - 博客</title>

        {% else %}

        <title>欢迎来到博客!</title>

        {% endif %}

    </head>

    <body>

       <div>博客 :

           <a href={{url_for('index')}}>首页</a>

           <a href={{url_for('login')}}>登录</a>

       </div>

       <hr>

       {% with messages = get_flashed_messages() %}

            {% if messages %}

            <ur>

                {% for message in messages %}

                    <li>{{ message }}</li>

                {% endfor %}}

            </ur>

       {% endif %}

       {% endwith %}

        {% block content %}

        {% endblock %}

    </body>

</html>

子模板:

index.html:继承基模板的主页模板

{% extends 'base.html' %}

{% block content %}

<p>{{ movies|length }} Titles</p>

<ul class="movie-list">

    {% for movie in movies %}

    <li>{{ movie.title }} - {{ movie.year }}

        <span class="float-right">

            <a class="imdb" href="https://www.imdb.com/find?q={{ movie.title }}" target="_blank" title="Find this movie on IMDb">IMDb</a>

        </span>

    </li>

    {% endfor %}

</ul>

<img alt="Walking Totoro" class="totoro" src="{{ url_for('static', filename='images/totoro.gif') }}" title="to~to~ro~">

{% endblock %}

7.      表单

在 HTML 页面里,我们需要编写表单来获取用户输入。一个典型的表单如下所示:

<form method="post">  <!-- 指定提交方法为 POST -->

    <label for="name">名字</label>

    <input type="text" name="name" id="name"><br>  <!-- 文本输入框 -->

    <label for="occupation">职业</label>

    <input type="text" name="occupation" id="occupation"><br>  <!-- 文本输入框 -->

    <input type="submit" name="submit" value="登录">  <!-- 提交按钮 -->

</form>

添加表单元素:templates/index.html

<p>{{ movies|length }} Titles</p>

<form method="post">

    Name <input type="text" name="title" autocomplete="off" required>

    Year <input type="text" name="year" autocomplete="off" required>

    <input class="btn" type="submit" name="submit" value="Add">

</form>

CSS格式:

/* 覆盖某些浏览器对 input 元素定义的字体 */

input[type=submit] {

    font-family: inherit;

}

input[type=text] {

    border: 1px solid #ddd;

}

input[name=year] {

    width: 50px;

}

.btn {

    font-size: 12px;

    padding: 3px 5px;

    text-decoration: none;

    cursor: pointer;

    background-color: white;

    color: black;

    border: 1px solid #555555;

    border-radius: 5px;

}

.btn:hover {

    text-decoration: none;

    background-color: black;

    color: white;

    border: 1px solid black;

}

7.1.    处理表单数据

默认情况下,当表单中的提交按钮被按下,浏览器会创建一个新的请求,默认发往当前URL(在<form>元素使用action属性可以自定义目标URL)。

在模板里为表单定义了POST方法,输入数据,按下提交钮,一个POST请求会被构造并发送。接着,会看到一个405MethodNotAllowed错误提示。这是因为处理请求的index视图默认只接受GET请求。

为了处理POST请求,需要修改一下视图函数:

@app.route('/', methods=['GET', 'POST'])

在app.route()装饰器里,我们可以用methods关键字传递一个包含HTTP方法字符串的列表,表示这个视图函数处理哪种方法类型的请求。默认只接受GET请求,上面的写法表示同时接受GET和POST请求。

# 注意:只有第一个路由支持POST请求

@app.route('/', methods=['GET', 'POST'])
@app.route('/index')
@app.route('/home')

两种方法的请求有不同的处理逻辑:对于GET请求,返回渲染后的页面;对于POST请求,则获取提交的表单数据并保存。为了加以区分,用if判断:

@app.route('/', methods=['GET', 'POST'])
@app.route('/index')
def index():
    if request.method == 'POST'# 判断是否是 POST 请求
        # 获取表单数据
       
title = request.form.get('title'# 传入表单对应输入字段的 name 值
       
year = request.form.get('year')
        # 验证数据
       
if not title or not year or len(year) > 4 or len(title) > 60:
            flash('Invalid input.'# 显示错误提示
           
return redirect(url_for('index'))  # 重定向回主页
        # 保存表单数据到数据库
       
movie = Movie(title=title, year=year)  # 创建记录
       
db.session.add(movie)  # 添加到数据库会话
       
db.session.commit()  # 提交数据库会话
       
flash('Item Created.'# 显示成功创建的提示
       
return redirect(url_for('index'))  # 重定向回主页

    
user = User.query.first()
    movies = Movie.query.all()
    return render_template('index.html', user=user, movies=movies)

有几个点:

request对象:

flask会在有请求时把请求封装成request对象;

它包含请求相关的所有信息,比如请求的路径(request.path)、请求的方法(request.method)、表单数据(request.form)、查询字符串(request.args)等等。

 request.form 获取表单数据。request.form 是一个字典,用表单字段的 name 属性值可以获取用户的输入数据:

if request.method == 'POST':

    title = request.form.get('title')

    year = request.form.get('year')

flash

在用户执行某些动作后,我们通常在页面上显示一个提示消息。最简单的实现就是在视图函数里定义一个包含消息内容的变量,传入模板,然后在模板里渲染显示它。因为这个需求很常用,Flask 内置了相关的函数。其中 flash() 函数用来在视图函数里向模板传递提示消息,get_flashed_messages() 函数则用来在模板中获取提示消息。

from flask import flash

然后在视图函数里调用,传入要显示的消息内容:

flash('Item Created.')

flash() 函数在内部会把消息存储到 Flask 提供的 session 对象里。session 用来在请求间存储数据,它会把数据签名后存储到浏览器的 Cookie 中,所以我们需要设置签名所需的密钥:

app.config['SECRET_KEY'] = '123456'  # 等同于 app.secret_key = 'dev'

提示 这个密钥的值在开发时可以随便设置。基于安全的考虑,在部署时应该设置为随机字符,且不应该明文写在代码里, 在部署章节会详细介绍。

下面在基模板(base.html)里使用 get_flashed_messages() 函数获取提示消息并显示:

<!-- 插入到页面标题上方 -->

{% for message in get_flashed_messages() %}

       <div class="alert">{{ message }}</div>

{% endfor %}

<h2>...</h2>

CSS类:

.alert {

    position: relative;

    padding: 7px;

    margin: 7px 0;

    border: 1px solid transparent;

    color: #004085;

    background-color: #cce5ff;

    border-color: #b8daff;

    border-radius: 5px;

}

重定向

重定向响应是一类特殊的响应,它会返回一个新的 URL,浏览器在接受到这样的响应后会向这个新 URL 再次发起一个新的请求。Flask 提供了 redirect() 函数来快捷生成这种响应,传入重定向的目标 URL 作为参数,比如 redirect('http://helloflask.com')。

上面的代码展示的是创建新数据的过程,增删改查中的增和查已经了解了,下面试一下改和删。

7.2.    改

编辑的实现和创建类似,创建一个用于显示编辑页面和处理编辑表单提交请求的视图函数。

@app.route('/movie/edit/<int:movie_id>', methods=['GET', 'POST'])

def edit(movie_id):

    movie = Movie.query.get_or_404(movie_id)

    if request.method == 'POST':  # 处理编辑表单的提交请求

        title = request.form['title']

        year = request.form['year']

       

        if not title or not year or len(year) > 4 or len(title) > 60:

            flash('Invalid input.')

            return redirect(url_for('edit', movie_id=movie_id))  # 重定向回对应的编辑页面

       

        movie.title = title  # 更新标题

        movie.year = year  # 更新年份

        db.session.commit()  # 提交数据库会话

        flash('Item Updated.')

        return redirect(url_for('index'))  # 重定向回主页

   

    return render_template('edit.html', movie=movie)  # 传入被编辑的电影记录

templates/edit.html:编辑页面模板

{% extends 'base.html' %}

{% block content %}

<h3>Edit item</h3>

<form method="post">

    Name <input type="text" name="title" autocomplete="off" required value="{{ movie.title }}">

    Year <input type="text" name="year" autocomplete="off" required value="{{ movie.year }}">

    <input class="btn" type="submit" name="submit" value="Update">

</form>

{% endblock %}

不可能每次手动输入地址,需要为修改页面提供一个入口链接,把它放在index页面上。

index.html:编辑电影条目的入口链接

<span class="float-right">

    <a class="btn" href="{{ url_for('edit', movie_id=movie.id) }}">Edit</a>

    ...

</span>

现在可以编辑并保存了。

7.3.    删除

删除比更改简单一些。

app.py:删除电影条目

@app.route('/movie/delete/<int:movie_id>', methods=['POST'])  # 限定只接受 POST 请求

def delete(movie_id):

    movie = Movie.query.get_or_404(movie_id)  # 获取电影记录

    db.session.delete(movie)  # 删除对应的记录

    db.session.commit()  # 提交数据库会话

    flash('Item Deleted.')

    return redirect(url_for('index'))  # 重定向回主页

为了安全的考虑,我们一般会使用 POST 请求来提交删除请求,也就是使用表单来实现(而不是创建删除链接):

同样的,添加入口链接并指定显示样式。

index.html:删除电影条目表单

<span class="float-right">

    ...

    <form class="inline-form" method="post" action="{{ url_for('delete', movie_id=movie.id) }}">

        <input class="btn" type="submit" name="delete" value="Delete" onclick="return confirm('Are you sure?')">

    </form>

    ...

</span>

8.      用户认证

通常会把用户分成两类,管理员,通过用户名和密码登入程序,可以执行数据相关的操作;另一个是访客,只能浏览页面。

用户认证离不开一个话题-安全

用户口令不能明文存储,需要hash+加盐

Flask的依赖包Werkzeug内置了用于生成和验证密码散列值的函数,werkzeug.security.generate_password_hash()生成密码散列值,而 werkzeug.security.check_password_hash()则用来检查给定的散列值和密码是否对应。

8.1.    声明数据对象

在存储用户信息的 User 模型类添加 username 字段和 password_hash 字段,分别用来存储登录所需的用户名和密码散列值,同时添加两个方法来实现设置密码和验证密码的功能:

from werkzeug.security import generate_password_hash, check_password_hash

class User(db.Model):

    id = db.Column(db.Integer, primary_key=True)

    name = db.Column(db.String(20))

    username = db.Column(db.String(20))  # 用户名

    password_hash = db.Column(db.String(128))  # 密码散列值

    def set_password(self, password): 

        self.password_hash = generate_password_hash(password) 

    def validate_password(self, password):  # 用于验证密码的方法,接受密码作为参数

        return check_password_hash(self.password_hash, password)  # 返回布尔值

声明完成后需要更新数据库表结构

注意:因为数据库的表结构发生的变化,需要重新建立表,简单点,删库了。

重建数据库

python db_create_test.py

8.2.    生成管理员账户

因为程序只允许一个人使用,没有必要编写一个注册页面。我们可以编写一个命令来创建管理员账户,下面是实现这个功能的 admin() 函数:

$ flask admin

Username: greyli

Password: 123  # hide_input=True 会让密码输入隐藏

Repeat for confirmation: 123  # confirmation_prompt=True 会要求二次确认输入

Updating user...

Done.

8.3.    验证

扩展 Flask-Login 提供了实现用户认证需要的各类功能函数,我们将使用它来实现程序的用户认证,首先使用 Pipenv 安装它:

pip install flask-login

app.py:初始化 Flask-Login

from flask_login import LoginManager

login_manager = LoginManager(app)  # 实例化扩展类

@login_manager.user_loader

def load_user(user_id):  # 创建用户加载回调函数,接受用户 ID 作为参数

    user = User.query.get(int(user_id))  # ID 作为 User 模型的主键查询对应的用户

    return user  # 返回用户对象

Flask-Login 提供了一个 current_user 变量,注册这个函数的目的是,当程序运行后,如果用户已登录, current_user 变量的值会是当前用户的用户模型类记录。

另一个步骤是让存储用户的 User 模型类继承 Flask-Login 提供的 UserMixin 类:

from flask_login import UserMixin

class User(db.Model, UserMixin):

继承这个类会让 User 类拥有几个用于判断认证状态的属性和方法,其中最常用的是 is_authenticated 属性:如果当前用户已经登录,那么 current_user.is_authenticated 会返回 True, 否则返回 False。有了 current_user 变量和这几个验证方法和属性,我们可以很轻松的判断当前用户的认证状态。

8.4.    login

终于到了可以登录的时候了。

登录用户使用 Flask-Login 提供的 login_user() 函数实现,需要传入用户模型类对象作为参数。

老套路,视图函数+模板[+CSS]:

from flask_login import login_user

# ...

@app.route('/login', methods=['GET', 'POST'])

def login():

    if request.method == 'POST':

        username = request.form['username']

        password = request.form['password']

        if not username or not password:

            flash('Invalid input.')

            return redirect(url_for('login'))

       

        user = User.query.first()

        # 验证用户名和密码是否一致

        if username == user.username and user.validate_password(password):

            login_user(user)  # 登入用户

            flash('Login success.')

            return redirect(url_for('index'))  # 重定向到主页

        flash('Invalid username or password.')  # 如果验证失败,显示错误消息

        return redirect(url_for('login'))  # 重定向回登录页面

   

    return render_template('login.html')

页面:

templates/login.html:登录页面

{% extends 'base.html' %}

{% block content %}

<h3>Login</h3>

<form method="post">

    Username<br>

    <input type="text" name="username" required><br><br>

    Password<br>

    <!-- 密码输入框的 type 属性使用 password,会将输入值显示为圆点 -->

    <input type="password" name="password" required><br><br>

    <input class="btn" type="submit" name="submit" value="Submit">

</form>

{% endblock %}

8.5.    登出

和登录相对,登出操作则需要调用 logout_user() 函数,使用下面的视图函数实现:

from flask_login import login_required, logout_user

@app.route('/logout')

@login_required  # 用于视图保护,后面会详细介绍

def logout():

    logout_user()  # 登出用户

    flash('Goodbye.')

    return redirect(url_for('index'))  # 重定向回首页

8.6.    认证保护

登录是安全措施的开始,下一步是页面级的访问授权验证。

视图保护

在视图保护层面来说,未登录用户不能执行下面的操作:

访问编辑页面

访问设置页面

执行注销操作

执行删除操作

执行添加新条目操作

对于不允许未登录用户访问的视图,只需要为视图函数附加一个 login_required 装饰器就可以将未登录用户拒之门外。以删除条目视图为例:

@app.route('/movie/delete/<int:movie_id>', methods=['POST'])

@login_required  # 登录保护

def delete(movie_id):

    movie = Movie.query.get_or_404(movie_id)

    db.session.delete(movie)

    db.session.commit()

    flash('Item deleted.')

    return redirect(url_for('index'))

添加了这个装饰器后,如果未登录的用户访问对应的 URL,Flask-Login 会把用户重定向到登录页面,并显示一个错误提示。为了让这个重定向操作正确执行,我们还需要把 login_manager.login_view 的值设为我们程序的登录视图端点(函数名):

login_manager.login_view = 'login'

创建新条目的操作有些不同,因为没法从视图函数的层面进行操作过滤,只能在函数内部的POST请求处理代码前进行过滤。

from flask_login import login_required, current_user

# ...

@app.route('/', methods=['GET', 'POST'])

def index():

    if request.method == 'POST':

        if not current_user.is_authenticated:  # 如果当前用户未认证

            return redirect(url_for('index'))  # 重定向到主页

模板内容保护

认证保护的另一形式是页面模板内容的保护。比如,不能对未登录用户显示下列内容:

创建新条目表单

编辑按钮

删除按钮

这几个元素的定义都在首页模板(index.html)中,以创建新条目表单为例,我们在表单外部添加一个 if 判断:

<!-- 在模板中可以直接使用 current_user 变量 -->

{% if current_user.is_authenticated %}

<form method="post">

    Name <input type="text" name="title" autocomplete="off" required>

    Year <input type="text" name="year" autocomplete="off" required>

    <input class="btn" type="submit" name="submit" value="Add">

</form>

{% endif %}

有些地方则需要根据登录状态分别显示不同的内容,比如基模板(base.html)中的导航栏。如果用户已经登录,就显示设置和登出链接,否则显示登录链接:

{% if current_user.is_authenticated %}

       <li><a href="{{ url_for('settings') }}">Settings</a></li>

       <li><a href="{{ url_for('logout') }}">Logout</a></li>

{% else %}

       <li><a href="{{ url_for('login') }}">Login</a></li>

{% endif %}

9.      总结

对如何使用flask编写web应用有了一个基本了解。

  1. 核心是通过server返回一个html文本;
  2. 每个html文本的初始通常是一个模板文件,模板可以继承和复用;
  3. 对模板进行渲染后得到响应的返回内容;
  4. flask只提供了基本的请求和渲染功能,很多附加工具需要其它库的支持——应该少有人会自己写一套工具。

10.    附

code url: https://github.com/usnnu/web_python/tree/master/website_test1

参考文档:https://zhuanlan.zhihu.com/p/51530577

猜你喜欢

转载自www.cnblogs.com/wodeboke-y/p/10241642.html
今日推荐