用户个人主页
现在我们来写一个用户个人主页,请求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(b'john@example.com').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.datacurrent_user.about_me = form.about_me.datadb.session.commit()flash('Your changes have been saved.')return redirect(url_for('edit_profile'))elif request.method == 'GET':form.username.data = current_user.usernameform.about_me.data = current_user.about_mereturn 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 %}
好了,到此,所有的工作完成之后,你得到的效果应该像下面这样,快试试吧