前言
最近在看Flask Web開發(fā),感覺這本書寫的真不錯(cuò),里面教開發(fā)者如何一步步開發(fā)一個(gè)博客系統(tǒng)。剛開始看的時(shí)候,感覺完全看不懂,語法實(shí)在太靈活了。耐著性子看了一段時(shí)間,大概了解了開發(fā)流程,昨天完成了注冊(cè)登陸發(fā)郵件功能,下面講下我在學(xué)習(xí)過程中的心得和一些坑。
創(chuàng)建工程的一些配置
-
1.我是在mac下的pycharm進(jìn)行開發(fā),為了讓html文件有Jinja2的提示,進(jìn)入控制臺(tái)來到工程目錄,輸入ls-a,可以看到一個(gè)叫.idea的隱藏文件,進(jìn)入該文件,如下所示
- 2.用vim編輯sample.iml文件,輸入命令vim sample.iml,在文本最后加上如下編輯
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
-
3.這樣就可以在pycharm的html文件中有Jinja2的提示,點(diǎn)擊pycharm最下角的醫(yī)生頭標(biāo),可以看到下面這個(gè)樣子就配置成功了。
- 4.pycharm里可能有波浪線感覺很別扭,選擇上面的Inspection到Syntax波浪線就消失了。
程序結(jié)構(gòu)組織
- 1.在flask開發(fā)中使用藍(lán)圖對(duì)程序進(jìn)行重構(gòu),我的藍(lán)圖結(jié)構(gòu)為
|-sample
|-app
|-auth
|-__init__.py
|- forms.py
|- views.py
|-main
|-__init__.py
|- views.py
|-static
|-templates
|-__init__.py
|- config
|- db.sqlite
|- email.py
|- models.py
|- doc
|- migrations
|- test
|- venv
|- manager.py
|- requirements.txt
- 2.把應(yīng)用程序都放在app包里,app里的auth包專門處理用戶登陸注冊(cè)這一塊,app里的main包放其余的路由視圖函數(shù),包括錯(cuò)誤(404,500)視圖函數(shù)。在auth和main中的
__init__.py文件中創(chuàng)建藍(lán)圖。 - 3.static文件夾放靜態(tài)文件,像css和js文件。templates 文件夾中模板文件,就是用于視圖函數(shù)加載的html文件。
- 4.在與app同層的
__init__函數(shù)用于創(chuàng)建初始變量,并創(chuàng)建工廠函數(shù)。 - 5.config文件中用于存儲(chǔ)配置參數(shù)。
- 6.migrations是數(shù)據(jù)庫遷移所創(chuàng)建中的文件夾
- 7.test文件夾里可做單元測(cè)試
- 8.venv是創(chuàng)建工程中所創(chuàng)建的虛擬環(huán)境,我是利用pycharm進(jìn)行操作的。
- 9.manager.py文件中調(diào)用上面
__init__文件中的工廠函數(shù),編寫這個(gè)文件是為了調(diào)試方便。requirements.txt文件中是所有工程所依賴的庫文件,可以在該文件上編輯需要安裝的三方庫,然后在命令臺(tái)先激活虛擬壞境. venv/bin/activate,然后執(zhí)行pip freeze > requirements.txt安裝。
Web首頁搭建
-
1.效果如下:
- 2.首頁的模板
index.html是繼承base.html,而base.html是繼承flask-bootstrap庫中bootstrap/base.html,首先安裝flask-bootstrap,在base.html利用Jinja2的{% block xxx%} {% end block %}語法重定義了幾個(gè)塊
-
{% extends 'bootstrap/base.html' %}繼承基模板 - 重定義基模板中的head塊
{% block head %}
{{ super() }}
{% include 'include/_header.html' %}
{% endblock %}
- 重定義基模板中的style塊, 這里一定要先初始化父類,下面這個(gè)bootstrap鏈接可以去百度下bootstrap的cdn,網(wǎng)上資源有很多,有各種各樣的樣式。
{% block styles %}
{{ super() }}
<link rel="stylesheet"
>
{% endblock %}
- 重定義基模板的navbar塊,
_navbar_html就是上面頁面的導(dǎo)航欄
{% block navbar %}
{% include 'include/_navbar.html' %}
{# {{ nav.top.render() }} #}
{% endblock %}
- 3.首頁的視圖函數(shù)很簡(jiǎn)單,只要加載這個(gè)
index.html即可
@main.route('/')
def index():
return render_template('index.html')
注冊(cè)頁面
-
1.效果如下:
2.采用flask-wtf模塊創(chuàng)建表單,每個(gè)Web表單都由一個(gè)繼承自FlaskForm的類表示,這個(gè)類定義表單中的一組字段,每個(gè)字段都由一個(gè)對(duì)象表示,例如注冊(cè)表單
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, BooleanField
from wtforms.validators import DataRequired, Email, Length, EqualTo
#注冊(cè)表
class RegisterForm(FlaskForm):
email = StringField(label=u'郵箱地址',validators=[DataRequired(), Length(1,64), Email()])
username = StringField(label=u'用戶名',validators=[DataRequired(), Length(1,64)])
password = PasswordField(label=u'密碼',validators=[DataRequired(), EqualTo('password2', message=u'密碼必須相同')])
password2 = PasswordField(label=u'確認(rèn)密碼',validators=[DataRequired()])
submit = SubmitField(label=u'馬上注冊(cè)')
- 3.將表單渲染成HTML,表單字段是可以調(diào)用的,在模板中調(diào)用會(huì)渲染成HTML。例如將RegisterForm實(shí)例對(duì)象通過參數(shù)form傳入模板,flask-bootstrap提供了一個(gè)函數(shù)可以使用Bootstrap中預(yù)先定義好的表單樣式渲染整個(gè)Flask-WTF表單。
{% import 'bootstrap/wtf.html' as wtf %}
<form method="post">
{{ wtf.quick_form(form) }}
</form>
- 4.在注冊(cè)成功后,要將用戶的郵箱、密碼保存到數(shù)據(jù)庫中,并發(fā)送一份確認(rèn)郵件到注冊(cè)的郵箱中,數(shù)據(jù)庫就是程序結(jié)構(gòu)組織中的db.sqlite文件,下面介紹下如何使用數(shù)據(jù)庫和發(fā)送郵件。
數(shù)據(jù)庫的使用
- 1.使用flask-sqlalchemy,在ORM中,模型一般是一個(gè)Python類,類中的屬性對(duì)應(yīng)數(shù)據(jù)表中的列,在models.py文件中創(chuàng)建了兩個(gè)類,用戶類和角色類,角色類在這里暫時(shí)用不到,先與對(duì)象類建立一對(duì)多的關(guān)系
-
角色類模型
class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=True) users = db.relationship('User', backref='itsrole') # Role對(duì)象引用users,User對(duì)象引用itsrole,是隱形存在的屬性 用戶類模型
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, nullable=True)
password = db.Column(db.String, nullable=True)
email = db.Column(db.String, nullable=True, unique=True) # 新建一個(gè)郵箱字段
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash = db.Column(db.String, nullable=True) # 模型中加入密碼散列值
confirmed = db.Column(db.Boolean, default=False) # 郵箱令牌是否點(diǎn)擊
為了保護(hù)用戶輸入的密碼,User表中的password_hash字段是密碼散列化的結(jié)果,這個(gè)后面會(huì)提到。
- 2.上面一對(duì)多的關(guān)系是如何構(gòu)建的可以看下我博客中的sqlalchemy文章,里面介紹了數(shù)據(jù)庫各種映射關(guān)系,下面創(chuàng)建下數(shù)據(jù)庫。在控制臺(tái)輸入命令
python manager.py shell進(jìn)入交互式壞境(首先要激活虛擬venv壞境)
>>>from app import db
>>>db.drop_all() #刪除數(shù)據(jù)庫中的表
>>>db.create_all() #創(chuàng)建數(shù)據(jù)庫中的表,如果沒有數(shù)據(jù)庫文件會(huì)自動(dòng)創(chuàng)建
-
3.pycharm提供了很好用的數(shù)據(jù)庫可視化工具
點(diǎn)擊右邊的Database可以導(dǎo)入一個(gè)數(shù)據(jù)庫文件,圖中已經(jīng)導(dǎo)入了db.sqlite文件
- 4.關(guān)于python的sqlalchemy可以看下我博客中的sqlalchemy文章,里面頁介紹了一些基本的數(shù)據(jù)庫操作
用戶密碼的保護(hù)
- 1.數(shù)據(jù)庫User表的字段password_hash就是用于存儲(chǔ)散列化的密碼,使用werkzeug模塊實(shí)現(xiàn),每次注冊(cè)的時(shí)候創(chuàng)建了一個(gè)用戶對(duì)象,利用python的@property和@setter屬性,在創(chuàng)建對(duì)象對(duì)密碼賦值時(shí)實(shí)現(xiàn)密碼散列化
@property # 試圖讀取password的值,返回錯(cuò)誤, 因?yàn)閜assword已經(jīng)不可能恢復(fù)了
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter # 設(shè)置password屬性的值時(shí),賦值函數(shù)會(huì)調(diào)用generate_password_hash函數(shù)
def password(self, password):
self.password_hash = generate_password_hash(password)
如果試圖讀取用戶密碼就會(huì)拋出一個(gè)錯(cuò)誤,在注冊(cè)調(diào)用user = User(username=form.username.data, password=form.password.data, email=form.email.data)時(shí)會(huì)自動(dòng)來到setter函數(shù),利用werkzeug的generate_password_hash產(chǎn)生散列化密碼。
郵件的發(fā)送
1.利用flask-mail模塊完成郵件的發(fā)送,重要的是你的郵箱要開啟smtp服務(wù),gmail和國(guó)內(nèi)的163和qq郵箱有不同,這里以我的163郵箱做下解釋。
-
2.郵箱開啟smtp服務(wù),開啟的時(shí)候會(huì)讓你設(shè)置一個(gè)密碼,這個(gè)密碼就是程序里面需要設(shè)置的MAIL_PASSWORD,這個(gè)密碼不需要是你的登陸密碼
-
3.程序中配置壞境變量,如下所示:
這里寫圖片描述
163郵箱的端口號(hào)為465,注意開啟的是TLS協(xié)議,國(guó)內(nèi)郵箱使用SSL會(huì)失敗的,國(guó)外的郵箱例如gmail 是SSL協(xié)議。
從壞境變量中導(dǎo)入郵箱賬號(hào)密碼export MAIL_USERNAME=及export MAIL_PASSWORD=, 導(dǎo)入后可用echo MAIL_USERNAME查看郵箱名,echo MAIL_PASSWORD查看密碼。
確認(rèn)賬戶
1.在用戶注冊(cè)完成后,會(huì)往郵箱發(fā)送一份確認(rèn)郵件,新用戶的狀態(tài)是待確認(rèn)狀態(tài),按照郵箱的說明操作后,狀態(tài)變?yōu)榇_認(rèn)。往往郵箱要求用戶點(diǎn)擊一個(gè)特殊的URL 鏈接。
-
2.我們采用的確認(rèn)鏈接 方式為為http://www.example.com/auth/confirm/id, 其中id為數(shù)據(jù)庫中用戶的id,通過使用itsdangerous包對(duì)id進(jìn)行加密處理。導(dǎo)入相關(guān)包
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer代碼如下:
itsdangerous提供了多種生成令牌的方法,TimedJSONWebSignatureSerializer類生成具有過期時(shí)間的JSON Web簽名,這個(gè)類的構(gòu)造函數(shù)接收的參數(shù)為一個(gè)鑰匙,使用SECRET_KEY設(shè)置。expires_in設(shè)置令牌過期時(shí)間,單位為秒。 -
3.上面已經(jīng)生成了加密簽名,解碼簽名采用loads()方法,唯一的參數(shù)就是令牌字符串,這個(gè)方法會(huì)檢驗(yàn)簽名和過期時(shí)間,如果通過則返回原始id,異?;蜻^期則拋出異常。解碼代碼如下:
解碼成功后,用戶的確認(rèn)字段設(shè)為True
-
4.發(fā)送確認(rèn)郵件,當(dāng)前的/register路由把用戶添加到數(shù)據(jù)庫中,會(huì)重定向到/index,重定向之前這個(gè)路由要發(fā)送確認(rèn)郵件,把產(chǎn)生的令牌token傳入模板,代碼如下:
-
5.點(diǎn)擊確認(rèn)郵件的鏈接后,來到/confirm/token這個(gè)鏈接,首先要保證用戶已登陸,
flask_login包中的login_required就是用來保證用戶已經(jīng)登陸的。
Github鏈接
基于flask的個(gè)人博客系統(tǒng)代碼我已經(jīng)上傳到github了,鏈接為https://github.com/happyte/flask-blog