Django實戰(一)-----使用者登入與註冊系統7(郵件確認)
通常而言,我們在使用者註冊成功,實際登陸之前,會發送一封電子郵件到對方的註冊郵箱中,表示歡迎。進一步的還可能要求使用者點選郵件中的連結,進行註冊確認。
下面就讓我們先看看如何在Django中傳送郵件吧。
一、在Django中傳送郵件
其實在Python中已經內建了一個smtp郵件傳送模組,Django在此基礎上進行了簡單地封裝。
首先,我們需要在專案的settings檔案中配置郵件傳送引數,分別如下:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.sina.com' EMAIL_PORT = 25 EMAIL_HOST_USER = '[email protected]' EMAIL_HOST_PASSWORD = 'xxxxxxxxxxx'
- 第一行指定傳送郵件的後端模組,大多數情況下照抄!
- 第二行,不用說,傳送方的smtp伺服器地址,建議使用新浪家的;
- 第三行,smtp服務埠,預設為25;
- 第四行,你在傳送伺服器的使用者名稱;
- 第五行,對應使用者的密碼。
特別說明:
- 某些郵件公司可能不開放smtp服務
- 某些公司要求使用ssl安全機制
- 某些smtp服務對主機名格式有要求
這些都是前人踩過的坑。
配置好了引數,就可以先測試一下郵件功能了。
在專案根目錄下新建一個 send_mail.py
檔案,然後寫入下面的內容:
import os from django.core.mail import send_mail os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' if __name__ == '__main__': send_mail( '來自www.cnblogs.com的測試郵件', '歡迎訪問,這裡是部落格園,本站專注於Python和Django技術的分享!', '[email protected]', ['[email protected]'], )
對於send_mail方法,第一個引數是郵件主題subject;第二個引數是郵件具體內容;第三個引數是郵件傳送方,需要和你settings中的一致;第四個引數是接受方的郵件地址列表。請按你自己實際情況修改傳送方和接收方的郵箱地址。
另外,由於我們是單獨執行 send_mail.py
檔案,所以無法使用Django環境,需要通過os模組對環境變數進行設定,也就是:
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
執行 send_mail.py
檔案,注意不是執行Django伺服器。然後到你的目的地郵箱檢視郵件是否收到。
二、傳送HTML格式的郵件
通常情況下,我們傳送的郵件內容都是純文字格式。但是很多情況下,我們需要傳送帶有HTML格式的內容,比如說超級連結。一般情況下,為了安全考慮,很多郵件服務提供商都會禁止使用HTML內容,幸運的是對於以 http
和 https
開頭的連結還是可以點選的。
下面是傳送HTML格式的郵件例子。刪除 send_mail.py
檔案內原來的所有內容,新增下面的程式碼:
import os from django.core.mail import EmailMultiAlternatives os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' if __name__ == '__main__': subject, from_email, to = '來自www.cnblogs.com的測試郵件', '[email protected]', '[email protected]' text_content = '歡迎訪問www.cnblogs.com,這裡是劉江的部落格和教程站點,專注於Python和Django技術的分享!' html_content = '<p>歡迎訪問<a href="http://www.cnblogs.com" target=blank>www.cnblogs.com</a>,這裡是部落格園,專注於Python和Django技術的分享!</p>' msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) msg.attach_alternative(html_content, "text/html") msg.send()
其中的 text_content
是用於當HTML內容無效時的替代txt文字。
開啟測試用的接收郵箱,可以看到連結能夠正常點選,如下圖所示:
這個 send_mail.py
檔案只是一個測試指令碼,可以從專案裡刪除。
三、 建立郵件確認模型
既然要區分通過和未通過郵件確認的使用者,那麼必須給使用者新增一個是否進行過郵件確認的屬性。
另外,我們要建立一張新表,用於儲存使用者的確認碼以及註冊提交的時間。
全新、完整的 /login/models.py
檔案如下:
from django.db import models # Create your models here. class User(models.Model): gender = ( ('male', "男"), ('female', "女"), ) name = models.CharField(max_length=128, unique=True) password = models.CharField(max_length=256) email = models.EmailField(unique=True) sex = models.CharField(max_length=32, choices=gender, default="男") c_time = models.DateTimeField(auto_now_add=True) has_confirmed = models.BooleanField(default=False) def __str__(self): return self.name class Meta: ordering = ["-c_time"] verbose_name = "使用者" verbose_name_plural = "使用者" class ConfirmString(models.Model): code = models.CharField(max_length=256) user = models.OneToOneField('User',on_delete=models.CASCADE,) c_time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.user.name + ":" + self.code class Meta: ordering = ["-c_time"] verbose_name = "確認碼" verbose_name_plural = "確認碼"
說明:
has_confirmed c_time
這裡有個問題可以討論一下:是否需要建立ConfirmString新表,可否都放在User表裡?我認為如果全都放在User中,不利於管理,查詢速度慢,建立新表有利於區分已確認和未確認的使用者。最終的選擇可以根據你的實際情況具體分析。
模型修改和建立完畢,需要執行migrate命令,一定不要忘了。
順便修改一下admin.py檔案,方便我們在後臺修改和觀察資料。
# login/admin.py from django.contrib import admin # Register your models here. from . import models admin.site.register(models.User) admin.site.register(models.ConfirmString)
四、修改檢視
首先,要修改我們的 register()
檢視的邏輯:
def register(request): if request.session.get('is_login', None): # 登入狀態不允許註冊。你可以修改這條原則! return redirect("/index/") if request.method == "POST": register_form = forms.RegisterForm(request.POST) message = "請檢查填寫的內容!" if register_form.is_valid():# 獲取資料 username = register_form.cleaned_data['username'] password1 = register_form.cleaned_data['password1'] password2 = register_form.cleaned_data['password2'] email = register_form.cleaned_data['email'] sex = register_form.cleaned_data['sex'] if password1 != password2:# 判斷兩次密碼是否相同 message = "兩次輸入的密碼不同!" return render(request, 'login/register.html', locals()) else: same_name_user = models.User.objects.filter(name=username) if same_name_user:# 使用者名稱唯一 message = '使用者已經存在,請重新選擇使用者名稱!' return render(request, 'login/register.html', locals()) same_email_user = models.User.objects.filter(email=email) if same_email_user:# 郵箱地址唯一 message = '該郵箱地址已被註冊,請使用別的郵箱!' return render(request, 'login/register.html', locals()) # 當一切都OK的情況下,建立新使用者 new_user = models.User() new_user.name = username new_user.password = hash_code(password1)# 使用加密密碼 new_user.email = email new_user.sex = sex new_user.save() code = make_confirm_string(new_user) send_email(email, code) message = '請前往註冊郵箱,進行郵件確認!' return render(request, 'login/confirm.html', locals())# 跳轉到等待郵件確認頁面。 register_form = forms.RegisterForm() return render(request, 'login/register.html', locals())
關鍵是多了下面兩行:
code = make_confirm_string(new_user) send_email(email, code)
make_confirm_string()
是建立確認碼物件的方法,程式碼如下:
def make_confirm_string(user): now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") code = hash_code(user.name, now) models.ConfirmString.objects.create(code=code, user=user,) return code
在檔案頂部要先匯入 datetime
模組。
make_confirm_string()
方法接收一個使用者物件作為引數。首先利用datetime模組生成一個當前時間的字串now,再呼叫我們前面編寫的 hash_code()
方法以使用者名稱為基礎,now為‘鹽’,生成一個獨一無二的雜湊值,再呼叫ConfirmString模型的create()方法,生成並儲存一個確認碼物件。最後返回這個雜湊值。
send_email(email, code)
方法接收兩個引數,分別是註冊的郵箱和前面生成的雜湊值,程式碼如下:
def send_email(email, code): from django.core.mail import EmailMultiAlternatives subject = '來自www.cnblogs.com的註冊確認郵件' text_content = '''感謝註冊www.cnblogs.com,專注於Python和Django技術的分享!\ 如果你看到這條訊息,說明你的郵箱伺服器不提供HTML連結功能,請聯絡管理員!''' html_content = ''' <p>感謝註冊<a href="http://{}/confirm/?code={}" target=blank>www.cnblogs.com</a>,\ 專注於Python和Django技術的分享!</p> <p>請點選站點連結完成註冊確認!</p> <p>此連結有效期為{}天!</p> '''.format('127.0.0.1:8000', code, settings.CONFIRM_DAYS) msg = EmailMultiAlternatives(subject, text_content, settings.EMAIL_HOST_USER, [email]) msg.attach_alternative(html_content, "text/html") msg.send()
首先我們需要匯入settings配置檔案 from django.conf import settings
。
郵件內容中的所有字串都可以根據你的實際情況進行修改。其中關鍵在於 <a href=''>
中連結地址的格式,我這裡使用了硬編碼的'127.0.0.1:8000',請酌情修改,url裡的引數名為 code
,它儲存了關鍵的註冊確認碼,最後的有效期天數為設定在settings中的 CONFIRM_DAYS
。所有的這些都是可以定製的!
下面是郵件相關的settings配置:
# 郵件配置 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.sina.com' EMAIL_PORT = 25 EMAIL_HOST_USER = '[email protected]' EMAIL_HOST_PASSWORD = 'xxxxxx' # 註冊有效期天數 CONFIRM_DAYS = 7
五、處理郵件確認請求
首先,在根目錄的 urls.py
中新增一條url:
url(r'^confirm/$', views.user_confirm),
其次,在 login/views.py
中新增一個 user_confirm
檢視。
def user_confirm(request): code = request.GET.get('code', None) message = '' try: confirm = models.ConfirmString.objects.get(code=code) except: message = '無效的確認請求!' return render(request, 'login/confirm.html', locals()) c_time = confirm.c_time now = datetime.datetime.now() if now > c_time + datetime.timedelta(settings.CONFIRM_DAYS): confirm.user.delete() message = '您的郵件已經過期!請重新註冊!' return render(request, 'login/confirm.html', locals()) else: confirm.user.has_confirmed = True confirm.user.save() confirm.delete() message = '感謝確認,請使用賬戶登入!' return render(request, 'login/confirm.html', locals())
說明:
- 通過
request.GET.get('code', None)
從請求的url地址中獲取確認碼; - 先去資料庫內查詢是否有對應的確認碼;
- 如果沒有,返回
confirm.html
頁面,並提示; - 如果有,獲取註冊的時間
c_time
,加上設定的過期天數,這裡是7天,然後與現在時間點進行對比; - 如果時間已經超期,刪除註冊的使用者,同時註冊碼也會一併刪除,然後返回
confirm.html
頁面,並提示; - 如果未超期,修改使用者的
has_confirmed
欄位為True,並儲存,表示通過確認了。然後刪除註冊碼,但不刪除使用者本身。最後返回confirm.html
頁面,並提示。
這裡需要一個 confirm.html
頁面,我們將它建立在 /login/templates/login/
下面:
{% extends 'base.html' %} {% block title %}註冊確認{% endblock %} {% block content %} <div class="row"> <h1 class="text-center">{{ message }}</h1> </div> <script> window.setTimeout("window.location='/login/'",2000); </script> {% endblock %}
頁面中通過JS程式碼,設定2秒後自動跳轉到登入頁面。
六、修改登入規則
既然未進行郵件確認的使用者不能登入,那麼我們就必須修改登入規則,如下所示:
def login(request): if request.session.get('is_login', None): return redirect("/index/") if request.method == "POST": login_form = forms.UserForm(request.POST) message = "請檢查填寫的內容!" if login_form.is_valid(): username = login_form.cleaned_data['username'] password = login_form.cleaned_data['password'] try: user = models.User.objects.get(name=username) if not user.has_confirmed: message = "該使用者還未通過郵件確認!" return render(request, 'login/login.html', locals()) if user.password == hash_code(password):# 雜湊值和資料庫內的值進行比對 request.session['is_login'] = True request.session['user_id'] = user.id request.session['user_name'] = user.name return redirect('/index/') else: message = "密碼不正確!" except: message = "使用者不存在!" return render(request, 'login/login.html', locals()) login_form = forms.UserForm() return render(request, 'login/login.html', locals())
關鍵是下面的部分:
if not user.has_confirmed: message = "該使用者還未通過郵件確認!" return render(request, 'login/login.html', locals())
七、功能展示
首先,通過admin後臺刪除原來所有的使用者。
進入註冊頁面,如下圖所示:
點選提交後,跳轉到提示資訊頁面,2秒後再跳轉到登入頁面。
進入admin後臺,檢視剛才建立的使用者,可以看到其處於未確認狀態,嘗試登入也不通過:
進入你的測試郵箱,檢視註冊郵件:
點選確認後再進來,ok了。