1. 程式人生 > >Flask 教程 第六章:個人主頁和頭像

Flask 教程 第六章:個人主頁和頭像

這是Flask Mega-Tutorial系列的第六部分,我將告訴你如何建立個人主頁。

本章將致力於為應用添加個人主頁。個人主頁用來展示使用者的相關資訊,其個人資訊由本人錄入。 我將為你展示如何動態地生成每個使用者的主頁,並提供一個編輯頁面給他們來更新個人資訊。

本章的GitHub連結為:BrowseZipDiff.

個人主頁

作為建立個人主頁的第一步,讓我們為其URL /user/ 新建一個對應的檢視函式。

@app.route('/user/<username>')
@login_required
def user(username):
    user = User.query.filter_by(username=username).first_or_404()
    posts = [
        {'author'
: user, 'body': 'Test post #1'}, {'author': user, 'body': 'Test post #2'} ] return render_template('user.html', user=user, posts=posts)

我用來裝飾該檢視函式的@app.route裝飾器看起來和之前的有點不一樣。 本例中被<>包裹的URL <username>是動態的。 當一個路由包含動態元件時,Flask將接受該部分URL中的任何文字,並將以實際文字作為引數呼叫該檢視函式。 例如,如果客戶端瀏覽器請求URL /user/susan

,則檢視函式將被呼叫,其引數username被設定為'susan'。 因為這個檢視函式只能被已登入的使用者訪問,所以我添加了@login_required裝飾器。

這個檢視函式的實現相當簡單。 我首先會嘗試在資料庫中以使用者名稱來查詢和載入使用者。 之前你見過通過呼叫all()來得到所有的結果的查詢,或是呼叫first()來得到結果中的第一個或者結果集為空時返回None的查詢。 在本檢視函式中,我使用了first()的變種方法,名為first_or_404(),當有結果時它的工作方式與first()完全相同,但是在沒有結果的情況下會自動傳送404 error給客戶端。 以這種方式執行查詢,我省去檢查使用者是否返回的步驟,因為當用戶名不存在於資料庫中時,函式將不會返回,而是會引發404異常。

如果執行資料庫查詢沒有觸發404錯誤,那麼這意味著找到了具有給定使用者名稱的使用者。 接下來,我為這個使用者初始化一個虛擬的使用者動態列表,最後用傳入的使用者物件和使用者動態列表渲染一個新的user.html模板。

user.html模板如下所示:

{% extends "base.html" %}{% block content %}
    <h1>User: {{ user.username }}</h1>
    <hr>
    {% for post in posts %}
    <p>
    {{ post.author.username }} says: <b>{{ post.body }}</b>
    </p>
    {% endfor %}{% endblock %}

個人主頁雖然已經完成了,但是網站上卻沒有一個入口連結。我將會在頂部的導航欄中新增這個入口連結,以便使用者可以輕鬆檢視自己的個人資料:

    <div>
      Microblog:
      <a href="{{ url_for('index') }}">Home</a>
      {% if current_user.is_anonymous %}
      <a href="{{ url_for('login') }}">Login</a>
      {% else %}
      <a href="{{ url_for('user', username=current_user.username) }}">Profile</a>
      <a href="{{ url_for('logout') }}">Logout</a>
      {% endif %}
    </div>

這裡唯一有趣的變化是用來生成連結到個人主頁的url_for()呼叫。 由於個人主頁檢視函式接受一個動態引數,所以url_for()函式接收一個值作為關鍵字引數。 由於這是一個指向當前登入個人主頁的連結,我可以使用Flask-Login的current_user物件來生成正確的URL。

個人主頁

嘗試點選頂部的Profile連結就能將你帶到自己的個人主頁。 此時,雖然沒有連結來訪問其他使用者的主頁,但是如果要訪問這些頁面,則可以在瀏覽器的位址列中手動輸入網址。 例如,如果你在應用中註冊了名為“john”的使用者,則可以通過在位址列中鍵入http:// localhost:5000/user/john來檢視該使用者的個人主頁。

頭像

我相信你也覺得我剛剛建立的個人主頁非常枯燥乏味。為了使它們更加有趣,我將新增使用者頭像。與其在伺服器上處理大量的上傳圖片,我將使用Gravatar為所有使用者提供圖片服務。

Gravatar服務使用起來非常簡單。 要請求給定使用者的圖片,使用格式為的URL即可,其中<hash>是使用者的電子郵件地址的MD5雜湊值。 在下面,你可以看到如何生成電子郵件為[email protected]的使用者的Gravatar URL:

>>> from hashlib import md5
>>> 'https://www.gravatar.com/avatar/' + md5(b'[email protected]').hexdigest()
'https://www.gravatar.com/avatar/d4c74594d841139328695756648b6bd6'

Miguel的頭像

另一個可傳遞給Gravatar的有趣引數是d,它讓Gravatar為沒有向服務註冊頭像的使用者提供的隨機頭像。 我最喜歡的隨機頭像型別是“identicon”,它為每個郵箱都返回一個漂亮且不重複的幾何設計圖片。 如下:

Identicon頭像

請注意,一些Web瀏覽器外掛(如Ghostery)會遮蔽Gravatar影象,因為它們認為Automattic(Gravatar服務的所有者)可以根據你傳送的獲取頭像的請求來判斷你正在訪問的網站。 如果在瀏覽器中看不到頭像,你在排查問題的時候可以考慮以下是否在瀏覽器中安裝了此類外掛。

由於頭像與使用者相關聯,所以將生成頭像URL的邏輯新增到使用者模型是有道理的。

from hashlib import md5
# ...

class User(UserMixin, db.Model):
    # ...
    def avatar(self, size):
        digest = md5(self.email.lower().encode('utf-8')).hexdigest()
        return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
            digest, size)

User類新增的avatar()方法需要傳入需求頭像的畫素大小,並返回使用者頭像圖片的URL。 對於沒有註冊頭像的使用者,將生成“identicon”類的隨機圖片。 為了生成MD5雜湊值,我首先將電子郵件轉換為小寫,因為這是Gravatar服務所要求的。 然後,因為Python中的MD5的引數型別需要是位元組而不是字串,所以在將字串傳遞給該函式之前,需要將字串編碼為位元組。

如果你對Gravatar服務很有興趣,可以學習他們的文件

下一步需要將頭像圖片插入到個人主頁的模板中:

{% extends "base.html" %}{% block content %}
    <table>
        <tr valign="top">
            <td><img src="{{ user.avatar(128) }}"></td>
            <td><h1>User: {{ user.username }}</h1></td>
        </tr>
    </table>
    <hr>
    {% for post in posts %}
    <p>
    {{ post.author.username }} says: <b>{{ post.body }}</b>
    </p>
    {% endfor %}{% endblock %}

使用User類來返回頭像URL的好處是,如果有一天我不想繼續使用Gravatar頭像了,我可以重寫avatar()方法來返回其他頭像服務網站的URL,所有的模板將自動顯示新的頭像。

我的個人主頁的頂部有一個不錯的大頭像,不止如此,底下的所有使用者動態都會有一個小頭像。 對於個人主頁而言,所有的頭像當然都是對應使用者的。我將會在主頁面上實現每個使用者動態都用其作者的頭像來裝飾,這樣一來看起來就非常棒了。

為了顯示使用者動態的頭像,我只需要在模板中進行一個小小的更改:

{% extends "base.html" %}{% block content %}
    <table>
        <tr valign="top">
            <td><img src="{{ user.avatar(128) }}"></td>
            <td><h1>User: {{ user.username }}</h1></td>
        </tr>
    </table>
    <hr>
    {% for post in posts %}
    <table>
        <tr valign="top">
            <td><img src="{{ post.author.avatar(36) }}"></td>
            <td>{{ post.author.username }} says:<br>{{ post.body }}</td>
        </tr>
    </table>
    {% endfor %}{% endblock %}

頭像

使用Jinja2子模板

我設計的個人主頁,使用頭像和文字組合的方式來展示了使用者動態。 現在我想在主頁也使用類似的風格來佈局。 我可以複製/貼上來處理使用者動態渲染的模板部分,但這實際上並不理想,因為之後如果我想要對此佈局進行更改,我將不得不記住要更新兩個模板。

取而代之,我要建立一個只渲染一條使用者動態的子模板,然後在user.htmlindex.html模板中引用它。 首先,我要建立這個只有一條使用者動態HTML元素的子模板。 我將其命名為app/templates/_post.html_字首只是一個命名約定,可以幫助我識別哪些模板檔案是子模板。

    <table>
        <tr valign="top">
            <td><img src="{{ post.author.avatar(36) }}"></td>
            <td>{{ post.author.username }} says:<br>{{ post.body }}</td>
        </tr>
    </table>

我在user.html模板中使用了Jinja2的include語句來呼叫該子模板:

{% extends "base.html" %}{% block content %}
    <table>
        <tr valign="top">
            <td><img src="{{ user.avatar(128) }}"></td>
            <td><h1>User: {{ user.username }}</h1></td>
        </tr>
    </table>
    <hr>
    {% for post in posts %}{% include '_post.html' %}{% endfor %}{% endblock %}

應用的主頁還沒有完善,所以現在我不打算在其中新增這個功能。

更多有趣的個人資料

新增的個人主頁存在的一個問題是,真正顯示的內容不夠豐富。 使用者喜歡在個人主頁上展示他們的相關資訊,所以我會讓他們寫一些自我介紹並在這裡展示。 我也將跟蹤每個使用者最後一次訪問該網站的時間,並顯示在他們的個人主頁上。

為了支援所有這些額外的資訊,首先需要做的是用兩個新的欄位擴充套件資料庫中的使用者表:

class User(UserMixin, db.Model):
    # ...
    about_me = db.Column(db.String(140))
    last_seen = db.Column(db.DateTime, default=datetime.utcnow)

每次資料庫被修改時,都需要生成資料庫遷移。 在第四章中,我向你展示瞭如何設定應用以通過遷移指令碼跟蹤資料庫的變更。 現在有兩個新的欄位我想新增到資料庫中,所以第一步是生成遷移指令碼:

(venv) $ flask db migrate -m "new fields in user model"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'user.about_me'
INFO  [alembic.autogenerate.compare] Detected added column 'user.last_seen'
  Generating /home/miguel/microblog/migrations/versions/37f06a334dbf_new_fields_in_user_model.py ... done

migrate命令的輸出表示一切正確執行,因為它顯示User類中的兩個新欄位已被檢測到。 現在我可以將此更改應用於資料庫:

(venv) $ flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 780739b227a7 -> 37f06a334dbf, new fields in user model

我希望你認識到使用遷移框架是多麼有用。 資料庫中的使用者資料仍然存在,遷移框架如同實施手術教學般地精準執行遷移指令碼中的更改並且不損壞任何資料。

下一步,我將會把新增的兩個欄位增加到個人主頁中:

{% extends "base.html" %}{% block content %}
    <table>
        <tr valign="top">
            <td><img src="{{ user.avatar(128) }}"></td>
            <td>
                <h1>User: {{ user.username }}</h1>
                {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}{% if user.last_seen %}<p>Last seen on: {{ user.last_seen }}</p>{% endif %}
            </td>
        </tr>
    </table>
    ...
{% endblock %}

請注意,我用Jinja2的條件語句來封裝了這兩個欄位,因為我只希望它們在設定後才可見。 目前,所有使用者的這兩個欄位都是空的,所以如果現在執行應用,則不會看到這些欄位。

記錄使用者的最後訪問時間

讓我們從更容易實現的last_seen欄位開始。 我想要做的就是一旦某個使用者向伺服器傳送請求,就將當前時間寫入到這個欄位。

為每個檢視函式新增更新這個欄位的邏輯,這麼做非常的枯燥乏味。在檢視函式處理請求之前執行一段簡單的程式碼邏輯在Web應用中十分常見,因此Flask提供了一個內建功能來實現它。解決方案如下:

from datetime import datetime

@app.before_request
def before_request():
    if current_user.is_authenticated:
        current_user.last_seen = datetime.utcnow()
        db.session.commit()

Flask中的@before_request裝飾器註冊在檢視函式之前執行的函式。這是非常有用的,因為現在我可以在一處地方編寫程式碼,並讓它在任何檢視函式之前被執行。該程式碼簡單地實現了檢查current_user是否已經登入,並在已登入的情況下將last_seen欄位設定為當前時間。我之前提到過,應用應該以一致的時間單位工作,標準做法是使用UTC時區,使用系統的本地時間不是一個好主意,因為如果那麼的話,資料庫中儲存的時間取決於你的時區。最後一步是提交資料庫會話,以便將上面所做的更改寫入資料庫。如果你想知道為什麼在提交之前沒有db.session.add(),考慮在引用current_user時,Flask-Login將呼叫使用者載入函式,該函式將執行一個數據庫查詢並將目標使用者新增到資料庫會話中。所以你可以在這個函式中再次新增使用者,但是這不是必須的,因為它已經在那裡了。

如果在進行此更改後檢視你的個人主頁,則會看到“Last seen on”行,並且時間非常接近當前時間。 如果你離開個人主頁,然後返回,你會看到時間在不斷更新。

事實上,我在儲存時間和在個人主頁顯示時間的時候,使用的都是UTC時區。 除此之外,顯示的時間格式也可能不是你所預期的,因為實際上它是Python datetime物件的內部表示。 現在,我不會操心這兩個問題,因為我將在後面的章節中討論在Web應用中處理日期和時間的主題。

最後訪問時間

個人資料編輯器

我還需要給使用者一個表單,讓他們輸入一些個人資料。 表單將允許使用者更改他們的使用者名稱,並且寫一些個人介紹,以儲存在新的about_me欄位中。 讓我們開始為它寫一個表單類吧:

from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length

# ...

class EditProfileForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    about_me = TextAreaField('About me', validators=[Length(min=0, max=140)])
    submit = SubmitField('Submit')

我在這個表單中使用了一個新的欄位型別和一個新的驗證器。 對於“about_me”欄位,我使用TextAreaField,這是一個多行輸入文字框,使用者可以在其中輸入文字。 為了驗證這個欄位的長度,我使用了Length,它將確保輸入的文字在0到140個字元之間,因為這是我為資料庫中的相應欄位分配的空間。

該表單的渲染模板程式碼如下:

{% extends "base.html" %}{% block content %}
    <h1>Edit Profile</h1>
    <form action="" method="post">
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.about_me.label }}<br>
            {{ form.about_me(cols=50, rows=4) }}<br>
            {% for error in form.about_me.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

最後一步,使用檢視函式將它們結合起來:

from app.forms import EditProfileForm

@app.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():
    form = EditProfileForm()
    if form.validate_on_submit():
        current_user.username = form.username.data
        current_user.about_me = form.about_me.data
        db.session.commit()
        flash('Your changes have been saved.')
        return redirect(url_for('edit_profile'))
    elif request.method == 'GET':
        form.username.data = current_user.username
        form.about_me.data = current_user.about_me
    return render_template('edit_profile.html', title='Edit Profile',
                           form=form)

這個檢視函式處理表單的方式和其他的檢視函式略有不同。如果validate_on_submit()返回True,我將表單中的資料複製到使用者物件中,然後將物件寫入資料庫。但是當validate_on_submit()返回False時,可能是由於兩個不同的原因。這可能是因為瀏覽器剛剛傳送了一個GET請求,我需要通過提供表單模板的初始版本來響應。也可能是這種情況,瀏覽器傳送帶有表單資料的POST請求,但該資料中的某些內容無效。對於該表單,我需要區別對待這兩種情況。當第一次請求表單時,我用儲存在資料庫中的資料預填充欄位,所以我需要做與提交相反的事情,那就是將儲存在使用者欄位中的資料移動到表單中,這將確保這些表單欄位具有使用者的當前資料。但在驗證錯誤的情況下,我不想寫任何表單欄位,因為它們已經由WTForms填充了。為了區分這兩種情況,我需要檢查request.method,如果它是GET,這是初始請求的情況,如果是POST則是提交表單驗證失敗的情況。

個人資料編輯器

我將個人資料編輯頁面的連結新增到個人主頁,以便使用者使用:

                {% if user == current_user %}
                <p><a href="{{ url_for('edit_profile') }}">Edit your profile</a></p>
                {% endif %}

請注意我巧妙使用的條件,它確保在檢視自己的個人主頁時出現編輯個人資料的連結,而在檢視其他人的個人主頁時不會出現。

個人主頁和編輯連結

相關推薦

Flask 教程 個人主頁頭像

這是Flask Mega-Tutorial系列的第六部分,我將告訴你如何建立個人主頁。 本章將致力於為應用添加個人主頁。個人主頁用來展示使用者的相關資訊,其個人資訊由本人錄入。 我將為你展示如何動態地生成每個使用者的主頁,並提供一個編輯頁面給他們來更新個人

Flask 教程 十三國際化本地化

這是Flask Mega-Tutorial系列的第十三部分,我將告訴你如何擴充套件Microblog應用以支援多種語言。 作為其中的一部分,你還將學習如何為flask命令建立自己的CLI擴充套件。 本章的主題是國際化和本地化,通常縮寫為I18n和L10n。

Flask 教程 錯誤處理

這是Flask Mega-Tutorial系列的第七部分,我將告訴你如何在Flask應用中進行錯誤處理。 本章將暫停為microblog應用開發新功能,轉而討論處理BUG的策略,因為它們總是無處不在。為了幫助本章的演示,我故意在第六章新增的程式碼中遺留了一

Flask 教程 郵件支援

這是Flask Mega-Tutorial系列的第十部分,在其中我將告訴你,應用如何向你的使用者傳送電子郵件,以及如何在電子郵件支援之上構建密碼重置功能。 現在,應用在資料庫方面做得相當不錯,所以在本章中,我想拋開這個主題,開始添加發送電子郵件的功能,這是

GObject學習教程---GObject 的子類繼承

本文是學習學習他人的部落格的心得(具體詳見“樓主見解”),如果源網站可訪問的話,建議直接訪問源網站: 樓主見解: 此章節和繼承GObject一樣,只是換一個基類而已,繼承機制一樣。 f:GObject 的子類繼承 在文件 [1] 中,我們構造了一個 KbB

資料庫系統概念(機械工業出版社,七版)複習——資料庫設計E-R模型

E-R模型 實體-聯絡模型:Entity-Relationship Model E-R圖要點 實體(Entity) 客觀存在並可相互區分的事物叫實體(唯一標識)。 實體集(Entity Set) 是具有相同型別及共享相同性質(屬性)的實體集合。如全體學生。組成實體集的各實

Node入門教程(8)path 模塊詳解

format QQ 調用 保留 微軟 posix interface join 結果 path 模塊詳解 path 模塊提供了一些工具函數,用於處理文件與目錄的路徑。由於windows和其他系統之間路徑不統一,path模塊還專門做了相關處理,屏蔽了彼此之間的差異。 可移

全國計算機等級考試二級教程--python語言程式設計(2018年版)組合資料型別

宣告:本篇文章只是個人知識盲區、知識弱點、重點部分的歸納總結,望各位大佬不喜勿噴。梳理順序是按照書籍的實際順序梳理,轉載請註明出處。 作者:sumjess   一、組合資料型別的基本概念:       Python語言中最常用的組合資料型別

FlaskFlask中內建的session

Flask中內建的session Flask中的session非常的奇怪,它會將你的sessionID存放在客戶端的Cookie中,使用起來也非常的奇怪。 1. Flask中的session是需要secret_key的,secret_key 實際上是用來加密字串的,如果在例項化的app中沒有 secret

FLASK模版引擎以及模版方法

temp 不想 set 並且 art xtend string 截取字符串 一個數 一,上節回顧:   1) 什麽是藍圖?   2) 為什麽用藍圖?   3) 怎麽實例化一個藍圖?   4) 怎麽將藍圖註冊到flask的核心對象上? 二,什麽是模版引擎?

異常機制

() 不同 finall try arr 運行時 運行 ror 則無 第六章:異常機制 異常的定義 異常:在程序運行過程中出現的意外事件,導致程序中斷執行。 異常處理 try...catch 語法:try{ //可能出現異常的代碼}catch(異常類型 異常對象名){

循環結構(二)

結構 不執行 三種 表達式 成了 不改變 條件 運算符 步驟 第六章:循環結構(二) 一. for 循環 1.循環結構的四個組成部分 (1). 初始部分:設置循環的初始狀態,比如我們設置記錄循環次數的變量 i 為 0 . (2). 循環體:重復執行的代碼 .

需求評審如何進行

角色 來源 職責 介紹 技術 產品介紹 好的 通過 協調 前言今天我們講的需求評審包括兩個部分,需求過濾和需求評審。 需求過濾 1.需求分析不是所有需求都要做進產品,我們要根據公司和產品的定位,進行合適地分析和過濾。 我們需要分析出用戶需求所對應的本質,將其轉化為產品能夠提

Python基礎教程 學習筆記

作用 actor int bsp python基礎 clas 最好 col 學習 收集函數 把實際參收集到元組和字典當中 1 def print_params(*params): 2 print(params) 3 """ 4 print_parasm

Docker | 構建私有倉庫

推送 sun 指定 公司 網絡環境 add 屬性 提示 回收機制 前言 上一章節,講解了利用Dockerfile和commit進行自定義鏡像的構建。大部分時候,公司運維或者實施部門在構建了符合公司業務的鏡像環境後,一般上不會上傳到公共資源庫的。這就需要自己搭建一個私有倉庫

編寫安全應用

利用 flash 網站 這一 ade 第六章 用戶數據 ack else 很多時候,安全應用是以犧牲復雜度(以及開發者的頭痛)為代價的。Tornado Web服務器從設計之初就在安全方面有了很多考慮,使其能夠更容易地防範那些常見的漏洞。安全cookies防止用戶的本地狀態被

《JAVA多線程編程核心技術》 筆記單例模式與多線程

會有 isp left sync con 多線程編程 鎖機制 數據 range 一、立即加載/"餓漢模式"和延遲加載/"懶漢模式" 立即加載(又稱餓漢模式):在使用類的時候已經將對象創建完畢,常見實現方法是直接new實例化 延遲加載(又稱懶漢模式):在調用get

隨機化

gin fat 偽隨機數發生器 偽隨機 運行 合成 內嵌 想要 ini 隨著設計變得越來越復雜,要想產生一個完整的激勵集來測試設計的功能也變得越來越困難。采用受約束的隨機測試法(CRT)自動產生測試集是目前的一種主要的方法。CRT由兩部分組成:使用隨機的數據流為DUT產生

隨機化(續1)

限制 調試 each 範圍 實例 func 文件中 約束 hand 6.6 pre_randomize和post_randomize函數 我們在調用randomize()函數之前或者之後要立即執行一些操作。比如,在隨機化之前可能要設置類裏的一些非隨機變量(上下限、權重),或

隨機化(續2)

重要 之間 ilog -o bbbb 動態 調試 即使 不同 6.10 隨機化句柄數組 如果想要產生多個隨機對象,那麽你可能需要建立隨機句柄數組,和整數數組不同,隨機求解器不會創建對象,所以你需要在隨機化前分配所有的元素。 動態數組可以按照需要分配最大數量的元素,然後按照約