如需转载请注明出处。
win10 64位、Python 3.6.3、Notepad++、Chrome 67.0.3396.99(正式版本)(64 位)
注:作者编写时间2018-02-21,linux、python 3.5.2
以下内容均是加入自己的理解与增删,以记录学习过程。不限于翻译,部分不完全照搬作者Miguel Grinberg的博客,版权属于作者,感谢他提供免费学习的资料。
目前为止,一直忽略Microblog应用程序显示日期和时间的问题,只是让Python渲染了User
模型中的datetime
对象,并完全忽略Post
模型中的datetime
对象。
时区“地狱”
在服务器上用 Python去呈现日期和时间,在Web浏览器上以这种方式渲染给用户可不是一个好主意。如示例,在Python解释器中运行如下内容:(venv) D:\microblog>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> str(datetime.now())
'2018-08-23 09:52:14.895893'
>>> str(datetime.utcnow())
'2018-08-23 01:52:28.247986'
>>> quit()
(venv) D:\microblog>
调用datetime.now()
将返回我当前所在地区(中国-北京(时间))的正确的时间,而调用datetime.utcnow()
将返回UTC时区的时间。如果我能让生活在世界不同地区的许多人同时一起运行上述代码,datetime.now()
函数将为每个人返回不同的结果,但datetime.utcnow()
无论地理位置在哪,都将返回相同的时间。那么你认为哪一个更适合一个很可能让用户遍布全球的Web应用程序中使用?
很明显,服务器必须管理 一致且独立于位置的时间。如果这个应用程序增长到世界各地需要多个生产服务器的程序,当然就不希望每个服务器在不同时区写入数据库的时间戳,因为这样就无法使用这些时间。由于UTC是最常用的统一时区,并且在datetime类
中受支持,因此在此将使用它。
但这种方法存在一个重要问题。对于不同时区的用户,如果他们在UTC看到时间,那么将很难弄清楚发布帖子的时间。他们需要提前时间是UTC,以便他们能够“精神上”调整时间到他们自己的时区。想象一下,PDT时区的用户在下午3点发布了一些内容,并立即看到这个帖子在UTC时间晚上10点出现,或更准确的说是22:00。这将是很令人困惑的。
虽然从服务器的角度上,将时间戳标准化很有意义,但这会用户带来可用性问题。本章的目标是解决这个问题,同时保持服务器以UTC格式管理的所有时间戳。
时区转换
这个问题显而易见的解决方案是:将所有时间戳 从存储的UTC单位转换为每个用户的本地时间。这允许服务器继续使用UTC来保持一致性,同时为每个用户量身定制地即时转换解决可用性问题。这个解决方案的棘手部分 是了解每个用户的位置。许多网站都有一个配置页面,用户可以在其中指定时区。这将要求我添加一个带表单的新页面,这个表单中,我可以向用户显示带有时区列表的下拉列表。作为注册的一部分,用户可以在第一次访问网站时要求输入他们的时区。
虽然这是解决问题的一个不错解决方案,但要求用户输入他们已在其操作系统中配置的信息有点奇怪。如果能从他们的计算机中获取时区设置似乎会更有效率。
事实证明,Web浏览器知道用户的时区,并通过标准日期和时间JavaScript API公开它。实际上,通过JavaScript,有两种方法可利用时区信息:
- “老派”方法是在用户首次登陆应用程序时,让Web浏览器以某种方式将时区信息发送到服务器。这可以通过Ajax调用完成,或更简单地使用
meta refresh tag
。一旦服务器知道时区,它就可以将其保存在用户的会话中,或将其写入数据库中的用户条目,然后在渲染模板时用它调整所有时间戳。 - “新派”方法是不改变服务器中的东西,而在客户端中使用JavaScript在客户端中进行从UTC到本地时区的转换。
这两个选项都是有效的,但第二个选项有很大的优势。光是知道用户的时区,并不足以以用户期望的格式呈现日期和时间。浏览器还可访问系统区域配置,这个配置指定AP/PM与24小时制,DD/MM/YYYY与MM/DD/YYYY,以及许多其他文化或区域样式之类的内容。
如果上述还不够,那么“新派”方法还有一个优势。有一个开源库可完成所有这些工作。
介绍Moment.js和Flask-Moment
[Moment.js](http://momentjs.com/)是一个小型开源JavaScript库,它将日期和时间渲染成另一个级别,因为它提供每一个可想象的格式化选项。不久前,建立了 Flask-Moment,它是一个小型 Flask扩展,它可以轻易地将`moment.js`合并到应用程序中。首先,安装Flask-Moment:版本0.6.0
(venv) D:\microblog>pip install flask-moment
Collecting flask-moment
Using cached https://files.pythonhosted.org/packages/dd/f7/13e9d7480f9097e0efe945e17309c34e0a547a6cfb3f9728324d2f9bf462/Flask_Moment-0.6.0-py2.py3-none-any.whl
Requirement already satisfied: Flask in d:\microblog\venv\lib\site-packages (from flask-moment)
Requirement already satisfied: Jinja2>=2.10 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: itsdangerous>=0.24 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: Werkzeug>=0.14 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: click>=5.1 in d:\microblog\venv\lib\site-packages (from Flask->flask-moment)
Requirement already satisfied: MarkupSafe>=0.23 in d:\microblog\venv\lib\site-packages (from Jinja2>=2.10->Flask->flask-moment)
Installing collected packages: flask-moment
Successfully installed flask-moment-0.6.0
以常规方式添加到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>
标签。
app/templates/base.html:在基础模板中包含moment.js
...
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
在这添加的scripts块
是Flask-Bootstrap的基础模板导出的另一个块。这是包含JavaScript导入的地方。这个块与之前的块不同,因为它已经在基础模板中定义了一些内容。我想的是 添加moment.js
库,而不会失去基本内容。这是通过super()
语句实现的,这个语句将保留基础模板中的内容。如果在没有使用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"
上述示例 创建了一个时刻对象,初始化为 2017年9月28日晚上9:45 UTC
。上面尝试的所有选项都以UTC-7时区(在中国的话,将用UTC+8)来呈现,因为这是作者的计算机上配置的时区。可在浏览器的控制台中输入上述命令,得确保打开控制台的页面包含moment.js。如果引入了moment.js,也可以在Microblog上操作。或者在https://momentjs.com/上操作。
注意不同方法是如何创建不同的表示的。使用format()
,可以控制字符串的输出格式,类似Python中的strftime()
函数。fromNow()
和calendar()
方法很有趣,因为它们会根据当前时间显示时间戳,所以会得到如“一分钟前”或“两个小时内”的输出。
如果直接使用JavaScript,那么上述调用将返回具有呈现时间戳的字符串。然后,可将此文本插入页面上的适当位置,遗憾的是,需要JavaScript与DOM配合使用。Flask-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
子模板,它是从/index
和/user
页面中调用的。在当前版本的模板中,每个帖子前面都有一个“username says:”行
。现在添加一个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 run
运行程序,效果:
C:\Users\Administrator>d:
D:\>cd D:\microblog\venv\Scripts
D:\microblog\venv\Scripts>activate
(venv) D:\microblog\venv\Scripts>cd D:\microblog
(venv) D:\microblog>set MAIL_SERVER=smtp.qq.com
(venv) D:\microblog>set MAIL_PORT=465
(venv) D:\microblog>set MAIL_USE_SSL=True
(venv) D:\microblog>set MAIL_USERNAME=your_qq_email@qq.com
(venv) D:\microblog>set MAIL_PASSWORD=16位授权码
(venv) D:\microblog>flask run
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
[2018-08-23 15:10:27,042] INFO in __init__: Microblog startup
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [23/Aug/2018 15:10:36] "GET /user/oldiron HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:04] "GET /edit_profile HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:05] "GET /index HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:08] "GET /user/oldiron HTTP/1.1" 200 -
127.0.0.1 - - [23/Aug/2018 15:11:21] "GET /explore HTTP/1.1" 200 -
目前为止,项目结构:
microblog/ app/ templates/ email/ reset_password.html reset_password.txt _post.html 404.html 500.html base.html edit_profile.html index.html login.html register.html reset_password.html reset_password_request.html user.html __init__.py email.py errors.py forms.py models.py routes.py logs/ microblog.log migrations/ venv/ app.db config.py microblog.py tests.py
如需转载请注明出处。