唐抉的个人博客

Flask Web开发学习笔记(三)

字数统计: 3.7k阅读时长: 14 min
2022/10/21

模板

Jinja配置

Jinja2默认配置为:

  • 所有扩展名为.html.htm.xml以及.xhtml的模板会开启自动转义

  • 模板可以利用

    1
    {% autoescape %}

    标签选择自动转义的开关

  • Flask在Jinja2上下文中插入了几个全局函数和助手,另外还有一些目前默认的值。

标准上下文

默认在Jinja2模板中可用的全局变量有:

config: 当前的配置对象(flask.config)。

request: 当前的请求对象(flask.request) 。当模板不是在活动的请求上下文中渲染时,这个变量不可用。

session: 当前的会话对象(flask.session)。当模板不是在活动的请求上下文中渲染时,这个变量不可用。

g: 请求相关的全局变量(flask.g)。当模板不是在活动的请求上下文中渲染时,这个变量不可用。

url_for(): flask.url_for()函数。

get_flashed_messages(): flask.get_flashed_messages()函数。

Jinja上下文行为

这些变量被添加到了请求的上下文中,而非全局变量。其区别在于,默认不会在导入模板的上下文中出现。这样一方面是考虑到性能,另一方面是为了让事情显式透明。

若想要导入一个需要访问请求对象的宏,有两种方法:

  • 显式地传入请求或请求对象的属性作为宏的参数
  • 与上下文一起导入宏,其方式如下:
1
{% from '_helpers.html' import my_macro with contexr %}

标准过滤器

tojson()函数把给定的对象转换为JSON表示。例如动态生成JavaScript:

1
2
3
<script type=text/javascript>
doSomethingWith( { {user.username|tojson|safe } } );<!--|safe禁用转义-->
</script>

控制自动转义

自动转义的概念是自动转义特殊字符。HTML(或XML)意义下的特殊字符是&><"'

控制自动转义可以有三种方法:

  • 在传递到模板之前,用Markup对象封装HTML字符串。一般推荐这个方法。
  • 在模板中,使用|safe过滤器显式地标记一个字符串为安全的HTML(myvariable|safe)。
  • 临时地完全禁用自动转移系统,其方式如下:
1
2
3
4
{% autoescape false %}
<p>autoescaping is disabled here
<p>{ { wii_not_be_escaped } }
{% endautoescape %}

注册过滤器

若要在Jinja2中注册过滤器,可以把它们手动添加到应用的jinja_env或使用template_filter()装饰器:

1
2
3
4
5
6
7
8
9
#使用template_filter()装饰器
@app.template_filter('reverse')
def reverse_filter(s):
return s[::-1]

#手动添加到应用的jinja_env
def reverse_filter(s):
return s[::-1]
app.jinja_env.filters['reverse']=reverse_filter

在使用装饰器的情况下,若想以函数名作为过滤器名,参数是可选的。注册之后,可以在模板中使用过。例如在上下文中有一个名为mylist的Python列表:

1
2
{% for x in mylist | reverse %}
{% endfor %}

上下文处理器

Flask上下文处理器自动向模板的上下文中插入新变量。上下文处理器在模板渲染之前运行,并且可以在模板上下文中插入新值。上下文处理器是一个返回字典的函数,这个字典的键值最终将传入应用中所有模板的上下文。:

1
2
3
4
5
6
7
8
9
10
11
12
#上下文处理器
@app.context_processor
def inject_user():
return dict(user=g.user)#模板可以使用一个名为user,值为g.user的变量

@app.context_processor
def utility_processor():
def format_price(amount,currency=u'$'):
return u'{0:.2f} {1}.format(amount,currency)
return dict(format_price=format_price)#format_price函数在所有模板中可用
#调用format_price函数
{ {format_price(0.33)} }

由于Python允许传递函数,其变量不仅仅限于值,上下文处理器也可以使某个函数在模板中可用。

即插视图

即插视图主要目的是可以替换已实现的部分,并且这个方式可以定制即插视图。

基本原则

若有一个从数据库载入一个对象列表并渲染到视图的函数:

1
2
3
4
@app.route('/users/')
def show_users(page):
users=User.query.all()
return render_template('user.html',users=users)

这是简单而灵活的实现,但若想要用一种通用的,同样可以适应其他模型和模板的方式来提供这个视图,会需要更大的灵活性,而这就是基于类的即插视图所做的。

第一步,把它转换为基于类的视图:

1
2
3
4
5
6
from flask.views import View
class ShowUsers(View):#创建flask.views.View的子类
def dispatch_request(self):
users=User.query.all()
return render_template('users.html',objects=users)
app.add_url_rule('/users/',ShowUsers.as_view('show_users'))#as_view把类转换到实际的视图

上面实现的方法还不够有效,因此需要重构一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask.views import View
class ListView(View):
def get_template_name(self):#self无论何时请求被调度都会创建这个类的新实例
raise NotImplementedError()
def render_template(self,context):
return render_template(self.get_template_name(),**context)
def dispatch_request(self):#以URL规则为参数调用
context={ 'objects':self.get_objects() }
return self.render_template(context)
class UserView(ListView):
def get_template_name(self):
return 'user.html'
def get_objects(self):
return User.query.all()
#将类实例化并进行注册
app.add_url_rule('/about',view_func=RenderTemplateView.as_view(
'about_page',template_name='about.html'))

方法提示

即插视图可以像常规函数一样用route()或更好的add_url_rule()附加到应用中。而当进行附加时,必须提供HTTP方法的名称。为了将这个信息加入到类中,可以提供methods属性类承载它:

1
2
3
4
5
6
class MyView(View):
methods=['GET','POST']
def dispatch_request(self):
if request_method=='POST':
pass
app.add_url_rule('/myview',view_func=MyView.as_view('myview'))

基于调度的方法

对于每个HTTP方法执行不同的函数,对RESTfulAPI非常有用。可以通过flask.views.MethodView实现。每个HTTP方法映射到同名函数中(只有名称为小写的):

1
2
3
4
5
6
7
8
9
from flask.views import MethodView
class UserAPI(MethodView):
def get(self):
users=User.query.all()
pass
def post(self):
user=User.from_form_data(request.form)
pass
app.add_url_rule('/users/',view_func=UserAPI.as_view('users'))

如此便可以不提供method属性,其会自动按照类中定义的方法来设置。

装饰视图

视图类自己不是加入到路由系统的视图函数,那么就没有必要去装饰视图类,但可以手动装饰as_view()的返回值:

1
2
3
4
5
6
7
8
9
def user_required(f):
#检查用户是否登录,若没登录返回401错误
def decorator(*args,**kwargs):
if not g.user:
abort(401)
return f(*args,**kwargs)
return decorator
view=user_required(UserAPI.as_view('users'))
app.add_url_rule('/users/',view_func=view)

从Flask0.8开始,增加了另一种在类声明中设定一个装饰器列表的方法:

1
2
class UserAPI(MethodView):
decorators=[user_required]

由于从调用者的视角来看self是不明确的,因此不能在单独的视图方法上使用常规的视图装饰器。

用于API的方法视图

WebAPI的工作通常与HTTP动词紧密相关,因此API需要不同的URL规则来访问相同的方法视图。

若要在web上暴露一个用户对象:

URL HTTP 方法 描述
/users/ GET 获得全部用户的列表
/users/ POST 创建一个新用户
/users/<id> GET 显示某个用户
/users/<id> PUT 更新某个用户
/users/<id> DELETE 删除某个用户

可以利用MethodView对相同的视图提供多个规则,此时的视图为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class UserAPI(MethodView):
def get(self,user_id):
if user_id is None:
#返回全部用户列表
pass
else:
#显示一个用户
pass
def post(self):
#创建一个新用户
pass
def delete(self,user_id):
#删除一个用户
pass
def put(self,user_id):
#更新一个用户
pass

添加两条规则,并为每条规则显式地指出HTTP方法,将它挂载到路由系统中:

1
2
3
4
5
6
user_view=UserAPI.as_view('user_api')
app.add_url_rule('/users/',default={'user_id':None},
view_func=user_view,methods=['GET',])
app.add_url_rule('/users/',view_func=user_view,methods=['POST',])
app.add_url_rule('/users/<int:user_id>',view_func=user_view,
methods=['GET','PUT','DELETE'])

若有很多类似的API,可以重构上述的注册代码:

1
2
3
4
5
6
7
def register_api(view,endpoint,url,pk='id',pk_type='int'):
view_func=view.as_view(endpoint)
app.add_url_rule(url,defaults={pk:None},
view_func=view_funv,methods=['GET',])
app.add_url_rule(url,view_func=view_func,methods=['POST',])
app.add_url_rule('%s < %s : %s > ' % (url,pk_type,pk),view_func=view_func,methods=['GET','PUT','DELETE'])
register_api(UserAPI,'user_api','/users/',pk='user_id')

用蓝图实现模块化的应用

一个应用中或跨应用制作应用组件和支持通用的模式称为蓝图。蓝图简化了大型应用的工作方式,并提供给Flask扩展在应用上注册操作的核心方法。一个Blueprint对象与Flask应用对象的工作方式很像,但它确实不是一个应用,而是描述如何构建或扩展应用的蓝图。

为什么使用蓝图

Flask中的蓝图为这些情况设计:

  • 把一个应用分解为一个蓝图的集合。一个项目可以实例化一个应用对象,初始化几个扩展,并注册一集合的蓝图。
  • 以URL前缀和/或子域名,在应用上注册一个蓝图。默认情况下,URL前缀/子域名中的参数即为这个蓝图下的所有视图函数的共同的视图参数。
  • 在一个应用中用不同的URL规则多次注册一个蓝图。
  • 通过蓝图提供模板过滤器、静态文件、模板和其他功能。一个蓝图不一定要实现应用或者视图函数。
  • 初始化一个Flask扩展时,在这些情况中注册一个蓝图。

Flask中的蓝图不是即插应用,因为它虽然可以注册,甚至可以多次注册到应用上的操作集合中,但其实际上不是一个应用。可以使用多个应用对象,但应用的配置的分开的,并在WSGI层管理。

蓝图作为Flask层提供分隔的替代,共享应用配置,并且在必要情况下可以更新所注册的应用对象。其缺点是不能在应用创建后撤销注册一个蓝图而不销毁整个应用对象。

蓝图的设想

蓝图的基本设想是当它们注册到应用上时,它们记录将会被执行的操作。当分派请求和生成从一个端点到另一个URL时,Flask会关联蓝图中的视图函数。

蓝图的实现

实现一个简单渲染静态模板的蓝图如下:

1
2
3
4
5
6
7
8
9
10
11
from flask import Blueprint,render_template,abort
from jinja2 import TemplateNotFound
simple_page=Blueprint('simple_page',__name__,template_folder='templates')

@simple_page.route('/',defaults={'page':'index'})#绑定函数
@simple_page.route('/<page>')
def show(page):
try:
return render_template('pages/ %s.html' %page)
except TemplateNotFound:
abort(404)

当使用@simple_page.route装饰器绑定函数时,在蓝图之后被注册时它会记录把show函数注册到应用上的意图。除此之外还会给函数的端点加上有Blueprint的构造函数中给出的蓝图的名称作为前缀(如上述代码中是simple_page)

注册蓝图

注册蓝图:

1
2
3
4
from flask import Flask
from yourapplicarion.simple_page import simple_page
app=Flask(__name__)
app.register_blueprint(simple_page)

若检查已经注册到应用的规则,会发现这些生成出的规则:

1
2
3
[ <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,#来自应用本身,用于静态文件
<Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,#用于simple_page蓝图中的show函数
<Rule '/' (HEAD, OPTIONS, GET) -> simple_page.show> ]#用于simple_page蓝图中的show函数

蓝图在不同位置挂载时生成的规则也不同,如:

1
2
3
4
5
6
app.register_blueprint(simple_page,url_prefix='/pages')

#生成的规则如下:
[ <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,#来自应用本身,用于静态文件
<Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,#挂载到pages页下,用于simple_page蓝图中的show函数
<Rule '/pages/' (HEAD, OPTIONS, GET) -> simple_page.show> ]

除此之外还可以多次注册蓝图,但不是每个蓝图都会正确地响应。实际上蓝图能否被多次挂载,取决于蓝图是怎样实现的。

蓝图资源

蓝图也可以提供资源。有时候会只为它提供的资源而引入一个蓝图。

蓝图资源文件夹

蓝图被设想为包含在一个文件夹中。也有多个蓝图源于同一个文件夹的情况,但不推荐这种做法。

这个文件夹会从Blueprint的第二个参数中推断出来,通常是__name__。这个参数决定对应蓝图的是哪个逻辑的Python模块或包。若它指向一个存在的Python包,这个包就是资源文件夹。若是一个模块,模块所在的包就是资源文件夹。可以访问Blueprint.root_path属性来查看资源文件夹是什么,其语句为:simple_page.root_path

可以使用open_resource()函数来快速从这个文件夹打开源文件:

1
2
with simple_page.open_resource('static/style.css') as f:
code=f.read()

静态文件

一个蓝图可以通过static_folder关键字参数提供一个指向文件系统上文件夹的路径,并以此来暴露一个带有静态文件的文件夹。这可以是一个绝对路径,也可以是相对于蓝图资源文件夹的路径:

1
admin=Blueprint('admin',__name__,static_folfer='static')

默认情况下,路径最右边的部分就是它在web所暴露的地址。这个static的文件夹会在蓝图+/static的位置上可用,即蓝图为/admin把静态文件夹注册到/admin/static。

最后是用于命名的blueprint_name.static,也可以生成它的URL:

1
url_for('admin.static',filename='style.css')

模板

若想要蓝图暴露模板,可以通过Blueprint构造函数中的template_folder参数来实现:

1
admin=Blueprint('admin',__name__,template_folder='templates')

路径可以是绝对的或是相对蓝图资源文件夹的。模板文件夹会被加到模板的搜索路径中,但比实际的应用模板文件夹优先级低,因此可以在实际的应用中覆盖蓝图提供的资源。

因此当有一个yourapplication/admin文件夹中的蓝图并且想要渲染admin/index.html模板时,提供templates作为template_folder,则创建文件的路径为:

yourapplication/admin/templates/admin/index.html

构造URL

当想要用蓝图从一个页面链接到另一个页面时,可以使用url_for()函数,但要在UTL的末端加上蓝图的名称和一个点(.)来作为前缀。若在一个蓝图的视图函数或是模板中想要从链接到同一蓝图下另一个端点时,可以通过对端点只加上一个点作为前缀来使用相对的重定向:

1
2
url_for('admin.index')#链接到admin蓝图的index页面
url_for('.index')#链接到同一蓝图下的index页面
CATALOG
  1. 1. 模板
    1. 1.1. Jinja配置
    2. 1.2. 标准上下文
      1. 1.2.1. Jinja上下文行为
    3. 1.3. 标准过滤器
    4. 1.4. 控制自动转义
    5. 1.5. 注册过滤器
    6. 1.6. 上下文处理器
  2. 2. 即插视图
    1. 2.1. 基本原则
    2. 2.2. 方法提示
    3. 2.3. 基于调度的方法
    4. 2.4. 装饰视图
    5. 2.5. 用于API的方法视图
  3. 3. 用蓝图实现模块化的应用
    1. 3.1. 为什么使用蓝图
    2. 3.2. 蓝图的设想
    3. 3.3. 蓝图的实现
    4. 3.4. 注册蓝图
    5. 3.5. 蓝图资源
      1. 3.5.1. 蓝图资源文件夹
    6. 3.6. 静态文件
    7. 3.7. 模板
    8. 3.8. 构造URL