使用django發送郵件

django通過封裝python的smtplib實現發送郵件功能。django 1.11官網翻譯內容見:http://www.lxweimin.com/p/c02aac458a71

下面的內容結合django by example CH2 第一節的內容完成,翻譯了第一節的內容,使用python2.7+Django1.11測試,修正了書中代碼的錯誤,添加了注意事項,并寫出了測試結果。

通過e-mail分享文章

首先,我們允許用戶通過發送郵件的方式分享文章。想一下我們如何通過上一章學到views、URLs和templates實現這個功能。如果要允許用戶通過e-mail發送郵件,我們需要:

  • 為用戶創建一個form來填寫它們的名字、e-mail,接收文章的e-mail和評論(可選);
  • 在views.py文件中創建一個視圖來處理post的數據并發送e-mail;
  • 在urls.py文件中為新建立的視圖添加URL。
  • 創建一個模板來展示表單。

使用Django創建form

form即為表單,下面表述中form與表單意義相同。

我們從創建分享文章的form開始。Django內置form框架幫助我們非常方便的創建form。form礦建允許我們定義form的字段、指定它們展示的方式、輸入數據的驗證方式。Django form礦建還提供靈活的方法來渲染form和處理數據。

Django提供兩個基準類來創建forms:

Form:幫助我們創建標準forms;

ModelForm:幫助我們創建增加或者修改模型實例的forms。

首先,在blog應用的根目錄新建一個名為forms.py的文件,并添加以下代碼:

from django import forms


class EmailPostForm(forms.Form):
    name = forms.CharField(max_length=25)
    email = forms.EmailField()
    to = forms.EmailField()
    comments = forms.CharField(required=False, widget=forms.Textarea)

這是你的第一個django表單(form)。我們來看一下通過集成Form類創建的form。我們使用不同的字段對輸入進行驗證。

注意:

Forms可以放在Django項目的任何位置,為了方便起見,我們將其放在每個項目的forms.py文件中。

name字段是一個CharField。這種類型的字段渲染一個<input type="text">的HTML元素。每一個字段都對應一個小組件,這個小組件決定HTML如何展示該字段。默認的組件可以通過設置widget屬性進行覆蓋。在comments字段中,我們使用Textarea組件將其展示為一個<textarea>HTML元素來代替默認的<input>元素。

字段驗證還依賴字段類型。例如,email和to字段為EmailField,兩個字段都需要有效地e-mail地址,否則字段驗證將引發forms.ValidationError異常并且form無法通過驗證。form驗證還會考慮其他參數:我們定義了一個最大長度為25的name字段并將comment字段設置為required=False。form驗證時會將這些都考慮在內。這個form中使用的字段類型只是django表單字段的一小部分,所有的表單字段可以參考https://docs.djangoproject.com/en/1.11/ref/forms/fields/

在視圖中處理表單

我們需要創建了一個新的視圖來處理表單并在它成功提交時發送e-mail。編輯blog應用的views.py寫入以下代碼:

from .forms import EmailPostForm


def post_share(request, post_id):
    # Retrieve post by id
    post = get_object_or_404(Post, id=post_id, status='published')

    if request.method == 'POST':
        # Form was submitted
        form = EmailPostForm(request.POST)
        if form.is_valid():
            # Form fields passed validation
            cd = form.cleaned_data
            # ... send email
    else:
        form = EmailPostForm()
    return render(request, 'blog/post/share.html', {'post': post, 'form': form})

該表單將實現以下功能:

我們定義了post_share視圖,該視圖輸入request對象和post_id作為參數。

我們使用get_object_or_404()通過id獲取文章并且要求文章狀態為published。

展示初始表單和處理提交的數據使用相同的視圖。我們使用request方法對其進行區分,如果request方法為GET,我們將展示一個空的表單;如果request方法為POST,那么表單將被提交并且需要處理。因此我們使用request.method="POST"來區分這兩種情況。

下面是展示和處理表單的過程:

  1. 當使用GET請求視圖時,我們創建一個新的form類在模板中展示空的表單: form=EmailPostForm()

  2. 用戶填寫表單并通過POST提交,然后,我們在POST部分使用提交的數據創建了一個表單類視圖:

if form.is_valid():
            # Form fields passed validation
            cd = form.cleaned_data
            # ... send email
  1. 然后,我們使用is_valid方法對提交的數據進行驗證,這個方法對表單中的數據進行驗證,如果數據均為有效數據,則會返回Ture,否則會返回False。如果驗證為False我們可以通過訪問form.errors看到錯誤列表:

  2. 如果表單沒有通過驗證,我們將使用提交的數據再次渲染表單,并且在模板中顯示驗證錯誤。

  3. 如果表單通過驗證,我們通過form.cleaned_data獲取數據,這個屬性為表單字段名和值的屬性。

    注意:

    如果表單字段沒有驗證,cleaned_data將值包含通過驗證的字段。

現在我們需要學習如何使用Django發送郵件了。

使用Django發送郵件

使用Django發送郵件非常簡單。首先,我們需要一個本地SMTP服務器或者在項目setting.py中添加以下設置來配置一個外部SMTP服務器:

EMAIL_HOST: SMTP服務器主機。默認為localhost;

EMAIL_PORT: SMTP服務器端口。默認為25;

EMAIL_HOST_USER: the SMTP 服務器的用戶名;

EMAIL_HOST_PASSWORD: SMTP 服務器的密碼;

EMAIL_USE_TLS: 是否使用TLS安全連接;

EMAIL_USE_SSL: 是否使用隱式TLS安全連接。

如果沒有本地SMTP服務器,可以使用e-mail提供者的SMTP服務器。下面的簡單配置是使用hotmail賬戶通過hotmail服務器發送e-mail的配置(https://outlook.live.com/owa/?path=/options/popandimap):

EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'your_account@gmail.com'
EMAIL_HOST_PASSWORD = 'your_password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True

注意:

配置中,EMAIL_USE_TLS和EMAIL_USE_SSL都默認設置為False,需要配置其中一個為True,但是不能兩個都設置為True。一般端口587對應TLS,端口465對應SSL(加強TSL)。

在teminal的項目根目錄輸入命令:python manage.py shell打開Python shell并發送郵件:

from django.core.mail import send_mail
>>> send_mail('Django mail', 'This e-mail was sent with Django.', 'your_account@gmail.com', ['your_account@gmail.com'], fail_silently=False) 

send_mail()輸入主題、消息、發送者、接受者列表作為參數,通過設置fail_silently=False可以在郵件沒有正確發送的時候引發異常。如果輸出為1,那么郵件就正常發送了。如果采用setting.py中設置google web服務器發送郵件,需要正常訪問以下網址:https://www.google.com/settings/security/lesssecureapps

發送郵件測試

國內無法訪問https://www.google.com/settings/security/lesssecureapps。因此,測試了hotmail郵箱和163郵箱:

測試環境:python2.7,Django1.11

hotmail郵箱

下面的簡單配置是使用hotmail賬戶通過hotmail服務器發送e-mail的配置(https://outlook.live.com/owa/?path=/options/popandimap):

EMAIL_HOST = 'smtp-mail.outlook.com'

EMAIL_HOST_USER = 'your_account@hotmail.com'

EMAIL_HOST_PASSWORD = 'your_password'

EMAIL_PORT= 587

EMAIL_USE_TLS = True

在teminal的項目根目錄輸入命令:python manage.py shell打開Python shell并發送郵件:

from django.core.mail import send_mail
>>> send_mail('Django mail', 'This e-mail was sent with Django.', 'your_account@hotmail.com', ['your_account@163.com'], fail_silently=False) 

如果send_mail第三個參數

your_account@hotmail.com

與EMAIL_HOST_USER一致,郵件正常發送。但是如果接收郵件只有阿里云企業郵箱,該郵箱沒有返回,需要在程序中增加時間限制,否則郵件發送正常但是程序會長時間等待接收反饋信息而無法執行其他命令。

如果send_mail第三個參數與EMAIL_HOST_USER不一致,會引發SMTPDataError 異常,無法發送郵件。

163郵箱
EMAIL_HOST = 'smtp.163.com'
EMAIL_HOST_USER = 'your_account@163.com'
EMAIL_HOST_PASSWORD = 'your_auth_code'  #郵箱的授權碼而非密碼
EMAIL_PORT = 465
EMAIL_USE_SSL = True

注意,這里的password不是郵箱密碼,而是在[郵箱]-[設置]-[POP3/SMTP/IMAP]中設置的授權碼。

在teminal的項目根目錄輸入命令:python manage.py shell打開Python shell并發送郵件:

from django.core.mail import send_mail
>>> send_mail('Django mail', 'This e-mail was sent with Django.', 'your_account@hotmail.com', ['your_account@163.com'], fail_silently=False) 

如果send_mail第三個參數與EMAIL_HOST_USER一致,引發SMTPDataError: (554, 'DT:SPM 163 smtp11,D8CowADnvQK5UwFa6C2ZAw--.46120S2 1510036409,please see http://mail.163.com/help/help_spam_16.htm?ip=120.194.143.53&hostid=smtp11&time=1510036409')異常,
http://mail.163.com/help/help_spam_16.htm?ip=120.194.143.53&hostid=smtp11&time=1510036409的對應內容為該郵件被視為垃圾郵件,更改主題與內容后仍無效。需要進一步了解163判斷垃圾郵件的依據。

如果send_mail第三個參數為hotmail郵箱,郵件無法發送,引發SMTPSenderRefused: (553, 'Mail from must equal authorized user')異常。即發送信息郵箱必須與授權郵箱一致。

發送郵件測試總結

  1. 測試環境:python2.7+Django1.11。

  2. 盡量使用hotmail郵件作為settings中的郵箱;

  3. send_mail中的發送郵箱最好與settings中的授權郵箱一致。

現在,將發送郵件功能添加到視圖中,將post_share視圖更改為:

from .forms import EmailPostForm
from django.core.mail import send_mail


def post_share(request, post_id):
    # Retrieve post by id
    post = get_object_or_404(Post, id=post_id, status='published')
    sent = False
    if request.method == 'POST':
        # Form was submitted
        form = EmailPostForm(request.POST)
        if form.is_valid():
            # Form fields passed validation
            cd = form.cleaned_data
            post_url = request.build_absolute_uri(post.get_absolute_url())
            subject = '{}({})recommends you read "{}"'.format(cd['name'],
                                                              cd['email'],
                                                              post.title)
            message = 'read"{}" at {} \n\n\'s comments:{}'.format(post.title,
                                                                  post_url,
                                                                  cd['name'],
                                                                  cd[
                                                                      'comments'])
            send_mail(subject, message, cd['email'], [cd['to']])
            sent = True
            # ... send email
    else:
        form = EmailPostForm()
    return render(request, 'blog/post/share.html',
                  {'post': post, 'form': form, 'sent': sent})

這里,我們定義了一個變量sent,當郵件發送成功時,sent設為True。后續我們會在模板中使用該變量,當表單正確提交且郵件發送成功時顯示成功信息。這里使用get_absolute_url()方法獲取在郵件中用到的文章的鏈接,我們將該函數作為request.build_absolute_uri()的輸入來構建包含http的完整URL。我們使用驗證的表單的cleaned data作為郵件的主題和內容,然后將郵件發送到表單中to一欄中添加的地址中。

現在視圖完成了,我們為其添加URL,打開blog應用下的urls.py文件,添加以下內容:

urlpatterns = [
    # ...
    url(r'^(?P<post_id>\d+)/share/$', views.post_share,
        name='post_share'),
]

在模板中渲染表單

創建完表單,完成視圖并添加URL后,我們還需要為這個視圖添加模板。在blog/templates/blog/post目錄下創建名為share.html的文件,添加以下內容:

{% extends "blog/base.html" %}

{% block title %}Share a post{% endblock %}

{% block content %}
    {% if sent %}
        <h1>E-mail successfully sent</h1>
        <p>
            "{{ post.title }}" was successfully sent to {{ form.to }}.
        </p>
    {% else %}
        <h1>Share "{{ post.title }}" by e-mail</h1>
        <form action="." method="post">
            {{ form.as_p }}
            {% csrf_token %}
            <input type="submit" value="Send e-mail">
        </form>
    {% endif %}
{% endblock %}

這是展示表單的模板,當發送郵件成功時該模板則會顯示成功信息。我們創建了通過POST提交的表單:

<form action="." method="post">

然后,我們包含了form實例,我們通過as_p方法告訴Django將字段渲染為HTML的p元素(我們還可以通過as_table方法將字段渲染為table或者通過as_ul方法將字段渲染為ul)。如果我們要渲染每個字段,我們可以對每個字段進行迭代:

 {%  for field in form %}
    <div>
      {{ field.errors }}
      {{ field.label_tag }}{{ field.field }}
    </div>
 {% endfor %}

模板標簽中的{% csrf_token %}模板標簽引入一個自動生成避免CSRF襲擊所用token的隱藏字段。關于CSRF可以從以下網站獲取更多信息:“https://en.wikipedia.org/wiki/Cross-site_request_forgery。該字段將會自動生成一個隱藏輸入:

 <input type='hidden' name='csrfmiddlewaretoken' value='4WZ53yXqRomGrnH1xFeaXlVGeqPQzgJdGAE3D9ZoWpY9qknlUFLNyRMgFqATIRea' />

注意:

默認,Django為所有POST請求檢查CSRFtoken。所以我們需要通過POST提交的表單添加csrf_token。

編輯blog/post/detail.html模板并在{{ post.body|linebreaks }}后添加分享鏈接:

<p>
 <a href="{% url "blog:post_share" post.id %}">
    Share this post
 </a>
</p>

我們通過Django的url模板標簽動態生成URL。我們使用了命名空間為blog名稱為post_share的URL,我們將post.id作為參數傳入絕對URL中。

現在,在teminal中項目根目錄下運行:

python manage.py runserver 

在瀏覽器中打開http://127.0.0.1:8000/blog/,點擊任意文章標題到文章詳細內容頁面,在正文后面,你可以看到以下鏈接:

post_share_form.png

點擊Share this post,我們將跳到分享郵件分享頁面:
post_share_link.png

表單的css位于static/css/blog.css文件匯總。當我們點擊send e-mail按鈕時,表單將被提交并驗證。如果所有字段都通過驗證,且郵件正常發送,我們將得到以下頁面:
post_shared.png

如果輸入包含無效數據,根據瀏覽器的不同,可能點擊send e-mail顯示錯誤提示從而無法跳轉,也可能跳轉后顯示錯誤提示,重新填寫表單(通過驗證的字段無需重復填寫)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容