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 = 'xxx@sina.com'
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技術的分享!',
        '1129719492@qq.com',
        ['1129719492@qq.com'],
    )

對於send_mail方法,第一個參數是郵件主題subject;第二個參數是郵件具體內容;第三個參數是郵件發送方,需要和你settings中的一致;第四個參數是接受方的郵件地址列表。請按你自己實際情況修改發送方和接收方的郵箱地址。

另外,由於我們是單獨運行send_mail.py文件,所以無法使用Django環境,需要通過os模塊對環境變量進行設置,也就是:

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

運行send_mail.py文件,注意不是運行Django服務器。然后到你的目的地郵箱查看郵件是否收到。

二、發送HTML格式的郵件

通常情況下,我們發送的郵件內容都是純文本格式。但是很多情況下,我們需要發送帶有HTML格式的內容,比如說超級鏈接。一般情況下,為了安全考慮,很多郵件服務提供商都會禁止使用HTML內容,幸運的是對於以httphttps開頭的鏈接還是可以點擊的。

下面是發送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的測試郵件', '1129719492@qq.com', '1129719492@qq.com'
    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 = "確認碼" 

說明:

  • User模型新增了has_confirmed字段,這是個布爾值,默認為False,也就是未進行郵件注冊;
  • ConfirmString模型保存了用戶和注冊碼之間的關系,一對一的形式;
  • code字段是哈希后的注冊碼;
  • user是關聯的一對一用戶;
  • 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 = 'xxx@sina.com'
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了。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM