現在我們來寫一個用戶個人主頁,請求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給每個用戶添加頭像。這個東西用起來很簡單,就是用這個鏈接https://www.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
{% 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
{% 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 %}
上面這些步驟做完,得到的頁面如下:
上面寫完了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函數
{% 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
{% 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
{% 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 %}
好了,到此,所有的工作完成之後,你得到的效果應該像下面這樣,快試試吧