欧美一区精品二区三区|不卡国产丝袜在线观看|亚洲色中文字幕无码av|欧美色综合高清视频在线|亚洲欧美日韩丝袜另类一区|无码国产手机在线a√片无|国产精品主播福利大秀小视频|精品国产一区二区三区无码动图

  • <ul id="kssky"></ul>
  • <tr id="kssky"><blockquote id="kssky"></blockquote></tr>
    <tr id="kssky"></tr>
    <tr id="kssky"></tr>
  • <ul id="kssky"><center id="kssky"></center></ul><strike id="kssky"><rt id="kssky"></rt></strike>
  • <tr id="kssky"></tr>
    <kbd id="kssky"></kbd>
  • <ul id="kssky"><acronym id="kssky"></acronym></ul>

    「干貨」帶你認識 flask 郵件發(fā)送

    2020-03-29 06:31:48  閱讀:-  來源:

    原創(chuàng): 志學(xué)Python 志學(xué)Python

    01 Flask-Mail 簡介

    就實際的郵件發(fā)送而言,F(xiàn)lask有一個名為Flask-Mail的流行插件,可以使任務(wù)變得非常簡單。和往常一樣,該插件是用pip安裝的:

    (venv) $ pip install flask-mail

    密碼重置鏈接將包含有一個安全令牌。為了生成這些令牌,我將使用JSON Web Tokens,它也有一個流行的Python包:

    (venv) $ pip install pyjwt

    Flask-Mail插件是通過app.config對象來配置的。還記得在第七章中,我添加了用于在生產(chǎn)環(huán)境中發(fā)生錯誤時發(fā)送電子郵件的配置項? 當(dāng)時我沒有告訴你,不過,我選擇的配置變量都是Flask-Mail的需求的,所以不需要任何額外的工作,配置的活已經(jīng)完工。

    像大多數(shù)Flask插件一樣,你需要在Flask應(yīng)用創(chuàng)建之后創(chuàng)建一個郵件實例。本處,mail是類Mail的一個實例:

    # ...from flask_mail import Mail
    app = Flask(__name__)# ...mail = Mail(app)

    第七章中我提到過,測試發(fā)送電子郵件的方式有兩種。如果你想使用一個模擬的電子郵件服務(wù)器,Python提供了一個非常好用的方法,你可以使用下面的命令在第二個終端中啟動它:

    (venv) $ python -m smtpd -n -c DebuggingServer localhost:8025

    要配置此服務(wù)器,需要設(shè)置兩個環(huán)境變量:

    (venv) $ export MAIL_SERVER=localhost(venv) $ export MAIL_PORT=8025

    如果你希望真實地發(fā)送電子郵件,則需要使用真實的電子郵件服務(wù)器。那么你只需要為它設(shè)置MAIL_SERVER、MAIL_PORT、MAIL_USE_TLS、MAIL_USERNAME和MAIL_PASSWORD環(huán)境變量。如果你想要快速解決方案,可以使用Gmail帳戶發(fā)送電子郵件,并使用以下設(shè)置:

    (venv) $ export MAIL_SERVER=smtp.googlemail.com(venv) $ export MAIL_PORT=587(venv) $ export MAIL_USE_TLS=1(venv) $ export MAIL_USERNAME=&lt;your-gmail-username&gt;(venv) $ export MAIL_PASSWORD=&lt;your-gmail-password&gt;

    如果你使用的是Microsoft Windows,則需要在上面的每個export語句中將export替換為set。

    Gmail帳戶中的安全功能可能會阻止應(yīng)用通過它發(fā)送電子郵件,除非你明確允許“安全性較低的應(yīng)用程序”訪問你的Gmail帳戶??梢蚤喿x此處來了解具體情況,如果你擔(dān)心帳戶的安全性,可以創(chuàng)建一個輔助郵箱帳戶,配置它來僅用于測試電子郵件功能,或者你可以暫時啟用允許不太安全的應(yīng)用程序來運行此測試,完成后恢復(fù)為默認值。

    02 Flask-Mail 使用

    為了學(xué)習(xí)Flask-Mail如何工作,我將向你展示如何用Python shell發(fā)送電子郵件。那么,運行flask shell以激活Python,然后運行下面的命令:

    &gt;&gt;&gt; from flask_mail import Message&gt;&gt;&gt; from app import mail&gt;&gt;&gt; msg = Message(&#39;test subject&#39;, sender=app.config[&#39;ADMINS&#39;][0],... recipients=[&#39;your-email@example.com&#39;])&gt;&gt;&gt; msg.body = &#39;text body&#39;&gt;&gt;&gt; msg.html = &#39;&lt;h1&gt;HTML body&lt;/h1&gt;&#39;&gt;&gt;&gt; mail.send(msg)

    上面的代碼片段將發(fā)送一個電子郵件到你在recipients參數(shù)中設(shè)置的電子郵件地址列表。發(fā)件人配置項我在第七章中已經(jīng)配置過了,是ADMINS。該電子郵件將具有純文本和HTML版本,所以根據(jù)你的電子郵件客戶端的配置,可能會看到它們之中的其中之一。

    如你所見,相當(dāng)簡單?,F(xiàn)在讓我們將電子郵件整合到應(yīng)用中。

    03 簡單的電子郵件框架

    我將從編寫一個發(fā)送電子郵件的幫助函數(shù)開始,這個函數(shù)基本上是上一節(jié)中shell函數(shù)的通用版本。我將把這個函數(shù)放在一個名為app/email.py的新模塊中:

    from flask_mail import Messagefrom app import mail
    def send_email(subject, sender, recipients, text_body, html_body): msg = Message(subject, sender=sender, recipients=recipients) msg.body = text_body msg.html = html_body mail.send(msg)

    Flask-Mail支持一些我不在這里使用的功能,如抄送和密件抄送列表。如果你對這些選項感興趣,務(wù)必查閱Flask-Mail文檔。

    04 請求重置密碼

    我上面提到過,用戶有權(quán)利重置密碼。因此我將在登錄頁面提供一個鏈接:

     &lt;p&gt; Forgot Your Password? &lt;a href=&#34;{{ url_for(&#39;reset_password_request&#39;) }}&#34;&gt;Click to Reset It&lt;/a&gt;&lt;/p&gt;

    當(dāng)用戶點擊鏈接時,會出現(xiàn)一個新的Web表單,要求用戶輸入注冊的電子郵件地址,以啟動密碼重置過程。這里是表單類:

    class ResetPasswordRequestForm(FlaskForm): email = StringField(&#39;Email&#39;, validators=[DataRequired(), Email()]) submit = SubmitField(&#39;Request Password Reset&#39;)

    當(dāng)然也需要一個視圖函數(shù)來處理表單:

    from app.forms import ResetPasswordRequestFormfrom app.email import send_password_reset_email
    @app.route(&#39;/reset_password_request&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;])def reset_password_request(): if current_user.is_authenticated: return redirect(url_for(&#39;index&#39;)) form = ResetPasswordRequestForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user: send_password_reset_email(user) flash(&#39;Check your email for the instructions to reset your password&#39;) return redirect(url_for(&#39;login&#39;)) return render_template(&#39;reset_password_request.html&#39;, title=&#39;Reset Password&#39;, form=form)

    該視圖函數(shù)與其他的表單處理視圖函數(shù)非常相似。我從確保用戶沒有登錄開始,如果用戶登錄,那么使用密碼重置功能就沒有意義,所以我重定向到主頁。

    當(dāng)表格被提交并驗證通過,我使用表格中的用戶提供的電子郵件來查找用戶。如果我找到用戶,就發(fā)送一封密碼重置電子郵件。我執(zhí)行此操作使用的send_password_reset_email()輔助函數(shù),將在下面向你展示。

    電子郵件發(fā)送后,我會閃現(xiàn)一條消息,指示用戶查看電子郵件以獲取進一步說明,然后重定向回登錄頁面。你可能會注意到,即使用戶提供的電子郵件不存在,也會顯示閃現(xiàn)的消息,這樣的話,客戶端就不能用這個表單來判斷一個給定的用戶是否已注冊。

    05 請求重置密碼

    在實現(xiàn)send_password_reset_email()函數(shù)之前,我需要一種方法來生成密碼重置鏈接,它將被通過電子郵件發(fā)送給用戶。當(dāng)鏈接被點擊時,將為用戶展現(xiàn)設(shè)置新密碼的頁面。這個計劃中棘手的部分是確保只有有效的重置鏈接可以用來重置帳戶的密碼。

    生成的鏈接中會包含令牌,它將在允許密碼變更之前被驗證,以證明請求重置密碼的用戶是通過訪問重置密碼郵件中的鏈接而來的。JSON Web Token(JWT)是這類令牌處理的流行標(biāo)準。JWTs的優(yōu)點是它是自成一體的,不但可以生成令牌,還提供對應(yīng)的驗證方法。

    如何運行JWTs?讓我們通過Python shell來學(xué)習(xí)一下:

    &gt;&gt;&gt; import jwt&gt;&gt;&gt; token = jwt.encode({&#39;a&#39;: &#39;b&#39;}, &#39;my-secret&#39;, algorithm=&#39;HS256&#39;)&gt;&gt;&gt; tokenb&#39;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYiJ9.dvOo58OBDHiuSHD4uW88nfJikhYAXc_sfUHq1mDi4G0&#39;&gt;&gt;&gt; jwt.decode(token, &#39;my-secret&#39;, algorithms=[&#39;HS256&#39;]){&#39;a&#39;: &#39;b&#39;}

    {&#39;a&#39;:&#39;b&#39;}字典是要寫入令牌的示例有效載荷。為了使令牌安全,需要提供一個秘密密鑰用于創(chuàng)建加密簽名。在這個例子中,我使用了字符串&#39;my-secret&#39;,但是在應(yīng)用中,我將使用配置中的SECRET_KEY。algorithm參數(shù)指定使用什么算法來生成令牌,而HS256是應(yīng)用最廣泛的算法。

    如你所見,得到的令牌是一長串字符。但是不要認為這是一個加密的令牌。令牌的內(nèi)容,包括有效載荷,可以被任何人輕易解碼(不相信我?復(fù)制上面的令牌,然后粘貼在JWT調(diào)試器上就可以看到它的內(nèi)容)。使令牌安全的是,有效載荷是被簽名的。如果有人試圖偽造或篡改令牌中的有效載荷,則簽名將會無效,并且生成新的簽名依賴秘密密鑰。令牌驗證通過時,有效負載的內(nèi)容將被解碼并返回給調(diào)用者。如果令牌的簽名驗證通過,有效載荷才可以被認為是可信的。

    我要用于密碼重置令牌的有效載荷格式為{&#39;reset_password&#39;:user_id,&#39;exp&#39;:token_expiration}。 exp字段是JWTs的標(biāo)準,如果它存在,則表示令牌的到期時間。如果一個令牌有一個有效的簽名,但是它已經(jīng)過期,那么它也將被認為是無效的。對于密碼重置功能,我會給這些令牌10分鐘的有效期。

    當(dāng)用戶點擊電子郵件鏈接時,令牌將被作為URL的一部分發(fā)送回應(yīng)用,處理這個URL的視圖函數(shù)首先要做的就是驗證它。如果簽名是有效的,則可以通過存儲在有效載荷中的ID來識別用戶。一旦得知用戶的身份,應(yīng)用可以要求一個新的密碼,并將其設(shè)置在用戶的帳戶上。

    由于這些令牌屬于用戶,因此我將在User模型中編寫令牌生成和驗證的方法:


    from time import timeimport jwtfrom app import app
    class User(UserMixin, db.Model): # ...
    def get_reset_password_token(self, expires_in=600): return jwt.encode( {&#39;reset_password&#39;: self.id, &#39;exp&#39;: time() + expires_in}, app.config[&#39;SECRET_KEY&#39;], algorithm=&#39;HS256&#39;).decode(&#39;utf-8&#39;)
    @staticmethod def verify_reset_password_token(token): try: id = jwt.decode(token, app.config[&#39;SECRET_KEY&#39;], algorithms=[&#39;HS256&#39;])[&#39;reset_password&#39;] except: return return User.query.get(id)

    get_reset_password_token()函數(shù)以字符串形式生成一個JWT令牌。請注意,decode(&#39;utf-8&#39;)是必須的,因為jwt.encode()函數(shù)將令牌作為字節(jié)序列返回,但是在應(yīng)用中將令牌表示為字符串更方便。

    verify_reset_password_token()是一個靜態(tài)方法,這意味著它可以直接從類中調(diào)用。靜態(tài)方法與類方法類似,唯一的區(qū)別是靜態(tài)方法不會接收類作為第一個參數(shù)。這個方法需要一個令牌,并嘗試通過調(diào)用PyJWT的jwt.decode()函數(shù)來解碼它。如果令牌不能被驗證或已過期,將會引發(fā)異常,在這種情況下,我會捕獲它以防止出現(xiàn)錯誤,然后將None返回給調(diào)用者。如果令牌有效,那么來自令牌有效負載的reset_password的值就是用戶的ID,所以我可以加載用戶并返回它。

    06 發(fā)送密碼重置郵件

    現(xiàn)在我有了令牌,可以生成密碼重置電子郵件。 send_password_reset_email()函數(shù)依賴于上面寫的send_email()函數(shù)。

    from flask import render_templatefrom app import app
    # ...
    def send_password_reset_email(user): token = user.get_reset_password_token() send_email(&#39;[Microblog] Reset Your Password&#39;, sender=app.config[&#39;ADMINS&#39;][0], recipients=[user.email], text_body=render_template(&#39;email/reset_password.txt&#39;, user=user, token=token), html_body=render_template(&#39;email/reset_password.html&#39;, user=user, token=token))

    這個函數(shù)中有趣的部分是電子郵件的文本和HTML內(nèi)容是使用熟悉的render_template()函數(shù)從模板生成的。模板接收用戶和令牌作為參數(shù),以便可以生成個性化的電子郵件消息。以下是重置密碼電子郵件的文本模板:


    Dear {{ user.username }},
    To reset your password click on the following link:
    {{ url_for(&#39;reset_password&#39;, token=token, _external=True) }}
    If you have not requested a password reset simply ignore this message.
    Sincerely,
    The Microblog Team

    這是更美觀的的HTML版本:

    &lt;p&gt;Dear {{ user.username }},&lt;/p&gt;&lt;p&gt; To reset your password &lt;a href=&#34;{{ url_for(&#39;reset_password&#39;, token=token, _external=True) }}&#34;&gt; click here &lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Alternatively, you can paste the following link in your browser&#39;s address bar:&lt;/p&gt;&lt;p&gt;{{ url_for(&#39;reset_password&#39;, token=token, _external=True) }}&lt;/p&gt;&lt;p&gt;If you have not requested a password reset simply ignore this message.&lt;/p&gt;&lt;p&gt;Sincerely,&lt;/p&gt;&lt;p&gt;The Microblog Team&lt;/p&gt;

    請注意,這兩個電子郵件模板中的url_for()調(diào)用中引用的reset_password路由尚不存在,這將在下一節(jié)中添加。在這兩個模板中,url_for()函數(shù)中的_external=True參數(shù)是一個新玩意兒。不帶這個參數(shù)的情況下,url_for()函數(shù)生成的是相對路徑。例如url_for(&#39;user&#39;, username=&#39;susan&#39;)生成/user/susan。這樣的路徑在本站的Web頁面中使用是完全足夠的,因為其余的協(xié)議、主機、端口部分,會沿用本站的當(dāng)前值。一旦通過郵件發(fā)送時,就脫離了這個上下文,這時候就需要URL的完全路徑了。一旦傳入_external=True參數(shù)給url_for()函數(shù),就會生成一個URL的完全路徑。本處示例為http://localhost:5000/user/susan。如果應(yīng)用被部署到一個域名下,則協(xié)議、主機名和端口會發(fā)生對應(yīng)的變化。

    07 重置用戶密碼

    當(dāng)用戶點擊電子郵件鏈接時,會觸發(fā)與此功能相關(guān)的第二個路由。這是密碼重置視圖函數(shù):

    from app.forms import ResetPasswordForm
    @app.route(&#39;/reset_password/&lt;token&gt;&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;])def reset_password(token): if current_user.is_authenticated: return redirect(url_for(&#39;index&#39;)) user = User.verify_reset_password_token(token) if not user: return redirect(url_for(&#39;index&#39;)) form = ResetPasswordForm() if form.validate_on_submit(): user.set_password(form.password.data) db.session.commit() flash(&#39;Your password has been reset.&#39;) return redirect(url_for(&#39;login&#39;)) return render_template(&#39;reset_password.html&#39;, form=form)

    在這個視圖函數(shù)中,我首先確保用戶沒有登錄,然后通過調(diào)用User類的令牌驗證方法來確定用戶是誰。如果令牌有效,則此方法返回用戶;如果不是,則返回None,并將重定向到主頁。

    如果令牌是有效的,那么我向用戶呈現(xiàn)第二個表單,需要用戶其中輸入新密碼。這個表單的處理方式與以前的表單類似,表單提交驗證通過后,我調(diào)用User類的set_password()方法來更改密碼,然后重定向到登錄頁面,以便用戶登錄。

    這是ResetPasswordForm類:

    class ResetPasswordForm(FlaskForm): password = PasswordField(&#39;Password&#39;, validators=[DataRequired()]) password2 = PasswordField( &#39;Repeat Password&#39;, validators=[DataRequired(), EqualTo(&#39;password&#39;)]) submit = SubmitField(&#39;Request Password Reset&#39;)

    這是相應(yīng)的HTML模板:

    {% extends &#34;base.html&#34; %}
    {% block content %} &lt;h1&gt;Reset Your Password&lt;/h1&gt; &lt;form action=&#34;&#34; method=&#34;post&#34;&gt; {{ form.hidden_tag() }} &lt;p&gt; {{ form.password.label }}&lt;br&gt; {{ form.password(size=32) }}&lt;br&gt; {% for error in form.password.errors %} &lt;span style=&#34;color: red;&#34;&gt;[{{ error }}]&lt;/span&gt; {% endfor %} &lt;/p&gt; &lt;p&gt; {{ form.password2.label }}&lt;br&gt; {{ form.password2(size=32) }}&lt;br&gt; {% for error in form.password2.errors %} &lt;span style=&#34;color: red;&#34;&gt;[{{ error }}]&lt;/span&gt; {% endfor %} &lt;/p&gt; &lt;p&gt;{{ form.submit() }}&lt;/p&gt; &lt;/form&gt;{% endblock %}

    密碼重置功能現(xiàn)已完成,一定要多嘗試幾次。

    08異步電子郵件

    如果你正在使用Python提供的模擬電子郵件服務(wù)器,可能沒有注意到這一點,那就是發(fā)送電子郵件會大大減慢應(yīng)用的速度,原因是發(fā)送電子郵件時所發(fā)生的和電子郵件服務(wù)器的網(wǎng)絡(luò)交互。通常需要幾秒鐘的時間才能收到電子郵件,如果收件人的電子郵件服務(wù)器速度較慢,或者收件人有多個,則可能會更久。

    我真正想要的send_email()函數(shù)是異步的。那是什么意思?這意味著當(dāng)這個函數(shù)被調(diào)用時,發(fā)送郵件的任務(wù)被安排在后臺進行,釋放send_email()函數(shù)以立即返回,以便應(yīng)用可以在發(fā)送郵件的同時繼續(xù)運行。

    Python實際上有多種方式支持運行異步任務(wù),threading和multiprocessing模塊都可以做到這一點。為發(fā)送電子郵件啟動一個后臺線程,比開始一個全新的進程需要的資源少得多,所以我打算采用這種方法:

    from threading import Thread# ...
    def send_async_email(app, msg): with app.app_context(): mail.send(msg)
    def send_email(subject, sender, recipients, text_body, html_body): msg = Message(subject, sender=sender, recipients=recipients) msg.body = text_body msg.html = html_body Thread(target=send_async_email, args=(app, msg)).start()

    send_async_email函數(shù)現(xiàn)在運行在后臺線程中,它通過send_email()的最后一行中的Thread()類來調(diào)用。 有了這個改變,電子郵件的發(fā)送將在線程中運行,并且當(dāng)進程完成時,線程將結(jié)束并自行清理。 如果你已經(jīng)配置了一個真正的電子郵件服務(wù)器,當(dāng)你按下密碼重置請求表單上的提交按鈕時,肯定會注意到訪問速度的提升。

    你可能預(yù)期只有msg參數(shù)會被發(fā)送到線程,但正如你在代碼中所看到的那樣,我也傳入了應(yīng)用實例。 使用線程時,需要牢記Flask的一個重要設(shè)計方面。 Flask使用上下文來避免必須跨函數(shù)傳遞參數(shù)。 我不打算詳細討論這個問題,但是需要知道的是,有兩種類型的上下文,即應(yīng)用上下文和請求上下文。 在大多數(shù)情況下,這些上下文由框架自動管理,但是當(dāng)應(yīng)用啟動自定義線程時,可能需要手動創(chuàng)建這些線程的上下文。

    許多Flask插件需要應(yīng)用上下文才能工作,因為這使得他們可以在不傳遞參數(shù)的情況下找到Flask應(yīng)用實例。這些插件需要知道應(yīng)用實例的原因是因為它們的配置存儲在app.config對象中,這正是Flask-Mail的情況。mail.send()方法需要訪問電子郵件服務(wù)器的配置值,而這必須通過訪問應(yīng)用屬性的方式來實現(xiàn)。 使用with app.app_context()調(diào)用創(chuàng)建的應(yīng)用上下文使得應(yīng)用實例可以通過來自Flask的current_app變量來進行訪問。

    最后,我自己是一名從事了多年開發(fā)的Python老程序員,辭職目前在做自己的Python私人定制課程,今年年初我花了一個月整理了一份最適合2019年學(xué)習(xí)的Python學(xué)習(xí)干貨,可以送給每一位喜歡Python的小伙伴,想要獲取的可以關(guān)注我的頭條號并在后臺私信我:01,即可免費獲取。

    丰城市| 茂名市| 靖江市| 磐安县| 张家口市| 长沙市| 饶河县| 弥渡县| 江油市| 门头沟区| 惠水县| 营山县| 江都市| 南靖县| 玉环县| 东阳市| 龙里县| 昭平县| 怀远县| 微博| 鄂州市| 忻州市| 会宁县| 崇义县| 岗巴县| 都昌县| 安化县| 舟山市| 朝阳区| 册亨县| 浮山县| 温宿县| 遂平县| 淮阳县| 武清区| 古浪县| 绥芬河市| 五河县| 万年县| 新民市| 晋中市|