用戶個人主頁

現在我們來寫一個用戶個人主頁,請求url為/user/<username>

app/routes.py: User profile view function

@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和之前的有所不同,裡麪包含了一個username的變數,也就是說這個鏈接會根據不同的username進到不同的人的主頁中。當然,前提是你必須登錄,所以我加了一個@loginrequired的裝飾器。上面這個view函數很簡單,first_or_404()這個可以找到唯一一條數據,找不到就拋錯。下面是對應的前端頁面代碼。

app/templates/user.html: User profile template

{% 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 %}

功能完成了,然後在頁面上加一個個人主頁的點擊入口,如下:

app/templates/base.html: User profile template

<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>

以上步驟得到的效果如下:

點擊Profile試試看,是不是登錄到你自己的主頁裡面去了

頭像

加了個人主頁之後,想要讓用戶能夠添加自己喜歡的頭像。如果直接把頭像放在伺服器上的話,除了佔位置沒什麼好處--,所以使用Gravatar給每個用戶添加頭像。這個東西用起來很簡單,就是用這個鏈接gravatar.com/avatar/<hash>獲取你的頭像。<hash>是你的email地址的hash值。下面是使用的一個例子

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

默認圖片是80*80,可以帶上一個參數改變大小

https://www.gravatar.com/avatar/729e26a2a2c7ff24a71958d4aa4e5f35?s=128

好了,頭像有了,現在把他加到user的model裡面吧

app/models.py: User avatar URLs

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)

identicon參數就是說,如果用戶沒有自己的頭像的話,就給他統一發放一個標誌性圖像。下滿完善一下用戶主頁模板。

app/templates/user.html: User avatar in template

{% 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 %}

現在我們的用戶主頁那裡會有一個大的頭像,那如果下面有評論,為了好區分,每個用戶名前都有一個小的個人頭像圖片,把上面的模板再改一下。

app/templates/user.html: User avatars in posts

{% 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的使用

上面寫完了profile頁面後,如果又想寫一個跟這個頁面佈局一樣的index頁面。如果複製粘貼的話,只要一有修改,就得同時修改兩個頁面,這種肯定是不行的。

所以我寫了一個子模板,這樣就可以在一個頁面當中使用同一套代碼,不必來回修改,我取名_port.html,加前綴就是為了和其他模板進行區分

app/templates/_post.html: Post sub-template

<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函數

app/templates/user.html: User avatars in posts

{% 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 %}

更多有趣的功能

我想在個人主頁那裡加上上次登錄的時間,先要在user表裡面多加兩個欄位

app/models.py: New fields in user model

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

參照第4章講到的,怎麼同步表的改動呢?先執行

flask db migrate -m "new fields in user model"

再執行

flask db upgrade

然後把這兩個欄位加到user的模板中

app/templates/user.html: Show user information in user profile template

{% 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 %}

注意一下,我加了一個判斷條件,只有點擊過這個頁面之後,才用顯示,不然剛開始是看不到

下面我們來實現存儲last_seen的值。

我想的是在用戶下發請求之前就記錄這個時間,flask有一個很有用的裝飾器可以做到這一點,看下面的代碼

app/routes.py: Record time of last visit

from datetime import datetime

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

大家可能會好奇為什麼沒有執行db.session.add(),就可以直接提交了。因為當你調用current_user的時候Flask-Login會有一個回調函數已經做好了這一步,感興趣的,可以去看下源碼。執行完上面的操作會得到下面的效果,時間展示可能不是很好看,後面會有專門章節來解決這個問題。

用戶中心編輯

上面加的欄位還有一個about_me,我是想允許用戶自己編輯自己的信息,比如名字啥的,也可以加上一些對自己的介紹,下面,我們看看怎麼加表單吧

app/forms.py: Profile editor form

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我用的是文本域,規定的文字輸入長度。下面的模板實現展示上面的表單

app/templates/edit_profile.html: Profile editor form

{% 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 %}

下面這個view函數把我們上面講的所有東西串起來

app/routes.py: Edit profile view function

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)

上面這些操作得到的效果如下:

然後再加一個編輯用戶信息的入口,這個入口當然是隻有你自己可以進的

app/templates/user.html: Edit profile link

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

好了,到此,所有的工作完成之後,你得到的效果應該像下面這樣,快試試吧


推薦閱讀:
相關文章