close

Python Django 學習紀錄:架設個人部落格(十) 

發揮信箱作用

上一回發送驗證碼至信箱進行驗證

接下來繼續拓展信箱的相關功能

一.、功能 

1.需求

  1. 減少垃圾用戶
  2. 保證帳戶安全
  3. 推送消息(通知) 

2.設計(則一)

  • 註冊的時候要求填寫信箱
  • 發送信箱鏈接
  • 直接使用信箱註冊 

二.、註冊發送驗證碼信件

1.註冊表單添加驗證碼

在user應用<forms.py>中RegForm表單添加驗證碼欄位:   

class RegForm(forms.Form):
    username = forms.CharField(label='用戶名', required=False,
                               max_length=20, min_length=3,
                               widget=forms.TextInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入3-20位用戶名'}))
    email = forms.EmailField(label='信箱', required=False,
                             widget=forms.EmailInput(
                                          attrs={'class':'form-control', 'placeholder':'請輸信箱'}))
    verification_code = forms.CharField(label='驗證碼',
                            max_length=20,required=False,
                            widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'點擊"發送驗證碼"發送到信箱'}))

    password = forms.CharField(label='密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入6-12位密碼'}))
    password_again = forms.CharField(label='再次輸入密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請再次輸入密碼'}))
    def clean_username(self):
        username = self.cleaned_data['username']
        if User.objects.filter(username=username).exists():
            raise forms.ValidationError('用戶名稱已存在')
        return username
    def clean_email(self):
        email = self.cleaned_data['email']
        if User.objects.filter(email=email).exists():
            raise forms.ValidationError('信箱已存在')
        return email
    def clean_password_again(self):
        password = self.cleaned_data['password']
        password_again = self.cleaned_data['password_again']
        if password != password_again: #比對密碼是否相同
            raise forms.ValidationError('兩次輸入密碼不一致')
        return password

2.註冊頁面添加發送驗證碼按鈕與js處理

在user應用templates/user目錄的<register.html>添加驗證碼按鈕:   

{% block content %}
    <div class="container">
        <div class="row">
            <!-- 響應式 -->
            <div class="col-xs-12 col-md-4 col-md-offset-4">
                {% if not user.is_authenticated %}
                    <!-- 添加面板 -->
                    <div class="panel panel-default">
                        <!-- 面板標題 -->
                        <div class="panel-heading">註冊</div>
                        <div class="panel-body">
                            <!-- 表單 -->
                            <form action="" method="POST">
                                {% csrf_token %}
                                <!-- 進行表單細部調整 -->
                                {% for field in reg_form %}
                                    <!-- 移除冒號 -->
                                    <label for="{{ field.id_for_label }}">{{field.label}}</label>
                                    {{ field }}
                                    <!-- 錯誤訊息紅字顯示 -->
                                    <p class="text-danger">{{ field.errors.as_text }}</p>
                                {% endfor %}
                                <span class="text-danger">{{ reg_form.non_field_errors }}</span>
                                <div class="clearfix"></div>
                                <button id="send_code" class="btn btn-primary">發送驗證碼</button>
                                <input type="submit" value="註冊" class="btn btn-primary pull-right">
                            </form>
                        </div>
                    </div>
                {% else %}
                    <span>已登錄,跳轉到首頁...</span>
                    <script type="text/javascript">
                        window.location.href =  '/';
                    </script>
                {% endif %}
            </div>
        </div>
    </div>


{% endblock %}

添加js處理(可以從bind_email直接引用):   

{% block script_extends %}
    <script type="text/javascript">
        $('#send_code').click(function(){
            var email = $('#id_email').val();
            if(email==''){
                $('#tip').text('* 信箱不能為空')
                return false;
            }
            //發送驗證碼
            $.ajax({
                url:"{% url 'send_verification_code' %}",
                type:"GET",
                data:{
                    'email': email
                },
                cache: false,
                success: function(data){
                    if(data['status']=='ERROR'){
                        alert(data['status']);
                    }
                }
            });
            //把按鈕變灰,進行倒數計時
            $(this).addClass('disabled'); //按鈕變灰
            $(this).attr('disabled', true); //按鈕變灰
            var time = 30;
            $(this).text(time + 's');
            var interval = setInterval(() => { //setInterval 週期性執行代碼
                if(time <= 0){
                    clearInterval(interval);  //取消已設置的動作
                    $(this).removeClass('disabled'); //按鈕復原
                    $(this).attr('disabled', false); //按鈕復原
                    $(this).text('發送驗證碼');    //按鈕文字
                    return false
                }
                time --;  //倒數
                $(this).text(time + 's');
            }, 1000);
        });
    </script>
{% endblock %}

3.驗證碼方法通用化

在user應用<views.py>中

原先使用session['bind_email_code']來記錄驗證碼

但由於註冊頁面也需要紀錄

所以這裡session紀錄的資料改由js傳入:   

def send_verification_code(request):
    email = request.GET.get('email','')
    send_for = request.GET.get('send_for','') #js傳入code資訊
    data = {}
    if email != '':
        #生成驗證碼
        code = ''.join(random.sample(string.ascii_letters + string.digits, 4)) #添加(四個字元 (字母+數字))至code
        now = int(time.time())
        send_code_time = request.session.get('send_code_time', 0)
        if now - send_code_time <30:
            data['status'] = ERROR
        else:
            request.session[send_for] = code
            request.session['send_code_time'] = now
        #發送信件
        send_mail(
            '綁定信箱', #標題
            '驗證碼: %s' % code, #內容
            '',  #發送來源
            [email],  #發送目的(list)
            fail_silently=False, #是否忽略錯誤
            )
        data['status'] = 'SUCCESS'
    else:
        data['status'] = 'ERROR'
    return JsonResponse(data)

將<bind_email.html>的js代碼添加code資訊:   

data:{
    'email': email,
    'send_for': 'bind_email_code'
},

將<register.html>的js代碼添加code資訊:   

data:{
    'email': email,
    'send_for': 'register_code'
},

4.註冊表單添加驗證碼驗證

在user應用<forms.py>的RegForm中

添加關於驗證碼的驗證

與綁定信箱的代碼大同小異:   

    def __init__(self, *args, **kwargs):
        if 'request' in kwargs:
            self.request = kwargs.pop('request')
        super(RegForm, self).__init__(*args, **kwargs)
    def clean(self):
        #判斷驗證碼
        code = self.request.session.get('register_code','')
        verification_code = self.cleaned_data.get('verification_code','')
        if not (code != '' and code == verification_code):
            raise forms.ValidationError('驗證碼不正確')
        return self.cleaned_data 

在user應用<views.py>的register添加表單請求的request

在儲存完資料時清除session避免產生錯誤:   

def register(request):

    if request.method == 'POST':
        reg_form = RegForm(request.POST, request=request)
        if reg_form.is_valid():
            username = reg_form.cleaned_data['username']
            email = reg_form.cleaned_data['email']
            password = reg_form.cleaned_data['password']
            #創建用戶
            user = User() #創建物件
            user.username = username
            user.email = email
            user.set_password(password) #儲存密文密碼
            user.save() #將用戶資料儲存至資料庫
             #清除session
            del request.session['register_code']
            #登錄用戶
            user = auth.authenticate(username=username, password=password)
            auth.login(request, user)
            return redirect(request.GET.get('from',reverse('home'))) 
    else:
        reg_form = RegForm()
    context = {}
    context['reg_form'] = reg_form
    return render(request, "user/register.html", context)

三.、修改登錄方式

使用戶名與信箱皆可進行登錄

登錄表單修改  

在user應用<forms.py>中LoginForm表單修改username欄位

並判斷不符合user,則驗證email是否存在

email存在則取出該email的username進行驗證

以下為代碼:   

class LoginForm(forms.Form):
    #label修改標籤文字,required不寫則預設接受空白內容,attrs添加bootstrap屬性,placeholder設置預設文字
    username_or_email = forms.CharField(label='用戶名或信箱', required=False,
                               widget=forms.TextInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入用戶名或信箱'}))
     #widget定義標籤內容,這裡將密碼改為密文,attrs添加bootstrap屬性,placeholder設置預設文字
    password = forms.CharField(label='密碼',
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入密碼'}))

    def clean(self): #在forms中進行驗證
        username_or_email = self.cleaned_data['username_or_email']
        password = self.cleaned_data['password']

        user = auth.authenticate(username=username_or_email, password=password)
        if user is None: #若user驗證失敗
            if User.objects.filter(email=username_or_email).exists(): #檢查信箱是否存在
                username = User.objects.get(email=username_or_email).username #取得該信箱的username
                user = auth.authenticate(username=username, password=password) #驗證username
                if not user is None:
                    self.cleaned_data['user'] = user #返回登錄資料
                    return self.cleaned_data
            raise forms.ValidationError('用戶名或密碼不正確') #拋出錯誤訊息
        else: #若驗證成功
            self.cleaned_data['user'] = user #返回登錄資料
        return self.cleaned_data

四.、修改密碼

1.已登錄,修改密碼

2.未登錄,忘記密碼→發送驗證碼至信箱  

1.已登錄,修改密碼 

(1)創建修改密碼表單

在user應用<forms.py>建立修改密碼表單:   

class ChangePasswordForm(forms.Form):
    old_password = forms.CharField(label='舊的密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入舊的密碼'}))
    new_password = forms.CharField(label='新的密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入新的密碼'}))
    new_password_again = forms.CharField(label='再次輸入新的密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請再次輸入新的密碼'}))
    def __init__(self, *args, **kwargs):
        if 'user' in kwargs:
            self.user = kwargs.pop('user')
        super(ChangePasswordForm, self).__init__(*args, **kwargs)

    def clean(self):
        #驗證新的密碼是否一致
        new_password = self.cleaned_data.get('new_password','')
        new_password_again = self.cleaned_data.get('new_password_again','')
        if new_password != new_password_again and new_password or new_password_again =='':
            raise ValidationError('兩次輸入的密碼不一致')
        return self.cleaned_data

    def clean_old_password(self):
        #驗證舊的密碼是否正確
        old_password = self.cleaned_data.get('old_password','')
        if not self.user.check_password(old_password):
            raise ValidationError('舊的密碼錯誤')
        return self.cleaned_data

(2)修改密碼處理方法

在user應用<views.py>進行修改密碼處理:   

from .forms import ChangePasswordForm
def change_password(request):
    if request.method == 'POST':
        form = ChangePasswordForm(request.POST, user=request.user)
        if form.is_valid():
            user = request.user
            old_password = form.cleaned_data['old_password']
            new_password = form.cleaned_data['new_password']
            user.set_password(new_password)
            user.save()
            auth.logout(request)
            return redirect(request.GET.get('from',reverse('home')))
    else:
        form = ChangePasswordForm()
    context = {}
    context['form'] = form
    context['page_title'] = '修改密碼'
    context['form_title'] = '修改密碼'
    context['submit_text'] = '修改'
    context['return_back_url'] = request.GET.get('from',reverse('home'))

    return render(request, 'form.html', context)

(3)urls配置

在user應用<urls.py>進行url配置:   

path('change_password/', views.change_password, name='change_password'),

(4)在前端添加方法

在<base.html>導航欄添加修改密碼項目:   

<ul class="dropdown-menu">
    <li><a href="{% url 'user_info' %}">個人資料</a></li>
    <li><a href="{% url 'change_password' %}">修改密碼</a></li>

在<user.info.html>添加修改密碼超連結:   

<li><a href="{% url 'change_password' %">修改密碼</a></li>

 

2.未登錄,忘記密碼→發送驗證碼至信箱

(1)創建修改密碼表單

在user應用<forms.py>建立忘記密碼表單:   

class ForgotPasswordForm(forms.Form):
    email = forms.EmailField(label='信箱',
        widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'請輸入綁定過的信箱'}))

    username = forms.CharField(label='用戶名', required=False,
                               max_length=20, min_length=3,
                               widget=forms.TextInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入用戶名'}))

    verification_code = forms.CharField(label='驗證碼',
        max_length=20,required=False,
        widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'點擊"發送驗證碼"發送到信箱'}))

    new_password = forms.CharField(label='新的密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入新的密碼'}))

    def __init__(self, *args, **kwargs):
        if 'request' in kwargs:
            self.request = kwargs.pop('request')
        super(ForgotPasswordForm, self).__init__(*args, **kwargs)

        #判斷信箱是否已被綁定、用戶是否綁定該信箱
    def clean(self):
        username = self.cleaned_data['username']
        email = self.cleaned_data['email']
        if not User.objects.filter(email=email).exists():
            raise forms.ValidationError('信箱不存在')
        if not User.objects.filter(username=username).exists():
            raise forms.ValidationError('用戶名稱不存在')
        if username != User.objects.get(email=email).username:
            raise forms.ValidationError('用戶名或信箱錯誤')
        else:
            return self.cleaned_data

    def clean_verification_code(self):
        verification_code = self.cleaned_data.get('verification_code','').strip()
        if verification_code == '':
            raise forms.ValidationError('驗證碼不能為空')
        #判斷驗證碼
        code = self.request.session.get('forgot_password_code','')
        verification_code = self.cleaned_data.get('verification_code','')
        if not (code != '' and code == verification_code):
            raise forms.ValidationError('驗證碼不正確')

(2)忘記密碼處理方法

在user應用<forms.py>建立忘記密碼表單:   

def forgot_password(request):
    if request.method == 'POST':
        form = ForgotPasswordForm(request.POST, request=request)
        if form.is_valid():
            email = form.cleaned_data['email']
            new_password = form.cleaned_data['new_password']
            user =User.objects.get(email=email)
            user.set_password(new_password)
            user.save()
            #清除session
            del request.session['forgot_password_code']
            return redirect(request.GET.get('from',reverse('home')))
    else:
        form = ForgotPasswordForm()
    context = {}
    context['form'] = form
    context['page_title'] = '重置密碼'
    context['form_title'] = '重置密碼'
    context['submit_text'] = '重置'
    context['return_back_url'] = request.GET.get('from',reverse('home'))

    return render(request, 'user/forgot_password.html', context)

(3)urls配置

在user應用<urls.py>進行url配置:   

    path('forgot_password/', views.forgot_password, name='forgot_password'),

(4)忘記密碼頁面與js處理

在user應用templates/user目錄 建立<forgot_password.html>頁面

繼承<form.html>頁面與進行js處理:   

{% extends 'form.html' %}

{% block other_buttons %}
    <button id="send_code" class="btn btn-primary">發送驗證碼</button>
{% endblock %}

{% block script_extends %}
    <script type="text/javascript">
        $('#send_code').click(function(){
            var email = $('#id_email').val();
            if(email==''){
                $('#tip').text('* 信箱不能為空')
                return false;
            }
            var username = $('#id_username').val();
            if(username==''){
                $('#tip').text('* 用戶名不能為空')
                return false;
            }

            //發送驗證碼
            $.ajax({
                url:"{% url 'send_verification_code' %}",
                type:"GET",
                data:{
                    'email': email,
                    'send_for': 'forgot_password_code'
                },
                cache: false,
                success: function(data){
                    if(data['status']=='ERROR'){
                        alert(data['status']);
                    }
                }
            });
            //把按鈕變灰,進行倒數計時
            $(this).addClass('disabled'); //按鈕變灰
            $(this).attr('disabled', true); //按鈕變灰
            var time = 30;
            $(this).text(time + 's');
            var interval = setInterval(() => { //setInterval 週期性執行代碼
                if(time <= 0){
                    clearInterval(interval);  //取消已設置的動作
                    $(this).removeClass('disabled'); //按鈕復原
                    $(this).attr('disabled', false); //按鈕復原
                    $(this).text('發送驗證碼');    //按鈕文字
                    return false
                }
                time --;  //倒數
                $(this).text(time + 's');
            }, 1000);
        });
    </script>
{% endblock %}
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 ivankao 的頭像
    ivankao

    IvanKao的部落格

    ivankao 發表在 痞客邦 留言(0) 人氣()