Python全栈(七)Flask框架之5.视图高级--类视图和蓝图

一、标准类视图及使用

在前面,我们定义视图都是通过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)

显示:
flask class view addurlrule
显然,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/,显示:
flask class view standard list
此时,如果要用url_for()方法来获取URL,需要注意:
如果add_url_rule()中指定了endpoint参数,url_for()中传的参数就应该是endpoint参数的值;
如果没有指定,就是as_view()方法的值。

在定义视图类时,必须要重写dispatch_request()方法,否则会抛出异常,如下:
flask class view NotImplementedError
可以查看源码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错误。

扫描二维码关注公众号,回复: 10895580 查看本文章

再尝试用类视图传递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)

显示:
flask class view standard json
显然,此时返回页面的是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.htmlregister.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>

显示:
flask class view standard public variables
显然,可以正常访问登录和注册页面,但是两个视图类中含有相同的变量,可以进行优化:

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)

显示:
flask class view method login
显然,通过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)

显示:
flask class view method decorator outside
很明显,只有登录之后才可以正常访问到个人中心。

通过在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之后,显示:
flask blueprint simple use
显然,基本功能已经实现。
心啊在访问/news//book/,都是在执行news.py、book.py中的视图函数,从而实现了项目的模块化。
如果在blue_flask.py中不能导入blueprints中的文件或者出现警告信息,可以将当前目录设为根目录,如下:
flask blueprint source root
此时再导入就会出现提示,导入后也不会报错。
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>

运行后,显示:
flask blueprint template
显然,显示的是项目中的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;
}

运行后可以看到:
flask blueprint static root
显然,此时加载的模板文件是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>

显示:
flask blueprint static blue
此时加载的样式文件即是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

显示:
flask blueprint urlfor
此时查看控制台打印如下:

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 -

显然,此时得到了视图函数对应的路由。

发布了116 篇原创文章 · 获赞 1196 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/105586154