经过上一节,对url有一定了解,这一节学习静态文件管理、视图、模板(jiaja2)
web应用通常会提供静态资源的服务以便给用户更好的访问体验,静态文件主要是css,js以及各种图片和字体文件等。
在flask中只需要在项目根目录下创建static目录,在路径中使用/static开头即可。但是为了更好的处理能力,建议使用ngnix或者其他web服务器进行管理。
使用url_for构建路径来访问静态资源,举个例子:
url_for('static',filename='style.css')
生成的路径是“/static/style.css”。
当然了,也可以自定义静态文件的目录
app = Flask(__name__,static_folder='/tmp')
这时访问/static开头的路径时,就是到/tmp目录下寻找资源,而不是默认的static
不难发现之前所有的视图都是函数的,简称视图函数。
事实上视图还可以基于类来实现,这样的视图可以支持继承,简称类视图。
但类视图还需要向flask添加路径规则。即app.add_url_rule(url_rule,view_func)进行注册。视图类型有两种。
标准视图
标准视图需要继承flask.views.View,必须实现dispatch_request。
from flask import views
...
class BaseView(views.View):
def get_template_name(self):
raise NotImplementedError()
def render_template(self, context):
return render_template(self.get_template_name(), **context)
def dispatch_request(self):
if request.method != 'GET':
return ''
context = {'users': self.get_users()}
return self.render_template(context)
# 继承父类视图BaseView
class UserView(BaseView):
def get_template_name(self):
return 'demo.html'
@staticmethod
def get_users():
return [{
'username': 'demo',
'avator': 'this is demo'
}]
# 注册访问路径
app.add_url_rule('/users', view_func=UserView.as_view('UserView'))
if __name__ == '__main__':
app.run(debug=True)
当访问/users时,由UserView类执行dispatch_request方法,装配数据,最终返回demo.html页面.
基于调度方法的视图
flask.views.MethodView对每种HTTP方法执行不同的函数,这对RESTful API极其有用。
class UserAPI(views.MethodView):
def get(self):
return 'this is get'
def post(self):
return 'this is post'
def delete(self):
return 'this is delete'
app.add_url_rule('/user', view_func=UserAPI.as_view('userview'))
即请求为get时执行get方法,post时执行post方法。
通过装饰as_view对视图进行装饰,最常用于权限的检查,登录验证。
# 装饰函数
def user_isLogin(f):
def decorator(*args,**kwargs):
if not u.user: # 判断用户是否已登录
abort(401)
return f(*args,**kwargs)
return decorator
app.add_url_rule('/user/',view_func=user_isLogin(UserAPI.as_view('user')))
从flask8.0开始还可以在继承MethodView的类中直接修改decorator属性实现相同效果:
class UserAPI(views.MethodView):
decorator = [user_isLogin] #user_isLogin为函数名
蓝图
蓝图是应用的模块化,使用蓝图可以让应用层次清晰,更易于开发维护项目。
一般用在具有相同url前缀的情况,比如跟用户相关的各种页面/user/center,/user/address,...等都以user开头,那么就可以把它们放在同一个模块中。一个简单的例子如下:
bp_demo.py
from flask import Blueprint bp = Blueprint('user', __name__, url_prefix='/user') @bp.route('/') def bp_demo(): return '这是bp_demo'
app.py
... import bp_demo ... app.register_blueprint(bp_demo.bp) if __name__ == '__main__': app.run(debug=True)
那么以/user开头的视图函数就可以写在bp_demo.py中,从而与app.py的视图函数区分开来,用户模块和主页模块分别完成,而不是都写在app.py中。
子域名
说到蓝图就少不了子域名。现在很多网站中都有使用,比如现在一个网站是jh.com,那么可以加一个子域名cms.jh.com作为cms系统的网址,以下例子:
bp_demo.py
from flask import Blueprint bp = Blueprint('user', __name__, url_prefix='/user',subdomain='cms') @bp.route('/') def bp_demo(): return '这是bp_demo'
app.py
... import bp_demo ... # 配置`SERVER_NAME` app.config['SERVER_NAME'] = 'jh.com:5000' app.register_blueprint(bp_demo.bp) if __name__ == '__main__': app.run(debug=True)
配置完成后还不能访问,因为host文件中没有相关映射,在主机的host文件中的127.0.0.1 后面增加子域名后即可正常访问。
终于到这一步了,可以把html页面拿来显示了,不过在这之前再来看python自带的字符串模板:
from string import Template
s = Template("$one : ---- :$two ")
out = s.substitute(one='one的值为1',two='two的值为2')
print(out)
# 输出结果
# one的值为1 : ---- :two的值为2
这里就是通过字符串模板对象的substitute方法把参数插入到对应的变量上,或者说就是在s这个模板字符串上挖了坑,然后往坑里塞东西。
自带的模板功能有限,不能写控制语句,无法继承重用,这样肯定是不够的。所以第三方模板引擎就非常有必要了,目前最知名的就是jiaja2和mako,本次学习以jinja2为主。
jinja2是仿django的一个模板引擎,它速度快,使用广泛,并且提供可选的沙箱模板保证环境的安全,与其他诸多web开发一样具有一些优点,如下:
- 让HTML设计者和后端python开发分离
- 减少使用python 的复杂度,程序易于维护
- 模板灵活、快速、安全,对设计者和开发者更友好。
- 提供了控制语句和模板继承等高级功能,减少开发复杂度。
jinja2的单独使用和string.Template很像:
from jinja2 import Template
t = Template('Hello {{ var }}')
t.render(var='World')
print(t)
# 输出结果
# Hello World
基本语法
模板仅仅是文本文件,通常web开发中使用.html做后缀名。模板包含“变量”或“表达式”,将被替换为值,确定模板中还有表情和控制语句。
一个简单的模板(simple.html)
Title
{# 这个是jinja的注释,它不会被返回给客户端 #}
{{ title | trim }}
{{ content }}
以上代码的解释:
- {#...#}:模板注释,在渲染的页面里不会出现。
- {%...%}:执行语句块,如for,if等等。
- {{...}}:用于把变量输出到模板上。
- jiaja中的控制语句,如for循环,需要以endfor作为结束。
- 在python代码中将变量传递给模板,可以通过点(.)来访问变量的属性,也可以使用括号([]),其效果是一样的
- 在{{...}}中出现的竖线"|"是过滤器,在这里是将title变量对应字符串的空格去除,jinja2有很多的过滤器,他们大多都很常用。
模板继承
模板继承让模板重用,提高工作效率和代码质量。
先定义一个基础的骨架模板(base.html)
{% block head %}
{% block title %} {% endblock %}
{% endblock %}
{% block header %}
{% endblock %}
{% block content %}
{% endblock %}
{% block footer %}
{% endblock %}
- {% block xxx %}...{% endblock%}是一个代码块,可以在子模版重载,其中xxx为该代码块命名
- head中有默认内容,如果子模板中没有重载,那么该内容也将被字模板使用
然后是子模板index.html
{% extends 'base.html' %}
{% block title %} index {% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block header %}
header
{% endblock %}
{% block content %}
content
{% endblock content %}
{% block footer %}
footer
{% endblock %}
那写一个视图函数来渲染显示看看吧,向模板中传入title变量。
@app.route('/index')
def index():
title = 'index'
return render_template('index.html', title=title)
可以看到title变量被渲染到标题中。
- index.html继承了base.html的内容。extentds标签应该在模板一开始使用
- index.html中调用super(),表示先使用base.html 的head内容,在基于此添加css样式。
- 在代码块的结束标签中写上名称,可以改善模板的可读性。
- 如果想要多次使用一个块,可以使用特殊的self变量调用与块同名的函数:
{% block title %}{% endblock %}
{{ self.title() }}
宏
宏类似函数,把常用行为抽象成可重用的函数。
在index.html中定义宏
此后便能类似函数一样使用这个宏
赋值
在代码块中也可以为变量赋值,赋值使用set标签,而且可以为多个变量赋值。
在index.html中
include、import
include语句用于包含一个模板,渲染时在include语句对应的位置添加被包含的内容:
{% include 'header.html' ignore missing%}
{# ignore missing :如果模板不存在,jinja会忽略这条语句 #}
jinja的import和python类似,可以把整个模板导入到一个变量,或者从其中导入特定的宏。
{% import 'index.html' as index %} {# 导入整个,调用时index.hello #}
{% from 'index.html' import hello as hello%} {# 导入特定宏 #}