Flask框架高级视图 -- 潘登同学的flask学习笔记
app.route原理剖析
这个装饰器底层,其实也是使用 add_url_rule
来实现url与视图函数映射的。add_url_rule
与app.route
都是映射函数,是通过路由地址来映射函数对象;
add_url_rule(rule,endpoint=None,view_func=None)
from flask import Flask,url_for
@app.route('/',endpoint='index')
def index():
print(url_for('show')) # 如果这里url_for中填写的是函数名就会报错,只有在没有endponit时才能写函数名
print(url_for('index'))
return "Hello"
def show_me():
return "这个介绍信息!!"
# endpoint 没有设置,url_for中就写函数的名字,如果
设置了,就写endpoint的值
app.add_url_rule('/show_me/',view_func=show_m
e,endpoint='show')
# @app.route 底层就是使用的 add_url_rule
类视图
之前我们接触的视图都是函数,所以一般简称函数视图。其实视图也可以基于类来实现,类视图的好处是支持继承;
但是类视图不能跟函数视图一样,写完类视图还需要通过app.add_url_rule(url_rule,view_func)
来进行注册
标准类视图的使用步骤
- 1.标准类视图,必须继承自
flask.views.View
- 2.必须实现
dispatch_request
方法,以后请求过来后,都会执行这个方法。这个方法的返回值就相当于是之前的视图函数一样。也必须返回Response
或者子类的对象,或者是字符串,或者是元组。 - 3.必须通过
app.add_url_rule(rule,endpoint,view_func)
来做url与视图的映射。view_func
这个参数,需要使用类视图下的as_view
类方法类转换:ListView.as_view('list')
- 4.如果指定了
endpoint
,那么在使用url_for
反转的时候就必须使用endpoint
指定的那个值。如果没有指定endpoint
,那么就可以使用as_view(视图名字)
中指定的视图名字来作为反转。
from flask import Flask
from flask.helpers import url_for
from flask.views import View
# 创建对象
app = Flask(__name__)
# 路由地址
@app.route("/")
def index():
print(url_for('mylist'))
# print(url_for('my'))
return 'Hello'
# 第一步: 创建类视图,继承自View
class ListView(View):
# 重写dispatch_request方法
def dispatch_request(self):
return "类视图的使用"
# mylist相当于视图的名字
app.add_url_rule("/list/",view_func=ListView.as_view('mylist'))
# app.add_url_rule("/list/",endpoint='my',view_func=ListView.as_view('mylist'))
if __name__ == "__main__":
# 这行测试代码可以不用打开浏览器就能看到结果
with app.test_request_context():
print(url_for("mylist"))
类视图的特点
可以继承,把一些共性的东西抽取出来放到父视图中,子视图直接拿来用就可以了。(但是也不是说所有的视图都要使用类视图,这个要根据情况而定。视图函数用得最多。)
- 需求:返回的结果都是json数据
class BaseView(View):
def get_date(self):
# 如果方法不重写就会报错
raise NotImplementedError
def dispatch_request(self):
return jsonify(self.get_date())
class Json1View(BaseView):
# 方法重写
def get_date(self):
return {
'uname':'pandeng',
'age':20,
}
class Json2View(BaseView):
def get_date(self):
return [
{
'uname':'pandeng',
'subject':'finance',
},
{
'uname':'daijiawei',
'subject':'Java',
}
]
app.add_url_rule('/json/1',view_func=Json1View.as_view('json1'))
app.add_url_rule('/json/2',view_func=Json2View.as_view('json2'))
类视图实战
需求: 同一样式的页面对应登录和注册两个内容,url是不同的;
from flask import Flask,render_template
from flask.views import View
# 创建对象
app = Flask(__name__)
# 路由地址
@app.route("/")
def index():
return 'Hello'
class BaseView(View):
def __init__(self):
self.msg = {
"main":"百战 陈博老师好!"
}
class LoginView(BaseView):
def dispatch_request(self):
my_msg = "我能帮助您登录"
return render_template('./10_类视图的使用/login.html',msg=self.msg.get('main'), my_msg=my_msg)
class RegisterView(BaseView):
def dispatch_request(self):
my_msg = "我能帮助您注册"
return render_template('./10_类视图的使用/register.html',msg=self.msg.get('main'), my_msg=my_msg)
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.config.from_pyfile("./setting.py")
app.run()
新建文件login.html
<body>
<div class="zhuce_box">
<div class="beijing_box"></div>
<div class="zhuce_con_box">
<div class="zhuce_nav clearfix">
<a href="/" class="zhuce_logo_box fl"><img src="../../static/img/logo.png" alt=""></a>
<p class="phone_zhuce_box fr"><img src="../../static/img/zhucephone.png" alt="">联系电话:18514000360</p>
</div>
<div class="zhuce_info_box denglu_info_box clearfix">
<div class="zhuce_txt_box fl">
<h2>{{ msg }}</h2>
<p>让人人享有高品质教育</p>
<p style="color:red">
{{ my_msg }}</p>
</div>
<div class="user_in_con_b fr">
<div class="user_in_info">
<ul class="login_btn clearfix">
<li class="user_li_on"><a href="{{ url_for('login') }}">登录</a></li>
<li class="user_li_on1"><a href="{{ url_for('register') }}">注册</a></li>
</ul>
<div class="user_dengzhu_box">
<div class="user_dengzhu_info">
<form method="post" action="" onsubmit="return login()">
<div class="user_input_info">
<input type="text" placeholder="手机号/邮箱" name="phone" class="login_phone">
<p class="info_error error_login_phone"></p>
</div>
<div class="captcha">
<div id="your-dom-id" class="nc-container"></div>
<input type="hidden" name="captcha" value="0">
</div>
<div class="phone_code">
<div class="phone_code_info">
<input type="text" placeholder="短信验证码" name="code"
class="dxcode login_code">
<input type="button" value="获取验证码" class="get_dxcode get_login_code">
</div>
<p class="info_error error_login_code"></p>
</div>
<div class="zhuce_btn">
<input type="submit" class="submit" value="登录">
</div>
</form>
<div class="user_zhu_txt">手机号不能用?<a href="">通过申诉更改手机号</a></div>
<a href="" class="weixin_button"><span
class="icon iconfont icon-weixin6"></span>微信扫码一键登录</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% block script %}
<script></script>
{% endblock %}
</body>
新建文件register.html
{% extends "./10_类视图的使用/login.html" %}
{% block title %}
注册页面
{% endblock %}
{% block script %}
<script>
var login = document.querySelector(".user_li_on");
var register = document.querySelector(".user_li_on1");
login.setAttribute("style", "border-bottom: none;")
register.setAttribute("style", "border-bottom: 5px solid #00b683;")
</script>
{% endblock %}
最终效果:
基于调度方法的类视图
基于方法的类视图,是根据请求的 method
来执行不同的方法的。如果用户是发送的 get
请求,那么将会执行这个类的 get
方法。类似的post
,delet
,put
这种方式,可以让代码更加简洁。所有和 get
请求相关的代码都放在 get
方法中,所有和 post
请求相关的代码都放在 post
方法中。就不需要跟之前的函数一样,通过 request.method == 'GET'
- 原始方式
from flask import Flask,render_template
from flask.views import View
# 创建对象
app = Flask(__name__)
# 路由地址
@app.route("/")
def index():
return 'Hello'
class BaseView(View):
def __init__(self):
self.msg = {
"main":"百战 陈博老师好!"
}
class LoginView(BaseView):
def dispatch_request(self):
my_msg = "我能帮助您登录"
return render_template('./10_类视图的使用/login.html',msg=self.msg.get('main'), my_msg=my_msg)
class RegisterView(BaseView):
def dispatch_request(self):
my_msg = "我能帮助您注册"
return render_template('./10_类视图的使用/register.html',msg=self.msg.get('main'), my_msg=my_msg)
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.config.from_pyfile("./setting.py")
app.run()
<form action="/login/" method="post">
<table>
<tr>
<td>账号:</td>
<td><input type="text" name="uname"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="pwd"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="立即登录"></td>
</tr>
<tr>
<td colspan="2">
{# <font color="red">{{ error }}</font>#}
{# 优化写法 : 判断 #}
{% if error %}
<font color="red">{{ error }}</font>
{% endif %}
</td>
</tr>
</table>
<p style="color:red">{{ msg }}</p>
</form>
- 基于类视图的方式
from flask.views import MethodView
class LoginView(MethodView):
def get(self):
return render_template('./10_类视图的使用/login11.html')
def post(self):
uname = request.form['uname']
pwd = request.form['pwd']
if uname == "zs" and pwd == "123":
return "登录成功"
else:
return render_template('./10_类视图的使用/login11.html',msg="用户名密码错误")
app.add_url_rule("/login/", view_func=LoginView.as_view('login'))
- 优化上面代码
class LoginView(MethodView):
def _jump(self,msg=None):
#
return render_template('./10_类视图的使用/login11.html',msg=msg)
def get(self):
# 从数据库中获取数据 sql
# 从游标获取数据
# 重新封装数据
return self._jump()
def post(self):
uname = request.form['uname']
pwd = request.form['pwd']
if uname == "zs" and pwd == "123":
return "登录成功"
else:
return self._jump(msg="用户名密码错误")
视图装饰器
python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,(闭包),使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。
- 1.在视图函数中使用自定义装饰器,那么自己定义的装饰器必须放在
app.route
下面。否则这个装饰器就起不到任何作用。
需求:
查看设置个人信息时,只有检测到用户已经登录了才能查看,若没有登录,则无法查看并给出提示信息
from functools import wraps
import logging
# 设置日志模块
logging.basicConfig(level=logging.INFO)
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
uname = request.args.get('uname')
pwd = request.args.get('pwd')
if uname == 'zs' and pwd == '123':
logging.info(f'{uname}:登录成功!')
return func(*args, **kwargs)
else:
logging.info(f'{uname}:尝试登录失败!!')
return "请先" + "<a href='/login/'>登录!</a>"
return wrapper
@app.route("/user/")
@login_required # 直接放到路由地址下面即可
def user():
return 'Hello'
- 2.在类视图中使用装饰器,需要重写类视图的一个类属性
decorators
,这个类属性是一个列表或者元组都可以,里面装的就是所有的装饰器。
def looger_test(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info("这个是测试日志信息!!")
return func(*args, **kwargs)
return wrapper
class MycountView(MethodView):
# 直接将函数名以数组形式赋给decorators即可,有多少个写多少个
decorators = [login_required,looger_test]
def get(self):
return f'您的余额为:0.03'
app.add_url_rule("/mycount/", view_func=MycountView.as_view('mycount'))
蓝图
在Flask中,使用蓝图Blueprint来分模块组织管理。
蓝图实际可以理解为是存储一组视图方法的容器对象,其具有如下特点:
- 一个应用可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如
“/user”
、“/goods”
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个应用初始化时,就应该要注册需要使用的Blueprint
注意
Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中
创建蓝图
- 1.创建蓝图对象
# 创建的蓝图
user_bp=Blueprint('user',__name__)
- 2.在这个蓝图对象上
# 管理子功能
@user_bp.route('/login/')
def login():
return '登录模块'
@user_bp.route('/register/')
def register():
return '注册模块'
- 3.在应用对象上注册这个蓝图对象
# 注册蓝图对象 url_prefix用于给url加一个前缀能辨别出是哪个模块的
app.register_blueprint(user_bp,url_prefix="/user")
还可以多加几个蓝图对象,一旦不需要使用了,就直接把注册语句给注释掉即可;
蓝图的目录管理
对于一个打算包含多个文件的蓝图,通常将创建蓝图对象放到Python包的 __init__.py
文件中
- 根据功能模块划分
--------- project # 工程目录
|------ main.py # 启动文件
|------ user #用户蓝图
| |--- __init__.py # 此处创建蓝图对象
| |--- view.py
| |--- ...
|------ goods # 商品蓝图
| |--- __init__.py
| |--- ...
|...
# user/__init__.py
from flask import Blueprint
# 创建的蓝图
user_bp=Blueprint('user',__name__)
from user import view # 注意这个就是要放后面的
# user/view.py
from user import user_bp
# 管理子功能
@user_bp.route('/login/')
def login():
return '登录模块'
@user_bp.route('/register/')
def register():
return '注册模块'
# user/__init__.py
from flask import Blueprint
# 创建的蓝图
item_bp=Blueprint('item',__name__)
from item import view # 注意这个就是要放后面的
# user/view.py
from item import item_bp
# 管理子功能
@item_bp.route('/item/')
def item():
return '产品模块'
# main.py
from flask import Flask
# 创建对象
app = Flask(__name__)
# 路由地址
@app.route("/")
def index():
return "Hello"
from user.view import user_bp
# 注册蓝图对象 url_prefix用于给url加一个前缀能辨别出是哪个模块的
app.register_blueprint(user_bp, url_prefix="/user")
from item.view import item_bp
app.register_blueprint(item_bp, url_prefix="/item")
if __name__ == "__main__":
app.run(debug=True)
- 根据技术模块
--------- project # 工程目录
|------ main.py # 启动文件
|------ view #用户蓝图
| |--- user.py # 此处创建蓝图对象
| |--- item.py
| |--- view.py
| |--- ...
|...
# view/user.py
from view import user_bp
# 管理子功能
@user_bp.route('/login/')
def login():
return '登录模块'
@user_bp.route('/register/')
def register():
return '注册模块'
# view/item.py
from view import item_bp
# 管理子功能
@item_bp.route('/item/')
def item():
return '产品模块'
# view/__init__.py
from flask import Blueprint
# 创建的蓝图
item_bp=Blueprint('item',__name__)
from view import item
# 创建的蓝图
user_bp=Blueprint('user',__name__)
from view import user
# main.py
from flask import Flask
# 创建对象
app = Flask(__name__)
# 路由地址
@app.route("/")
def index():
return "Hello"
from view import user_bp, item_bp
# 注册蓝图对象 url_prefix用于给url加一个前缀能辨别出是哪个模块的
app.register_blueprint(user_bp, url_prefix="/user")
app.register_blueprint(item_bp, url_prefix="/item")
if __name__ == "__main__":
app.run(debug=True)
蓝图中模板文件
仍继续采用功能模块划分的目录结构
--------- project # 工程目录
|------ main.py # 启动文件
|------ user #用户蓝图
| |--- __init__.py # 此处创建蓝图对象
| |--- view.py
| |--- ...
|------ goods # 商品蓝图
| |--- __init__.py
| |--- ...
|...
在main.py
中创建templates
文件,随便写点内容,取名为index.html
;在user
子集目录中创建templates
文件,随便写点内容,只要与main的那个不一样即可,取名为index1.html
;
# main.py
from flask import Flask,render_template
# 创建对象
app = Flask(__name__)
# 路由地址
@app.route("/")
def index():
return render_template('index.html')
from user.view import user_bp
# 注册蓝图对象 url_prefix用于给url加一个前缀能辨别出是哪个模块的
app.register_blueprint(user_bp, url_prefix="/user")
from item.view import item_bp
app.register_blueprint(item_bp, url_prefix="/item")
if __name__ == "__main__":
app.run(debug=True)
# user/view.py
from user import user_bp
from flask import render_template
# 蓝图模板总结: 默认走最外层的 templates,如果最外层找不到,就会找设置的独有的蓝图模板目录,找不到设置的模板就会报错
# 蓝图中,如果想让独有的模板目录生效,需要在__init__.py文件中声明,template_folder="templates"
# 如果不设置,且最外层没有就直接报错
@user_bp.route('/')
def user():
# 如果想引入自己user下的index1模板,要在__init__.py文件中设置,
# template_folder="templates"
return render_template('index_user.html')
# 管理子功能
@user_bp.route('/login/')
def login():
return '登录模块'
@user_bp.route('/register/')
def register():
return '注册模块'
# user/__init__.py
from flask import Blueprint
# 创建的蓝图
user_bp=Blueprint('user',__name__,template_folder="templates")
from user import view
蓝图中的模板的寻找规则
- (如果设置了
template_folder="templates"
,默认走最外层的templates
,如果最外层找不到,就会找设置的独有的蓝图模板目录,找不到设置的模板就会报错 - 蓝图中,如果想让独有的模板目录生效,需要在__init__.py文件中声明,
- 如果不设置,且最外层没有就直接报错(不会回来找内层的)
最后,也可以起不同的名字,只要template_folder="xxx"
设置了就行
蓝图中加载静态文件
在最外层创建static
文件,里面放静态资源,无论外层index模板还是蓝图的index模板都能访问到
<video src="/static/aaa.mp4" width=“50%” autoplay muted="muted"></video>
在user
文件下,创建一个子目录static
,蓝图目标想要应用里面的内容,要在__init__.py
中设置
# user/__init__.py
from flask import Blueprint
# 创建的蓝图
user_bp=Blueprint('user',__name__,template_folder="templates",static_folder="static")
from user import view
除此之外,在引用内层的静态资源的时候,要注意加上之前在main.py
中写的url_prefix
# main.py
app.register_blueprint(item_bp, url_prefix="/item")
<video src="/user/static/user_aaa.mp4" width=“50%” autoplay muted="muted"></video>
也可以完全不管内层的静态文件名称是什么,采用static_url_path='/ustatic'
来设置引用
user_bp=Blueprint('user',__name__,template_folder="templates",static_folder="static",static_url_path='/ustatic')
static_url_path='/ustatic'
其实是表示,在url_prefix
后紧跟着什么地址,而static_folder
是指明在哪个文件中
<video src="/user/ustatic/user_aaa.mp4" width=“50%” autoplay muted="muted"></video>
蓝图中使用url_for函数
url_for函数传递的参数为: 蓝图名.函数名
需要在函数前面加上蓝图的名字,注意:蓝图的名字是第一个参数
# 这里的就是user 而不是user_bp,user_bp只是变量名
user_bp=Blueprint('user',__name__,template_folder="templates",static_folder="static")
# 在main.py中
@app.route('/test/')
def test():
return url_for('user.login')
引入静态文件也是一样的操作,注意 static
是表示静态资源的存放,不表示文件夹的名称
<video src="{{url_for('user.static',filename='user_aaa.mp4')}}" width=“50%” autoplay muted="muted"></video>
蓝图中的子域名
子域名就是: https://www.baidu.com/ -- 百度主页, http://news.baidu.com/ -- 百度新闻主页,这种在前面的就是子域名
设置主域名:在windows: C:\Windows\System32\drivers\etc
下,找到hosts
文件,然后添加域名与本机的映射。 这里要使用一个特殊的编辑器才能,而且要以管理员身份运行才行,下载地址 http://www.sublimetext.com/3
127.0.0.1 pandeng.com
修改main.py文件
if __name__ == "__main__":
app.config["SERVER_NAME"] = 'pandeng.com:5000'
app.run(debug=True)
在需要增加子域名的蓝图初始化__init__.py
中,新增参数subdomain
# 创建的蓝图
user_bp=Blueprint('user',__name__,subdomain='cms',template_folder="templates",static_folder="static")
写完这个还不够,还要在hosts
文件,再把这个子域名给加进去
127.0.0.1 cms.pandeng.com
注意
- ip地址不能有子域名
- localhost也不能有子域名