文章目录
一、标准类视图及使用
在前面,我们定义视图都是通过route装饰器装饰函数来定义的,一般称之为视图函数。除了这种方式,还可以基于类实现。
类视图支持继承,但是类视图不能跟函数视图一样通过装饰器添加路由,需要通过app.add_url_rule(url_rule,view_func)
来注册。
1.添加url映射规则的其他方法尝试
在之前的代码中,都是通过@app.route装饰器来实现url与视图函数的映射的,除了这种方法外,我们也可以通过Flask对象的add_url_rule()
方法来定义映射规则。
测试(新建一个Python文件class_view.py)如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
def profile():
return '个人中心'
# 添加url规则
app.add_url_rule('/profile/', view_func=profile)
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,add_url_rule()
可以定义路由规则。
add_url_rule()
方法有一个参数endpoint,表示结束点,指定后url_for()方法中传入的就不再是视图函数名了,而是指定的endpoint,相当于给url取了一个名字。
通过请求上下文函数可以输出url_for的结果,测试如下:
from flask import Flask,url_for
app = Flask(__name__)
@app.route('/')
def index():
print(url_for('personal'))
return '首页'
def profile():
return '个人中心'
app.add_url_rule('/profile/', endpoint='personal', view_func=profile)
if __name__ == '__main__':
app.run(debug=True)
此时访问http://127.0.0.1:5000/在控制台会打印/profile/
。
此时不能再在url_for()
方法中传入’profile’参数,否则会报错。
2.标准类视图
标准类视图继承自flask.views.View
,并且类视图中必须重写dispatch_request()
方法,这个方法类似于视图函数,会返回一个基于Response或者其子类的对象。类视图定义后,需要通过add_url_rule()
方法和url进行映射,还需要在as_view()
方法中指定该url的名称,方便url_for()
函数调用。
定义类视图如下:
from flask import Flask, url_for, views
app = Flask(__name__)
@app.route('/')
def index():
print(url_for('personal'))
return '首页'
def profile():
return '个人中心'
class ListView(views.View):
def dispatch_request(self):
return 'class view'
def demo(self):
return '测试'
app.add_url_rule('/profile/', endpoint='personal', view_func=profile)
app.add_url_rule('/list/', endpoint='list', view_func=ListView.as_view('list'))
if __name__ == '__main__':
app.run(debug=True)
访问http://127.0.0.1:5000/list/,显示:
此时,如果要用url_for()
方法来获取URL,需要注意:
如果add_url_rule()
中指定了endpoint参数,url_for()
中传的参数就应该是endpoint参数的值;
如果没有指定,就是as_view()
方法的值。
在定义视图类时,必须要重写dispatch_request()
方法,否则会抛出异常,如下:
可以查看源码views.py:
def dispatch_request(self):
"""Subclasses have to override this method to implement the
actual view function code. This method is called with all
the arguments from the URL rule.
"""
raise NotImplementedError()
显然,如果不重写dispatch_request()
方法,会直接抛出NotImplementedError错误。
再尝试用类视图传递Json数据:
from flask import Flask, url_for, views, jsonify
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
def profile():
return '个人中心'
class JsontView(views.View):
def get_response(self):
raise NotImplementedError()
def dispatch_request(self):
response = self.get_response()
return jsonify(response)
class ListJsonView(JsontView):
def get_response(self):
return {'username': 'Corley'}
app.add_url_rule('/listjson/', view_func=ListJsonView.as_view('listjson'))
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,此时返回页面的是json数据。
返回公共变量测试:
from flask import Flask, url_for, views, jsonify, render_template
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
class LoginView(views.View):
def dispatch_request(self):
self.context = {
'name': 'Corley'
}
return render_template('login.html', **self.context)
class RegisterView(views.View):
def dispatch_request(self):
self.context = {
'name': 'Corley'
}
return render_template('register.html', **self.context)
app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))
if __name__ == '__main__':
app.run(debug=True)
在templates目录下创建login.html和register.html,login.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>这是登录页面</h1>
<h2>{{ name }}</h2>
</body>
</html>
register.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>
<h1>这是注册页面</h1>
<h2>{{ name }}</h2>
</body>
</html>
显示:
显然,可以正常访问登录和注册页面,但是两个视图类中含有相同的变量,可以进行优化:
from flask import Flask, url_for, views, jsonify, render_template
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
class BaseView(views.View):
"""
保存公共变量
"""
def __init__(self):
super().__init__()
self.context = {
'name': 'Corley'
}
class LoginView(BaseView):
def dispatch_request(self):
return render_template('login.html', **self.context)
class RegisterView(BaseView):
def dispatch_request(self):
return render_template('register.html', **self.context)
app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))
if __name__ == '__main__':
app.run(debug=True)
将公有变量存储到BaseView,其他类视图再继承自BaseView。
二、基于调度方法的视图
Flask中除了标准类视图,还有一种类视图flask.views.MethodView,对每个HTTP方法执行不同的函数,映射到对应方法的同名方法上。
测试如下:
from flask import Flask, url_for, views, jsonify, render_template, request
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
class BaseView(views.View):
"""
保存公共变量
"""
def __init__(self):
super().__init__()
self.context = {
'name': 'Corley'
}
class LoginView(views.MethodView):
# 当客户端通过get方法访问的时候执行
def get(self):
return render_template('login.html')
# 当客户端通过post方法访问的时候执行
def post(self):
name = request.form.get('name')
password = request.form.get('password')
if name == 'Corley' and password == "123":
return '登录成功!!!'
else:
error = '账号或密码错误⚠'
return render_template('login.html', error=error)
class RegisterView(BaseView):
def dispatch_request(self):
return render_template('register.html')
app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,通过get和post请求都能实现登录的效果,代码还可以进行一定的优化:
from flask import Flask, url_for, views, jsonify, render_template, request
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
class BaseView(views.View):
"""
保存公共变量
"""
def __init__(self):
super().__init__()
self.context = {
'name': 'Corley'
}
class LoginView(views.MethodView):
def get(self, error=None):
return render_template('login.html', error=error)
def post(self):
name = request.form.get('name')
password = request.form.get('password')
if name == 'Corley' and password == "123":
return '登录成功!!!'
else:
error = '账号或密码错误⚠'
return self.get(error)
class RegisterView(BaseView):
def dispatch_request(self):
return render_template('register.html')
app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))
if __name__ == '__main__':
app.run(debug=True)
运行效果与前者相同。
类视图的一个缺陷就是比较难用装饰器来装饰,比如需要做权限验证的时候。此时可以在类视图中定义一个默认属性decorators,用于存储装饰器。以后每次调用这个类视图的时候,就会执行这个装饰器。
在类外添加装饰器,限制只有登录之后才能访问个人中心,如下:
from flask import Flask, url_for, views, jsonify, render_template, request
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
def login_required(func):
'''装饰器,登录之后才能访问个人中心'''
def wrapper(*args, **kwargs):
username = request.args.get('username')
if username:
return func(*args, **kwargs)
else:
return '请先登录再访问'
return wrapper
@app.route('/profile/')
@login_required
def profile():
return '个人中心'
if __name__ == '__main__':
app.run(debug=True)
显示:
很明显,只有登录之后才可以正常访问到个人中心。
通过在decorators列表中添加装饰器实现在类中使用装饰器,测试如下:
from flask import Flask, url_for, views, jsonify, render_template, request
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
def login_required(func):
'''装饰器,登录之后才能访问个人中心'''
def wrapper(*args, **kwargs):
username = request.args.get('username')
if username:
return func(*args, **kwargs)
else:
return '请先登录再访问'
return wrapper
class ProfileView(views.View):
# 在类中用装饰器
decorators = [login_required]
def dispatch_request(self):
return '个人中心页面'
app.add_url_rule('/profile/', view_func=ProfileView.as_view('profile'))
if __name__ == '__main__':
app.run(debug=True)
三、Flask蓝图的基本使用
之前,我们的代码都是放在一个Python文件里的,不同的功能模块都在一个文件中实现,代码量较少时尚可,随着业务需求的复杂化和代码的增多,项目逐渐变大,会显得很臃肿,也没有条理层次感,这显然不是一个合理的结构。此时就需要定义多个文件(夹),不同的文件(夹)放不同的功能模块,此时蓝图可以优雅地实现这种需求。
定义了蓝图文件之后,需要在主程序中通过app.register_blueprint()
方法将这个蓝图注册进url映射中。
简单使用示例如下:
先创建项目根目录,如blue_demo,在下面创建Python文件blue_flask.py如下
from flask import Flask
from blueprints.news import news_bp
from blueprints.book import book_bp
app = Flask(__name__)
app.register_blueprint(news_bp)
app.register_blueprint(book_bp)
@app.route('/')
def index():
return '这是首页'
if __name__ == '__main__':
app.run(debug=True)
再创建blueprints目录,下边创建news.py和book.py,news.py如下:
from flask import Blueprint
news_bp = Blueprint('news', __name__) # 第一个参数是蓝图名称,一般是当前的Python文件名,第二个参数是蓝图所在的包或模块,一般用__name__变量
@news_bp.route('/news/')
def news():
return '新闻页面'
book.py如下:
from flask import Blueprint
book_bp = Blueprint('book', __name__) # 第一个参数是蓝图名称,一般是当前的Python文件名,第二个参数是蓝图所在的包或模块,一般用__name__变量
@book_bp.route('/book/')
def book():
return '图书列表'
@app.route('/book/detail/<bid>')
def book_detail(bid):
return '当前图书ID:%s' % bid
运行blue_flask.py之后,显示:
显然,基本功能已经实现。
心啊在访问/news/
、/book/
,都是在执行news.py、book.py中的视图函数,从而实现了项目的模块化。
如果在blue_flask.py中不能导入blueprints中的文件或者出现警告信息,可以将当前目录设为根目录,如下:
此时再导入就会出现提示,导入后也不会报错。
Blueprint对象在实例化时,还可以传入一个参数url_prefix,即url前缀,会给当前文件下所有的路由加上指定的前缀,如上面的book.py可以改成
from flask import Blueprint
book_bp = Blueprint('book', __name__, url_prefix='/book') # 第一个参数是蓝图名称,一般是当前的Python文件名,第二个参数是蓝图所在的包或模块,一般用__name__变量
@book_bp.route('/')
def news():
return '图书列表'
@book_bp.route('/detail/<bid>')
def book_detail(bid):
return '当前图书ID:%s' % bid
这与之前的效果是一样的。
四、Flask蓝图寻找文件和url_for()寻找路由
1.Flask蓝图寻找模板文件
Flask蓝图默认不设置任何模板文件的路径,将会在项目的templates中寻找模板文件。
也可以设置其他的路径,在构造函数Blueprint中有一个template_folder参数可以设置模板的路径。
修改news.py如下:
from flask import Blueprint,render_template
news_bp = Blueprint('news', __name__, template_folder='blue_templates')
@news_bp.route('/news/')
def news():
return render_template('news.hmtl')
修改templates中的news.html如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新闻</title>
</head>
<body>
<h1>这是模板中的新闻首页</h1>
</body>
</html>
在blueprints中创建目录blue_templates,在下面创建news.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新闻</title>
</head>
<body>
<h1>这是蓝图中的新闻首页</h1>
</body>
</html>
运行后,显示:
显然,显示的是项目中的templates目录中的模板,进一步可以总结:
在蓝图中定义的视图函数在寻找模板时,首先会在项目的默认模板目录templates中寻找是否有指定的模板,如果存在则进行渲染,不存在则继续看蓝图文件中初始化Blueprint对象时是否传入了template_folder参数来指定模板目录,如果传了则在参数指定的目录中寻找,找到则进行渲染,否则报错,如果没有指定template_folder参数参数也会直接报错。
在实际项目中,一般直接将模板文件放在默认的模板目录templates下。
2.Flask蓝图寻找静态文件
默认不设置任何静态文件路径,Jinja2会在项目的static文件夹中寻找静态文件。
也可以设置其他的路径,在初始化蓝图的时候,Blueprint构造函数有一个参数static_folder可以指定静态文件的路径。
static_folder可以是相对路径(相对蓝图文件所在的目录),也可以是绝对路径。配置完蓝图后,在模板中引用静态文件应该使用蓝图名.static
。
测试如下:
先在blue_demo目录下创建static目录,并在static目录下创建news.css如下:
body{
background: pink;
}
templates目录下的news.html导入css文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="{{ url_for('static', filename='news.css') }}">
<title>新闻</title>
</head>
<body>
<h1>这是模板中的新闻首页</h1>
</body>
</html>
同时在blueprints目录下创建blue_static目录,下面创建news.css如下:
body{
background: blue;
}
运行后可以看到:
显然,此时加载的模板文件是templates目录中的news.html,使用的样式文件是static目录下的news.css。
要想使用blue_static目录下的样式文件,需要改动,首先需要在Blueprint初始化时指定静态文件的文件夹:
from flask import Blueprint,render_template
news_bp = Blueprint('news', __name__, static_folder='blue_static')
@news_bp.route('/news/')
def news():
return render_template('news.html')
templates目录下的news.html导入css文件的路径修改如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="{{ url_for('news.static', filename='news.css') }}">
<title>新闻</title>
</head>
<body>
<h1>这是模板中的新闻首页</h1>
</body>
</html>
显示:
此时加载的样式文件即是blueprints目录下的静态文件夹中的样式文件。
3.蓝图中使用url_for()方法获取路由
在存在蓝图的情况下使用使用url_for()
方法,不能直接给路由对应的函数名,需要在前面加上定义该路由所在的蓝图名字,即初始化蓝图时的第一个参数,一般即为该函数所在的Python文件的文件名,使用的格式是蓝图名称.视图函数名称
。
blue_flask.py如下:
from flask import Flask, url_for
from blueprints.news import news_bp
from blueprints.book import book_bp
app = Flask(__name__)
app.register_blueprint(news_bp)
app.register_blueprint(book_bp)
@app.route('/')
def index():
print(url_for('news.news'))
print(url_for('book.book_detail', bid=2))
return '这是首页'
if __name__ == '__main__':
app.run(debug=True)
book.py如下:
from flask import Blueprint, url_for
book_bp = Blueprint('book', __name__, url_prefix='/book')
@book_bp.route('/')
def book():
print(url_for('book.book_detail', bid=3))
return '图书列表'
@book_bp.route('/detail/<bid>')
def book_detail(bid):
return '当前图书ID:%s' % bid
显示:
此时查看控制台打印如下:
127.0.0.1 - - [17/Apr/2020 17:41:17] "GET / HTTP/1.1" 200 -
/news/
/book/detail/2
/book/detail/3
127.0.0.1 - - [17/Apr/2020 17:41:27] "GET /book/ HTTP/1.1" 200 -
显然,此时得到了视图函数对应的路由。