扩展Bootstrap-Flask内置了能够快速渲染Bootstrap样式HTML组件的宏,并提供了内置的Bootstap资源,方便快速开发,使用它能够简化在web程序里使用Bootstrap的过程。javascript
扩展Bootstrap-Flask基于Flask-Bootstrap实现,旨在替代缺少维护的后者。和Flask-Bootstrap相比,Bootstrap-Flask简化了大部分功能(好比未内置基模板),添加了Bootstrap4支持,并增长了一些辅助功能。css
须要注意,Bootstrap-Flask提供的包名称为flask_bootstrap,咱们从这个包导入并实例化Bootstrap类,传入程序实例app,以完成扩展的初始化:
from flask import Flask from flask_bootstrap import Bootstrap app = Flask(__name__) bootstrap = Bootstrap(app)
Bootstrap-Flask在模板中提供了一个bootstrap对象,这个对象提供了两个方法能够用来生成资源引用代码:用来加载CSS文件的bootstrap.load_css()方法和用来加载JavaScript文件(包括Bootstrap、jQuery、Popper.js)的bootstrap_load_js()方法。 Flask-Bootstrap默认从CDN(content Delivery Network,内容分发网络)加载Bootstrap资源,同时也提供了内置的本地资源。若是你想用Bootstrap-Flask提供的本地资源,能够将配置变量BOOTSTRAP_SERVER_LOCAL设为True。另外,当FLASK_ENV环境变量设为development时,Bootstrap-Flask将自动使用本地资源。
尽管使用这些方法很是方便,但咱们最好在开发时自动手动管理本地静态资源。messageBoard的static目录下包含了全部须要的资源文件,基模板中的资源文件都从static文件夹中引入。
若是想使用Bootstrap-Flask提供的方法加载资源,那么只须要在相应的位置分别调用资源加载方法,替换掉这些对应的资源加载语句便可:
<head> {{ bootstrap.load_css() }} </head> <body> {{ bootstrap.load_js() }} </body>
另外,在bootstrap_load_js()方法中,使用with_jquery和with_popper能够设置是否加载jQuery和Popper.js的JavaScript资源,默认为True,设为False能够关闭。
Bootstrap-Flask内置了两个用于渲染WTForms表单类的宏,一个是与咱们以前建立的form_field宏相似的render_field()宏,另外一个是用来快速渲染整个表单的render_form()宏。这两个宏都会自动渲染错误消息,渲染表单的验证状态样式。
Bootstrap-Flask提供的表单渲染宏经过其内置的bootstrap/form.html模板导入,render_field()宏的使用方式和咱们本身编写的form_field()宏彻底相同。值得特别介绍的是render_form()宏,它使用起来更加简单,使用一行代码能够渲染整个表单,并且会自动帮咱们渲染CSRF令牌字段form.csrf_token。下面使用这个宏在index.html模板中渲染问候表单:
{% extends 'base.html' %} {% from 'bootstrap/form.html' import render_form %} {% block contetn %} <div class="hello-form"> {{ render_form(form), action=request.full_path }} </div> {% endblock %}
它将会自动为你建立一个<form>标签,而后在标签内一次渲染包括CSRF令牌在内的全部字段。除了渲染表单字段,它还会根据表单的验证状态来渲染表单状态和错误消息。通常状况下,你只须要传入表单类实例做为参数。除此以外,quick_form()宏还支持许多参数来自定义表单,经常使用的参数及说明以下表:html
render_form()宏经常使用参数:java
包括用来渲染表单的render_field()和render_form()宏在内,Bootstrap-Flask还提供了许多其余用途的宏,这些宏均可以经过bootstrap目录下的模板导入,经常使用的Bootstrap-Flask宏如表:jquery
在Message类中,咱们存储时间戳时使用的是datatime模块的datetime.now()方法生成的datetime对象,它是一个本地时间。具体来讲,这个方法会返回服务器(也就是运行程序的计算机)设置的时区所对应的时间。对于测试来讲这足够了,若是要把程序部署到真正的服务器上,就可能会面临时区问题。好比,我把程序部署到美国的服务器上,那么这个时间将再也不是咱们期待的东八区时间,而是服务器本地的美国时间。另外一方面,若是咱们的程序被其余时区的人访问,他们更但愿看到本身所在时区的时间,而不是固定的东八区时间。git
如何让世界各地的用户访问程序时都能看到本身的本地时间呢?一个简单的方法是使用JavaScript库在客户端(浏览器)中进行时间的转换,由于浏览器能够获取到用户浏览器/电脑上的时区设置信息。github
为了可以在客户端进行时间的转换,咱们须要在服务器端提供更纯正的时间(naive time),即不包含时区信息的时间戳(与之相对,包含时区的时间戳被称为细致的时间,即aware time)。datetime模块的datetime.utcnow()方法用来生成当前的UTC(Coordinated Universal Time, 协调世界时间),而UTC格式时间就是不包含时区信息的纯正时间。咱们将使用它在时间戳字段上替代以前的datetime.now方法,做为时间戳timestamp字段的默认值:web
from datetime import datetime class Message(db.Model): timestamp = db.Column(db.DateTime, default=datetime.utcnow)
Moment.js是一个用于处理时间和日期的开源JavaScript库,它能够对时间和日期进行各类方式的处理。它会根据用户电脑上的时区设置在客户端使用JavaScript来渲染时间和日期,另外还提供了丰富的时间渲染格式支持。sql
扩展Flask-Moment简化了在Flask项目中使用Moment.js的过程,集成了经常使用的时间和日期处理函数。首先使用pipenv安装:flask
pipenv install flask-moment
而后咱们实例化扩展提供的Moment类,并传入程序实例app,以完成扩展的初始化:
from flask_moment import Moment app = Flask(__name__) moment = Moment(app)
为了使用Moment.js,咱们须要在基模板中加入Moment.js资源。Flask-Moment在模板中提供了moment对象,这个对象提供了两个方法来加载资源:moment.include_moment()方法用来加载Moment.js的JavaScript资源;moment.include_jquery()用来加载jQuery。这两个方法默认从CDN加载资源,传入local_js参数能够指定本地资源URL。
咱们在使用Bootstrap时已经加载了jQuery,这里只须要加载Moment.js的JavaScript文件。
为了更好的管理资源,咱们将在程序中手动加载资源。首先访问moment.js官网下载相应的资源文件到static文件夹中:moment-with-locales.min.js,而后在基模板中引入。由于moment.include_moment()会用来生成执行时间渲染的JavaScript函数,因此咱们必须调用它,能够经过local_js参数传入本地资源的URL,若是不传入这个参数则会从CDN加载资源:
{{ moment.include_moment(local_js=url_for('static', filename='js/moment-with-locales.min.js')) }}
Moment.js官网提供的文件moment.min.js仅包含英文语言的 时间日期字符,若是要使用其余语言,须要下载moment-with-locales.min.js。
Flask-Moment默认以英文显示时间,咱们能够传入区域字符串到locale()方法来更改显示语言,下面在base.html中将语言设为简体中文:
… {{ moment.locale('zh-cn') }} </body>
在Moment.js中,简体中文的地区字符串为“zh-cn”,中国香港繁体中文和中国台湾繁体中文,则分别使用“zh-hk”和“zh-tw”。
除了使用locale参数固定地区,更合理的方式是根据用户浏览器或计算机的语言来设置语言,咱们能够在locale()方法中将auto_detect参数设为True,这会自动探测客户端语言设置并选择合适的区域设置:base.html
… {{ moment.locale(auto_detect=True) }} </body>
Moment.js提供了很是丰富、灵活的时间日期格式化方式。在模板中,咱们能够经过moment类调用format()方法来格式化时间和日期,moment的构造方法接收使用utcnow()方法建立的datetime对象做为参数,即Message对象的timestamp属性。format()方法接收特定的格式字符串来渲染时间格式,好比:
{{ moment(timestamp).format(‘格式字符串’) }}
时间日期会在页面加载完成后执行JavaScript函数使用Moment.js渲染,因此时间日期的显示会有微小的延迟。
Moment.js提供了一些内置的格式字符串,字符串及对应的中文输出实例以下:
咱们也能够经过Moment.js支持的时间单位参数自定义时间输出,好比使用格式字符串“YYYYMMMMDo, ah: mm: ss”将会获得输出:2019四月14日,早上9:00:00。
除了输出普通的时间日期,Moment.js还支持输出相对时间。好比相对于当前时间的“三分钟前”,“一个月前”等。这经过formNow()方法实现,在原做者新版本的示例中,时间戳就使用这个函数渲染:
<small>{{ moment(message.timestamp).fromNow(refresh=True) }}</small>
将refresh参数设为True(默认为False)可让时间戳在不重载页面的状况下,随着时间的变化自动刷新。若是在页面上等待一下子,就会看到时间戳从“几秒前”变成了“1分钟前”。
Flask-Moment实现了Moment.js的formt()、fromNow()、fromTime()、calendar()、valueof()和unix()方法,
有些时候,使用Flask-Moment提供的方法还不够灵活,这时能够手动使用Moment.js渲染时间日期。好比,当鼠标悬停在问候消息的时间日期上时,咱们但愿可以显示一个包含具体的绝对时间的弹出窗口(tooltip)。
为了可以在JavaScript中使用Moment.js渲染时间日期,咱们须要在显示相对时间的HTML元素中建立一个data-timestamp属性存储原始的时间戳,以便在JavaScript中获取:
index.html:
<small data-toggle="tooltip" data-placemoment="top" data-timestamp="{{ message.timestamp.strftime('%Y-%m-%dT%H:%M:%SZ') }}" data-delay="500"> {{ moment(message.timestamp).fromNow(refresh=True) }} </small>
为了让时间戳可以正常被Moment.js解析,咱们须要使用strftime()方法对原始的时间字符串按照ISO8061标准进行格式化处理。
咱们在script.js脚本存储JavaScript代码,下面的JavaScript代码将时间日期对应元素的tooltip内容设置为渲染后的时间日期:
$(function){ function render_time(){ return moment($(this).data('timestamp')).format('lll') } $('[data-toggle="tooltip"]').tooltip( {title: render_time} ); };
data-toggle指以什么事件触发,经常使用的如modal,popover,tooltips等
在Bootstrap中,Tooltip组件须要调用tooltip()方法进行初始化。咱们使用data-toggle属性做为选择器选择全部设置了tooltip的元素,对其调用tooltip()方法。在调用这个方式时,能够传入一些选项,如title选项用来设置弹出的内容,能够是字符串也能够是函数对象。
在渲染时间日期的render_time()函数中,渲染时间日期使用的moment()函数是由Moment.js提供的,而不是Flask-Moment传入模板的类。$(this).data(‘timestamp’)获取了对应元素的data-timestamp属性值,特殊变量this表示当前触发事件的元素对象。如今,当鼠标悬停在时间戳上时,会弹出包含具体时间的小窗口,以下所示:
在Bootstrap中,Popover和Tooltip组件依赖于JavaScript包Popper.js,要使用这两个组件,需确保在及模板中加载了对应的JavaScript文件。做为替代,你也能够加载Bootstrap提供的合集包文件bootstrap.bundle.min.css。
#encoding=utf-8 from flask import Flask from flask_bootstrap import Bootstrap from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy app = Flask('messageBoard') app.config.from_pyfile('settings.py') app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True db = SQLAlchemy(app) bootstrap = Bootstrap(app) moment = Moment(app) from messageBoard import views, errors, commands
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>{% block title %}How you are doing ?{% endblock %}</title> <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}" type="text/css"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css"> </head> <body> <main class="container"> <header> <h1 class="text-center display-4"> <a href="{{ url_for('index') }}" class="text-success"><strong>Leave Message</strong></a> <small class="text-muted sub-title">to the world</small> </h1> </header> {% for message in get_flashed_messages() %} <div class="alert alert-info"> <button type="button" class="close" data-dismiss="alert">×</button> {{ message }} </div> {% endfor %} {% block content %}{% endblock %} <footer class="text-center"> {% block footer %} <small> © 2019 <a href="https://www.cnblogs.com/xiaxiaoxu/" title="xiaxiaoxu's blog">夏晓旭的博客</a> / <a href="https://github.com/xiaxiaoxu/hybridDrivenTestFramework" title="Contact me on GitHub">GitHub</a> / <a href="http://helloflask.com" title="A HelloFlask project">Learning from GreyLi's HelloFlask</a> </small> <p><a id="bottom" href="#" title="Go Top">↑</a></p> {% endblock %} </footer> </main> <script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.2.1.slim.min.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/popper.min.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/script.js') }}"></script> {{ moment.include_moment(local_js=url_for('static', filename="js/moment-with-locales.min.js")) }} </body> </html>
{% extends 'base.html' %} {% from 'bootstrap/form.html' import render_form %} {% block content %} <div class="hello-form"> {{ render_form(form, action=request.full_path) }} </div> <h5>{{ messages|length }} messages <small class="float-right"> <a href="#bottom" title="Go Bottom">↓</a> </small> </h5> <div class="list-group"> {% for message in messages %} <a class="list-group-item list-group-item-action flex-column"> <div class="d-flex w-100 justify-content-between"> <h5 class="mb-1 text-success">{{ message.name }} <small class="text-muted"> #{{ loop.revindex }}</small> </h5> <small data-toggle="tooltip" data-placement="top" data-timestamp="{{ message.timestamp.strftime('%Y-%m-%dT%H:%M:%SZ') }}" data-delay="500"> {{ moment(message.timestamp).fromNow(refresh=True) }} </small> </div> <p class="mb-1">{{ message.body }}</p> </a> {% endfor %} </div> {% endblock %}
{{ moment(message.timestamp).fromNow(refresh=True) }}
这里用到的bootstrap和js的文件基本都是固定的框架文件,不需改动太大