Python Flask開發(fā)之注冊(cè)登陸功能

前言

最近在看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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容