Web开发-Flask从零开始的学习(二)


3 模板

业务逻辑表现逻辑进行分离,把表现逻辑移到模板中能提升应用的可维护性。

模板是包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染。为了渲染模板,Flask 使用一个名为 Jinja2 的强大模板引擎。

Jinja2模板引擎

默认情况下,Flask 在应用目录中的 templates 子目录里寻找模板。

jinja2模板:


Hello World!


Hello, {{name}} !

渲染模板:

from flask import  Flask
from flask import request,render_template

app = Flask(__name__)

@app.route('/')
def index():
	return render_template('index.html')

@app.route('/user/')
def user(name):
	return render_template('user.html',name=name)

Flask 提供的 render_template() 函数把 Jinja2 模板引擎集成到了应用中。

这个函数的第一个参数是模板的文件名,随后的参数都是键 – 值对,表示模板中变量对应的具体值。左边为模板中使用的占位符,右边为当前作用域的变量

变量

在模板中使用的 {{ name }} 结构表示一个变量,这是一种特殊的占位符,告诉模板引擎这个位置的值从渲染模板时使用的数据中获取。

  • jinja2能识别许多类型的变量:

A value from a dictionary: {{ mydict['key'] }}.

A value from a list: {{ mylist[3] }}.

A value from a list, with a variable index: {{ mylist[myintvar] }}.

A value from an object's method: {{ myobj.somemethod() }}.

  • 变量的值可以使用过滤器修改。过滤器添加在变量名之后,二者之间以竖线分隔,例如{{name|capitalize}} 首字母大写

safe 渲染值时不转义
capitalize 把值的首字母转换成大写,其他字母转换成小写
lower 把值转换成小写形式
upper 把值转换成大写形式
title 把值中每个单词的首字母都转换成大写
trim 把值的首尾空格删掉
striptags 渲染之前把值中所有的 HTML 标签都删掉

默认情况下,出于安全考虑,Jinja2 会转义所有变量。

很多情况下需要显示变量中存储的HTML 代码,这时就可使用 safe 过滤器。

page41

控制结构

Jinja2 提供了多种控制结构,可用来改变模板的渲染流程。

  • 条件判断
{% if user %}
	Hello, {{ user }}!
{% else %}	
	Hello, Stranger!
{% endif %}
  • 循环结构
    {% for comment in comments %}
  • {{ comment }}
  • {% endfor %}
  • 支持宏(类似函数)
{% macro render_comment(comment) %}
	
  • {{ comment }}
  • {% endmacro %}
      {% for comment in comments %} {{ render_comment(comment) }} {% endfor %}
    • 代码复用:

      • 重复使用宏:把宏保存在单独的文件中,然后在需要使用的模板中导入

        {% import 'macros.html' as macros %}
        
          {% for comment in comments %} {{ macros.render_comment(comment) }} {% endfor %}

        或者

        {% include 'common.html' %}
        
      • 模板继承,类似类继承

        基模板base.html

         
        	{% block head %}
        	{% block title %}{% endblock %} - My Application
        	{% endblock %}
        
        
        	{% block body %}
        	{% endblock %}
         
        

        基模板中定义的区块(block)可在衍生模板中覆盖。

        衍生模板:

        {% extends "base.html" %}
        {% block title %}Index{% endblock %}
        {% block head %}
        	{{ super() }}
        	
        {% endblock %}
        {% block body %}	

        Hello, World!

        {% endblock %}

        如果基模板和衍生模板中的同名区块中都有内容,显示衍生模板

        在衍生模板的区块里可以调用 super(),引用基模板中同名区块里的内容

    Flask-Bootstrap拓展集成Bootstrap

    Bootstrap是一个开源Web前端框架。按照flask-bootstrap拓展

    • 在实例py文件中初始化
    from flask_bootstrap import Bootstrap 
    # ...
    bootstrap = Bootstrap(app)
    
    • 导入后可以利用jinja2的模板继承机制来拓展基模板
    {% extends "bootstrap/base.html" %}
    {% block title %}Flasky{% endblock %}
    {% block navbar %}
    
    {% endblock %}
    {% block content %}
    
    {% endblock %}

    extends 指令从 Flask-Bootstrap 中导入bootstrap/base.html,从而实现模板继承。Flask-Bootstrap 的基模板提供了一个网页骨架,引入了 Bootstrap 的所有 CSS 和 JavaScript文件。

    很多区块都是 Flask-Bootstrap 自用的,如果直接覆盖可能会导致一些问题。

    例如,Bootstrap 的 CSS 和 JavaScript 文件在 styles 和 scripts 区块中声明。如果应用需要向已经有内容的块中添加新内容,必须使用 Jinja2 提供的 super() 函数。

    {% block scripts %}
    {{ super() }}
    
    {% endblock %}
    

    自定义错误界面

    Flask 允许应用使用模板自定义错误页面。最常见的错误代码有两个:

    • 404,客户端请求未知页面或路由时显示
    • 500,应用有未处理的异常时显示

    使用 app.errorhandler 装饰器为这两个错误提供自定义的处理函数:

    @app.errorhandler(404)
    def page_not_found(e):
    	return render_template('404.html'), 404
    @app.errorhandler(500)
    def internal_server_error(e):
    	return render_template('500.html'), 500
    

    与视图函数一样,错误处理函数也返回一个响应。此外,错误处理函数还要返回与错误对应的数字状态码。


    同时这两个错误处理视图函数的模板也需要我们编写,而再去分别创建两个html文件是重复劳动。

    利用jinja2的模板继承机制解决这一问题。Flask-Bootstrap 提供了一个具有页面基本布局的基模板;同样,应用也可以自定义一个具有统一页面布局的基模板,其中包含导航栏,而页面内容则留给衍生模板定义

    建立我们自己定义的应用基模板templates/base.html

    {% extends "bootstrap/base.html" %}
    {% block title %}Flasky{% endblock %}
    {% block navbar %}
    
    {% endblock %}
    {% block content %}
    
    {% block page_content %}{% endblock %}
    {% endblock %}

    自定义的基模板继承自bootstrap,后续应用的其他模板(404.html等)继承该自定义基模板

    链接

    任何具有多个路由的应用都需要可以连接不同页面的链接,例如导航栏

    • 直接编写简单路由的URL链接
    • 对于包含可变部分的动态路由

    为了避免直接编写导致URL对路由产生不必要的依赖关系:

    • url_for():使用应用中URL映射中保存的信息生成URL(或者使用端点名)
    url_for('index') # 结果:/
    url_for('index',_external=True) # 返回绝对地址 http://localhost:5000/
    
    # 动态URL生成
    url_for('user',name='join',_external=True) # 返回http://localhost:5000/user/join
    # 同时不限于动态路由中的参数,也包括非动态参数
    url_for('user',name='john',page=2,version=1) #返回结果 /user/john?page=2&version=1
    

    静态文件

    Web 应用不是仅由 Python 代码和模板组成,多数应用还会使用静态文件:HTML 代码引用的图像、JavaScript 源码文件和 CSS。

    审查 hello.py 应用的 URL 映射时,其中有一个 static 路由。
    这是 Flask 为了支持静态文件而自动添加的,这个特殊路由的 URL 是 /static/。

    例如调用 url_for('static', filename='css/styles.css', _external=True) 得到的结果是 http://localhost:5000/static/css/styles.css。

    默认设置下,Flask 在应用根目录中名为 static 的子目录中寻找静态文件。如果需要,可在static 文件夹中使用子文件夹存放文件。

    {% block head %}
    {{ super() }}
    
    
    {% endblock %}
    

    这个图标的声明插入 head 区块的末尾。注意,为了保留基模板中这个区块里的原始内容,我们调用了 super()。

    Flask-Moment本地化日期和时间

    服务器需要统一时间单位,这和用户所在的地理位置无关,所以一般使用协调世界时(UTC,coordinated universal time)

    而用户往往需要当地时间和当地惯用的时间格式

    Flask-Moment 是一个 Flask 扩展,能简化把 Moment.js 集成到 Jinja2 模板中的过程。

    Moment.js是使用 JavaScript 开发的优秀客户端开源库,它可以在浏览器中渲染日期和时间。

    • hello.py:初始化 Flask-Moment
    from flask_moment import Moment
    moment = Moment(app)
    
    • 基模板的scripts块中引入Moment.js库
    {% block scripts %}
    {{ super() }}
    {{ moment.include_moment() }}
    {{ moment.locale('zh-cn') }} 
    {% endblock %}
    

    为了处理时间戳,Flask-Moment 向模板开放了 moment 对象。

    • hello.py:添加一个 datetime 变量
    from datetime import datetime
    @app.route('/')
    def index():
    	return render_template('index.html',current_time=datetime.utcnow())
    
    • templates/index.html:使用 Flask-Moment 渲染时间戳

    The local date and time is {{ moment(current_time).format('LLL') }}.

    That was {{ moment(current_time).fromNow(refresh=True) }}

    一篇关于flask-moment的

    format('LLL') 函数根据客户端计算机中的时区和区域设置渲染日期和时间。

    第二行中的 fromNow() 渲染相对时间戳,而且会随着时间的推移自动刷新显示的时间。设定 refresh=True 参数后,其内容会随着
    时间的推移而更新。