最近在Flask Web Development作者博客看到第二版Flask Mega-Tutorial已在2017年底更新,现翻译给大家参考,希望帮助大家学习flask。
这是Flask Mega-Tutorial系列的第十二章,其中我将告诉你如何以适配所有用户的方式处理日期和时间,无论他们身处地球上的何处。
供您参考,以下是本系列文章的列表。
- 第1章:Hello, World!
- 第2章:模板
- 第3章:Web表单
- 第4章:数据库
- 第5章:用户登录
- 第6章:配置文件页面和头像
- 第7章:错误处理
- 第8章:关注与被关注
- 第9章:分页
- 第10章:电子邮件支持
- 第11章:整容
- 第12章:日期和时间(本文)
- 第13章:I18n和L10n
- 第14章:Ajax
- 第15章:大型应用程序结构
- 第16章:全文搜索
- 第17章:在Linux上部署
- 第18章:在Heroku上部署
- 第19章:Docker容器上的部署
- 第20章:一些JavaScript Magic
- 第21章:用户通知
- 第22章:后台工作
- 第23章:应用程序编程接口(API)
注意1:如果您正在寻找本教程的旧版本,请在此处。
注意2:如果您想在此博客上支持我的工作,或者只是没有耐心等待每周的文章,我将提供完整的本教程版本,打包成电子书或视频集。欲了解更多信息,请访问courses.miguelgrinberg.com。
显示日期和时间,在Microblog应用中,是我长期被忽略的一个方面。 直到现在,我也只是让Python渲染了User
模型中的datetime
对象,并且完全忽略了Post
模型中的datetime
对象。
本章的GitHub链接是:Browse, Zip, Diff。
时区地域
使用服务器端的Python渲染日期和时间来展示到用户的浏览器并非一个好主意。考虑如下的例子, 我在2017年9月28日下午4点06分撰写本文。我身处的时区是PDT(UTC-7),在Python解释器中运行,得到如下信息:
>>> from datetime import datetime
>>> str(datetime.now())
'2017-09-28 16:06:30.439388'
>>> str(datetime.utcnow())
'2017-09-28 23:06:51.406499'
datetime.now()
调用返回我所处位置的本地时间,而datetime.utcnow()
调用则返回UTC时区的时间。如果我可以让世界各地的人与我同时运行上述代码,那么datetime.now()
函数将为每个人返回不同的结果,但是无论在什么位置,datetime.utcnow()
函数总是返回相同的时间。那么,您认为哪一种更适合在用户遍布全球的Web应用中使用呢?
很明显,服务器必须管理统一且独立于位置的时间。如果这个应用增长到需要在全球不同地区部署多个生产服务器的地步,我就不希望每个服务器根据各自时区将时间戳的写入数据库中,因为这将使统一处理这些时间变得不可能。由于UTC是最常用的统一时区,并且在datetime
类中受支持,因此我将使用UTC 。
但这样的话,存在一个严重的问题。对处于不同时区的用户,如果他们看到的是UTC时间,将很难确定发帖的时间。他们需要提前知道看到的是UTC时间,以便他们可以在心理上将UTC时间调整到自己时区的时间。想象一下,在PDT时区中的某个用户在下午3:00发布某个内容,然后立即看到该帖子的显示时间是UTC时间10:00 pm,或更确切地说是22:00,这样时间就太混乱了。
从服务器的角度来看,将时间戳标准化为UTC是很有意义的,但这给用户带来了可用性问题。本章的目的是要解决该问题,同时保持在服务器中使用UTC时区管理所有的时间戳。
时区转换
直接解决该问题的方法是将存储的所有时间戳,由UTC时间转换为每个用户的本地时间。这样一来,服务器可以继续使用UTC来保持时区的一致性,同时为每个用户量身定制的即时转换又解决了可用性问题。这个解决方案的棘手部分,就是知道每个用户的位置。
许多网站都有一个配置页面,用户可以在其中指定时区。这将需要我添加一个带表单的新页面,在该表单中可以向用户显示带有时区列表的下拉列表。也可以在首次注册时,要求用户输入时区,作为他们注册的一部分。
虽然这是解决问题的一种不错的方案,但要求用户输入他们已经在操作系统中配置的信息有点奇怪。如果我能从他们的计算机上获取时区设置,似乎会更有效率。
事实证明,Web浏览器可以获得用户的时区,并通过标准的日期和时间JavaScript API暴露该时区。实际上,有两种方法可以利用可通过JavaScript获得的时区信息:
- “老派”方法是让在用户首次登录应用程序时,网络浏览器以某种方式将时区信息发送到服务器。这可以通过Ajax调用来完成,或者更简单地通过meta refresh标签来完成。一旦服务器知道了时区,就可以将其保留在用户会话中,或将其写入数据库中的用户条目,然后在渲染模板时使用其调整所有时间戳。
- “新派”方法是不更改服务器中的任何内容,而在客户端中使用JavaScript进行从UTC到本地时区的转换。
这两个选项都是有效的,但是第二个选项具有很大的优势。知道用户的时区并不总是足以以用户期望的格式显示日期和时间。浏览器还可以访问系统区域设置配置,该配置指定诸如AM / PM与24小时制,DD / MM / YYYY与MM / DD / YYYY之类的东西,以及许多其他文化或地区样式。
如果这还不够,那么新派方法还有另一个优势,就是用一个开源库可以完成所有这些工作!
引入Moment.js和Flask-Moment
Moment.js是一个小型的JavaScript开源库,它将日期和时间转换成目前可以想象到的所有格式。不久前,我创建了Flask-Moment,这是一个小的Flask插件,可以很轻松地将moment.js集成到您的应用程序中。
因此,让我们从安装Flask-Moment开始:
(venv) $ pip install flask-moment
使用常规方法添加该插件到Flask中:
app / __ init__.py:Flask-Moment实例。
# ...
from flask_moment import Moment
app = Flask(__name__)
# ...
moment = Moment(app)
与其他插件不同,Flask-Moment与moment.js一起使用,因此应用的所有模板都必须包含此库。为了确保该库始终可用,我将其添加到基本模板中。这可以通过两种方式完成。最直接的方法是显式添加一个<script>
标签来导入库,但是Flask-Moment通过暴露一个moment.include_moment()
函数可以更容易地实现它,它
生成<script>
标签并在其中包含moment.js:
app / templates / base.html:在基本模板中包含moment.js。
...
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
我在这里添加的scripts
块是Flask-Bootstrap基础模板暴露的另一个块,这是JavaScript引入的地方。该块与之前的块不同的地方在于它已经在基础模板中定义了一些内容了。我想要追加moment.js库的话,就需要使用super()
语句,才能继承基础模板中已有的内容,否则就是替换。
使用Moment.js
Moment.js为浏览器提供了一个moment
类。渲染时间戳的第一步是创建此类的对象,并以ISO 8601格式传递所需的时间戳。这里是一个例子:
t = moment('2017-09-28T21:45:23Z')
如果您不熟悉日期和时间的ISO 8601标准格式,则格式如下:{{ year }}-{{ month }}-{{ day }}T{{ hour }}:{{ minute }}:{{ second }}{{ timezone }}
。我已经决定只使用UTC时区,因此最后一部分始终是Z
,它代表ISO 8601标准中的UTC。
moment
对象提供了几种用于不同渲染选项的方法。以下是一些最常见的选项:
moment('2017-09-28T21:45:23Z').format('L')
"09/28/2017"
moment('2017-09-28T21:45:23Z').format('LL')
"September 28, 2017"
moment('2017-09-28T21:45:23Z').format('LLL')
"September 28, 2017 2:45 PM"
moment('2017-09-28T21:45:23Z').format('LLLL')
"Thursday, September 28, 2017 2:45 PM"
moment('2017-09-28T21:45:23Z').format('dddd')
"Thursday"
moment('2017-09-28T21:45:23Z').fromNow()
"7 hours ago"
moment('2017-09-28T21:45:23Z').calendar()
"Today at 2:45 PM"
本示例创建一个moment对象,该对象初始化为2017年9月28日晚上9:45 UTC。您可以看到,我在上面尝试过的所有选项都是在UTC-7中渲染的,UTC-7是在计算机上配置的时区。您可以在浏览器的控制台中输入上述命令,确保打开控制台的页面上引入了moment.js。您可以在应用中做到这一点,只要您进行了上述更改以引入moment.js,或者也可以在https://momentjs.com/上进行。
注意不同的方法是如何创建不同的表示形式。使用format()
格式字符串控制输出的格式,类似于Python的strftime函数。fromNow()
和calendar()
,因为它们相对于当前时间的时间戳渲染,所以你可以获得诸如“在两小时内”、“一分钟前”等输出。
如果您直接使用JavaScript工作,则上述调用将返回带有渲染时间戳的字符串。然后由您自己决定将此文本插入页面上的适当位置,不幸的是,这需要一些JavaScript才能与DOM一起使用。Flask-Moment扩展通过moment
在模板中启用类似于JavaScript的对象,大大简化了moment.js的使用。
让我们看一下配置文件页面中显示的时间戳。当前的user.html模板可让Python生成时间的字符串表示形式。我现在可以使用Flask-Moment渲染此时间戳,如下所示:
app / templates / user.html:使用moment.js渲染时间戳。
{% if user.last_seen %}
<p>Last seen on: {{ moment(user.last_seen).format('LLL') }}</p>
{% endif %}
如您所见,Flask-Moment使用的语法类似于JavaScript库的语法,不同之处在于,moment()的参数
现在是Python datetime
对象,而不是ISO 8601字符串。从模板发出的moment()
调用还会自动生成所需的JavaScript代码,以将渲染的时间戳插入DOM的适当位置。
我可以利用Flask-Moment和moment.js的第二个地方是从主页和用户页面调用的_post.html子模板。在模板的当前版本中,每个帖子前面都以“用户名说:”行开头。现在,我可以添加使用fromNow()
渲染的时间戳:
app / templates / _post.html:在发布子模板中渲染时间戳。
<a href="{{ url_for('user', username=post.author.username) }}">
{{ post.author.username }}
</a>
said {{ moment(post.timestamp).fromNow() }}:
<br>
{{ post.body }}
在下面,您可以看到使用Flask-Moment和moment.js渲染时这两个时间戳的外观:
原文链接:https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xii-dates-and-times