【Flask/跟着学习】Flask大型教程项目#05:个人资料

14 篇文章 3 订阅

跟着学习(新版):https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vi-profile-page-and-avatars
跟着学习(旧版):http://www.pythondoc.com/flask-mega-tutorial/profile.html
回顾上一章:https://blog.csdn.net/weixin_41263513/article/details/85013477

本章内容

  • 个人页面
  • 头像
  • 用Jinja2的子模版
  • 个人时间,访问痕迹
  • 记录用户的上次访问时间
  • 编辑个人资料

个人页面

要创建一个个人资料页面,先写一个新的视图函数,映射到/user/
文件:/app/routes.py

@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组件。当路由具有动态组件时,Flask将接受URL部分中的任何文本,并将以实际文本作为参数调用视图函数,如:如果客户端浏览器请求URL / user / susan,则将使用参数username设置为“susan”来调用视图函数,此视图功能只能由登录用户访问,因此我添加了Flask-Login中的@login_required装饰器。

在这个视图函数中,我使用名为first_or_404()的first()变体,当有结果时,它与first()完全相同,但是如果没有结果,则自动将404错误发送给请求的人看到

如果数据库查询未触发404错误,则表示找到具有给定用户名的用户。接下来,我为该用户初始化一个虚假的帖子列表,最后呈现一个新的user.html模板,我将该模板传递给用户对象和帖子列表。
文件:/app/templates/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 %}

个人页面已经完成,现在需要在顶部的导航栏中添加一个链接
文件:/app/templates/base.html

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

我们可以使用Flask-Login的current_user来生成正确的URL

头像

为了进一步改进资料页面的外观,可以在页面中显示用户的头像,我们将会用Gravatar提供用户头像。Gravatar是一个行业领先的头像服务,能把头像和电子邮箱关联起来,用户要先到该网站上注册账户,然后上传图像。这个服务通过一个特殊的URL对外开放用户的头像,这个URL中包含用户电子邮箱地址的MD5散列值(说实话,在学习之前,我根本不知道Gravata这东西,不过跟着学习嘛,就当是跟着了解一下,扩充眼界,以后再慢慢细改)

由于头像跟用户关联,因此需要将头像URL的逻辑添加到用户模型
文件:/app/models.py

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,缩放到请求的大小(以像素为单位)。 对于没有注册化身的用户,将生成“同心”图像。要生成MD5哈希,我首先将电子邮件转换为小写,因为Gravatar服务需要这样做。 然后,因为Python中的MD5支持在字节而不是字符串上工作,所以在将字符串传递给哈希函数之前,我将字符串编码为字节。

下一步是将头像图像插入用户资料模板中
文件:/app/templates/user.html

{% 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,所有模板将自动显示新的头像。

在用户个人资料页面的顶部有一个漂亮的大头像,但还有一些来自底部用户的帖子,每个帖子都应该有一个小头像。

要显示各个帖子的头像,我只需要在模板中进行一个小的更改:
文件:/app/templates/user.html

{% 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的子模版

我们已经实现了用户信息页,它能够显示用户的 blog。我们的首页也应该显示任何一个用户这个时候的 blog 。这样我们有两个页需要显示用户的 blog。当然我们可以直接拷贝和复制处理渲染 blog 的模板,但这不是最理想的。因为当我们决定要修改 blog 的布局的时候,我们要更新所有使用它的模板。

相反,我们将要制作一个渲染 blog 的子模板,我们在使用它的模板中包含这个子模板。

我们创建一个 blog 的子模板,这是一个再普通不过的模板, _前缀只是一个命名约定,可以帮助我识别哪些模板文件是子模板。
文件:/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语句
文件:/app/templates/user.html

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

个人时间,访问痕迹

用户除了头像,还少不了个人简介,让别人更好的认识ta,除此之外,我们还可以跟踪每个用户最后一次访问该网站的时间,并在他们的个人资料页面上显示时间

为此,我们需要用两个新字段扩展数据库中的users表:
文件:/app/models.py

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

每次修改数据库时,都必须生成数据库迁移,详情请看数据库这一章节,迁移数据库这一操作十分重要,数据库中的任何用户仍然存在,迁移数据库不会破坏任何数据

改了users模型,就必须要再对应的模板html中添加新字段
文件:/app/templates/user.html

{% 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字段开始,想做的是在用户向服务器发送请求时为用户写入此字段的当前时间:
文件:/app/routes.py

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字段设置为当前时间。该实现只是检查current_user是否已登录,并且在这种情况下将last_seen字段设置为当前时间。现在将这些时间戳存储在UTC时区中使得user.html上显示的时间也是UTC。除此之外,时间格式不是您所期望的,因为它实际上是Python日期时间对象的内部表示。现在,不用担心这两个问题,因为在后面的章节中将会讨论处理Web应用程序中的日期和时间的主题。

编辑个人资料

现在还需要向用户提供一个表格,在表格中他们可以输入一些关于他们自己的信息。 该表单将允许用户更改其用户名,并编写有关他们自己的内容,存储在新的about_me字段中。 让我们开始为它编写一个表单类
文件:/app/forms.py

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

有了新的表单类,就需要创建新的html模板来接收用户填写的表单了:
文件:/app/templates/edit_profile.html

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

最后,在视图函数中把一切内容给连接起来
文件:/app/routes.py

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)

可能有些朋友看不懂elif之后的内容,其实吧,我也不太懂蛤蛤,大概来说就是为保万无一失,某些原因会让validate_on_submit()返回false,导致提交失败,不过这种情况还是非常少的,到后面我们再来慢慢修改精简

那么为了让用户能更好,更快的编辑自己的资料,需要在顶部的导航栏多加一个链接
文件:/app/templates/user.html

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

激动人心效果图:
这一章节会拉出一个非常非常非常大的bug,下一章混合着解决办法一起说了
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值