天天看點

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

Python Web全棧開發入門實戰教程教程

   大家好,我叫亓官劼(qí guān jié ),這個​

​《Python Web全棧開發入門實戰教程教程》​

​​是一個​

​零基礎​

​​的實戰教程,手把手帶你開發一套系統,帶你了解​

​Python web全棧開發​

​​。寫這篇文章的初衷就是想給想入門Python Web開發還沒入門的小夥伴們一個詳細的,真正零基礎的一個開發教程,可以讓小夥伴們在入門的途中少走很多彎路。

   在服務端端開發上,主流還是Java開發,Python的話雖說開發快速、便捷,但是在一些地方還是有缺陷的。但是Python Web有一個非常好的優點就是友善、快捷、易學、易用,可以讓小夥伴們快速的入門Web開發。

  之前有不少小夥伴來問我,​​

​怎麼開發Web系統啊?畢業論文的中期檢查馬上就要來了,我的畢業設計還一動沒動,我完全不會Web系統的開發,我該怎麼辦?​

​​,雖說大學四年我們或多或少的都學過各種各樣的語音,但是項目開發做的卻很少,很多學校開了點項目開發的也,最後也都是上了點理論,開發?那是不可能開發的,最後還是一套試卷定成績,期末背背就是高分,導緻很多小夥伴們到了寫畢設的時候,連個簡簡單單的web系統都不會開發。這篇文章也可以給小夥伴們一個參考吧,如果畢設的時候開發web系統的時候無從下手,可以來嘗試一下Python Web,這篇文章将會真正零基礎的,每步都進行一個詳細的說明,帶你完成一個小項目,實作各種各樣的一個功能,給你提供一些思路。

  寫這篇文章的另一個初衷就是,想給想入門Python Web開發的同學或者想學習Web開發的同學,提供一個練手的項目,一套完整的項目實作過程,避免剛入門時的茫然無措。我當時大二剛剛開始做Web開發時,由于學校裡面做開發的比較少吧,自己一個人摸索,找網上的各種教程,在教程裡他們總是會忽略一些細節,然後我實踐運作的時候就是頻頻報錯,一個小小的BUG就要折騰很久,還記得當時項目完成,部署到CentOS伺服器上的時候,使用的Nginx+UWSGI進行部署,當時直接跑UWSGI可以跑,直接跑Nginx也可以,兩個加起來就是一直錯誤,各種百度、谷歌,也找不到問題所在,最後折騰了半個月,在一篇很小的文章上看到了UWSGI的一個接口檔案(安裝的時候不是自動生成的,需要自己建立),然後一個很小的bug吧,折騰了半個月。是以這篇文章,也是想給剛入門的小白提供一個參考,一個少走彎路的方法吧!

大家一起加油,時光荏苒,未來可期!

  大家好,我是亓官劼(qí guān jié ),在【亓官劼】公衆号、CSDN、GitHub、B站、華為開發者論壇等平台分享一些技術博文,主要包括前端開發、python後端開發、小程式開發、資料結構與算法、docker、Linux常用運維、NLP等相關技術博文,時光荏苒,未來可期,加油~
  如果喜歡部落客的文章可以關注部落客的個人公衆号【亓官劼】(qí guān jié),裡面的文章更全更新更快。如果有需要找部落客的話可以在公衆号背景留言,我會盡快回複消息,其他平台私信回複較慢。

由于學習工作的需要,算法刷題将會逐漸由C++向Python3過度,正在過度中,如實作的不太優美,請見諒。

本文原創為【亓官劼】(qí guān jié ),請大家支援原創,部分平台一直在惡意盜取部落客的文章!!! 全部文章請關注微信公衆号【亓官劼】。

  寫在最前面,本教程為小白教程,很多進階的内容都沒有做添加,後期項目會添加很多需要的功能,屆時會放到文章的最後,或者在Flask專欄中,有需要的可以自取,或者看其他的文章,文檔也一樣。例如現在登入時的圖像驗證碼功能的文章已在專欄中釋出,有需要的可以自己添加。本文章截止目前11.7萬字,中有一般的代碼,所有代碼都是實時運作通過的。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 1)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 2)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 3)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 4)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 5)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 6)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 7)​​
  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​Flask學習(基本文法)​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

本篇文章目錄

  • ​​Python Web全棧開發入門實戰教程教程​​
  • ​​@[TOC](本篇文章目錄)​​
  • ​​一、開發環境 & 項目簡介​​
  • ​​二、建立一個Flask項目​​
  • ​​2.1 建立項目​​
  • ​​2.2 安裝本項目所需要的一些插件​​
  • ​​2.3 建立資料庫​​
  • ​​三、Flask基礎知識部分​​
  • ​​3.1 Flask程式的基本構造​​
  • ​​3.2 路由器請求方法限定​​
  • ​​3.3 動态路由​​
  • ​​3.4 Jinja2模闆引擎​​
  • ​​3.4 傳參時,清單和字典的使用:​​
  • ​​3.5 控制語句的使用​​
  • ​​3.6 過濾器的基本使用​​
  • ​​3.7 使用flash傳遞消息​​
  • ​​3.8 使用WTF實作web表單​​
  • ​​3.8.1 WTForms支援的HTML标準字段​​
  • ​​3.8.2 WTForms驗證函數​​
  • ​​3.8.3Flask-WTF擴充的安裝​​
  • ​​3.8.4使用Flask-WTF建立表單示例​​
  • ​​3.9 Flask-SQLAlchemy拓展​​
  • ​​3.9.1 Flask-SQLAlchemy拓展安裝方法:​​
  • ​​3.9.2 配置資料庫:​​
  • ​​3.9.3建立資料庫類,映射資料庫表​​
  • ​​3.9.4在視圖中操作資料庫查詢​​
  • ​​3.9.5 常用的SQLAlchemy查詢過濾器​​
  • ​​3.9.6常用SQLAlchemy查詢執行方法​​
  • ​​3.10 使用werkzeug計算密碼散列值​​
  • ​​3.11 檔案的上傳、下載下傳​​
  • ​​3.11.1 上傳檔案​​
  • ​​3.11.2 在背景接收檔案并儲存​​
  • ​​3.11.3 在背景儲存檔案​​
  • ​​3.12 Flask 藍圖​​
  • ​​3.12.1 藍圖的基本用法​​
  • ​​四、導航欄、注冊、登入、登出、首頁功能的實作​​
  • ​​4.1 導航欄實作​​
  • ​​4.2 注冊功能實作​​
  • ​​4.2.1 注冊功能實作-資料庫​​
  • ​​4.2.2 注冊功能實作-前端​​
  • ​​4.2.3 注冊功能實作-後端​​
  • ​​4.3 登入功能實作​​
  • ​​4.3.1 登入功能實作-前端​​
  • ​​4.3.2 登入功能實作-後端​​
  • ​​4.3.2.1 登入功能實作-後端-驗證登入資訊​​
  • ​​4.3.2.2 登入功能實作-後端-登入狀态保持​​
  • ​​4.4 登出功能實作​​
  • ​​4.5 首頁實作​​
  • ​​五、釋出文章、論壇頁面實作​​
  • ​​5.1 釋出文章的功能實作​​
  • ​​5.1.1 釋出文章的實作-前端​​
  • ​​5.1.2 釋出文章的實作-資料庫​​
  • ​​5.1.3 釋出文章的實作-後端​​
  • ​​5.2 論壇頁面的實作​​
  • ​​5.2.1 論壇頁面的實作-前端​​
  • ​​5.2.2 論壇頁面的實作-後端​​
  • ​​六、文章詳情、回複文章、通路過濾功能實作​​
  • ​​6.1 文章詳情頁面、文章回複實作​​
  • ​​6.1.1 文章詳情頁面實作-後端​​
  • ​​6.1.2 文章詳情頁面實作-前端​​
  • ​​6.1.3 文章回複實作-前端​​
  • ​​6.1.3 文章回複實作-後端​​
  • ​​6.2通路過濾功能實作​​
  • ​​七、個人中心相關頁面功能實作​​
  • ​​7.1個人中心頁面實作​​
  • ​​7.1.1 個人中心頁面-後端​​
  • ​​7.1.2 個人中心頁面-前端​​
  • ​​7.2 修改密碼功能實作​​
  • ​​7.2.1 修改密碼功能實作-前端​​
  • ​​7.2.1 修改密碼功能實作-後端​​
  • ​​7.3 檢視釋出文章功能實作-後端​​
  • ​​7.3.1 檢視釋出文章功能實作-後端​​
  • ​​7.3.2 檢視釋出文章功能實作-前端​​
  • ​​八、資源專區相關功能實作​​
  • ​​8.1資源上傳功能實作​​
  • ​​8.1資源上傳功能實作-前端​​
  • ​​8.1.2資源上傳功能實作-資料庫端​​
  • ​​8.1.3資源上傳實作-後端​​
  • ​​8.2 資源專區功能實作​​
  • ​​8.2.1 資源專區功能實作-後端​​
  • ​​8.2.2資源專區功能實作-前端​​
  • ​​8.2.3 實作檔案線上檢視功能​​
  • ​​8.2.4 實作檔案下載下傳功能​​
  • ​​九、項目遷移​​
  • ​​9.1 生成插件檔案版本​​
  • ​​9.2生成資料庫表結構和資料庫資料遷移​​

  如果嫌本篇文章太長,不好拉動的話,可以使用上面的目錄今天跳轉,或者檢視部落客之前分篇幅寫的部落格,相對簡短一些,部落格位址為:

  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 1)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 2)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 3)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 4)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 5)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 6)​​
  • ​​小白都能看懂的實戰教程 手把手教你Python Web全棧開發 (DAY 7)​​
  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

一、開發環境 & 項目簡介

本項目源碼在GitHub上進行開源,本項目的GitHub位址為:​​OnlineForumPlatform​​​,大家可以去clone全部源碼,喜歡的話,也歡迎大家star一下。

   我們這裡使用的是Python Web的Flask架構進行開發,使用的內建開發環境為Pycharm,使用的開發語言為Python3,前端為HTML/CSS/JavaScript/Bootstrap,資料庫為MySQL,使用SQL語句進行描述。

  我們這次實戰采用的項目是一個線上論壇系統,字如其名,就是開發一個線上的論壇系統。需要實作各個的功能如下:

  • 注冊:新增賬號
  • 登入:登入賬号進入系統,如果登入普通使用者,則隻有普通使用者的權限,如果是管理者賬号,則有管理者賬号權限。
  • 檢視論壇問題清單:檢視線上論壇系統中所有已釋出的問題.
  • 釋出問題:
  • 釋出自己的問題,等待他人回答
  • 支援富文本輸入
  • 支援Markdown輸入
  • 問題詳情頁面:顯示目前問題的讨論、回複。
  • 回答問題:回答他人問題。
  • 個人中心:
  • 顯示個人賬号資訊
  • 可以修改個人賬号資訊
  • 顯示個人發帖情況
  • 資源專區
  • 檢視資源清單(已有資源)
  • 線上預覽資源
  • 下載下傳資源
  • 上傳資源

      項目目前所涉及的功能就是這些,同時還有一些網頁安全的一些措施,例如對通路客戶的過濾等等功能。這就是我們這個實戰項目的一個大緻的功能介紹。

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

二、建立一個Flask項目

  既然是開發項目,我們第一步當然是先建立一個項目啦!本來打算第一章寫我們的Flask基礎知識部分,但是又怕有的小夥伴不會建立我們的Flask項目,索性我們就保姆式教學到底,我們從建立項目開始。

2.1 建立項目

這裡使用Pycharm專業版作為開發工具,如果還沒有Pycharm的同學可以下載下傳安裝一個,如果沒有激活的話,可以檢視​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

這裡選擇Flask項目,我這裡的項目名稱為OnlineForumPltform,使用virtualenv虛拟環境,模闆引擎為Jinja2。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

這樣一個Flask基本項目就建立好啦。建立完成之後頁面為這樣:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

我們點選運作,測試一下第一個hello world:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

運作之後,我們通路運作欄中的位址,預設的位址為:http://127.0.0.1:5000

頁面傳回Hello World!就表示我們第一個已經完成了第一步!

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

下面我們來建立一些我們下面開發所需要的檔案和檔案夾。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

還有requirements.txt檔案是存放本項目所有插件和版本資訊的文本,README.md檔案是本項目的說明檔案。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

2.2 安裝本項目所需要的一些插件

點開設定,進行安裝插件:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

點選+進入添加插件頁面,這裡我們先添加一個PyMysql,其他的等我們需要的時候再進行添加。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

2.3 建立資料庫

這裡我們先建立一個空的資料庫,然後我們使用PyCharm連接配接這個資料庫,友善我們後面進行資料庫相關的操作。

(我這裡是mac系統,mysql隻安裝了指令行版本,如果是安裝圖像界面版本的可以直接進行建立).終端進入mysql的指令為​​

​mysql -u root -p​

​然後輸入密碼即可進入。

Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.16 MySQL Community Server - GPL

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database OnlineForumPlatform;
Query OK, 1 row affected (0.06 sec)

mysql>      

即可建立成功。

建立完成之後我們使用PyChram來連接配接資料庫。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

選擇mysql資料庫,然後我們填寫使用者名root和密碼,然後填寫建立的資料庫名稱即可,填寫完成之後點選測試連接配接

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

測試連接配接正确之後,我們點選OK即可

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

然後我們在pycharm右面的Database就可以看到我們的資料庫了

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

  好了,到目前為止我們項目所需要的一些基本的插件和資料庫都建立完成啦~大家都建立的順利嗎?下面我們先插播一章基礎的Flask基礎知識講解,然後就正式的到我們的項目實戰環節啦!如果Flask基礎知識部分已經了解的,可以直接跳到我們的四,開始我們的項目實戰部分啦!在文章頂部有我們文章内的目錄,可以直接進行跳轉。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

三、Flask基礎知識部分

  磨刀不誤砍柴工,既然是真正的零基礎,我們就先來溫習一下Flask的一些基礎的文法,如果已經了解的這部分的話,可以直接跳到第四章呦~

3.1 Flask程式的基本構造

  首先就是我們的Flask程式的一個基本構造,我們首先需要從flask子產品中導入我們的Flask,這是我們架構的主要子產品,然後我們還需要導入我們的render_template,這是用來渲染我們的模闆的,将我們的HTML頁面傳回給請求者。

  在前面我建立的時候已經有一個預設的app.py,是return一個Hello World,各位小夥伴們都運作成功了嗎?這裡我們傳回的是一個Html檔案,在這裡說一句,我們的HTML檔案一般都是存放在我們的​​

​templates​

​檔案夾中,我們就可以直接在這裡傳回呦,大家可以建立一個demo.html,然後使用下面這個語看看能否傳回你的頁面呢?

# 從flask中導入Flask和render_template
from flask import Flask,render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('demo.html')

if __name__ == '__main__':
    app.run()      

3.2 路由器請求方法限定

  在route的參數中設定method的值,預設是GET方法,但是我們很多涉及到表單的頁面為了資料的安全性,我們需要使用POST方法去建立,是以我們這裡就需要設定我們的請求方式啦​

​methods=['GET', 'POST']​

​,完整的樣例為:

@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template('demo.html')      

3.3 動态路由

  有時候我們需要一個動态的參數,然後我們根據參數來傳回一個對應的頁面,這個時候就需要使用到動态路由啦!例如下面這個路由,他需要一個參數交錯​

​name​

​​,然後他會傳回一個​

​hello + { 你輸入的name}​

​​,大家可以測試一下,是不是能正确的傳回你的名字。這裡當你在王志中隻輸入​

​http://127.0.0.1:5000/user/​

​則會報錯,因為沒有輸入name參數。

@app.route('/user/<name>', methods=['GET', 'POST'])
def user(name):
    return 'hello {}'.format(name)      

其中參數可以通過類型進行限定,例如:int:name即隻接受int類型的參數

3.4 Jinja2模闆引擎

  由于我們的Web系統是需要一個動态的網頁,可能有部分内容資料是需要計算出來的,或者是從後端伺服器的資料庫中擷取的,是以我們需要使用Jinja2模闆引擎來對我們的模闆進行一個渲染。在函數中傳遞參數,在HTML頁面中,利用​

​{{ }}​

​​擷取傳遞的參數,例如我們下面這個例子,在伺服器端向前端傳輸一個變量user_var,其中變量名稱為html_var,然後我們在前端進行擷取變量,并且顯示。需要注意的是我們在傳遞參數的時候,等号前面的傳遞的參數名​

​(也就是我們在前端擷取的時候所需要使用的名)​

​​,在等号後面的是我們當地的參數名,​

​也就是伺服器中的參數​

​​,一般來說,我們兩名的命名是一樣的。

app.py

from flask import Flask,render_template

app = Flask(__name__)

@app.route('/user', methods=['GET', 'POST'])
def user():
    user_var="程式設計類輔助實驗教學平台"
    return render_template('demo3.html', html_var=user_var)

if __name__ == '__main__':
    app.run()      

demo3.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>This is a Demo3 Page</h1>
<h2>It is only using for test</h2>
這是傳輸過來的變量:{{ html_var }}
</body>
</html>      

顯示效果:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

在模闆中注釋符号為​

​{# #}​

​,例如{# This is a annotation #}

3.4 傳參時,清單和字典的使用:

app.py

from flask import Flask,render_template

app = Flask(__name__)


@app.route('/', methods=['GET', 'POST'])
def user():
    user_var="程式設計類輔助實驗教學平台"
    list_test=['這是list的1','這是list的2','這是list的3','這是list的4','這是list的5']
    dict_test={
        'name': "這是測試name",
        'key': "這是測試key"
    }
    return render_template('demo3.html', html_var=user_var,list_test=list_test,dict_test=dict_test)


if __name__ == '__main__':
    app.run()      

demo3.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>This is a Demo3 Page</h1>
<h2>It is only using for test</h2>
這是傳輸過來的變量:{{ html_var }}<br>
<h2>清單使用:</h2>
{{ list_test }}<br>
{{ list_test.1 }}<br>
{{ list_test[2] }}<br>
<h2>dict使用:</h2>
{{ dict_test }}<br>
{{ dict_test.name }}<br>
{{ dict_test['key']}}<br>
</body>
</html>      

頁面效果:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

3.5 控制語句的使用

在{% %}中進行寫Python控制語句,這裡使用的文法規範是Python的,在結束控制的是要使用end例如​

​for​

​​指令結束的時候,我們要寫一行​

​{% endfor %}​

{% for num in list_test %}
    {% if num > 2 %}
    {{ num }}<br>
    {% endif %}
{% endfor %}      

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

3.6 過濾器的基本使用

{{ var_test|upper }}      

常見的過濾器

過濾器名 說明
safe 渲染時不轉義
capitalize 把值的首字母大寫,其餘小寫
lower 把值轉換成小寫形式
upper 把值轉換成大寫形式
title 把值中每個單詞的首字母都轉換成大寫
trim 把值的首位空格去掉
striptags 渲染之前把值中所有的HTML标簽删掉

3.7 使用flash傳遞消息

app.py

from flask import Flask,render_template,request,flash

app = Flask(__name__)
app.secret_key='this is a secret key'


@app.route('/', methods=['GET', 'POST'])
def user():
    #request請求對象,包括請求方式,資料等
    #flash需要對内容加密,是以要設定secret_key,做加密消息混淆
    if request.method == 'POST':
        username=request.form.get('username')
        password1=request.form.get('password1')
        password2=request.form.get('password2')
        if not all([username,password1,password2]):
            #return '參數不完整'
            flash('參數不完整')
        elif password1 == password2:
            #return 'true'
            flash("true")
        else:
            #return 'false'
            flash("false")
    return render_template('demo3.html')



if __name__ == '__main__':
    app.run()      

demo3.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
    <label>使用者名:</label><input type="text" name="username"><br>
    <label>密碼:</label><input type="password" name="password1"><br>
    <label>确認密碼:</label><input type="password" name="password2"><br>
    <input type="submit" value="送出"><br>

    {% for message in get_flashed_messages() %}
        {{ message }}
    {% endfor %}
</form>
</body>
</html>      

顯示效果:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

3.8 使用WTF實作web表單

這裡可以使用Flask-WTF擴充也可以直接使用我們HTML的​

​<form>​

​表單,都可以。

3.8.1 WTForms支援的HTML标準字段

字段類型 說明
BooleanField 複選框,值為True和False
DateField 文本字段,值為datetime.date格式
DareTimeField 文本字段,值為datetime.datetime格式
FileField 檔案上傳字段
HiddenField 隐藏的文本字段
MultipleFileField 多文本上傳字段
FieldList 一組指定類型的字段
FloatField 文本字段,值為浮點數
FormField 把一個表單作為字段嵌入另一個表單
IntegerField 文本字段,值為整數
PasswordField 密碼文本字段
RadioField 一組單選按鈕
SelectField 下拉清單
SelectMultipleField 下拉清單,可選擇多個值
SubmitField 表單送出按鈕
StringField 文本字段
TextAreaField 多行文本字段

3.8.2 WTForms驗證函數

驗證函數 說明
DateRequired 確定轉換類型後字段中有資料
Email 驗證電子郵件位址
EqualTo 比較兩個字段的值,常用于要求輸入兩次密碼進行确認的時候
InputRequired 確定轉換類型前字段中有資料
IPAddress 驗證IPv4網絡位址
Length 驗證輸入字元串的長度
MacAddress 驗證MAC位址
NumberRange 驗證輸入的值在數字範圍之内
Optional 允許字段中沒有輸入,将跳過其他驗證函數
Regexp 使用正規表達式驗證輸入值
URL 驗證URL
UUID 驗證UUID
AnyOf 確定輸入值在一組可能的值中
NoneOf 確定輸入值不在一組可能的值中

3.8.3Flask-WTF擴充的安裝

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

在PyCharm中打開Preferences

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

打開Project選項闆

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

進行Project Interpreter設定

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

點選添加‘+’,然後搜尋Flask-WTF

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

選擇好版本之後,Install Package,拓展包就添加完成了

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

在項目中導入Flask-WTF

from flask_wtf import FlaskForm      

在項目中導入Flaks-WTF的HTML标準字段

from wtforms import StringField,PasswordField,SubmitField      

在項目中導入Flask-WTF的驗證函數

from wtforms.validators import DataRequired, EqualTo      

在使用Flask-WTF拓展的時候,需要使用CSRF Token

在html頁面中加入

{{ html_form.csrf_token() }}      

3.8.4使用Flask-WTF建立表單示例

app.py

from flask import Flask, render_template, request, flash
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo

app = Flask(__name__)

app.secret_key = "dsauhfabf"


class LoginForm(FlaskForm):
    username = StringField('使用者名', validators=[DataRequired()])
    password1 = PasswordField('密碼', validators=[DataRequired()])
    password2 = PasswordField('确認密碼', validators=[DataRequired(), EqualTo('password1', "兩次密碼填入不一緻")])
    submit = SubmitField('送出')


@app.route('/form', methods=['GET', 'POST'])
def login():
    login_form = LoginForm()
    if request.method == 'POST':
        username = request.form.get('username')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')
        if login_form.validate_on_submit():
            return 'success'
        else:
            flash("false")

    return render_template('demo3.html', html_form=login_form)


if __name__ == '__main__':
    app.run()      

demo3.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
    <br>
    {{ html_form.csrf_token() }}
    {{ html_form.username.label }}{{ html_form.username }}<br>
    {{ html_form.password1.label }}{{ html_form.password1 }}<br>
    {{ html_form.password2.label }}{{ html_form.password2 }}<br>
    {{ html_form.submit }}<br>

    {% for message in get_flashed_messages() %}
        {{ message }}
    {% endfor %}
</form>
</body>
</html>      

頁面效果:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!
收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!
收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!
收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

3.9 Flask-SQLAlchemy拓展

使用Flask-SQLAichemy可以處理Python對象,而不是資料實體。這裡我們也可以在項目中直接使用sql語句進行操縱資料庫。

3.9.1 Flask-SQLAlchemy拓展安裝方法:

  • 通過PyCharm的project interpret 繼續安裝
  • 通過指令行進行安裝
pip install flask-sqlalchemy      

在Flask-SQLAlchemy中,資料庫使用URL進行指定,MySQL的URL格式為:

mysql://username:password@hostname/datebase      

例如 ​

​mysql://root:[email protected]:3306/demoDatabse​

安裝pymysql

​​

​pip install pymysql​

3.9.2 配置資料庫:

from flask_sqlalchemy import SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI']= 'mysql+pymysql://root:[email protected]:3306/Demo'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)      

3.9.3建立資料庫類,映射資料庫表

class ClassInfo(db.Model):
    __tablename__ = 'classinfo'
    id = db.Column(db.Integer(), primary_key=True)
    className = db.Column(db.String(20))
    classSize = db.Column(db.Integer())      

注意,其中的類型的第一個字母要大寫

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

3.9.4在視圖中操作資料庫查詢

通過資料庫類的query對象中的方法進行查詢

mylist = ClassInfo.query.all()
    print(mylist)
    for it in mylist:
        print(it)
    test = mylist[0].className
    return render_template('demo3.html', html_form=login_form, test=test)      

3.9.5 常用的SQLAlchemy查詢過濾器

過濾器 說明
filter() 把過濾器添加到原查詢上,傳回一個新查詢
filter_by() 把等值過濾器添加到原查詢上,傳回一個新查詢
limit() 使用指定的值限制原查詢傳回的結果數量,傳回一個新查詢
offset() 偏移原查詢傳回的結果,傳回一個新查詢
order_by() 根據指定條件對原查詢進行排序,傳回一個新查詢
group_by() 根據指定調教對原查詢結果進行分組,傳回一個新查詢

3.9.6常用SQLAlchemy查詢執行方法

方法 說明
all() 以清單形式傳回查詢的所有結果
first() 傳回查詢的第一個結果,如果沒有結果,則傳回None
first_or_404() 傳回查詢的第一個結果,如果沒有結果,則終止請求,傳回4040錯誤響應
get() 傳回指定主鍵對應的行,如果沒有對應的行,則傳回NOne
get_or_404 傳回指定主鍵對應的行,如果沒找到指定的主鍵,則終止請求,傳回404錯誤響應
count() 傳回查詢結果的數量
paginate() 傳回一個Paginate對象,包含指定範圍内的結果

3.10 使用werkzeug計算密碼散列值

  在項目中我們一般都需要進行注冊、登入賬戶,而賬戶密碼又是一個非常重要的使用者資訊,我們一般都需要對他進行加密處理,這裡我們在werkzeug中的security子產品實作了密碼散列值的計算。共有兩個函數​

​generate_password_hash(password, method='pbkdf2:sha256', salt_length=8)​

​​ 這個函數的輸入為原始密碼,傳回密碼散列值的字元串形式,供存入使用者資料庫。

​check_password_hash(hash,password)​

​ 這個函數的兩個參數為,資料庫中存放的密碼hash值,和使用者輸入的密碼,如果一緻,傳回True,如果不一緻,傳回False。

這裡是采用的128位的散列加密,是以我們在建立資料庫表的時候要注意我們字段的類型。

3.11 檔案的上傳、下載下傳

3.11.1 上傳檔案

html代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上傳檔案</title>
</head>
<body>
<div>
    <form action="" method="post" enctype="multipart/form-data">
        <table>
                <tr>
                    <td>
                        選擇你需要上傳的檔案
                    </td>
                    <td>
                        <input type="file" name="file">
                    </td>
                    <td>
                        <input type="submit">
                    </td>
                </tr>
        </table>

    </form>
</div>
</body>
</html>      

要注意,在中要設定屬性​

​enctype="multipart/form-data"​

3.11.2 在背景接收檔案并儲存

在背景接受檔案使用

uoload_file = request.files.get('file')      

進行擷取,get(‘’)中參數是前端頁面中的input的name

3.11.3 在背景儲存檔案

可以使用save()進行儲存

upload_file = request.files.get('file')
file_path = 'store'
wenjian.save(os.path.join(file_path,upload.filename))      
可以secure_filename()生成随機檔案名,在werkzeug.utils中,也可以手動設定檔案名,加安全性

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

3.12 Flask 藍圖

藍圖的作用是使Flask項目更加的子產品化,結構更清晰。可以将相同的子產品視圖函數放在同一個藍圖下,同一個檔案中,友善管理。

3.12.1 藍圖的基本用法

  • 在藍圖檔案中導入Blueprint:
from flask import Blueprint      
  • 初始化藍圖:例如user藍圖
user_bp = Blueprint('user',__name__)      
  • 在主app檔案中注冊藍圖:

    例如user_bp藍圖在Blueprint/user.py檔案中,注冊函數為下

from flask import Flask
from flask import Blueprint
from Blueprint.user import user_bp
app = Flask(__name__)
app.register_blueprint(user_bp)      
  • 給每個藍圖添加字首

    要給某個藍圖下的所有route添加各自的字首,可以在初始化藍圖的時候設定:例如給user_bp的每個route前添加/user字首

from flask import Blueprint
user_bp = Blueprint('user',__name__,url_prefix='/user')      

  到這裡為止,我們第Flask基本知識部分就技術了,這裡面涵蓋了一些Flask的一些基本的文法和應用,下面我們就要開始正式的進入我們的實戰系列教程了。

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

四、導航欄、注冊、登入、登出、首頁功能的實作

  下面來到我們正式的實戰環節了,在第四部分我們将一起實作導航欄、注冊、登入、登出、首頁功能,手把手的帶你來完成這些功能的實作。

更多部落客的文章可以檢視部落客的站内導航​​部落格文章内容導航(實時更新)​​ 本項目所有源碼在GitHub開源,GitHub位址為:​​OnlineForumPlatform​​   有需要源碼可以前去檢視,喜歡的話可以star一下。在做完準備工作之後,我們就正式的開始開發了。部落客将帶領你實作線上論壇系統的導覽列、注冊、登入和首頁功能,在實作的同時會講解各個功能實作的原理,手把手的教你進入Python Web全棧開發,一個字一個字的代碼完成本項目。

4.1 導航欄實作

我們首先從導航欄開始開發,每個頁面需要有導航欄,可以說一個非常常用的元件了。這裡我們先建立一個hmtl頁面base.html進行開發導航欄相關功能,将檔案建立在templates檔案夾中。

在這裡先說明一下為什麼要使用base.html這個名字,因為Jinja2模闆是支援繼承機制的,而導航欄又是幾乎每個頁面都需要使用到的一個元件,是以我們這裡将導航欄這個檔案base.html作為一個基類,其他所有的視圖檔案都繼承自它,并在它的基礎上進行重寫相關的内容,實作各個視圖的不同内容。

在建立完檔案之後,由于我們這個項目前端UI部分使用Bootstrap進行快速建站,還有不會Bootstarp的同學可以看一下之前的這篇教程:​​什麼?你還不會Bootstrap?一文教會你Bootstrap,讓你也可以快速建站​​

我們這裡選擇一個3.4.0的版本,在html中引入:

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">      

在引用完成之後,我們先去bootstrap樣式庫中找一個喜歡的導覽列樣式,然後加入到我們的base.html中。

我們這裡使用這個樣式的,直接加入到base.html中:

<nav class="navbar navbar-default">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">Brand</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>      

插入之後我們打開網頁就可以看到如下效果:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

然後我們在根據我們的需求,對這個導覽列進行一定的更改,并且為base.html頁面劃分幾個block。

暫時先修改為這樣,後續我們加完導航之後,還需再對各個标定的頁面進行一個修改。修改之後的完整代碼為:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}
{#        其他頁面重寫标題的地方#}
        {% endblock %}
    </title>
    {% block css %}
{#    其他頁面引用樣式或者js的地方#}
    {% endblock %}
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="navigation_bar">
        <nav class="navbar navbar-default">
          <div class="container-fluid">
{#              由于這裡我們不需要使用商标,是以對Bran部分進行了删除#}
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav">
                <li class="active"><a href="#">首頁<span class="sr-only">(current)</span></a></li>
                <li><a href="#">Link</a></li>
                <li class="dropdown">
                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li><a href="#">Action</a></li>
                    <li><a href="#">Another action</a></li>
                    <li><a href="#">Something else here</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">Separated link</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">One more separated link</a></li>
                  </ul>
                </li>
              </ul>
              <form class="navbar-form navbar-left">
                <div class="form-group">
                  <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
              </form>
              <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Link</a></li>
                <li class="dropdown">
                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li><a href="#">Action</a></li>
                    <li><a href="#">Another action</a></li>
                    <li><a href="#">Something else here</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">Separated link</a></li>
                  </ul>
                </li>
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    </div>
    <div>
        {% block content %}
{#        其他頁面重寫頁面内容的地方#}
        {% endblock %}
    </div>
</body>
</html>      

這裡暫時加了3個block,分别是title:标題。css:引用css樣式和js腳本。content:頁面主題内容部分。

導航欄的剩下内容将會在項目開發的過程中逐漸完善,因為它涉及多個頁面,下面我們進行下一項功能頁面。

4.2 注冊功能實作

4.2.1 注冊功能實作-資料庫

在實作注冊功能的時候,我們首先就需要在資料庫中建立一個表來存儲我們的注冊資訊了。這個項目預設2端(普通使用者端和管理者端),那麼我們表中需要存儲使用者的資訊有:使用者名(這裡使用郵箱),昵稱,密碼,使用者的權限,注冊時間,聯系方式等資訊,我們這裡暫定收集使用者名,昵稱,密碼,使用者的權限,注冊時間,聯系方式,這6種資訊用于注冊,下面我們建立一個UserInformation表。這裡可以使用pychram右面的Database使用圖像界面建立,也可以使用指令行建立。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

SQL語句為:

create table UserInformation
(
  email varchar(128) not null,
  nickname nvarchar(100) default '未設定昵稱' null,
  password varchar(128) not null,
  type int default '0' null,
  create_time datetime default '1999-9-9 9:9:9' not null,
  phone varchar(128) null,
  constraint UserInformation_pk
    primary key (email)
);      

這樣我們一個用于存儲使用者資訊的表就建立好了,下面我們來設計一個注冊的前端頁面。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

4.2.2 注冊功能實作-前端

在寫前端頁面之前,由于我們每個頁面都是繼承自base.html,是以我們可以寫個extend.html來友善我們每次進行繼承,在此基礎上進行開發。

extend.html頁面代碼為:

{% extends 'base.html' %}

{% block title %}

{% endblock %}

{% block css %}

{% endblock %}

{% block content %}

{% endblock %}      

下面我們就正式的開始寫register.html頁面了。

首先我們去bootstrap樣式庫中去找個頁頭,為頁面添加一個大标題。

<div class="page-header">
  <h1>Example page header <small>Subtext for header</small></h1>
</div>      

為了友善我們實時的調試我們的頁面,我們先在app.py中為注冊頁面添加一個路由:

from flask import *

app = Flask(__name__)


@app.route('/')
def hello_world():
    return render_template('base.html')


# 注冊頁面
@app.route('/register')
def register():
    return render_template('register.html')


if __name__ == '__main__':
    app.run()      

這樣我們通路http://127.0.0.1:5000/register就可以實時檢視我們頁面的資訊了,友善我們對UI部分進行調試,設計一個自己喜歡的UI。在引入頁頭之後,我們通路發現頁面有點差強人意,左對齊一點也不好看,我們可以讓他居中。我們在static/css檔案夾中建立一個register.css來為register.html提供樣式。建立完之後我們在register.html中引入這個css。

<link rel="stylesheet" href="/static/css/register.css">      

下面我們就來選擇一個表單進行設計一個注冊頁面。這裡調整UI部分比較簡單并且繁瑣,是以就不一步一步的記錄了,在調整完之後貼上完整的代碼,給大家看一下整體的效果。

初步設計之後,register.css:

#page_header{
    text-align: center;
}

.register_content{
    /*調整邊距,調到相對中間的位置*/
    margin:10% 25%;
}

#register_butt{
    text-align: center;
}      

register.html:

{% extends 'base.html' %}

{% block title %}
注冊
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/register.css">
{% endblock %}

{% block content %}
<div class="register_content">
    <div class="page-header" id="page_header">
      <h1>注冊<small>Register</small></h1>
    </div>
    <div id="register_form">
        <form method="post">
          <div class="form-group">
            <label for="exampleInputEmail1">Email address</label>
            <input type="email" class="form-control" name="email" id="exampleInputEmail1" placeholder="Email address">
          </div>
          <div class="form-group">
            <label for="exampleInputEmail1">昵稱</label>
            <input type="text" class="form-control" name="nickname" id="exampleInputEmail1" placeholder="昵稱">
          </div>
          <div class="form-group">
            <label for="exampleInputPassword1">密碼</label>
            <input type="password" class="form-control" name="password_1" id="exampleInputPassword1" placeholder="密碼">
          </div>
            <div class="form-group">
            <label for="exampleInputPassword1">确認密碼</label>
            <input type="password" class="form-control" name="password_2" id="exampleInputPassword1" placeholder="确認密碼">
          </div>
          <div class="form-group">
            <label for="exampleInputEmail1">聯系方式</label>
            <input type="text" class="form-control" name="phone" id="exampleInputEmail1" placeholder="聯系方式">
          </div>
          <div id="register_butt">
              <button type="submit" class="btn btn-default">注冊</button>
              <button type="button" class="btn btn-default" onclick="location.href='#'">登入</button>
          </div>
        </form>
    </div>

</div>
{% endblock %}      

頁面效果為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

整體還是簡潔大方的,下面我們在針對注冊頁面修改一下導覽列,在導覽列中添加到注冊頁面的導航,并且添加一個block來标定上面選取的頁面。

修改之後的base.html為:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}
{#        其他頁面重寫标題的地方#}
        {% endblock %}
    </title>
    {% block css %}
{#    其他頁面引用樣式或者js的地方#}
    {% endblock %}
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="navigation_bar">
        <nav class="navbar navbar-default">
          <div class="container-fluid">
{#              由于這裡我們不需要使用商标,是以對Bran部分進行了删除#}
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav">
                <li class="{% block homepage_class %}{% endblock %}"><a href="#">首頁<span class="sr-only">(current)</span></a></li>
                <li><a href="#">Link</a></li>
                <li class="dropdown">
                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li><a href="#">Action</a></li>
                    <li><a href="#">Another action</a></li>
                    <li><a href="#">Something else here</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">Separated link</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">One more separated link</a></li>
                  </ul>
                </li>
              </ul>
              <form class="navbar-form navbar-left">
                <div class="form-group">
                  <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
              </form>
              <ul class="nav navbar-nav navbar-right">
                <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注冊</a></li>
                <li><a href="#">登入</a></li>
{#                <li class="dropdown">#}
{#                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>#}
{#                  <ul class="dropdown-menu">#}
{#                    <li><a href="#">Action</a></li>#}
{#                    <li><a href="#">Another action</a></li>#}
{#                    <li><a href="#">Something else here</a></li>#}
{#                    <li role="separator" class="divider"></li>#}
{#                    <li><a href="#">Separated link</a></li>#}
{#                  </ul>#}
{#                </li>#}
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    </div>
    <div>
        {% block content %}
{#        其他頁面重寫頁面内容的地方#}
        {% endblock %}
    </div>
</body>
</html>      

同時在對register.html中進行添加block register_class,添加完成之後的register.html為:

{% extends 'base.html' %}

{% block title %}
注冊
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/register.css">
{% endblock %}

{% block content %}
<div class="register_content">
    <div class="page-header" id="page_header">
      <h1>注冊<small>Register</small></h1>
    </div>
    <div id="register_form">
        <form method="post">
          <div class="form-group">
            <label for="exampleInputEmail1">Email address</label>
            <input type="email" class="form-control" name="email" id="exampleInputEmail1" placeholder="Email address">
          </div>
          <div class="form-group">
            <label for="exampleInputEmail1">昵稱</label>
            <input type="text" class="form-control" name="nickname" id="exampleInputEmail1" placeholder="昵稱">
          </div>
          <div class="form-group">
            <label for="exampleInputPassword1">密碼</label>
            <input type="password" class="form-control" name="password_1" id="exampleInputPassword1" placeholder="密碼">
          </div>
            <div class="form-group">
            <label for="exampleInputPassword1">确認密碼</label>
            <input type="password" class="form-control" name="password_2" id="exampleInputPassword1" placeholder="确認密碼">
          </div>
          <div class="form-group">
            <label for="exampleInputEmail1">聯系方式</label>
            <input type="text" class="form-control" name="phone" id="exampleInputEmail1" placeholder="聯系方式">
          </div>
          <div id="register_butt">
              <button type="submit" class="btn btn-default">注冊</button>
              <button type="button" class="btn btn-default" onclick="location.href='#'">登入</button>
          </div>
        </form>
    </div>

</div>
{% endblock %}

{% block register_class %}
active
{% endblock %}      

調整之後的注冊頁面為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

當在注冊頁面時,會将注冊的導航着重顯示。同時在導覽列上添加了注冊和登入頁面的導航。

4.2.3 注冊功能實作-後端

下面我們就來實作注冊的伺服器端功能,将前端發送的資訊檢查,如果不正确則傳回提示,如果正确則将使用者資訊存儲到資料庫中。

既然要使用到資料庫,那我們就先來配置一下config.py,這個檔案主要存放項目的配置資訊。

這裡我們設定一下SECRET_KEY和db。

config.py:

# encoding:utf-8
import os
import pymysql

DEBUG = False

SECRET_KEY = os.urandom(24)

db = pymysql.connect(host='localhost', user='root', password='password1q!', db='OnlineForumPlatform', port=3306)      

配置完成之後我們在app.py中導入config.py,并且綁定配置。

from flask import *
import config

app = Flask(__name__)

# 從對象中導入config
app.config.from_object(config)      

然後我們開始寫注冊的後端邏輯功能。本系列第一次寫後端代碼,這裡做一個詳細的說明,有Flask基礎不錯的同學可以直接跳到後面的整體代碼。

由于我們路由預設的請求方式隻有一種是GET請求,但是我們注冊的話,為了安全,form表單一般使用POST請求,是以我們這裡先設定請求方式:

@app.route('/register',methods=['GET','POST'])      

然後我們再擷取前端表單的資訊:

email = request.form.get('email')
nickname = request.form.get('nickname')
password_1 = request.form.get('password_1')
password_2 = request.form.get('password_2')
phone = request.form.get('phone')      

在擷取完之後我們就需要對這些資料進行處理了,首先我們要檢查資料是否完整,如果資訊填寫不完整肯定是不可以注冊的,我們傳回提示,這裡我們使用flash傳遞提示回到注冊頁面。

if not all([email,nickname,password_1,password_2,phone]):
            flash("資訊填寫不全,請将資訊填寫完整")
            return render_template('register.html')      

既然使用flash進行傳遞消息,那我們就需要在前端将flash消息顯示出來。我們将這段代碼放到前端合适的一個位置,這個位置自己選擇一個顯眼的位置即可。這裡我講這個資訊提示放在了頁頭的下方。

<span style=" font-size:20px;color: red" >
  {% for item in get_flashed_messages() %}
    {{ item }}
  {% endfor %}
</span>      

下面繼續來完善我們的後端,資訊填寫完整之後,我們還需要驗證兩次密碼輸入的是否一緻,如果不一緻,則需要傳回錯誤提示。

if password_1 != password_2:
  flash("兩次密碼填寫不一緻!")
  return render_template('register.html')      

如果資訊都填寫正确了,那下面我們開始對密碼進行加密,我們這裡使用的是pbkdf2:sha256加密方式,對密碼進行128位的散列加密,可以極大的保護使用者資訊的安全性。要使用這個加密,我們需要導入它:

from werkzeug.security import generate_password_hash, check_password_hash      

然後我們使用generate_password_hash來加密我們的密碼,由于password_1和password_2是一樣的,那麼我們隻需要加密password_1即可:

password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)      

這樣我們的準備工作就完成了,下面我們開始将資訊存儲到資料庫中:

首先我們要将我們config檔案中配置的db導入進來:

from config import db      

然後我們來擷取db的cursor,來進行相關資料庫操作,這裡我們使用Python直接操縱資料庫,當然,大家也可以使用Flask-SQLAlchemy來進行操作資料庫,這裡就使用Python直接操縱資料庫的方式進行。

首先我們要檢查我們的資料庫中email是否存在,即目前使用者是否已經存在,我們使用email進行唯一表示使用者,是以不允許重複,我們還需要檢查一下email。

try:
    cur = db.cursor()
    sql = "select * from UserInformation where email = '%s'"%email
    db.ping(reconnect=True)
    cur.execute(sql)
    result = cur.fetchone()
    if result is not None:
        flash("該Email已存在!")
        return render_template('register.html')
except Exception as e:
    raise e      

這裡注冊的使用者類型我們肯定是不可以注冊管理者的,不然這個管理者就是形同虛設了,我們這裡通過register頁面來注冊的使用者,我們統一都是普通使用者,這裡type置為0。然後建立時間我們使用伺服器端來擷取目前的時間。這裡要使用time庫,需要導入

import time
create_time = time.strftime("%Y-%m-%d %H:%M:%S")      

然後将資料插入資料庫中,同時在插入完成之後我們應該傳回首頁,并且為登入狀态,由于我們這裡首頁和登入都還沒有實作,是以這裡我們先寫一個空的首頁路由,并且建立一個index.html,使用路由傳回index.html頁面。

index.html我們先這樣空置,等在本講的最後我們來實作這個首頁。

index.html:

{% extends 'base.html' %}

{% block title %}
首頁
{% endblock %}

{% block css %}

{% endblock %}

{% block content %}

{% endblock %}      

完成注冊伺服器端功能之後的app.py全部代碼為:

from flask import *
from werkzeug.security import generate_password_hash, check_password_hash
from config import db
import time
import config

app = Flask(__name__)

# 從對象中導入config
app.config.from_object(config)



@app.route('/')
def index():
    return render_template('index.html')


# 注冊頁面
@app.route('/register',methods=['GET','POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    if request.method == 'POST':
        email = request.form.get('email')
        nickname = request.form.get('nickname')
        password_1 = request.form.get('password_1')
        password_2 = request.form.get('password_2')
        phone = request.form.get('phone')
        if not all([email,nickname,password_1,password_2,phone]):
            flash("資訊填寫不全,請将資訊填寫完整")
            return render_template('register.html')
        if password_1 != password_2:
            flash("兩次密碼填寫不一緻!")
            return render_template('register.html')
        password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)
        try:
            cur = db.cursor()
            sql = "select * from UserInformation where email = '%s'"%email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result is not None:
                flash("該Email已存在!")
                return render_template('register.html')
            else:
                create_time = time.strftime("%Y-%m-%d %H:%M:%S")
                sql = "insert into UserInformation(email, nickname, password, type, create_time, phone) VALUES ('%s','%s','%s','0','%s','%s')" %(email,nickname,password,create_time,phone)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('index'))
        except Exception as e:
            raise e

if __name__ == '__main__':
    app.run()      

這樣我們的注冊功能就全部實作啦!可以先去注冊一個自己的測試賬号,友善我們後面的各項功能測試呦!

4.3 登入功能實作

下面我們就來實作登入功能

4.3.1 登入功能實作-前端

登入的話,前端和我們的注冊類似,上面一個頁頭,然後下面使用一個表單,并且設定一個block用來标記一下目前的頁面即可,在注冊的前端設計的時候已經有詳細的說明,這裡就不在贅述了,建立一個login.html,下面廢話不過說,直接上代碼:

首先我們在app.py中寫一個空的路由,友善我們在前端頁面中使用url_for來重定向頁面:

# 注冊頁面
@app.route('/login')
def login():
    return render_template('login.html')      

然後我們建立一個login.css來設定頁面的樣式:

#page_header{
    text-align: center;
}

.login_content{
    /*調整邊距,調到相對中間的位置*/
    margin:10% 30%;
}

#login_butt{
    text-align: center;
}      

login.html部分為:

{% extends 'base.html' %}

{% block title %}
登入
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/login.css">
{% endblock %}

{% block content %}
<div class="login_content">
    <div class="page-header" id="page_header">
      <h1>登入<small>Login</small></h1>
    </div>
    <div id="login_form">
        <form method="post">
            <span style=" font-size:20px;color: red" >
                {% for item in get_flashed_messages() %}
                {{ item }}
                {% endfor %}
            </span>
          <div class="form-group">
            <label for="exampleInputEmail1">Email address</label>
            <input type="email" class="form-control" name="email" id="exampleInputEmail1" placeholder="Email address">
          </div>
          <div class="form-group">
            <label for="exampleInputPassword1">密碼</label>
            <input type="password" class="form-control" name="password" id="exampleInputPassword1" placeholder="密碼">
          </div>
          <div id="login_butt">
              <button type="submit" class="btn btn-default">登入</button>
              <button type="button" class="btn btn-default" onclick="location.href='{{ url_for('register') }}'">注冊</button>
          </div>
        </form>
    </div>
</div>
{% endblock %}

{% block login_class %}
active
{% endblock %}      

在登入頁面完成之後,我們同時修改一下導覽列的内容,在base.html中,将首頁的導航修改為:

<li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登入</a></li>      

如果不修改base的話,直接運作上面的html修改之後的頁面會報錯,因為在login.html中使用了{% block login_class %} {% endblock %},這個是剛剛在base.html中添加的block,用于标記目前頁面。

修改完成之後我們的登入頁面效果為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

4.3.2 登入功能實作-後端

登入功能的後端我們主要分為兩部分,一個是接受表單并驗證表單資訊的正确性,并進行回報,第二部分就是登入狀态的保持,下面我們将分這兩部分來實作後端的功能。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。
4.3.2.1 登入功能實作-後端-驗證登入資訊

這裡同樣的,我們登入的表單為了安全性,一般都使用POST來發送,這裡先設定允許的請求方式。

@app.route('/login',methods=['GET','POST'])      

然後我們來擷取前端的Email和密碼:

# 登入頁面
@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    if request.method == 'POST':
        email = request.form.get('email')
        password = request.form.get('password')      

然後為了防止出錯,我們先驗證資料的完整性:

if not all([email,password]):
    flash("請将資訊填寫完整!")
    return render_template('login.html')      

然後我們來驗證密碼是否正确,首先我們先從資料庫中擷取目前登入email的密碼,并驗證它,如果密碼不存,則說明該使用者不存在,即未注冊,我們傳回提示。如果密碼存在,那麼我就擷取密碼,然後使用check_password_hash()函數來驗證它,如果密碼正确,我們将使用者名(即email)放入session中,友善我們下面進行實作登入狀态保持的功能。

代碼為:

# 登入頁面
@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    if request.method == 'POST':
        email = request.form.get('email')
        password = request.form.get('password')
        if not all([email,password]):
            flash("請将資訊填寫完整!")
            return render_template('login.html')
        try:
            cur = db.cursor()
            sql = "select password from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result is None:
                flash("該使用者不存在")
                return render_template('login.html')
            if check_password_hash(result[0],password):
                session['email'] = email
                session.permanent = True
                return redirect(url_for('index'))
            else:
                flash("密碼錯誤!")
                return render_template('login.html')
        except Exception as e:
            raise e      

這樣我們的登入的基本功能就實作了,大家可以去測試下,如果登入正常則會傳回首頁,如果登入失敗則會顯示各種各樣的提示。下面我們就要實作登入的另一個功能,登入狀态保持,我們登入之後如何讓系統一直保持我們的登入狀态呢?

4.3.2.2 登入功能實作-後端-登入狀态保持

要實作登入狀态保持,我們這裡可以使用上下文鈎子函數來一直保持登入狀态。

# 登入狀态保持
@app.context_processor
def login_status():
    # 從session中擷取email
    email = session.get('email')
    # 如果有email資訊,則證明已經登入了,我們從資料庫中擷取登陸者的昵稱和使用者類型,來傳回到全局
    if email:
        try:
            cur = db.cursor()
            sql = "select nickname,type from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result:
                return {'email':email,'nickname':result[0] ,'user_type':result[1]}
        except Exception as e:
            raise e
    # 如果email資訊不存在,則未登入,傳回空
    return {}      

有了登入狀态保持之後,我們就可以再次的修改導覽列,将右側的注冊登入進行修改,當未登入時,顯示登入注冊,當已登入的時候,顯示登出和{ 使用者昵稱 },使用者昵稱處我們可以使用下拉清單,等後期我們會在此添加功能。

修改之後的導航欄右面内容為:

{% if email %}
    <li class="{% block login_out_class %}{% endblock %}"><a href="{{ url_for('register') }}">登出</a></li>
    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
      <ul class="dropdown-menu">
        <li><a href="#">Action</a></li>
        <li><a href="#">Another action</a></li>
        <li><a href="#">Something else here</a></li>
        <li role="separator" class="divider"></li>
        <li><a href="#">Separated link</a></li>
      </ul>
    </li>
{% else %}
    <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注冊</a></li>
    <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登入</a></li>
{% endif %}      

這樣我們登入之後的頁面效果為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

為了防止小夥伴不知道上面的這段代碼到底在哪裡修改,這裡再貼一下修改後的base.html的全部代碼:這裡的登出的連結還沒有寫,在等會我們實作了登出的功能之後再在這裡添加連結。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}
{#        其他頁面重寫标題的地方#}
        {% endblock %}
    </title>
    {% block css %}
{#    其他頁面引用樣式或者js的地方#}
    {% endblock %}
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="navigation_bar">
        <nav class="navbar navbar-default">
          <div class="container-fluid">
{#              由于這裡我們不需要使用商标,是以對Bran部分進行了删除#}
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav">
                <li class="{% block homepage_class %}{% endblock %}"><a href="{{ url_for('index') }}">首頁<span class="sr-only">(current)</span></a></li>
                <li><a href="#">Link</a></li>
                <li class="dropdown">
                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li><a href="#">Action</a></li>
                    <li><a href="#">Another action</a></li>
                    <li><a href="#">Something else here</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">Separated link</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">One more separated link</a></li>
                  </ul>
                </li>
              </ul>
              <form class="navbar-form navbar-left">
                <div class="form-group">
                  <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
              </form>
              <ul class="nav navbar-nav navbar-right">
                {% if email %}
                    <li class=""><a href="{{ url_for('register') }}">登出</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="#">Action</a></li>
                        <li><a href="#">Another action</a></li>
                        <li><a href="#">Something else here</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                      </ul>
                    </li>
                {% else %}
                    <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注冊</a></li>
                    <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登入</a></li>
                {% endif %}
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    </div>
    <div>
        {% block content %}
{#        其他頁面重寫頁面内容的地方#}
        {% endblock %}
    </div>
</body>
</html>      

4.4 登出功能實作

下面我們就來完成使用者的登出功能,其實登出功能實作很簡單,我們這裡就簡單粗暴的将session清空即可達到登出的效果,清空之後我們重定向到首頁即可。

# 使用者登出
@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for(('index')))      

4.5 首頁實作

其實這裡排版有一點小小的失誤,我們應該第一個實作首頁的,不過現在實作也并不影響。首頁我們需要的功能很簡單,找個漂亮唯美的背景圖,中間加上幾個"線上論壇系統"大字即可,一個漂漂亮亮的首頁就完成了,因為我們目前首頁不需要别的功能在,暫時就設定這樣子就好了,我們先去随便找一個唯美的風景圖。我們這裡在百度圖庫中随便找了一個,然後把它下載下傳放到我們的/static/img檔案中。然後在我們之前建立的index.html中插入這張圖檔,再建立一個index.css,對樣式進行調整。

在插入之後我們發現圖檔和上面導航欄會有一點點的空隙,看着還不舒服。這裡我們需要為base.html建立一個base.css來設定一下導覽列的樣式,為他設定height = 52px;這樣會無縫銜接,這個值也可以稍微大一點。然後我們在base.html中引入樣式。

.navigation_bar{
    height: 52px;
}      
<link rel="stylesheet" href="/static/css/base.css">      

這樣之後我們的圖檔就可以無縫銜接了。我們的圖檔是作為一個大的div背景圖檔插入的,這樣友善我們在上面進行顯示标題。下面直接貼代碼:

index.html:

{% extends 'base.html' %}

{% block title %}
首頁
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/index.css">
{% endblock %}

{% block content %}
<div class="index_content">
    <div class="index_title">
        <h1>線上論壇系統<small>Online Forum Platform</small></h1>
    </div>
</div>
{% endblock %}      

index.css:

.index_content{
    margin: 0;
    padding: 0;
    width: 100%;
    /*這裡由于圖檔高為1200,是以直接設定了1200,大家可以可以根據自己的需求進行設定*/
    height: 1200px;
    background-image: url("/static/img/index.jpeg");
}
.index_title{
    text-align: center;
    padding: 20% 20%;
}
.index_title h1{
    font-style: italic;
    font-size: 60px;
    text-shadow: 0.15em 0.15em 0.1em #333;
    font-weight: bolder;
}      

在app.py中,我們首頁的路由暫時隻需要傳回頁面即可:

# 首頁
@app.route('/')
def index():
    return render_template('index.html')      

下面來讓我們看一下首頁的效果圖:(部落格直男審美,各位可以自己設計首頁的效果)

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

  好了,第四部分就講到這裡,下面我們将繼續實作論壇的功能,該到了實作論壇真正功能的時候,這部分主要是各個系統都通用的功能:導覽列、登入、注冊、登出、首頁。大家都順利實作了嗎?如果有什麼問題可以評論區裡進行詢問,如果喜歡這篇文章的話,可以點贊關注支援下部落客。

五、釋出文章、論壇頁面實作

本項目所有源碼在GitHub開源,GitHub位址為:​​OnlineForumPlatform​​ 有需要源碼可以前去檢視,喜歡的話可以star一下

下面就開始第五部分的内容了,這部分将會帶領大家一起實作論壇的主體功能,論壇的問題清單,文章詳情頁面,釋出文章,回答問題等功能,讓這個線上論壇系統成為一個“真正的論壇”,讓他具有論壇的功能。

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

5.1 釋出文章的功能實作

在這裡的haul,由于問題清單等頁面,沒問題的話無法具體的進行顯示,是以這裡我們先開發釋出問題的功能,然後再開發問題清單頁面。

5.1.1 釋出文章的實作-前端

前端的話我們先實作一個富文本編輯器來實作資訊的編輯,使我們編輯的文章具有格式化,如果不知道如何在前端嵌入富文本編輯器的話,可以看我之前的部落格:一本教你如何在前端實作富文本編輯器:​​一本教你如何在前端實作富文本編輯器​​如果需要建立Markdown文本編輯器的話,也可以看部落客的部落格:​​小白都能看得懂的教程 一本教你如何在前端實作富文本編輯器​​ 在此基礎上,我們來設計我們的前端頁面。

這裡我們直接上代碼:

post_issue.css:

.post_issue_content{
    margin-left: 20%;
    margin-right: 20%;
}

#page_header{
    text-align: center;
}

#issue_titile_div{
    height: 35px;
    margin: 0;
}

/*設定富文本編輯器預設的高度*/
#edui1_iframeholder{
    height: 200px;
}

#inputEmail3{
    margin-left: 0;
    padding-left: 0;
    width: 100%;
}

#post_issue_butt{
    margin-top: 60px;
    text-align: center;
    height: 35px;
}

#post_issue_butt button{
    height: 35px;
}      

post_issue.html:

{% extends 'base.html' %}

{% block title %}
釋出文章
{% endblock %}

{% block css %}
    <link rel="stylesheet" href="/static/css/post_issue.css">
    <!-- 引入配置檔案 -->
    <script src="/static/ueditor/ueditor.config.js"}></script>
    <!-- 引入編輯器源碼檔案 -->
    <script src="/static/ueditor/ueditor.all.min.js"}></script>
{% endblock %}

{% block content %}
<div class="post_issue_content">
    <div class="page-header" id="page_header">
      <h1>釋出文章<small>Post issue</small></h1>
    </div>
    <form class="post_issue_form">
        <div class="form-group" id="issue_titile_div">
            <input type="text" class="form-control" name="title" id="inputEmail3" placeholder="請輸入标題">
        </div>
        <div class="ueditor_div">
            <script id="editor" type="text/plain">
                請輸入釋出的内容!

            </script>
        </div>
        <div id="post_issue_butt">
          <button type="submit" class="btn btn-default">釋出文章</button>
        </div>
    </form>
</div>
<!-- 執行個體化編輯器 -->
<script type="text/javascript">
    var editor = UE.getEditor('editor');
</script>
{% endblock %}

{% block post_issue_class %}
active
{% endblock %}      

同時我們也可以修改下導航欄的右端,使登入時,顯示釋出文章的導航,如果不修改,則需要将post_issue.html的最後三行删除,否則會報錯。

添加的内容為:添加在登出的标簽之前即可.

<li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">釋出文章</a></li>      

為了友善調試,我們這裡在app.py中添加了釋出文章的路由:

# 釋出文章
@app.route('/post_issue')
def post_issue():
    return render_template('post_issue.html')      

此時釋出文章的前端頁面效果為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

至此,我們釋出文章的前端頁面就完成了,頁面還算不錯,下面我們繼續完成其他的功能。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

5.1.2 釋出文章的實作-資料庫

下面我們來設計文章的資料庫。首先我們需要明确我們需要存儲哪些内容:标題、内容、釋出人的Id(即email)、釋出時間、文章号(用來唯一标示一個文章)。但是由于我們是一個論壇,裡面的内容還需要很多人來回複,是以我們設計成2個表,分别為Issue和Comment,使用Issue表存儲每個Issue的标題、釋出人、釋出時間、Issue号,使用Comment來存儲每個評論(包括第一樓,發帖人的内容作為第一樓)的内容、釋出人、釋出時間和Connment号。

Issue表為:其中需要添加外鍵,email依賴UserInformation的email

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

SQL描述為:

create table Issue
(
  Ino varchar(128) null,
  email varchar(128) not null,
  title text default null,
  issue_time datetime null,
  constraint Issue_UserInformation_email_fk
    foreign key (email) references UserInformation (email)
);

create unique index Issue_Ino_uindex
  on Issue (Ino);

alter table Issue
  add constraint Issue_pk
    primary key (Ino);      

Comment表為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!
收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

SQL語句為:

create table Comment
(
  Cno varchar(128) not null,
  Ino varchar(128) not null,
  comment text null,
  comment_time datetime default '1999-9-9 9:9:9' not null,
  email varchar(128) null,
  constraint Comment_pk
    primary key (Cno, Ino),
  constraint Comment_Issue_Ino_fk
    foreign key (Ino) references Issue (Ino),
  constraint Comment_UserInformation_email_fk
    foreign key (email) references UserInformation (email)
);      

這樣我們釋出文章需要使用的資料庫就完成啦。

5.1.3 釋出文章的實作-後端

下面我們來完成釋出文章的後端實作。首先我們先這是允許的請求方式

@app.route('/post_issue',methods=['GET','POST'])      

然後我們再去将post_issue.html中的form的method設定為post:

<form class="post_issue_form" method="post">      

我們開始來後端擷取前端發送的标題和内容:

title = request.form.get('title')
comment = request.form.get('editorValue')      

然後我們來擷取釋出人(也就是目前登入賬号),目前時間。

email = session.get('email')
issue_time = time.strftime("%Y-%m-%d %H:%M:%S")      

下面我們開始擷取Issue号,這裡我們擷取資料庫中最大的Issue号,然後将它+1即可,如果沒有,那麼我們就設為1。

try:
    cur = db.cursor()
    sql = "select max(Ino) from Issue"
    db.ping(reconnect=True)
    cur.execute(sql)
    result = cur.fetchone()
    if result is None:
        Ino = int(1)
    else:
        Ino = int(request[0]) + 1
except Exception as e:
    raise e      

所有需要擷取的資料都擷取好了,我們将資料存入資料庫中,這樣釋出文章的功能也就算是完成了。在Issue中,為了資訊安全起見,我們使用128位随機數來作為ID,具體使用在代碼中展現。

後端代碼為;

# 生成128随機id
def gengenerateID():
    re = ""
    for i in range(128):
        re += chr(random.randint(65, 90))
    return re


# 釋出文章
@app.route('/post_issue', methods=['GET', 'POST'])
def post_issue():
    if request.method == 'GET':
        return render_template('post_issue.html')
    if request.method == 'POST':
        title = request.form.get('title')
        comment = request.form.get('editorValue')
        email = session.get('email')
        issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
        try:
            cur = db.cursor()
            Ino = gengenerateID()
            sql = "select * from Issue where Ino = '%s'" % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            # 如果result不為空,即存在該ID,就一直生成128位随機ID,直到不重複位置
            while result is not None:
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
            sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
            Ino, email, title, issue_time)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
            '1', Ino, comment, issue_time, email)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            return render_template('post_issue.html')
        except Exception as e:
            raise e      

這樣我們釋出文章的整體功能就是完成了,下面我們再來完成顯示文章清單的功能,讓我們釋出的文章顯示出來。

5.2 論壇頁面的實作

5.2.1 論壇頁面的實作-前端

論壇頁面我們先簡單做一個文章清單的顯示,後續有新功能的話,我們再來添加。這裡沒有什麼東西,這裡先設定一個簡潔的頁面,具體的功能完後面再逐漸的去完善,手把手的帶你完成這個項目的全部過程。下面我們直接上效果圖和代碼吧,都是上面細講過的東西,一個簡單的ul構成的頁面。這裡的頁面是靜态的,等我們完成後端功能之後,我們再來将這裡修改為一個動态的更新。

效果圖為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

formula.css代碼:

.formula_content{
    margin: 5% 20%;
}

#page_header{
    text-align: center;
}

.issue_list_ul{
    list-style-type: none;
    margin-left: 0;
    padding-left: 0;
}
.author_info{
    text-align: right;
}

.issue_div{
    border-bottom:1px solid #eee;
}

.issue_content{
    max-height: 200px;
}      

formula.html代碼;

{% extends 'base.html' %}

{% block title %}
論壇
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/formula.css">
{% endblock %}

{% block content %}
<div class="formula_content">
    <div class="page-header" id="page_header">
        <h1>文章清單</h1>
    </div>
    <div class="issue_list_div">
        <ul class="issue_list_ul">
            <li class="issue_list_li">
                <div class="issue_div">
                    <div class="issue_content">
                        <h3>
                            這裡是測試的标題
                        </h3>
                        <p>
                            這裡是測試釋出第一樓内容
                        </p>
                    </div>
                    <div class="author_info">
                        <p class="post-info">
                            <span>作者:測試作者</span>
                            <span>釋出時間:2020年3月28日 22:24:30</span>
                        </p>
                    </div>
                </div>
            </li>
            <li class="issue_list_li">
                <div class="issue_div">
                    <div class="issue_content">
                        <h3>
                            這裡是測試的标題
                        </h3>
                        <p>
                            這裡是測試釋出第一樓内容
                        </p>
                    </div>
                    <div class="author_info">
                        <p class="post-info">
                            <span>作者:測試作者</span>
                            <span>釋出時間:2020年3月28日 22:24:30</span>
                        </p>
                    </div>
                </div>
            </li>
        </ul>
    </div>
</div>
    
{% endblock %}
{% block formula_class %}
active
{% endblock %}      

在這裡設定了block,在base.html進行了添加,如果base.html不修改的話,把最後3行删掉,否則會報錯。在base.html中添加了論壇的導航​

​<li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">論壇</a></li>​

​ 修改完之後的base.html全部代碼為:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}
{#        其他頁面重寫标題的地方#}
        {% endblock %}
    </title>
    {% block css %}
{#    其他頁面引用樣式或者js的地方#}
    {% endblock %}
    <link rel="stylesheet" href="/static/css/base.css">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="navigation_bar">
        <nav class="navbar navbar-default">
          <div class="container-fluid">
{#              由于這裡我們不需要使用商标,是以對Bran部分進行了删除#}
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav">
                <li class="{% block homepage_class %}{% endblock %}"><a href="{{ url_for('index') }}">首頁<span class="sr-only">(current)</span></a></li>
                <li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">論壇</a></li>
                <li class="dropdown">
                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li><a href="#">Action</a></li>
                    <li><a href="#">Another action</a></li>
                    <li><a href="#">Something else here</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">Separated link</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">One more separated link</a></li>
                  </ul>
                </li>
              </ul>
              <form class="navbar-form navbar-left">
                <div class="form-group">
                  <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
              </form>
              <ul class="nav navbar-nav navbar-right">
                {% if email %}
                    <li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">釋出文章</a></li>
                    <li class=""><a href="{{ url_for('register') }}">登出</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="#">Action</a></li>
                        <li><a href="#">Another action</a></li>
                        <li><a href="#">Something else here</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                      </ul>
                    </li>
                {% else %}
                    <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注冊</a></li>
                    <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登入</a></li>
                {% endif %}
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    </div>
    <div class="content" style="padding: 0;margin: 0;">
        {% block content %}
{#        其他頁面重寫頁面内容的地方#}
        {% endblock %}
    </div>
</body>
</html>      

在這裡為了友善我們使用url_for,是以我們需要在app.py中為formula設定路由:

# 論壇頁面
@app.route('/formula')
def formula():
    return render_template('formula.html')      

這就是前端部分了,下面我們再來實作論壇的後端功能

5.2.2 論壇頁面的實作-後端

論壇頁面後端的需要也很明顯,就是要從資料庫中将所有Issue資料傳回到前端,在前端中顯示。這裡我們直接使用一個3表連接配接,将資料查出來,然後傳回到前端:

# 論壇頁面
@app.route('/formula')
def formula():
    if request.method == 'GET':
        try:
            cur = db.cursor()
                        sql = "select Issue.Ino, Issue.email,UserInformation.nickname,issue_time,Issue.title,Comment.comment from Issue,UserInformation,Comment where Issue.email = UserInformation.email and Issue.Ino = Comment.Ino and Cno = '1' order by issue_time DESC "
            db.ping(reconnect=True)
            cur.execute(sql)
            issue_information = cur.fetchall()
            cur.close()
            return render_template('formula.html',issue_information = issue_information)
        except Exception as e:
            raise e      

然後我們在前端進行顯示,這裡我們在前端的部分将兩個測試的li換成:

{% for issue in issue_information %}
    <li class="issue_list_li">
        <div class="issue_div">
            <div class="issue_content">
                <h3>
                    {{ issue[4] }}
                </h3>
                <article>
                    {{ issue[5]|safe }}
                </article>
            </div>
            <div class="author_info">
                <p class="post-info">
                    <span>作者:{{ issue[2] }}</span> 
                    <span>釋出時間:{{ issue[3] }}</span>
                </p>
            </div>
        </div>
    </li>
{% endfor %}      

這樣就完成了,這裡再貼一下完整的formula.html代碼,防止有的同學不知道替換哪些内容:

{% extends 'base.html' %}

{% block title %}
論壇
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/formula.css">
{% endblock %}

{% block content %}
<div class="formula_content">
    <div class="page-header" id="page_header">
        <h1>文章清單</h1>
    </div>
    <div class="issue_list_div">
        <ul class="issue_list_ul">
            {% for issue in issue_information %}
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                {{ issue[4] }}
                            </h3>
                            <article>
                                {{ issue[5]|safe }}
                            </article>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>作者:{{ issue[2] }}</span> 
                                <span>釋出時間:{{ issue[3] }}</span>
                            </p>
                        </div>
                    </div>
                </li>
            {% endfor %}

            <li class="issue_list_li">
                <div class="issue_div">
                    <div class="issue_content">
                        <h3>
                            這裡是測試的标題
                        </h3>
                        <p>
                            這裡是測試釋出第一樓内容
                        </p>
                    </div>
                    <div class="author_info">
                        <p class="post-info">
                            <span>作者:測試作者</span>
                            <span>釋出時間:2020年3月28日 22:24:30</span>
                        </p>
                    </div>
                </div>
            </li>
            <li class="issue_list_li">
                <div class="issue_div">
                    <div class="issue_content">
                        <h3>
                            這裡是測試的标題
                        </h3>
                        <p>
                            這裡是測試釋出第一樓内容
                        </p>
                    </div>
                    <div class="author_info">
                        <p class="post-info">
                            <span>作者:測試作者</span>
                            <span>釋出時間:2020年3月28日 22:24:30</span>
                        </p>
                    </div>
                </div>
            </li>
        </ul>
    </div>
</div>

{% endblock %}
{% block formula_class %}
active
{% endblock %}      

論壇現在的頁面效果:現在已經可以将我們之前釋出的文章顯示出來了,小夥伴們可以再發幾個文章測試下功能:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

好了,現在論壇頁面也完成了,那麼我們在釋出完文章之後,我們可以将頁面跳轉到文章清單。

在app.py中修改釋出文章的函數,直接将釋出成功的return修改為:​

​return redirect(url_for('formula'))​

​ 修改之後的釋出文章的完整功能代碼為:

# 釋出文章
@app.route('/post_issue', methods=['GET', 'POST'])
def post_issue():
    if request.method == 'GET':
        return render_template('post_issue.html')
    if request.method == 'POST':
        title = request.form.get('title')
        comment = request.form.get('editorValue')
        email = session.get('email')
        issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
        try:
            cur = db.cursor()
            Ino = gengenerateID()
            sql = "select * from Issue where Ino = '%s'" % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            # 如果result不為空,即存在該ID,就一直生成128位随機ID,直到不重複位置
            while result is not None:
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
            sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
            Ino, email, title, issue_time)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
            '1', Ino, comment, issue_time, email)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            cur.close()
            return redirect(url_for('formula'))
        except Exception as e:
            raise e      

  好了,現在小夥伴們可以去測試自己的論壇系統啦!現在已經完成了一半了,我們可以釋出問題并且顯示出來了。第五部分的内容到這裡結束啦~小夥伴們都順利實作了嗎?如果有問題可以在評論區裡進行問哦,如果喜歡這篇文章,可以點贊關注支援下部落客。

  部落客的更多文章導航可以檢視我的棧内導航文章,裡有各個文章的超連結,實時更新。​​部落格文章内容導航(實時更新)​​

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

本項目所有源碼在GitHub開源,GitHub位址為:​​OnlineForumPlatform​​有需要源碼可以前去檢視,喜歡的話可以star一下

六、文章詳情、回複文章、通路過濾功能實作

  在前面的幾個部分中,我們完成了項目的建立,導覽列、注冊、登入、論壇頁面、釋出文章等功能的實作,目前我們的系統已經可以釋出和檢視我們的文章了,并且可以登入我們的賬号。下面我們就來繼續完善我們的系統,讓他看檢視每個文章的詳情,并且能夠回複文章等功能。

6.1 文章詳情頁面、文章回複實作

  文章詳情頁面的資料庫我們已經在第三講中實作了,就是我們的Issue和Comment表,下面我們來一起實作以下文章詳情頁面的前端和後端功能,讓它能夠展示各個文章的一個詳情頁面。

6.1.1 文章詳情頁面實作-後端

  這裡我們先實作文章詳情頁面的後端,因為我們發現如果先做前端的話,在做後端的話,還需要對前端進行一定的修改,這樣不利于我們這樣文章式的進行表述。是以我們在這裡先做後端的功能,這裡先建立一個issue_detail.html頁面,留作後面的前端使用。

  如果要進入我們的文章詳情頁面,那麼首先我們需要一個參數,就是我們Ino(Issue的編号),因為這個編号是唯一表示我們的文章的。是以在這裡我們寫文章詳情的路由的時候就和前面不太一樣了,我們需要使用一個帶參數的路由,用來接收這個Ino,跳轉到我們相對應的文章詳情。

# 問題詳情
@app.route('/issue/<Ino>')
def issue_detail(Ino):
    return render_template('issue_detail.html')      

在有了Ino之後,我們就可以根據Ino來對資料庫中的文章資料進行查找了。下面我們來思考我們需要傳回到前端的資料由哪些呢?我們主要需要下面這項資料:

  • 文章标題
  • 每章每層樓的内容,包括:
  • 評論内容
  • 作者昵稱
  • 評論釋出時間
  • 樓号(Cno)

  我們主要就是需要這幾項資料,資料裡面的文章标題的每個問題詳情頁面固定的一個标題,每層樓的内容的每層樓不一樣的,是以兩個我們分别進行查詢。

# 問題詳情
@app.route('/issue/<Ino>')
def issue_detail(Ino):
    try:
        if request.method == 'GET':
            cur = db.cursor()
            sql = "select Issue.title from Issue where Ino = '%s'" % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            # 這裡傳回的是一個清單,即使隻有一個資料,是以這裡使用cur.fetchone()[0]
            issue_title = cur.fetchone()[0]
            sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            comment = cur.fetchall()
            cur.close()
            # 傳回視圖,同時傳遞參數
            return render_template('issue_detail.html',Ino=Ino,issue_title=issue_title,comment=comment)
    except Exception as e:
        raise e      

這就是目前簡單的後端邏輯,雖說我們目前隻有get請求方式,這裡也限定一下,為了安全起見。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

6.1.2 文章詳情頁面實作-前端

  前端的話,我們這裡為了便于了解,前端還是精簡布局(部落客直男審美,寫不出漂亮的UI)即可。我們需要一個頁頭來顯示我們的标題,然後下面使用一個​

​<ul></ul>​

​​顯示我們的文章詳情即可,在​

​<li></li>​

​​中固定一個div,每層樓的結構固定,然後再在中間填充上我們從後端擷取到的資料即可。前端的檔案這裡我們使用issue_detail.html和issue_detail.css。詳細的結構可以去看我的GitHub,每一次部落格更新之後我都會把源代碼push到GitHub中。本項目的GitHub位址為:​​OnlineForumPlatform​​​   這裡直接上代碼吧,前端的代碼和前面基本一樣,前面已經各個步驟都仔細講解過了,如果前端部分的還有不清楚的可以看看前面的。

issue_detail.html:

{% extends 'base.html' %}

{% block title %}
    {{ issue_title }}
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/issue_detail.css">
{% endblock %}

{% block content %}
<div class="formula_content">
    <div class="page-header" id="page_header">
        <h1>{{ issue_title }}</h1>
    </div>
    <div class="issue_list_div">
        <ul class="issue_list_ul">
            {% for comm in comment %}
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <article>
                                {{ comm[1]|safe }}
                            </article>
                        </div>
                        <div class="author_info">
{#                            <p class="cno_info">{{ comm[3] }}</p>#}
                            <p class="info">
                                <span class="cno_info">{{ comm[3] }}樓</span> 
                                <span>
                                    <span>作者:{{ comm[0] }}</span>  
                                    <span>釋出時間:{{ comm[2] }}</span>
                                </span>
                            </p>
                        </div>
                    </div>
                </li>
            {% endfor %}
        </ul>
    </div>
</div>
{% endblock %}      

issue_detail.css:

.formula_content{
    margin: 5% 20%;
}

#page_header{
    text-align: center;
}

.issue_list_ul{
    list-style-type: none;
    margin-left: 0;
    padding-left: 0;
}
.author_info{
    text-align: right;
}

.issue_div{
    border-bottom:1px solid #eee;
}

.issue_content{
    min-height: 88px;
}

.post-info{
    text-align: right;
}

.cno_info{
    text-align: left;
}      

  有仔細的小夥伴是不是發現了,這裡前端的部分和論壇頁面的前端基本類似,隻不過改掉了其中顯示的内容而已。頁面效果為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

6.1.3 文章回複實作-前端

  這裡其實有點糾結放在哪裡的,直接放在文章詳情的下面也可以,單獨一個頁面也可以,原本是打算單獨一個頁面的,因為那樣的話,會比較放富文本編輯器和Markdown編輯器的雙編輯器。不過又想了想,使用Markdown回複文章的人應該不多,是以這裡就放在下面的吧,使用富文本編輯器。如果想做雙編輯器的話也可以自己稍作修改。

  這裡隻需要在最後添加一個​​

​<li></li>​

​​然後再嵌套一個div,然後添加一個富文本編輯器的執行個體即可,還不會在前端實作富文本編輯器的可以去看看部落客之前的文章:​​一本教你如何在前端實作富文本編輯器​​如果需要實作Markdown文本編輯器也可以檢視部落客之前的文章:​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​ 這裡就直接上修改完的代碼和效果圖吧:

issue_detail.html:

{% extends 'base.html' %}

{% block title %}
    {{ issue_title }}
{% endblock %}

{% block css %}
    <link rel="stylesheet" href="/static/css/issue_detail.css">
    <!-- 引入配置檔案 -->
    <script src="/static/ueditor/ueditor.config.js"}></script>
    <!-- 引入編輯器源碼檔案 -->
    <script src="/static/ueditor/ueditor.all.min.js"}></script>
{% endblock %}

{% block content %}
<div class="formula_content">
    <div class="page-header" id="page_header">
        <h1>{{ issue_title }}</h1>
    </div>
    <div class="issue_list_div">
        <ul class="issue_list_ul">
            {% for comm in comment %}
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <article>
                                {{ comm[1]|safe }}
                            </article>
                        </div>
                        <div class="author_info">
{#                            <p class="cno_info">{{ comm[3] }}</p>#}
                            <p class="info">
                                <span class="cno_info">{{ comm[3] }}樓</span> 
                                <span>
                                    <span>作者:{{ comm[0] }}</span>  
                                    <span>釋出時間:{{ comm[2] }}</span>
                                </span>
                            </p>
                        </div>
                    </div>
                </li>
            {% endfor %}
                <li>
                    <div>
                        <form class="post_issue_form" method="post">
                            <input type="hidden" name="Ino" value="{{ Ino }}">
                            <div class="ueditor_div">
                                <script id="editor" type="text/plain">
                                請輸入回複的内容!
                                </script>
                            </div>
                            <div id="post_issue_butt">
                              <button type="submit" class="btn btn-default">回複</button>
                            </div>
                        </form>
                    </div>
                </li>
        </ul>
    </div>
<!-- 執行個體化編輯器 -->
<script type="text/javascript">
    var editor = UE.getEditor('editor');
</script>
</div>
{% endblock %}      

效果圖:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

  效果還是不錯的,這樣我們就完成了我們前端的富文本編輯器的輸入啦,是不是很有成就感~

6.1.3 文章回複實作-後端

  後端的話,我們需要做的就是将回複人的email、回複時間、回複樓号、回複内容存儲進資料庫中,并顯示。這裡由于我們是在同一個頁面中進行回複,是以我們要使用到POST方法進行傳輸資料。下面我們首先将這些需要存儲的資料在伺服器端擷取到:

Ino = request.values.get('Ino')
email = session.get('email')
comment = request.form.get('editorValue')
comment_time = time.strftime("%Y-%m-%d %H:%M:%S")      

  這裡的Cno需要來查找資料庫中的最大Cno,然後進行+1所得。問題詳情完整代碼為:文章回複實作的代碼主要為POST請求部分:

# 問題詳情
@app.route('/issue/<Ino>', methods=['GET', 'POST'])
def issue_detail(Ino):
    if request.method == 'GET':
        try:
            if request.method == 'GET':
                cur = db.cursor()
                sql = "select Issue.title from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                # 這裡傳回的是一個清單,即使隻有一個資料,是以這裡使用cur.fetchone()[0]
                issue_title = cur.fetchone()[0]
                sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                comment = cur.fetchall()
                cur.close()
                # 傳回視圖,同時傳遞參數
                return render_template('issue_detail.html', Ino=Ino, issue_title=issue_title, comment=comment)
        except Exception as e:
            raise e

    if request.method == 'POST':
        Ino = request.values.get('Ino')
        email = session.get('email')
        comment = request.values.get('editorValue')
        comment_time = time.strftime("%Y-%m-%d %H:%M:%S")
        try:
            cur = db.cursor()
            sql = "select max(Cno) from Comment where Ino = '%s' " % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            Cno = int(result[0]) + 1
            Cno = str(Cno)
            sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
            Cno, Ino, comment, comment_time, email)
            cur.execute(sql)
            db.commit()
            cur.close()
            return redirect(url_for('issue_detail',Ino = Ino))
        except Exception as e:
            raise e      

頁面效果:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

這裡提醒一下各位,在測試回複和釋出文章的時候一定要登入賬号,這裡還沒有完善過濾器功能,下面進行完善

6.2通路過濾功能實作

  在上面釋出文章和回複文章的時候是不是有很多小夥伴都都發現在未登入的時候進行釋出會報錯?是因為我們的email是函數依賴于UerInformation表的,如果未登入的話則沒有,也就無法插入到資料庫中。同時還有很多功能頁面我們是針對不同使用者的,比如有的管理者功能頁面,我們就不能讓普通使用者和訪客進入,有些頁面我們隻想讓登陸的使用者進行檢視,那我們就要讓訪客無法通路。

  下面我們就來實作這個功能,要使用這個功能,我們就需要使用Python的裝飾器功能。如果對裝飾器這個功能不太了解也沒關系,我們這邊隻需要一些簡單的功能即可。我會在代碼中進行詳細的進行注釋。我們将裝飾器放在decorators.py檔案中。我們這裡先簡單的寫一個限制登入的裝飾器,如下。

from functools import wraps
from flask import session, url_for, redirect


# 登入限制的裝飾器 用于某些隻讓登入使用者檢視的網頁
def login_limit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 我們這裡用來區分是否登入的方法很簡答,就是檢視session中是否指派了email,如果指派了,說明已經登入了
        if session.get('email'):
            # 如果登入了,我們就正常的通路函數的功能
            return func(*args, **kwargs)
        else:
            # 如果沒登入,我們就将它重定向到登入頁面,這裡大家也可以寫一個權限錯誤的提示頁面進行跳轉
            return redirect(url_for('login'))

    return wrapper      

  寫完之後,我們在app.py中導入它​

​from decorators import login_limit​

​​ 然後再每一個我們需要進行登入限制的路由之後加上我們的裝飾器​

​@login_limit​

​​即可,此時我們需要進行限制登入的兩個頁面就是我們的釋出文章頁面和我們的文章詳情頁面需要進行一個限制。進行限制之後,如果我們沒登入的時候進行釋出文章就會自動的跳轉到登入頁面,提醒你進行登入啦~

這裡在貼一下到目前為止app.py的完整檔案代碼吧:

from flask import *
from werkzeug.security import generate_password_hash, check_password_hash
from config import db
import random
import time
import config
from decorators import login_limit

app = Flask(__name__)

# 從對象中導入config
app.config.from_object(config)


# 登入狀态保持
@app.context_processor
def login_status():
    # 從session中擷取email
    email = session.get('email')
    # 如果有email資訊,則證明已經登入了,我們從資料庫中擷取登陸者的昵稱和使用者類型,來傳回到全局
    if email:
        try:
            cur = db.cursor()
            sql = "select nickname,type from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result:
                return {'email': email, 'nickname': result[0], 'user_type': result[1]}
        except Exception as e:
            raise e
    # 如果email資訊不存在,則未登入,傳回空
    return {}

# 首頁
@app.route('/')
def index():
    return render_template('index.html')


# 注冊頁面
@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    if request.method == 'POST':
        email = request.form.get('email')
        nickname = request.form.get('nickname')
        password_1 = request.form.get('password_1')
        password_2 = request.form.get('password_2')
        phone = request.form.get('phone')
        if not all([email, nickname, password_1, password_2, phone]):
            flash("資訊填寫不全,請将資訊填寫完整")
            return render_template('register.html')
        if password_1 != password_2:
            flash("兩次密碼填寫不一緻!")
            return render_template('register.html')
        password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)
        try:
            cur = db.cursor()
            sql = "select * from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result is not None:
                flash("該Email已存在!")
                return render_template('register.html')
            else:
                create_time = time.strftime("%Y-%m-%d %H:%M:%S")
                sql = "insert into UserInformation(email, nickname, password, type, create_time, phone) VALUES ('%s','%s','%s','0','%s','%s')" % (
                    email, nickname, password, create_time, phone)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('index'))
        except Exception as e:
            raise e


# 登入頁面
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    if request.method == 'POST':
        email = request.form.get('email')
        password = request.form.get('password')
        if not all([email, password]):
            flash("請将資訊填寫完整!")
            return render_template('login.html')
        try:
            cur = db.cursor()
            sql = "select password from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result is None:
                flash("該使用者不存在")
                return render_template('login.html')
            if check_password_hash(result[0], password):
                session['email'] = email
                session.permanent = True
                cur.close()
                return redirect(url_for('index'))
            else:
                flash("密碼錯誤!")
                return render_template('login.html')
        except Exception as e:
            raise e


# 使用者登出
@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for(('index')))


# 生成128随機id
def gengenerateID():
    re = ""
    for i in range(128):
        re += chr(random.randint(65, 90))
    return re


# 釋出文章
@app.route('/post_issue', methods=['GET', 'POST'])
@login_limit
def post_issue():
    if request.method == 'GET':
        return render_template('post_issue.html')
    if request.method == 'POST':
        title = request.form.get('title')
        comment = request.form.get('editorValue')
        email = session.get('email')
        issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
        try:
            cur = db.cursor()
            Ino = gengenerateID()
            sql = "select * from Issue where Ino = '%s'" % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            # 如果result不為空,即存在該ID,就一直生成128位随機ID,直到不重複位置
            while result is not None:
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
            sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
                Ino, email, title, issue_time)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                '1', Ino, comment, issue_time, email)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            cur.close()
            return redirect(url_for('formula'))
        except Exception as e:
            raise e


# 論壇頁面
@app.route('/formula')
def formula():
    if request.method == 'GET':
        try:
            cur = db.cursor()
            sql = "select Issue.Ino, Issue.email,UserInformation.nickname,issue_time,Issue.title,Comment.comment from Issue,UserInformation,Comment where Issue.email = UserInformation.email and Issue.Ino = Comment.Ino order by issue_time DESC "
            db.ping(reconnect=True)
            cur.execute(sql)
            issue_information = cur.fetchall()
            cur.close()
            return render_template('formula.html', issue_information=issue_information)
        except Exception as e:
            raise e


# 問題詳情
@app.route('/issue/<Ino>', methods=['GET', 'POST'])
@login_limit
def issue_detail(Ino):
    if request.method == 'GET':
        try:
            if request.method == 'GET':
                cur = db.cursor()
                sql = "select Issue.title from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                # 這裡傳回的是一個清單,即使隻有一個資料,是以這裡使用cur.fetchone()[0]
                issue_title = cur.fetchone()[0]
                sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                comment = cur.fetchall()
                cur.close()
                # 傳回視圖,同時傳遞參數
                return render_template('issue_detail.html', Ino=Ino, issue_title=issue_title, comment=comment)
        except Exception as e:
            raise e

    if request.method == 'POST':
        Ino = request.values.get('Ino')
        email = session.get('email')
        comment = request.values.get('editorValue')
        comment_time = time.strftime("%Y-%m-%d %H:%M:%S")
        try:
            cur = db.cursor()
            sql = "select max(Cno) from Comment where Ino = '%s' " % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            Cno = int(result[0]) + 1
            Cno = str(Cno)
            sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
            Cno, Ino, comment, comment_time, email)
            cur.execute(sql)
            db.commit()
            cur.close()
            return redirect(url_for('issue_detail',Ino = Ino))
        except Exception as e:
            raise e


if __name__ == '__main__':
    app.run()      

  好了,現在小夥伴們可以去測試自己的論壇系統啦!現在已經完成了一大半了,我們可以釋出問題并且顯示出來,還可以對文章進行回複,檢視文章詳情了,現在小小的線上論壇系統已經五髒俱全啦!(雖然設施簡陋點點),同時我們還實作了我們的通路過濾功能,不然非法使用者通路我們的頁面。~小夥伴們都順利實作了嗎?如果有問題可以在評論區裡進行問哦,如果喜歡這篇文章,可以點贊關注支援下部落客。

  部落客的更多文章導航可以檢視我的棧内導航文章,裡有各個文章的超連結,實時更新。​​部落格文章内容導航(實時更新)​​

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

七、個人中心相關頁面功能實作

  本項目所有源碼在GitHub開源,GitHub位址為:​​OnlineForumPlatform​​   今天來到我們實戰系列的第七部分,在之前的六部分裡,我們已經完成了線上論壇系統的導覽列、登入、注冊、論壇頁面、文章詳情頁面等的功能實作,目前我們的論壇已經可以進行文章釋出,檢視文章詳情,并且進行回複資訊了。在上一部分中,我們還是先了功能限制,對不同使用者的通路進行過濾。下面我們繼續來完善我們的這個線上論壇系統,今天我們來完善我們的線上論壇系統的個人中心。

7.1個人中心頁面實作

  今天我們就來實作我們的個人中心頁面,這裡本來是打算使用AJAX實作的,個人中心使用AJAX實作的話,可以使得整體不變,當我們點選各個分欄的時候隻傳回各個分欄的資訊,是一個非常好的選擇。但是由于這個系列打算讓每一個看的人都能夠看懂,實作這個功能,是以我們這簡化一點,直接使用普通的一個請求頁面。後面有興趣的同學可以進行進一步的優化,後面有空的話,我也會将後面逐漸優化的教程發出來。

  這裡既然是小白的入門專場,我們就一起從簡啦~主要是為了給大家練練手,帶大家進入Python Web開發的大門。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

7.1.1 個人中心頁面-後端

  那我們就開始實作我們我的個人中心了,這裡我們的個人中心隻顯示我們的一些基礎資料(我們也沒設定多少),這裡隻做一個大緻的樣式展示,更多的功能我們後續慢慢的進行一個拓展。

  我們去看了一下我們的資料庫,發現我們個人中心能夠進行展示的,也就隻有我們的email、昵稱、使用者類型、建立時間和手機号碼可以進行一個展示和修改。那我們就展示這麼多吧,大家也可以添加一下個性簽名,頭像等一系列的個人辨別進入資料庫中。

  那我們就開始擷取我們的資料了,首先我們需要限制隻有登入的使用者才可以進入到我們的個人中心,限制的方法在上一講中已經實作了。

# 個人中心
@app.route('/personal')
@login_limit
def personal():
    if request.method == 'GET':
        email = session.get('email')
        try:
            cur = db.close()
            sql = "select email, nickname, type, create_time, phone from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            personal_info = cur.fetchone()
        except Exception as e:
            raise e
        return render_template('personal.html',personal_info = personal_info)      

7.1.2 個人中心頁面-前端

  在後端擷取完資料之後,我們在前端對擷取到的資料進行展示即可。是以我們先設計個勉強能夠看得過去的架子,來展示我們的資料。先上個效果圖吧:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

确實是比較簡陋的,但是功能齊全吧。我們這裡使用的是​

​<table>​

​​标簽進行顯示的,也可以使用​

​<li>​

​進行顯示。這裡時間上代碼吧,personal.html的代碼為:

{% extends 'base.html' %}

{% block title %}
個人中心
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/personal.css">
{% endblock %}

{% block content %}
<div class="personal_content">
    <div class="page-header" id="page_header">
      <h1>個人中心<small>Personal center</small></h1>
    </div>
    <div class="personal_info">
        <table>
            <tr class="personal_tr">
                <td class="personal_td">
                    Email address:
                </td>
                <td class="personal_td">
                    {{ personal_info[0] }}
                </td>
            </tr>
            <tr class="personal_tr">
                <td class="personal_td">
                    昵稱:
                </td>
                <td class="personal_td">
                    {{ personal_info[1] }}
                </td>
            </tr>
            <tr class="personal_tr">
                <td class="personal_td">
                    注冊時間:
                </td>
                <td class="personal_td">
                    {{ personal_info[3] }}
                </td>
            </tr>
            <tr class="personal_tr">
                <td class="personal_td">
                    手機号:
                </td>
                <td class="personal_td">
                    {{ personal_info[4] }}
                </td>
            </tr>
            <tr class="personal_tr">
                <td class="personal_td">
                    使用者類型:
                </td>
                <td class="personal_td">
                    {% if personal_info[2] == 0 %}
                        普通使用者
                    {% else %}
                        管理者
                    {% endif %}
                </td>
            </tr>
        </table>
    </div>
</div>
{% endblock %}      

personal.css代碼為:

.personal_content{
    margin-left: 20%;
    margin-right: 20%;
    margin-top: 5%;
}
#page_header{
    text-align: center;
}

.personal_info{
    font-size: 24px;
    margin-left: 10%
;
}

.personal_td{
    width: 300px;
}

.personal_tr{
    height: 50px;
}      

同時這裡也修改了base.html的内容,我們讓個人中心在下拉清單中進行顯示,我們修改了下拉清單中第一個值和連結,效果圖為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

這裡的修改的代碼為:

<li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="{{ url_for('personal') }}">個人中心</a></li>
                        <li><a href="#">Another action</a></li>
                        <li><a href="#">Something else here</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                      </ul>
                    </li>      

7.2 修改密碼功能實作

  到這裡我們來實作我們的修改密碼功能,其實如果使用AJAX實作的話,這一講的所有功能都應該在個人中心一個頁面中進行展現的,我們這裡就先這樣分開實作吧。大家可以自行改進~

  在這裡我們就先從前端開始實作了。

7.2.1 修改密碼功能實作-前端

  我們修改密碼的話,這裡采用一個簡單的驗證,就是知道我們目前的密碼就可以進行一個修改密碼。我們設計一個表單,分别輸入舊密碼,新密碼,确認新密碼即可。先上個效果圖再說實作:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

  這效果圖還是大家熟悉的味道,有沒有!我們這裡還是一個簡單的div,裡面加個頁頭,下面3個​

​input​

​。這裡和前面的原理一樣,這裡我們預留了一個flash傳輸消息的地方,用于等會後端向前面傳遞消息提示,我們就直接上代碼吧:

change_password.html:

{% extends 'base.html' %}

{% block title %}
修改密碼
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/change_password.css">
{% endblock %}

{% block content %}
<div class="change_password_content">
    <div class="page-header" id="page_header">
      <h1>修改密碼<small>Change Password</small></h1>
    </div>
    <div class="change_password_div">
        <form method="post">
            <span style=" font-size:20px;color: red" >
                {% for item in get_flashed_messages() %}
                {{ item }}
                {% endfor %}
            </span>
            <div class="form-group">
                <label for="exampleInputPassword1">舊密碼:</label>
                <input type="password" class="form-control" name="old_password" id="exampleInputPassword1" placeholder="密碼">
            </div>
            <div class="form-group">
                <label for="exampleInputPassword1">新密碼:</label>
                <input type="password" class="form-control" name="new_password1" id="exampleInputPassword1" placeholder="密碼">
            </div>
            <div class="form-group">
                <label for="exampleInputPassword1">确認密碼:</label>
                <input type="password" class="form-control" name="new_password2" id="exampleInputPassword1" placeholder="密碼">
            </div>
            <div id="password_butt">
              <button type="submit" class="btn btn-default">修改密碼</button>
          </div>
        </form>
    </div>
</div>
{% endblock %}      

change_paasword.css:

#page_header{
    text-align: center;
}
.change_password_content{
    margin-left: 20%;
    margin-right: 20%;
    margin-top: 8%;
}
#password_butt{
    text-align: center;
}      

7.2.1 修改密碼功能實作-後端

  下面我們來實作修改密碼的後端功能,使他能夠正确的修改密碼。我們首先擷取我們的目前登入使用者的使用者名,這裡我們的修改密碼的功能也是隻有我們登入的使用者才可以通路的功能。首先我們判斷是不是3個資料都擷取到了,并且2個密碼一緻,如果有錯誤,我們傳回提示。如果資料都正确的話,我們開始處理,先擷取我們的email。我們擷取到email之後去資料庫中查找我們的密碼,這裡的密碼是加密的,是以我們要使用check_password_hash()來進行驗證,如果舊密碼是正确的,我們就進行修改新密碼,如果不正确,則傳回。完整的後端代碼為:

# 修改密碼
@app.route('/change_password',methods=['GET','POST'])
@login_limit
def change_password():
    if request.method == 'GET':
        return render_template('change_password.html')
    if request.method == 'POST':
        old_password = request.form.get('old_password')
        new_password1 = request.form.get('new_password1')
        new_password2 = request.form.get('new_password2')
        if not all([old_password,new_password1,new_password2]):
            flash("資訊填寫不全!")
            return render_template('change_password.html')
        if new_password1 != new_password2:
            flash("兩次新密碼不一緻!")
            return render_template('change_password.html')
        email = session.get('email')
        try:
            cur = db.cursor()
            sql = "select password from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            password = cur.fetchone()[0]
            if check_password_hash(password,old_password):
                password = generate_password_hash(new_password1, method="pbkdf2:sha256", salt_length=8)
                sql = "update UserInformation set password = '%s' where email = '%s'" % (password,email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return render_template('index.html')
            else:
                flash("舊密碼錯誤!")
                return render_template('change_password.html')
        except Exception as e:
            raise e      

在實作後端功能之後,我們可以再導航欄中加入修改密碼的導航,我們還是加載下來清單中,将第二個修改為修改密碼。

<li class="dropdown">
  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
  <ul class="dropdown-menu">
    <li><a href="{{ url_for('personal') }}">個人中心</a></li>
    <li><a href="{{ url_for('change_password') }}">修改密碼</a></li>
    <li><a href="#">Something else here</a></li>
    <li role="separator" class="divider"></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</li>      

效果為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!
  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

7.3 檢視釋出文章功能實作-後端

7.3.1 檢視釋出文章功能實作-後端

  下面我們來實作個人中心的另一個功能,就是檢視我們自己所釋出的所有的文章資訊,并且可以在此頁面進入到我們所釋出的文章中去。這裡我們還是先寫後端,向前端去傳輸我們的資料。這個頁面是也是需要我們登入才可以檢視的,因為檢視的是我們所登入的這個賬号所釋出的文章清單。

  是以這裡我們先擷取我們需要的資料,然後把它傳回到前端即可,這裡先建立一個前端的html檔案,這裡使用show_issue.html,我們後端的代碼為:

# 檢視已釋出的文章
# 檢視已釋出的文章
@app.route('/show_issue')
@login_limit
def show_issue():
    if request.method == 'GET':
        email = session.get('email')
        try:
            cur = db.cursor()
            sql = "select ino, email, title, issue_time from Issue where email = '%s' order by issue_time desc" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            issue_detail = cur.fetchall()
        except Exception as e:
            raise e
        return render_template('show_issue.html',issue_detail=issue_detail)      

7.3.2 檢視釋出文章功能實作-前端

  下面我們開始實作前端的一個功能,我們這裡使用和論壇清單相似的功能,我們這裡隻需要顯示文章的标題即可。這裡先上效果圖:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

我們這裡隻需要設計一個​

​<li>​

​将我們後端發送的資料僅顯示出來即可。show_issue.html:

{% extends 'base.html' %}

{% block title %}
已釋出的文章
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/show_issue.css">
{% endblock %}

{% block content %}
<div class="show_issue_content">
    <div class="page-header" id="page_header">
        <h1>已釋出的文章清單</h1>
    </div>
    <div class="issue_list_div">
        <ul class="issue_list_ul">
            {% for issue in issue_detail %}
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                <a href="{{ url_for('issue_detail',Ino = issue[0]) }}">
                                    {{ issue[2] }}
                                </a>
                            </h3>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>釋出時間:{{ issue[3] }}</span>
                            </p>
                        </div>
                    </div>
                </li>
            {% endfor %}
        </ul>
    </div>
</div>
{% endblock %}      

show_issue.css:

#page_header{
    text-align: center;
}

.issue_list_ul{
    list-style-type: none;
    margin-left: 0;
    padding-left: 0;
}
.author_info{
    text-align: right;
}

.issue_div{
    border-bottom:1px solid #eee;
}

.issue_content{
    max-height: 200px;
}
.show_issue_content{
    margin-right: 20%;
    margin-left: 20%;
    margin-top: 8%;
}      

這裡我們也修改一下base.html檔案,設定下來清單。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

我們在導航欄的下拉清單中添加我們這章個人中心的三個頁面,這裡貼一下目前base.html的全部代碼,防止有的小夥伴找不到修改的地方。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}
{#        其他頁面重寫标題的地方#}
        {% endblock %}
    </title>
    {% block css %}
{#    其他頁面引用樣式或者js的地方#}
    {% endblock %}
    <link rel="stylesheet" href="/static/css/base.css">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="navigation_bar">
        <nav class="navbar navbar-default">
          <div class="container-fluid">
{#              由于這裡我們不需要使用商标,是以對Bran部分進行了删除#}
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav">
                <li class="{% block index_class %}{% endblock %}"><a href="{{ url_for('index') }}">首頁<span class="sr-only">(current)</span></a></li>
                <li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">論壇</a></li>
              </ul>
              <form class="navbar-form navbar-left">
                <div class="form-group">
                  <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
              </form>
              <ul class="nav navbar-nav navbar-right">
                {% if email %}
                    <li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">釋出文章</a></li>
                    <li class=""><a href="{{ url_for('register') }}">登出</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="{{ url_for('personal') }}">個人中心</a></li>
                        <li><a href="{{ url_for('change_password') }}">修改密碼</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="{{ url_for('show_issue') }}">已釋出的文章</a></li>
                      </ul>
                    </li>
                {% else %}
                    <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注冊</a></li>
                    <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登入</a></li>
                {% endif %}
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    </div>
    <div class="content" style="padding: 0;margin: 0;">
        {% block content %}
{#        其他頁面重寫頁面内容的地方#}
        {% endblock %}
    </div>
</body>
</html>      

  好了,現在小夥伴們可以去測試自己的論壇系統啦!現在已經基本完成了了,我們可以釋出問題并且顯示出來,還可以對文章進行回複,檢視文章詳情了,現在小小的線上論壇系統已經五髒俱全啦!(雖然設施簡陋點點),同時我們還實作了我們的通路過濾功能,不然非法使用者通路我們的頁面,在這一講中我們實作了我們個人中心頁面的各個功能~小夥伴們都順利實作了嗎?如果有問題可以在評論區裡進行問哦,如果喜歡這篇文章,可以點贊關注支援下部落客。

  部落客的更多文章導航可以檢視我的棧内導航文章,裡有各個文章的超連結,實時更新。​​部落格文章内容導航(實時更新)​​

部落客的站内部落格導航:​​部落格文章内容導航(實時更新)​​更多精彩部落格文章推薦

  • ​​一本教你如何在前端實作富文本編輯器​​
  • ​​小白都能看得懂的教程 一本教你如何在前端實作markdown編輯器​​
  • ​​一文教會你Bootstrap,讓你也可以快速建站​​
  • ​​一文教你如何白嫖JetBrains全家桶(IDEA/PtChram/CLion)免費正版​​

八、資源專區相關功能實作

  本項目所有源碼在GitHub開源,GitHub位址為:​​OnlineForumPlatform​​   今天來到我們實戰系列的第八部分,資源專區相關功能實作,在之前的七個部分裡,我們已經完成了線上論壇系統的導覽列、登入、注冊、論壇頁面、文章詳情頁面、個人頁面、修改密碼、檢視已釋出文章,個人中心頁面,修改密碼,檢視已釋出文章等的功能實作,目前我們的論壇已經可以進行文章釋出,檢視文章詳情,并且進行回複資訊了。在上一講中,我們還是實作了功能限制,對不同使用者的通路進行過濾。下面我們繼續來完善我們的這個線上論壇系統,今天我們來為這個系統添加資源專區,這裡我們主要分為資源上傳,資源清單(檢視資源清單),線上檢視資源檔案、資源檔案下載下傳的功能。

8.1資源上傳功能實作

  這裡我們來開始實作資源上傳功能,我們先從前端開始實作,然後到後端進行存儲。之前寫過一篇如何實作檔案上傳下載下傳的博文,如果對這方面還不太了解的同學,可以先去看一下博文:​​Python Flask檔案上傳下載下傳​​,這篇博文簡單的介紹了如何實作檔案的上傳與下載下傳,這裡我們來進行一個詳細的實作。

8.1資源上傳功能實作-前端

  資源檔案上傳,我們這裡設計一個​

​<div>​

​​,然後裡面放一個​

​<form>​

​​,進行檔案的上傳,和檔案描述。這裡先建立一個post_file.html檔案用來目前的前端的檔案,post_file.css檔案用來記錄樣式。我們這個頁面還是繼承自​

​base.html​

​​。還是先上個效果圖,然後我們給一個源碼,因為這裡前端的話,我們就是一個​

​<table>​

​然後裡面放3行的資訊。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

這裡我們需要注意的就是,在我們的form中,不要忘記設定​

​enctype​

​​,需要把這個屬性設定為​

​multipart/form-data​

​。

post_file.html:

{% extends 'base.html' %}

{% block title %}
資源上傳
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/post_file.css">
{% endblock %}

{% block content %}
<div class="post_file_content">
    <div class="page-header" id="page_header">
      <h1>資源上傳<small>Resource upload</small></h1>
    </div>
    <div class="post_file_div">
        <form action="" method="post" enctype="multipart/form-data">
            <table id="file_table">
                <tr>
                    <td>
                        選擇你需要上傳的檔案
                    </td>
                    <td>
                        <input id="file_butt" type="file" name="file">
                    </td>
                </tr>
                <tr>
                    <td>
                        請輸入檔案名稱:
                    </td>
                    <td>
                        <div class="form-group">
                            <input type="text" class="form-control" name="filename" id="exampleInputEmail1" placeholder="請輸入檔案名稱:">
                        </div>
                    </td>
                </tr>
                <tr>
                    <td>
                        請輸入檔案描述資訊:
                    </td>
                    <td>
                        <div class="form-group">
                            <input type="text" class="form-control" name="file_info" id="exampleInputEmail1" placeholder="請輸入檔案描述資訊:">
                        </div>
                    </td>
                </tr>
                <tr>
                    <td colspan="2">
                        <div id="login_butt">
                          <button type="submit" class="btn btn-default">上傳</button>
                      </div>
                    </td>
                </tr>
            </table>
        </form>
    </div>
</div>
{% endblock %}      

post_file.css

.post_file_content{
    margin-top: 8%;;
    margin-left: 25%;
    margin-right: 25%;
}

#page_header{
    text-align: center;
}

#file_table td{
    width: 200px;
}
#login_butt{
    text-align: center;
}      
  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

8.1.2資源上傳功能實作-資料庫端

  這裡我們又需要新增一個表了,用來存儲我們的一個檔案的存儲資訊,我們主要存儲的資訊有:Fno(用來唯一辨別我們的檔案)、檔案名稱、檔案描述資訊、檔案上傳時間。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

SQL語句為:

create table Files
(
  Fno varchar(128) not null,
  filename nvarchar(128) default '未命名' null,
  file_info nvarchar(128) default '沒有描述資訊' null,
  file_time datetime null,
  email varchar(128) null,
  constraint Files_UserInformation_email_fk
    foreign key (email) references UserInformation (email)
);

create unique index Files_Fno_uindex
  on Files (Fno);

alter table Files
  add constraint Files_pk
    primary key (Fno);      

8.1.3資源上傳實作-後端

  下面我們來實作我們資源上傳的資料庫端,首先我們需要在我們的項目檔案中建立一個檔案夾用來存放我們上傳的檔案。這裡建立了一個store檔案夾用于存放上傳的檔案。

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

下面我們就來講從前端發送的檔案儲存到我們的伺服器的檔案夾中。這裡為了伺服器檔案的安全性,我們這裡檔案的名稱采用再次命名,一來可以防止重複的檔案名,二來可以防止檔案名稱中有一些特殊的字元。我們這裡的檔案名稱采用随機的120字元,然後加上原本檔案的字尾組成,這裡可以防止本地存儲位置的檔案有重複。在代碼中有各個步驟的詳細注解,大家可以檢視代碼中的資訊:

# 生成120位随機id
def gengenerateFno():
    re = ""
    for i in range(120):
        re += chr(random.randint(65, 90))
    return re

# 資源上傳頁面
@app.route('/post_file',methods=['GET','POST'])
@login_limit
def post_file():
    if request.method == 'GET':
        return render_template('post_file.html')
    if request.method == 'POST':
        email = session.get('email')
        upload_file = request.files.get('file')
        filename = request.form.get('filename')
        file_info = request.form.get('file_info')
        file_path = 'store'
        file_time = time.strftime("%Y-%m-%d %H:%M:%S")
        Fno = gengenerateFno()
        try:
            cur = db.cursor()
            sql = "select * from Files where Fno = '%s'" % Fno
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            # 如果result不為空,即該Fno已存在時,一直生成随機的Fno,隻到該資料庫中不存在
            while result is not None:
                Fno = gengenerateFno()
                sql = "select * from Files where Fno = '%s'" % Fno
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
            # 擷取檔案的字尾
            upload_name = str(upload_file.filename)
            houzhui = upload_name.split('.')[-1]
            # 儲存在本地的名字為生成的Fno+檔案字尾,同時修改Fno的值
            Fno = Fno+"."+houzhui
            # 儲存檔案到我們的伺服器中
            upload_file.save(os.path.join(file_path,Fno))
            # 将檔案資訊存儲到資料庫中
            sql = "insert into Files(Fno, filename, file_info, file_time,email) VALUES ('%s','%s','%s','%s','%s')" % (Fno,filename,file_info,file_time,email)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            cur.close()
            return render_template('index.html')
        except Exception as e:
            raise e      

  這樣我們就可以将我們的檔案上傳到我們是伺服器端,并且儲存到我們制定的檔案夾中了。

8.2 資源專區功能實作

  下面我們開始來實作我們的資源專區,這裡的資源專區我們用來顯示我們的所有的資源的一個清單,并且可以進行線上檢視資源和下載下傳資源。

8.2.1 資源專區功能實作-後端

  資源專區的話,我們還是先實作後端,發送資料到前端,然後我們前端将接收到的資料進行一個排版,然後顯示。大家有沒有發現一個問題,我們剛剛先實作了資源的上傳,但是我們并沒有導航,那為什麼我們要先實作資源上傳呢?因為如果我們先實作資源專區的話,沒有東西顯示,用于測試啊~是以我們先實作資源上傳,可以先上傳檔案,便于資源專區的實作。

  這裡我們需要傳回到前端的資料有Fno、檔案名、檔案描述、建立時間和建立人的昵稱。

# 資源專區
@app.route('/source')
def source():
    if request.method == 'GET':
        try:
            cur = db.cursor()
            sql = "select Fno,filename,file_info,file_time,nickname from Files,UserInformation where Files.email = UserInformation.email"
            db.ping(reconnect=True)
            cur.execute(sql)
            files = cur.fetchall()
            cur.close()
            return render_template('source.html',files = files)
        except Exception as e:
            raise e      
  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

8.2.2資源專區功能實作-前端

  資源專區的前端我們首先需要一個清單用來展示我們的伺服器中已上傳的檔案的資訊,并且導航到我們的檔案線上檢視,檔案下載下傳等功能。這裡使用source.html用來實作我們的前端功能,使用source.css存放樣式的描述資訊。頁面效果為:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

在這裡由于我們的線上檢視檔案和下載下傳檔案還沒有實作,所有我們這裡的超連結的位址是#,等下我們來進行實作。目前的前端的頁面代碼為:

{% extends 'base.html' %}

{% block title %}
資源專區
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/source.css">
{% endblock %}

{% block content %}
<div class="source_content">
    <div class="page-header" id="page_header">
      <h1>資源專區<small>Resources Zone</small></h1>
    </div>
    <div class="source_div">
        <ul class="source_ul">
            {% for file in files %}
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                <a href="#">
                                    {{ file[1] }}
                                </a>
                            </h3>
                            <article>
                                {{ file[2] }}
                            </article>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>上傳者:{{ file[4] }}</span>  
                                <span>上傳時間:{{ file[3] }}</span> 
                                <span><a href="#">線上檢視</a></span> 
                                <span><a href="#">下載下傳檔案</a></span>
                            </p>
                        </div>
                    </div>
                </li>
            {% endfor %}
        </ul>
    </div>
</div>
{% endblock %}
{% block source_class %}
active
{% endblock %}      

source.css:

.source_content{
    margin-right: 20%;
    margin-left: 20%;
    margin-top: 8%;
}

.author_info{
    text-align: right;
}

.source_ul{
    list-style-type: none;
    margin-left: 0;
    padding-left: 0;
}
#page_header{
    text-align: center;
}      

  我們在base.html中添加了資源專區的導航,并且添加了source_class的block,如果不修改base.html的話,需要将source.html的最後3行删除,不然會報錯。base中插入的代碼為​

​<li class="{% block source_class %}{% endblock %}"><a href="{{ url_for('source') }}">資源專區</a></li>​

​插入在論壇導航的下面一行。

8.2.3 實作檔案線上檢視功能

  其實這裡面要想實作檔案的線上檢視功能非常的容易實作,我們隻需要傳回這個檔案即可。我們這裡需要在路由處傳遞一個參數Fno,用來尋找我們的檔案。

# 線上檢視檔案
@app.route('/online_file/<Fno>')
def online_file(Fno):
    return send_from_directory(os.path.join('store'), Fno)      

8.2.4 實作檔案下載下傳功能

  我們再來實作我們的檔案下載下傳功能,這個也是非常簡單的,我們直接傳回一個send_file即可實作檔案的下載下傳。

# 檔案下載下傳功能
@app.route('/download/<Fno')
def download(Fno):
    return send_file(os.path.join('store') + "/" + Fno, as_attachment=True)      

在實作完我們的檔案線上檢視和檔案下載下傳之後,我們就可以修改我們的資源專區的前端,為其超連結添加一個正确的連結,讓他能夠實作正确的功能了。修改後的souce.html頁面為:

{% extends 'base.html' %}

{% block title %}
資源專區
{% endblock %}

{% block css %}
<link rel="stylesheet" href="/static/css/source.css">
{% endblock %}

{% block content %}
<div class="source_content">
    <div class="page-header" id="page_header">
      <h1>資源專區<small>Resources Zone</small></h1>
    </div>
    <div class="source_div">
        <ul class="source_ul">
            {% for file in files %}
                <li class="issue_list_li">
                    <div class="issue_div">
                        <div class="issue_content">
                            <h3>
                                <a href="{{ url_for('online_file',Fno = file[0]) }}">
                                    {{ file[1] }}
                                </a>
                            </h3>
                            <article>
                                {{ file[2] }}
                            </article>
                        </div>
                        <div class="author_info">
                            <p class="post-info">
                                <span>上傳者:{{ file[4] }}</span>  
                                <span>上傳時間:{{ file[3] }}</span> 
                                <span><a href="{{ url_for('online_file',Fno = file[0]) }}">線上檢視</a></span> 
                                <span><a href="{{ url_for('download',Fno = file[0]) }}">下載下傳檔案</a></span>
                            </p>
                        </div>
                    </div>
                </li>
            {% endfor %}
        </ul>
    </div>
</div>
{% endblock %}
{% block source_class %}
active
{% endblock %}      

 &emps;然後我們這裡再為我們上傳資源的頁面添加一個導航,由于我們上傳資源的功能隻有登入的使用者才可以進行使用,是以我們在導航欄中添加,當我們登入時,顯示上傳資源的導航,我們在base.html中添加:(加在資源專區後面即可)

{% if email %}
  <li class="{% block post_file_class %}{% endblock %}"><a href="{{ url_for('post_file') }}">上傳資源</a></li>
{% endif %}      

為了防止我們有的小夥伴不知道插入到哪裡,我們這裡再貼一下目前base.html整體的代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}
{#        其他頁面重寫标題的地方#}
        {% endblock %}
    </title>
    {% block css %}
{#    其他頁面引用樣式或者js的地方#}
    {% endblock %}
    <link rel="stylesheet" href="/static/css/base.css">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="navigation_bar">
        <nav class="navbar navbar-default">
          <div class="container-fluid">
{#              由于這裡我們不需要使用商标,是以對Bran部分進行了删除#}
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
              <ul class="nav navbar-nav">
                <li class="{% block index_class %}{% endblock %}"><a href="{{ url_for('index') }}">首頁<span class="sr-only">(current)</span></a></li>
                <li class="{% block formula_class %}{% endblock %}"><a href="{{ url_for('formula') }}">論壇</a></li>
                <li class="{% block source_class %}{% endblock %}"><a href="{{ url_for('source') }}">資源專區</a></li>
                {% if email %}
                    <li class="{% block post_file_class %}{% endblock %}"><a href="{{ url_for('post_file') }}">上傳資源</a></li>
                {% endif %}
              </ul>
              <form class="navbar-form navbar-left">
                <div class="form-group">
                  <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
              </form>
              <ul class="nav navbar-nav navbar-right">
                {% if email %}
                    <li class="{% block post_issue_class %}{% endblock %}"><a href="{{ url_for('post_issue') }}">釋出文章</a></li>
                    <li class=""><a href="{{ url_for('register') }}">登出</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ nickname }} <span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="{{ url_for('personal') }}">個人中心</a></li>
                        <li><a href="{{ url_for('change_password') }}">修改密碼</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="{{ url_for('show_issue') }}">已釋出的文章</a></li>
                      </ul>
                    </li>
                {% else %}
                    <li class="{% block register_class %}{% endblock %}"><a href="{{ url_for('register') }}">注冊</a></li>
                    <li class="{% block login_class %} {% endblock %}"><a href="{{ url_for('login') }}">登入</a></li>
                {% endif %}
              </ul>
            </div><!-- /.navbar-collapse -->
          </div><!-- /.container-fluid -->
        </nav>
    </div>
    <div class="content" style="padding: 0;margin: 0;">
        {% block content %}
{#        其他頁面重寫頁面内容的地方#}
        {% endblock %}
    </div>
</body>
</html>      

  然後我們app.py中的傳回值也可以進行一個适當的優化,例如我們上傳完資源之後可以重定向到我們的資源專區,而不是一開始的index(因為當時還沒有資源專區)。是以這裡也貼一下我們app.py的全部代碼,給小夥伴們進行一個參考

from flask import *
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
from config import db
import random
import time
import config
import os
import re
from decorators import login_limit

app = Flask(__name__)

# 從對象中導入config
app.config.from_object(config)


# 登入狀态保持
@app.context_processor
def login_status():
    # 從session中擷取email
    email = session.get('email')
    # 如果有email資訊,則證明已經登入了,我們從資料庫中擷取登陸者的昵稱和使用者類型,來傳回到全局
    if email:
        try:
            cur = db.cursor()
            sql = "select nickname,type from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result:
                return {'email': email, 'nickname': result[0], 'user_type': result[1]}
        except Exception as e:
            raise e
    # 如果email資訊不存在,則未登入,傳回空
    return {}


# 首頁
@app.route('/')
def index():
    return render_template('index.html')


# 注冊頁面
@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    if request.method == 'POST':
        email = request.form.get('email')
        nickname = request.form.get('nickname')
        password_1 = request.form.get('password_1')
        password_2 = request.form.get('password_2')
        phone = request.form.get('phone')
        if not all([email, nickname, password_1, password_2, phone]):
            flash("資訊填寫不全,請将資訊填寫完整")
            return render_template('register.html')
        if password_1 != password_2:
            flash("兩次密碼填寫不一緻!")
            return render_template('register.html')
        password = generate_password_hash(password_1, method="pbkdf2:sha256", salt_length=8)
        try:
            cur = db.cursor()
            sql = "select * from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result is not None:
                flash("該Email已存在!")
                return render_template('register.html')
            else:
                create_time = time.strftime("%Y-%m-%d %H:%M:%S")
                sql = "insert into UserInformation(email, nickname, password, type, create_time, phone) VALUES ('%s','%s','%s','0','%s','%s')" % (
                    email, nickname, password, create_time, phone)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return redirect(url_for('index'))
        except Exception as e:
            raise e


# 登入頁面
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    if request.method == 'POST':
        email = request.form.get('email')
        password = request.form.get('password')
        if not all([email, password]):
            flash("請将資訊填寫完整!")
            return render_template('login.html')
        try:
            cur = db.cursor()
            sql = "select password from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            if result is None:
                flash("該使用者不存在")
                return render_template('login.html')
            if check_password_hash(result[0], password):
                session['email'] = email
                session.permanent = True
                cur.close()
                return redirect(url_for('index'))
            else:
                flash("密碼錯誤!")
                return render_template('login.html')
        except Exception as e:
            raise e


# 使用者登出
@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for(('index')))


# 生成128随機id
def gengenerateID():
    re = ""
    for i in range(128):
        re += chr(random.randint(65, 90))
    return re


# 釋出文章
@app.route('/post_issue', methods=['GET', 'POST'])
@login_limit
def post_issue():
    if request.method == 'GET':
        return render_template('post_issue.html')
    if request.method == 'POST':
        title = request.form.get('title')
        comment = request.form.get('editorValue')
        email = session.get('email')
        issue_time = time.strftime("%Y-%m-%d %H:%M:%S")
        try:
            cur = db.cursor()
            Ino = gengenerateID()
            sql = "select * from Issue where Ino = '%s'" % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            # 如果result不為空,即存在該ID,就一直生成128位随機ID,直到不重複位置
            while result is not None:
                Ino = gengenerateID()
                sql = "select * from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
            sql = "insert into Issue(Ino, email, title, issue_time) VALUES ('%s','%s','%s','%s')" % (
                Ino, email, title, issue_time)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
                '1', Ino, comment, issue_time, email)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            cur.close()
            return redirect(url_for('formula'))
        except Exception as e:
            raise e


# 論壇頁面
@app.route('/formula')
def formula():
    if request.method == 'GET':
        try:
            cur = db.cursor()
            sql = "select Issue.Ino, Issue.email,UserInformation.nickname,issue_time,Issue.title,Comment.comment from Issue,UserInformation,Comment where Issue.email = UserInformation.email and Issue.Ino = Comment.Ino and Cno = '1' order by issue_time DESC "
            db.ping(reconnect=True)
            cur.execute(sql)
            issue_information = cur.fetchall()
            cur.close()
            return render_template('formula.html', issue_information=issue_information)
        except Exception as e:
            raise e


# 問題詳情
@app.route('/issue/<Ino>', methods=['GET', 'POST'])
@login_limit
def issue_detail(Ino):
    if request.method == 'GET':
        try:
            if request.method == 'GET':
                cur = db.cursor()
                sql = "select Issue.title from Issue where Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                # 這裡傳回的是一個清單,即使隻有一個資料,是以這裡使用cur.fetchone()[0]
                issue_title = cur.fetchone()[0]
                sql = "select UserInformation.nickname,Comment.comment,Comment.comment_time,Comment.Cno from Comment,UserInformation where Comment.email = UserInformation.email and Ino = '%s'" % Ino
                db.ping(reconnect=True)
                cur.execute(sql)
                comment = cur.fetchall()
                cur.close()
                # 傳回視圖,同時傳遞參數
                return render_template('issue_detail.html', Ino=Ino, issue_title=issue_title, comment=comment)
        except Exception as e:
            raise e

    if request.method == 'POST':
        Ino = request.values.get('Ino')
        email = session.get('email')
        comment = request.values.get('editorValue')
        comment_time = time.strftime("%Y-%m-%d %H:%M:%S")
        try:
            cur = db.cursor()
            sql = "select max(Cno) from Comment where Ino = '%s' " % Ino
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            Cno = int(result[0]) + 1
            Cno = str(Cno)
            sql = "insert into Comment(Cno, Ino, comment, comment_time, email) VALUES ('%s','%s','%s','%s','%s')" % (
            Cno, Ino, comment, comment_time, email)
            cur.execute(sql)
            db.commit()
            cur.close()
            return redirect(url_for('issue_detail',Ino = Ino))
        except Exception as e:
            raise e


# 個人中心
@app.route('/personal')
@login_limit
def personal():
    if request.method == 'GET':
        email = session.get('email')
        try:
            cur = db.cursor()
            sql = "select email, nickname, type, create_time, phone from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            personal_info = cur.fetchone()
        except Exception as e:
            raise e
        return render_template('personal.html',personal_info = personal_info)


# 修改密碼
@app.route('/change_password',methods=['GET','POST'])
@login_limit
def change_password():
    if request.method == 'GET':
        return render_template('change_password.html')
    if request.method == 'POST':
        old_password = request.form.get('old_password')
        new_password1 = request.form.get('new_password1')
        new_password2 = request.form.get('new_password2')
        if not all([old_password,new_password1,new_password2]):
            flash("資訊填寫不全!")
            return render_template('change_password.html')
        if new_password1 != new_password2:
            flash("兩次新密碼不一緻!")
            return render_template('change_password.html')
        email = session.get('email')
        try:
            cur = db.cursor()
            sql = "select password from UserInformation where email = '%s'" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            password = cur.fetchone()[0]
            if check_password_hash(password,old_password):
                password = generate_password_hash(new_password1, method="pbkdf2:sha256", salt_length=8)
                sql = "update UserInformation set password = '%s' where email = '%s'" % (password,email)
                db.ping(reconnect=True)
                cur.execute(sql)
                db.commit()
                cur.close()
                return render_template('index.html')
            else:
                flash("舊密碼錯誤!")
                return render_template('change_password.html')
        except Exception as e:
            raise e

# 檢視已釋出的文章
@app.route('/show_issue')
@login_limit
def show_issue():
    if request.method == 'GET':
        email = session.get('email')
        try:
            cur = db.cursor()
            sql = "select ino, email, title, issue_time from Issue where email = '%s' order by issue_time desc" % email
            db.ping(reconnect=True)
            cur.execute(sql)
            issue_detail = cur.fetchall()
        except Exception as e:
            raise e
        return render_template('show_issue.html',issue_detail=issue_detail)

# 生成120位随機id
def gengenerateFno():
    re = ""
    for i in range(120):
        re += chr(random.randint(65, 90))
    return re

# 資源上傳頁面
@app.route('/post_file',methods=['GET','POST'])
@login_limit
def post_file():
    if request.method == 'GET':
        return render_template('post_file.html')
    if request.method == 'POST':
        email = session.get('email')
        upload_file = request.files.get('file')
        filename = request.form.get('filename')
        file_info = request.form.get('file_info')
        file_path = 'store'
        file_time = time.strftime("%Y-%m-%d %H:%M:%S")
        Fno = gengenerateFno()
        try:
            cur = db.cursor()
            sql = "select * from Files where Fno = '%s'" % Fno
            db.ping(reconnect=True)
            cur.execute(sql)
            result = cur.fetchone()
            # 如果result不為空,即該Fno已存在時,一直生成随機的Fno,隻到該資料庫中不存在
            while result is not None:
                Fno = gengenerateFno()
                sql = "select * from Files where Fno = '%s'" % Fno
                db.ping(reconnect=True)
                cur.execute(sql)
                result = cur.fetchone()
            # 擷取檔案的字尾
            upload_name = str(upload_file.filename)
            houzhui = upload_name.split('.')[-1]
            # 儲存在本地的名字為生成的Fno+檔案字尾,同時修改Fno的值
            Fno = Fno+"."+houzhui
            # 儲存檔案到我們的伺服器中
            upload_file.save(os.path.join(file_path,Fno))
            # 将檔案資訊存儲到資料庫中
            sql = "insert into Files(Fno, filename, file_info, file_time,email) VALUES ('%s','%s','%s','%s','%s')" % (Fno,filename,file_info,file_time,email)
            db.ping(reconnect=True)
            cur.execute(sql)
            db.commit()
            cur.close()
            return redirect(url_for('source'))
        except Exception as e:
            raise e

# 資源專區
@app.route('/source')
def source():
    if request.method == 'GET':
        try:
            cur = db.cursor()
            sql = "select Fno,filename,file_info,file_time,nickname from Files,UserInformation where Files.email = UserInformation.email"
            db.ping(reconnect=True)
            cur.execute(sql)
            files = cur.fetchall()
            cur.close()
            return render_template('source.html',files = files)
        except Exception as e:
            raise e

# 線上檢視檔案
@app.route('/online_file/<Fno>')
def online_file(Fno):
    return send_from_directory(os.path.join('store'), Fno)

# 檔案下載下傳功能
@app.route('/download/<Fno>')
def download(Fno):
    return send_file(os.path.join('store') + "/" + Fno, as_attachment=True)

if __name__ == '__main__':
    app.run()      

  目前我們的項目35583行代碼了,這裡面其實有很多代碼是我們引入的插件的代碼和我們富文本編輯器的代碼,我們把它去掉,我們純手動碼的代碼也有3982行代碼了,也算一個五髒俱全的小項目了,(​

​到這裡,部落客已經碼字108256個字了​

​​)。

  好了,現在小夥伴們可以去測試自己的論壇系統啦!現在已經完成了,我們可以釋出問題并且顯示出來,還可以對文章進行回複,檢視文章詳情,個人中心,資源專區了,現在小小的線上論壇系統已經五髒俱全啦!(雖然設施簡陋了點),同時我們還實作了我們的通路過濾功能,不然非法使用者通路我們的頁面,在這一講中我們實作了我們個人中心頁面的各個功能~小夥伴們都順利實作了嗎?如果有問題可以在評論區裡進行問哦,如果喜歡這篇文章,可以點贊關注支援下部落客。

  部落客的更多文章導航可以檢視我的棧内導航文章,裡有各個文章的超連結,實時更新。​​部落格文章内容導航(實時更新)​​

九、項目遷移

  現在來到我們實戰系列的最後一個部分了,項目遷移的一些内容,在之前的八個部分裡,我們已經完成了線上論壇系統的導覽列、登入、注冊、論壇頁面、文章詳情頁面、個人頁面、修改密碼、檢視已釋出文章、資源專區、檔案上傳下載下傳等的功能實作,目前我們的論壇已經可以進行文章釋出,檢視文章詳情,并且進行回複資訊了、資源的上傳下載下傳,各個頁面的流量過濾。下面我們繼續來完善我們的這個線上論壇系統,今天我們來繼續完善我們的系統,今天我們來實作一些我們如何儲存我們系統的一些插件資訊和資料庫資訊,如何對項目進行遷移。

9.1 生成插件檔案版本

  我們項目中一般都插入了很多不同的插件,我們如果要将項目部署到其他的機器上,我們就需要知道我們安裝了哪些插件,并且在遷移的機器上進行安裝這些插件,這裡我們可以使用pip3将我們項目中使用的插件版本進行導出,可以導出到我們的requirments.txt檔案中,也可以自行選擇;

pip3 freeze >requirements.txt      

這裡導出的我們目前這個項目所使用的插件資訊有:

click==7.1.1
Flask==1.1.1
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
PyMySQL==0.9.3
Werkzeug==1.0.0      

随着我們項目功能的增加,用的是插件也會逐漸的增加,手動安裝費時費力。在導出到requirements.txt之後,我們就可以在遷移的機器上使用pip3進行安裝相對應版本的插件,安裝的指令為;

pip3 install -r requirements.txt      

這樣我們就可以在遷移的機器中安裝和我們開發環境中一緻的一個插件了。

  本文原創為CSDN部落客亓官劼,原文連結為:​​收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!​​,請大家支援原創,拒絕抄襲。

9.2生成資料庫表結構和資料庫資料遷移

  我們前面插件是完成遷移了,可是我們沒資料庫也沒法運作我們的項目啊,我們項目的很多資料都在我們的資料庫中,下面我們就來生成我們資料庫的表結構,首先我們得建立一個資料庫,和我們項目中的名稱一緻,然後我們可以是PyCharm中的Database的SQL Generate進行生成我們資料庫的表結構:

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!
收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

  然後我們直接将右面的SQL語句進行儲存就好啦,這裡我們可以選擇上面自帶的儲存為檔案的功能,也可以在migrate中建立一個檔案用來存儲我們的SQL語句,這裡我使用的db.sql用來存儲,然後把右面的代碼複制進去就好了。  現在我們資料庫表的結構也建立完成了,下面我們就需要來生成我們資料庫内資料的一個插入語句,将我們資料内的資料進行遷移啦。這裡還是使用我們的PyCharm進行自動的生成Insert語句(強大的PyCharm):

收藏!最詳細的Python全棧開發指南 看完這篇你還不會Python全棧開發 你來打我!!!

  我們選擇合适的一個檔案夾生成,點選之後就會自動生成我們各個表資料的一個insert語句,我們到遷移的機器上将這些語句運作就好了。

  到這裡我們就已經生成了我們的項目中所有插件的版本資訊和資料庫的遷移語句啦。(這裡如果使用Flask-SQLAlchemy進行使用資料庫的話,也有其他的方法進行遷移)。

到這裡為止,我們本系列《Python Web全棧開發入門實戰教程教程》也就到此結束了,全文​

​11萬字​

​多一點,本篇博文送給那些想入門Web開發而又不知所措的小夥伴們,希望對你們能夠有所幫助,帶你們打開Web開發的大門。進入大門之後的世界就需要你們自己去闖蕩啦!

在最後再插播一次自己簡介: