第6天:在Flask應用中添加個人主頁

原文: 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. 總結

學習完今天的內容,我們實現瞭如下功能:

  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-primary
cd day6

圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章