# Flask實作_建置一個使用者註冊頁面_01_基板 ###### tags: `python` `flask` :::danger 前置閱讀: * [Flask實作_ext_01_Flask-SQLAlchemy_初探](https://hackmd.io/s/SJ9x3N9zz) * [Flask實作_ext_02_Flask-SQLAlchemy_關聯](https://hackmd.io/s/S1Pj3fCMz) * [Flask實作_ext_18_Flask-SQLAlchemy_Query](https://hackmd.io/s/rkFtMQg47) * [Flask實作_ext_03_Flask-WTF_表單建立](https://hackmd.io/s/ByofdR1XG) * [Flask實作_ext_04_Flask-WTF_表單建立_進階](https://hackmd.io/s/r1pPM7ZXz) ::: 兩個擴展(`Flask_SQLAlcemy`與`Flask_WTF`)配合前面的『<font color='blue'>Flask-基礎</font>』練習已經足夠讓我們試著建立一個『<font color='red'>使用者註冊的頁面</font>』,專案架構的問題後續會有說明,目前以單一文件模式來建置,完成這個實作之後再依需求調整,科技始終來自人心,專案架構也是。 實作過程中我們會發現開發的固定模式,熟悉了這個模式之後,相信後續任何的追加功能都不是問題,萬丈高樓平地起,這一個章節是一個基礎,因此會有較多的資訊在裡面。 預計本章節會有下列工作: * 初始化flask與相關擴展 * 建立Model * 建立Form * 建立Html * 建立View Function * 初始化資料庫 ## 作業說明 ### 初始化flask與相關擴展 專案最開始我們需要先設置[flask基礎參數](http://flask.pocoo.org/docs/1.0/config/#builtin-configuration-values)並初始化flask、flask擴展<sub><a href='#note_1'>(註1)</a></sub>。 新增一個Python文件,命名為`flask_register.py`,如下: :::success 文件:flask_register.py 說明:初始化flask與相關擴展 ```python= from flask import Flask, render_template from flask_sqlalchemy import SQLAlchemy from flask_bootstrap import Bootstrap import os # 取得啟動文件資料夾路徑 pjdir = os.path.abspath(os.path.dirname(__file__)) app = Flask(__name__) # 新版本的部份預設為none,會有異常,再設置True即可。 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True # 設置資料庫為sqlite3 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \ os.path.join(pjdir, 'data_register.sqlite') app.config['SECRET_KEY']='your key' bootstrap = Bootstrap(app) db = SQLAlchemy(app) ``` 第15行:記得設置自己的密碼,或參考官方文件建議作法。 第17~18行:初始化flask擴展 ::: ### 建立Model 初始化擴展套件`flask_sqlalchemy`之後就可以利用`db`來建置Model,Model主要負責的是資料庫的操作,定義Model相對資料庫就是定義Table,後續才能利用這個Model做資料庫的操作。 新增一個Python文件,命名為`model.py`,如下: :::success * 文件:model.py * 說明:建立MODEL_設置使用者註冊需求的Model,這邊的作業如同資料庫中規劃資料表設置需求欄位 ```python= from flask_register import db class UserReister(db.Model): """記錄使用者資料的資料表""" __tablename__ = 'UserRgeisters' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(80), unique=True, nullable=False) password = db.Column(db.String(50), nullable=False) def __repr__(self): return 'username:%s, email:%s' % (self.username, self.email) ``` 第1行:import剛才初始化的db 第4行:若沒設置會以類別名稱為資料表名稱 ::: ### 建立Form Form所負責的是表單,借助`wtform`的協助,我們可以快速又有效率的建立需求表單並渲染成Html呈現在使用者面前,再將從使用者端所接收到的回傳表單資訊利用Model寫入資料庫。 新增一個Python文件,命名為`form.py`,如下: :::success * 文件:form.py * 說明:建立Form_設置使用者註冊所需要的表單 ```python= from flask_wtf import Form from wtforms import StringField, SubmitField, validators, PasswordField from wtforms.fields.html5 import EmailField class FormRegister(Form): """依照Model來建置相對應的Form password2: 用來確認兩次的密碼輸入相同 """ username = StringField('UserName', validators=[ validators.DataRequired(), validators.Length(10, 30) ]) email = EmailField('Email', validators=[ validators.DataRequired(), validators.Length(1, 50), validators.Email() ]) password = PasswordField('PassWord', validators=[ validators.DataRequired(), validators.Length(5, 10), validators.EqualTo('password2', message='PASSWORD NEED MATCH') ]) password2 = PasswordField('Confirm PassWord', validators=[ validators.DataRequired() ]) submit = SubmitField('Register New Account') ``` 第22行:驗證兩次輸入的密碼是否相同,避免使用者輸入錯誤 ::: ### 建立Html 有了表單,我們需要利用模板來渲染網頁並且提供使用者輸入資料,透過`jinja2`的模板繼承,我們可以很快速的完成這個工作。 範例中我們會利用`flask_bootstrap`來協助我們處理排版的問題,並且使用`flask_bootstrap`的`base.html`做為基板繼承。 新增一個資料夾,命名為`templates`,並在資料夾內加入Html文件,命名為`register.html`,如下: :::success * 文件:templates/register.html * 說明:建立Html_設置使用者註冊需求的網頁,繼承`flask-bootstrap`的`base.html`以及`macro(wtf.html)`建置。 ```htmlmixed= {% extends "bootstrap/base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block title %}Reister Page{% endblock title %} {% block content %} <div class="container"> <h1>Register Your Account</h1> <h2>Welcome To My Blog</h2> {{ wtf.quick_form(form) }} </div> {% endblock content%} ``` 第1行:繼承`flask_bootstrap`的`base.html` 第2行:`import macro wtf.html` ,這是`flask_bootstrap`提供的工具,可以快速的佈置表單 第8行:利用`wtf.quick_form(form)`來渲染頁面 ::: ### 建立View Function 有了Form(表單)與Html(頁面)之後,就可以透過View Function來設置路由(Route),路由主要提供使用者連線使用,沒有路由前面都是白搭。 將View Function直接編寫於`flask_register.py`,如下: :::success * 文件:flask_register.py * 說明:建立View Function,設置使用者連線路由 ```python= #...上略...# from form import FormRegister #...中略...# @app.route('/register', methods=['GET', 'POST']) def register(): form =FormRegister() if form.validate_on_submit(): return 'Success Thank You' return render_template('register.html', form=form) if __name__ == '__main__': app.debug = True app.run() ``` 第2行:載入稍早所建置的Form 第5行:設置路由,命名為`register`,並且接受GET與POST 第7行:實作表單 第8行:`flask_wtf`提供的函數,能驗證並確認是否為POST 第9行:提交之後先簡單回傳成功的訊息 第10行:如果是GET的時候就渲染網頁`register.html`,並且帶有參數`form`,此`form`即提供Html頁面中`wtf.quick_form(form)`使用 ::: 目前來說,我們已經成功的將一切大致就緒,但過程中個人習慣先測試網頁以及表單的渲染是否正常,測試如下: :::warning * 測試 * 說明:確認表單的渲染是否正常 * 測試連結:http://127.0.0.1/register * 測試結果:使用者註冊頁面已經可以正常呈現,如下圖: ![](https://i.imgur.com/0uXkt51.png) * 測試結果:透過`wtf.quick_form`的優點在於錯誤訊息的顯示它也有處理 ![](https://i.imgur.com/Jx0dEJB.png) ::: ### 完成View Function_寫入資料庫 表單的部份已經確定可以正常的呈現<sub>(開發過程中非常建議做這種簡單的測試確認,至少有問題的時候可以先行排除)</sub>,後續就是將使用者所填寫的資料回傳並且寫入資料庫,我們需要導入稍早設置的Model,只是在寫入之前還需要做部份資料驗證,驗證的方式可以搭配`wtform`自定義欄位驗證。 我們開啟設置表單的文件來編輯,如下: :::success * 文件:form.py * 說明:在寫入使用者註冊資料之前我們需要驗證`email`與`username`是否已被使用,我們將這個檢核機制寫在Form,也因此需要在Form中導入Model ```python= #...上略...# # import model from model import UserReister #...中略...# class FormRegister(Form): #...中略...# def validate_email(self, field): if UserReister.query.filter_by(email=field.data).first(): raise ValidationError('Email already register by somebody') def validate_username(self, field): if UserReister.query.filter_by(username=field.data).first(): raise ValidationError('UserName already register by somebody') ``` 第7行:驗證郵件是否已存在資料庫內 第11行:驗證使用者名稱是否已存在資料庫內 自定義的驗證在命名規則上為`validate_欄位` ::: 加入驗證之後就不必擔心會有重覆的電子郵件與使用者名稱被註冊,將使用者註冊的View Function做最後的調整,如下: :::success * 文件:flask_register.py * 說明:調整View Function,將使用者提交資料寫入資料庫 ```python= # import Model from model import UserReister #...中略...# @app.route('/register', methods=['GET', 'POST']) def register(): form =FormRegister() if form.validate_on_submit(): user = UserReister( username = form.username.data, email = form.email.data, password = form.password.data ) db.session.add(user) db.session.commit() return 'Success Thank You' return render_template('register.html', form=form) ``` 第8行:實作使用者類別並且賦值 第14行:`commit`確認寫入 ::: 調整完畢之後,執行專案,這時候會出現異常,主要原因如下說明: :::danger * 異常 * 文件:flask_register.py * 說明:這時候執行專案會出現異常,主要因為我們的專案架構還沒有設置好,所以在遞迴import的時候造成問題! ```python= #...上略...# @app.route('/register', methods=['GET', 'POST']) def register(): from model import UserReister from form import FormRegister #...下略...# ``` 第4、5行:將model跟form的部份拉到View Function這邊import就可以暫時排除這問題了 ::: ### 初始化資料庫 要將資料寫入資料庫之前,我們必需要初始化資料庫,透過Python Shell命令可以讓系統自動依Model所設置的物件來初始化資料表,操作方式如下: :::success * 文件:Python Shell Command * 說明:透過Python Shell來初始化資料庫 ```shell= from model import db db.create_all() ``` 第1行:導入初始化的`db` 第2行:執行命令`db.create_all()` 執行之後設置的連結處會產生相對應的`sqlite`檔,如下圖所示: ![](https://i.imgur.com/YRwsIv2.png) ::: ### 執行專案 :::warning * 測試 * 測試說明:確認資料是否正確寫入資料庫 ![](https://i.imgur.com/WL5hoVK.png) * 測試結果:流程順利走完,回傳成功訊息 ![](https://i.imgur.com/ktAICRz.png) ::: :::warning * 測試 * 測試說明:確認資料是否寫入資料庫 * 測試結果:資料寫入正常 ![](https://i.imgur.com/A5YNt8a.png) ::: :::warning * 測試 * 測試說明:測試相同帳號、郵件是否會阻擋 * 測試結果:卡控正常 ![](https://i.imgur.com/Ir60vZ2.png) ::: ## 註解說明 <span id='note_1'>註1</span>:所有的flask與相關擴展都是相同方式初始化,因此各別的擴展也都有其相關參數設置,需閱讀各擴展官方文件內關於參數的說明。 ## 總結 從這個很小的設置我們了解到一個功能的形成有四個步驟: * 定義Model(如果需要新增資料表) * 定義Form(如果需要表單輸入資料) * 設置Html(如果需要面呈現) * 建置Route(View Function) 目前註冊功能還有許多缺點需要改進,但不急,在後續的實作過程中我們會將需求功能一一補齊。 **github:**[github_happy_flask](https://github.com/shaoeChen/Happy_Flask/tree/master/%E5%BB%BA%E7%BD%AE%E4%B8%80%E5%80%8B%E4%BD%BF%E7%94%A8%E8%80%85%E8%A8%BB%E5%86%8A%E9%A0%81%E9%9D%A2/Flask%E5%AF%A6%E4%BD%9C_%E5%BB%BA%E7%BD%AE%E4%B8%80%E5%80%8B%E4%BD%BF%E7%94%A8%E8%80%85%E8%A8%BB%E5%86%8A%E9%A0%81%E9%9D%A2_01_%E5%9F%BA%E6%9D%BF) **上一話:**[Flask實作_基礎_12_jinja2_樣板繼承_保留父類樣板資訊](https://hackmd.io/s/S1ujRT2ZM) **下一話:**[Flask實作_建置一個使用者註冊頁面_02_專案架構說明](https://hackmd.io/s/HyP-nW9mf)