原文: http://www.catonlinepy.tech/
声明: 原创不易,未经许可,不得转载css
今天的教程主要给你们介绍,如何在Flask应用中添加我的主页以及在我的主页中如何上传用户头像。教程中的代码都会托管到github上,猫姐一如既往的强调,在学习本课内容时必定要亲自动手实现代码,遇到问题再到github上查看代码,若是实在不知道如何解决,能够在日志下方留言。html
在建立我的主页以前,先来建立今天的项目目录,猫姐直接将第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命令获得):python
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ tree day6 day6 ├── 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
一般,在用户登陆后,在主页导航栏中会有一个用户名的超连接,当用户点击这个超连接时,就会跳转到用户的我的主页。下面,咱们在layout.html文件的导航栏中添加我的主页的入口:git
<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/时,能够获得以下效果:github
完成了导航中我的主页的入口后,咱们须要完成我的主页中呈现的内容。这里account.html一样须要继承layout.html的导航栏。在account.html文件中添加以下代码:sql
{% 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的渲染:flask
#.. <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,就能够跳转到用户我的页面了,效果以下:服务器
上面只是在我的主页中显示了用户的用户名,这里咱们再添加显示用户头像的代码,只需在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目录下放置一张用户头像的图片,而后刷新我的主页,就能看到用户的头像了:
上文中只是让图像简单的显示在主页中,咱们并不能对其进行编辑,为了实现改换用户头像的功能,咱们还须要增长图片文件的上传功能。在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中导入UpdateAccountForm from 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的值显示在用户名输入框中)。再次刷新我的主页页面,效果以下:
在上面的account.html文件中,img标签中的src显示的是固定的图像,若是咱们须要显示其它的用户头像,每次都须要修改account.html文件中的img标签,可是咱们并不但愿这么作。为了可以方便的更新用户的头像信息,咱们须要使用更加聪明的方法。咱们首先须要在config.py文件中配置用户头像保存的目录,以下:
import os basedir = 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_app from 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图片文件做为用户的默认头像。
上面只是实现了用户头像的显示,这里将介绍如何对用户的头像进行更新。当用户选择了新的头像文件后,点击“更新”按钮时,会触发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,secrets from flask import current_app # 从PIL中导入Image类 from PIL import Image def 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”按钮,选择图片后点击“更新”按钮,就能够实现用户头像的更新了,效果以下:
学习完今天的内容,咱们实现了以下功能:
下一课的教程,猫姐将带领你们一块儿学习建立、更新和删除日志。今天的内容就到这里,喜欢的同窗们能够在下面点赞留言,或是访问个人博客地址:http://www.catonlinepy.tech/ 加入咱们的QQ群进一步交流学习!
你们能够到github上获取今天教程的全部代码:https://github.com/miaojie19/...
具体下载代码的命令以下:
# 使用git命令下载flask-course-primary仓库全部的代码 git clone https://github.com/miaojie19/flask-course-primary.git # 下载完成后,进入day6目录下面,便可看到今天的代码 cd flask-course-primary cd day6