原文: 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 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
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中導入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的值顯示在用戶名輸入框中)。再次刷新個人主頁頁面,效果如下:
3.3 在config.py文件中配置上傳頭像目錄
在上面的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圖片文件作爲用戶的默認頭像。
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,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”按鈕,選擇圖片後點擊“更新”按鈕,就可以實現用戶頭像的更新了,效果如下:
4. 總結
學習完今天的內容,我們實現瞭如下功能:
- 在導航欄中添加了個人主頁的入口
- 實現了用戶個人主頁的展示
- 實現了用戶個人頭像的更新
下一課的教程,貓姐將帶領大家一起學習創建、更新和刪除日誌。今天的內容就到這裏,喜歡的同學們可以在下面點贊留言,或是訪問我的博客地址: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-primary
cd day6