Python Django 學習紀錄:架設個人部落格(十)
發揮信箱作用
上一回發送驗證碼至信箱進行驗證
接下來繼續拓展信箱的相關功能
一.、功能
1.需求
- 減少垃圾用戶
- 保證帳戶安全
- 推送消息(通知)
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 %}