原文: http://www.catonlinepy.tech/
声明: 原创不易,未经许可,不得转载

1. 你将学会什么

今天的教程主要给大家介绍,如何在Flask应用中添加个人主页以及在个人主页中如何上传用户头像。教程中的代码都会托管到github上,猫姐一如既往的强调,在学习本课内容时一定要亲自动手实现代码,遇到问题再到github上查看代码,如果实在不知道如何解决,可以在日志下方留言。

2. 个人主页的实现

2.1 项目目录的创建

在创建个人主页之前,先来创建今天的项目目录,猫姐直接将第5天的day5目录复制后改成day6,然后程序里面的userauth_demo目录改成userprofile_demo目录,并将代码中的userauth_demo改为userprofile_demo。在此基础上,我们还需要创建如下文件和目录:

# 注意:以下所有的操作都必须在虚拟环境中进行# 在userprofile_demo新建文件utils.py文件,此文件用来保存一些功能独立的小函数(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo$ touch utils.py# 在userprofile_demo目录下新建static目录,此目录用来保存css,js及图片文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo$ mkdir static# cd到static目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo$ cd static# 在static目录中新建profile目录,用户上传的头像图片将保存到该目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo/static$ mkdir profile# 在templates目录中新建account.html文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6/userprofile_demo/templates$ touch account.html

最终,我们得到今天项目的目录结构如下(使用tree命令得到):

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ tree day6day6├── run.py└── userprofile_demo    ├── config.py    ├── database.db    ├── forms.py    ├── __init__.py    ├── models.py    ├── routes.py    ├── static    │   └── profile    │       └── default.jpg    ├── templates    │   ├── account.html    │   ├── index.html    │   ├── layout.html    │   ├── login.html    │   └── register.html    └── utils.py

2.2 为个人主页添加入口链接

通常,在用户登录后,在主页导航栏中会有一个用户名的超链接,当用户点击这个超链接时,就会跳转到用户的个人主页。下面,我们在layout.html文件的导航栏中添加个人主页的入口:

<html>    <head>      {% if title %}        <title>{{ title }}-喵星在线</title>      {% else %}        <title>喵星在线</title>      {% endif %}        {% block js %}        {% endblock js%}    </head>    <header>        <div>            <a href="{{ url_for('index') }}">主页</a>            {% if current_user.is_authenticated %}                <a href="{{ url_for('logout') }}">注销</a>                <a href="#">{{current_user.username}}</a>            {% else %}                <a href="{{ url_for('login') }}">登陆</a>            {% endif %}        </div>    </header>    <body>    <!-- 渲染flash消息 -->        {% with messages = get_flashed_messages(with_categories=true) %}            {% if messages %}                {% for category, message in messages %}                    <div class="alert alert-{{ category }}">                        {{ message }}                    </div>                {% endfor %}            {% endif %}        {% endwith %}       {% block content %}       {% endblock %}    </body></html>

此时用户在登录状态下,访问http://127.0.0.1:5005/时,可以得到如下效果:

2.3 开始创建个人主页

完成了导航中个人主页的入口后,我们需要完成个人主页中呈现的内容。这里account.html同样需要继承layout.html的导航栏。在account.html文件中添加如下代码:

{% extends "layout.html" %}{% block content %} <hr> <div>    <h1>用户:{{html_user.username}}</h1> </div>{% endblock %}

当点击顶部的miaojie时,它会将用户带到个人主页页面,服务器为了响应这一请求,还需要添加相应的路由函数,在routes.py文件中添加如下代码:

#...@app.route("/account")def account():    if not current_user.is_authenticated:        return redirect(url_for('index'))    return render_template("account.html", title="第六天", html_user=curent_user)#...

此时,还需要修改基模板layout.html文件中个人主页的入口链接,才能实现正常的url跳转,如下使用url_for函数完成个人主页url的渲染:

#..<header>        <div>            <a href="{{ url_for('index') }}">主页</a>            {% if current_user.is_authenticated %}                <a href="{{ url_for('logout') }}">注销</a>                <a href="{{ url_for('account') }}">{{current_user.username}}</a>            {% else %}                <a href="{{ url_for('login') }}">登陆</a>            {% endif %}        </div> </header>

此时刷新主页后,在主页中点击miaojie,就可以跳转到用户个人页面了,效果如下:

3. 更新用户头像

3.1 在主页中显示用户的头像

上面只是在个人主页中显示了用户的用户名,这里我们再添加显示用户头像的代码,只需在account.html中增加如下img标签即可:

<!--继承基模板-->{% extends "layout.html" %}{% block content %} <hr> <div>    <h1>用户:{{html_user.username}}</h1>    <img alt="" style="border-radius: 50%;" src='/static/profile/default.jpg'  width="60" height="60"> </div>{% endblock %}

同时在img标签中增加了一点css效果,style="border-radius:50%",长和高都为60px,radius会将图片渲染成圆形,src是图片所在的位置。此时我们只需要在static/profile目录下放置一张用户头像的图片,然后刷新个人主页,就能看到用户的头像了:

3.2 在个人主页中增加上传头像表单

上文中只是让图像简单的显示在主页中,我们并不能对其进行编辑,为了实现改换用户头像的功能,我们还需要增加图片文件的上传功能。在models.py文件中的user数据库中增加profile_image字段,该字段用来保存用户头像图片的文件名:

#..class User(UserMixin, db.Model):    __tablename__ = 'user'    id = db.Column(db.Integer, primary_key=True)    username = db.Column(db.String(20), unique=True, nullable=False)    email = db.Column(db.String(120), unique=True, nullable=False)    password = db.Column(db.String(60),  nullable=False)    profile_image = db.Column(db.String(20), nullable=True)    def __repr__(self):        return f"User('{self.username}','{self.email}','{self.password}')"

由于代表数据库中表的models.py文件发生了变化,所以需要进行数据库的迁移,但是在进行迁移之前,我们先将config.py文件的位置放到与run.py同级目录中,第4课中已经讲了数据库的迁移,大家可以直接按照第4课的5.3小结内容进行操作即可,同样在操作之前,需要设置环境变量,环境变量在第1课的3节最后已经讲过如何设置了。迁移完成后database.db会在run.py同级目录中生成。

数据库的用户表更新后,需要在forms.py文件中增加更新用户信息使用的表单UpdateAccountForm:

#..from flask_wtf.file import FileField, FileAllowed#..class UpdateAccountForm(FlaskForm):    username = StringField(u'用户名',                             validators=[DataRequired(), Length(min=2,max=20)])    email = EmailField(u"邮箱",                       validators=[DataRequired()])    password = StringField(u'密码', validators=[DataRequired()])    profile_image = FileField(u"更新头像", validators=[FileAllowed(["png", "jpg"])])    submit = SubmitField(u'更新')

在表单中FileField字段使用了flask_wtf提供的FileAllowed验证函数,它确保上传的图像只能是png和jpg两种格式,FileField字段会被Jinja2渲染生成type="file"的<input>标签。

在模板account.html中添加渲染前端表单的内容(html_form对象是通过路由函数传到这里的):

<!--继承基模板-->{% extends "layout.html" %}{% block content %} <hr> <div>    <h1>用户:{{html_user.username}}</h1>    <img alt="" style="border-radius: 50%;" src='/static/profile/default.jpg'  width="60" height="60"> </div><hr> <div>    <form method="POST" action="{{ url_for('account') }}" enctype="multipart/form-data">        {{ html_form.hidden_tag() }}        <fieldset>            <div class="form-group">                {{ html_form.username.label(class="form-control-label") }} <br>                {{ html_form.username(class="form-control form-control-lg"  ) }}            </div>            <div class="form-group">                {{ html_form.email.label(class="form-control-label") }} <br>                {{ html_form.email(class="form-control form-control-lg") }}            </div>            <div class="form-group">                {{ html_form.profile_image.label() }} <br>                {{ html_form.profile_image(class="form-control-file") }}            </div>        </fieldset>        <div class="form-group">            {{ html_form.submit(class="btn btn-outline-info") }}        </div>    </form> </div>{% endblock %}

上面已经完成account.html页面前台form表单的显示(渲染)工作,这时就需要在视图函数中(python文件)将代表表单的类传递到前端模板文件(html文件)中,下面在routes.py中完成视图函数的编写:

#..# 从userprofile_demo.forms中导入UpdateAccountFormfrom userprofile_demo.forms import LoginForm, UpdateAccountForm# ..@app.route("/account")def account():    if not current_user.is_authenticated:        return redirect(url_for('index'))    form = UpdateAccountForm()    form.email.data = user.email    form.username.data = user.username    return render_template("account.html", html_user=current_user, html_form=form)#..

这里,视图函数默认收到的是get请求,并且将当前登录用户的信息传递到form表单,最终用户的信息将会在前端显示出来(user.email是从数据库中读取email的值显示在邮箱输入框中,user.username表示从数据库中读取username的值显示在用户名输入框中)。再次刷新个人主页页面,效果如下:

3.3 在config.py文件中配置上传头像目录

在上面的account.html文件中,img标签中的src显示的是固定的图像,如果我们需要显示其它的用户头像,每次都需要修改account.html文件中的img标签,但是我们并不希望这么做。为了能够方便的更新用户的头像信息,我们需要使用更加聪明的方法。我们首先需要在config.py文件中配置用户头像保存的目录,如下:

import osbasedir = os.path.abspath(os.path.dirname(__file__))class Config(object):    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'database.db')    # 使用表单,对Flask_WTF进行配置    SECRET_KEY = 'miaojie is great!'    # 配置上传用户头像的目录    PROFILE_PATH = "profile/"

然后我们修改模板文件account.html中的img标签,将src属性的值更改为视图函数传过来的变量,如下:

<!--继承基模板-->{% extends "layout.html" %}{% block content %} <hr> <div>    <h1>用户:{{html_user.username}}</h1>    <img alt="" style="border-radius: 50%;" src='{{url_for("static", filename = html_user_image)}}'   width="60" height="60"> </div>#..{% endblock %}

下面我们在routes.py文件中,对用户的头像图片变量profile_image进行处理,使其能向前端传入正确的用户头像文件:

#..# 从flask中导入current_appfrom flask import current_app#..@app.route("/account")def account():    if not current_user.is_authenticated:        return redirect(url_for('index'))    form = UpdateAccountForm()    profile_image = current_user.profile_image    if profile_image:        profile_image = current_app.config["PROFILE_PATH"] + profile_image    else:        profile_image = current_app.config["PROFILE_PATH"] + "default.jpg"    form.email.data = current_user.email    form.username.data = current_user.username    return render_template("account.html", title="第六天", html_user=current_user, html_form=form, html_user_image=profile_image)#..

当我们还没有上传新的用户头像文件时,profile_image变量为None值,所以会使用default.jpg图片文件作为用户的默认头像。

3.4 更新用户头像的post请求

上面只是实现了用户头像的显示,这里将介绍如何对用户的头像进行更新。当用户选择了新的头像文件后,点击“更新”按钮时,会触发post请求,此时account视图函数需要对该post进行正确的处理,才能实现用户头像文件的上传:

#..@app.route("/account", methods=["GET", "POST"])def account():    if not current_user.is_authenticated:        return redirect(url_for('index'))    form = UpdateAccountForm()    profile_image = current_user.profile_image    if profile_image:        profile_image = current_app.config["PROFILE_PATH"] + profile_image    else:        profile_image = current_app.config["PROFILE_PATH"] + "default.jpg"    if request.method == "POST":        username = form.username.data        email = form.email.data        if form.profile_image.data:            picture_file = save_user_face_image(form.profile_image.data)            current_user.profile_image = picture_file        current_user.username = username        current_user.email = email        db.session.commit()        return redirect(url_for("account"))    form.email.data = current_user.email    form.username.data = current_user.username    return render_template("account.html", title="第六天", html_user=current_user, html_form=form, html_user_image=profile_image)#..

上面代码中,当account视图函数拿到post请求后,从表单中获取用户输入的用户名和邮箱,并保存到username和email变量中。这里,如果form.profile_image.data为True,则代表用户上传了新的头像文件,此时我们需要将用户上传的文件保存至profile目录,并使用新的用户头像文件名更新数据库中的profile_image字段。

在上面的post请求中,还没有实现save_user_face_image()函数,该函数的定义,我们将它单独放在utils.py文件中。由于该函数在处理图片文件时,需要用到PIL模块,所以我们先使用pip命令来安装pillow库:

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day6$ pip install pillow

现在,在utils.py文件中实现save_user_face_image()函数:

import os,secretsfrom flask import current_app# 从PIL中导入Image类from PIL import Imagedef save_user_face_image(image_data):    random_hex = secrets.token_hex(8)    f_name, f_ext = os.path.splitext(image_data.filename)    new_image_name = random_hex + f_ext    new_image_path = os.path.join(current_app.root_path, "static/profile", new_image_name)    output_size = (125,125)    i = Image.open(image_data)    i.thumbnail(output_size)    i.save(new_image_path)    return new_image_name

在route.py文件中还需要导入save_user_face_image:

#..from userprofile_demo.utils import save_user_face_image#..

OK!大功告成,此时,我们浏览http://127.0.0.1:5005/account页面,点击“Browse”按钮,选择图片后点击“更新”按钮,就可以实现用户头像的更新了,效果如下:

4. 总结

学习完今天的内容,我们实现了如下功能:

  1. 在导航栏中添加了个人主页的入口
  2. 实现了用户个人主页的展示
  3. 实现了用户个人头像的更新

下一课的教程,猫姐将带领大家一起学习创建、更新和删除日志。今天的内容就到这里,喜欢的同学们可以在下面点赞留言,或是访问我的博客地址:http://www.catonlinepy.tech/ 加入我们的QQ群进一步交流学习!

5. 代码的获取

大家可以到github上获取今天教程的所有代码:https://github.com/miaojie19/...

具体下载代码的命令如下:

# 使用git命令下载flask-course-primary仓库所有的代码git clone https://github.com/miaojie19/flask-course-primary.git# 下载完成后,进入day6目录下面,即可看到今天的代码cd flask-course-primarycd day6