如需轉載請注明出處。
win10 64位、Python 3.6.3、Notepad++、Chrome 67.0.3396.99(正式版本)(64 位)
注:作者編寫時間2018-02-21,linux、python 3.5.2
以下内容均是加入自己的了解與增删,以記錄學習過程。不限于翻譯,部分不完全照搬作者Miguel Grinberg的部落格,版權屬于作者,感謝他提供免費學習的資料。
傳送門 | |||
---|---|---|---|
00 開篇 | 01 Hello world | 02 模闆 | 03 Web表單 |
04 資料庫 | 05 使用者登入 | 06 個人資料和頭像 | 07 錯誤處理 |
08 關注 | 09 分頁 | 10 支援QQ郵箱 | 11 美化頁面 |
12 時間和日期 | 13 I18n和L10n 翻譯成中文 zh-CN | 14 Ajax(百度翻譯API | 15 更好的App結構(藍圖) |
16 全文搜尋 | 17 部署到騰訊雲Ubuntu | 18 部署到Heroku | 19 部署到Docker容器 |
20 JavaScript魔法 | 21 使用者通知 | 22 背景工作(Redis) | 23 應用程式程式設計接口(API) |
本章将學習如何以适合所有使用者的方式使用
日期和時間,無論他們身居何處。
目前為止,一直忽略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) }}" target="_blank" rel="external nofollow" >
{{ 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 [email protected]
(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 -
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1TPB50MRpmTxkERPpHOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DN5YDO1QTM1EzMygDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
目前為止,項目結構:
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
參考:
作者部落格
源代碼
如需轉載請注明出處。