owned this note
owned this note
Published
Linked with GitHub
# 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
* 測試結果:使用者註冊頁面已經可以正常呈現,如下圖:

* 測試結果:透過`wtf.quick_form`的優點在於錯誤訊息的顯示它也有處理

:::
### 完成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`檔,如下圖所示:

:::
### 執行專案
:::warning
* 測試
* 測試說明:確認資料是否正確寫入資料庫

* 測試結果:流程順利走完,回傳成功訊息

:::
:::warning
* 測試
* 測試說明:確認資料是否寫入資料庫
* 測試結果:資料寫入正常

:::
:::warning
* 測試
* 測試說明:測試相同帳號、郵件是否會阻擋
* 測試結果:卡控正常

:::
## 註解說明
<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)