笔记-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

笔记-falsk-入门-1的更多相关文章

  1. python学习笔记--Django入门四 管理站点--二

    接上一节  python学习笔记--Django入门四 管理站点 设置字段可选 编辑Book模块在email字段上加上blank=True,指定email字段为可选,代码如下: class Autho ...

  2. WebSocket学习笔记——无痛入门

    WebSocket学习笔记——无痛入门 标签: websocket 2014-04-09 22:05 4987人阅读 评论(1) 收藏 举报  分类: 物联网学习笔记(37)  版权声明:本文为博主原 ...

  3. Java学习笔记之---入门

    Java学习笔记之---入门 一. 为什么要在众多的编程语言中选择Java? java是一种纯面向对象的编程语言 java学习起来比较简单,适合初学者使用 java可以跨平台,即在Windows操作系 ...

  4. Bootstrap笔记--快速入门

    首先是Bootstrap的简介: 业余了解:下面这个网址可以查询IP地址的地理位置 下面学习:(具体可以参考Bootstrap中文网) 栅格系统 Bootstrap 提供了一套响应式.移动设备优先的流 ...

  5. DBFlow框架的学习笔记之入门

    什么是DBFlow? dbflow是一款android高性的ORM数据库.可以使用在进行项目中有关数据库的操作.github下载源码 1.环境配置 先导入 apt plugin库到你的classpat ...

  6. MongoDB学习笔记:快速入门

    MongoDB学习笔记:快速入门   一.MongoDB 简介 MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统.在高负载的情况下,添加更多的节点,可以保证服务器性能.M ...

  7. 学习笔记_J2EE_SpringMVC_01_入门

    1.    概述 笔者作为一个不太正经的不专业佛教信仰者,习惯了解事物的因果关系,所以概述就有点BBB...了.如果不喜欢这些的,请自行跳过概述章节,直接进入第二章的操作实践:2 入门示例. 1.1. ...

  8. dubbo入门学习笔记之入门demo(基于普通maven项目)

    注:本笔记接dubbo入门学习笔记之环境准备继续记录; (四)开发服务提供者和消费者并让他们在启动时分别向注册中心注册和订阅服务 需求:订单服务中初始化订单功能需要调用用户服务的获取用户信息的接口(订 ...

  9. SpringBoot学习笔记<一>入门与基本配置

    毕业实习项目技术学习笔记 参考文献 学习视频 2小时学会Spring Boot:https://www.imooc.com/learn/767 学习资料 SpringBoot入门:https://bl ...

  10. [学习笔记]SiftGPU入门

    当有读者看到我这篇SiftGPU入门的学习笔记时,相信你已经读过了高博那篇<SLAM拾萃:SiftGPU>,那篇文章写于16年,已经过去两年的时间.在我尝试配置SiftGPU的环境时,遇到 ...

随机推荐

  1. 浅谈python的深浅拷贝

    python中有两种数据类型:一种是可变数据类型,一种是不可变数据类型 不可变数据类型包括(整型及其他数据类型,字符串及元组) 可变数据类型(列表,集合,字典,类和类实例) 鉴定是否为拷贝还是只是引用 ...

  2. AngularJS 指令解析(二)

    AngularJS 指令解析(二) 第一篇我们讲过了作用域(scope)这块内容,现在我们进入正题,讲AngularJS的指令. 什么是指令? 这里我们引用官方的一句话: Custom directi ...

  3. SQL Union和Union All使用方法

    格式: [SQL 语句 1]UNION [SQL 语句 2] 对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序: select ID,NAME from A UNION select I ...

  4. 解决javascript四舍五入不准确

    function roundFixed(num, fixed) { var pos = num.toString().indexOf('.'), decimal_places = num.toStri ...

  5. zabbix 编译安装指导

    zabbix 编译安装 下载 安装 安装后的配置 下载源码包 zabbix官网:https://www.zabbix.com/ zabbix下载:https://www.zabbix.com/down ...

  6. npm升级自身

    参考:https://github.com/felixrieseberg/npm-windows-upgrade Usage First, ensure that you can execute sc ...

  7. Apache Module mod_ssl

    http://httpd.apache.org/docs/current/mod/mod_ssl.html Description: Strong cryptography using the Sec ...

  8. python读入文档中的一行

    从文件log_fusion中读入数据 方法1 f = open("log_fusion.txt") # 返回一个文件对象 line = f.readline() # 调用文件的 r ...

  9. Association, Composition and Aggregation in UI5, CRM, S/4HANA and C4C

    UI5 UI5使用Association和Aggregation描述控件之间的关系. Aggregation:parent和子控件在lifecycle上存在依赖关系: When a ManagedOb ...

  10. Jmeter入门7 测试中使用到的几个定时器和逻辑控制器

    1 测试中提交数据有延时1min,所以查询数据是否提交成功要设置定时器. 固定定时器页面:单位是毫秒 [dinghanhua] 2 集合点.Synchronizing Timer 集合点编辑:集合用户 ...