关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
前些天有网友找我问密码,他忘记了,问我有没有找回密码的功能。
好吧,还没有...我让他提供一个新的密码(知道哪个邮箱)先给他改一下。进入服务器后台,找到Django网站目录。依次输入下面的代码($表示是Linux命令行,>>>表示Python命令行):
$ python manage.py shell
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(email = 'xxxxx@xx.com'
>>> user.set_password('xxxxxx')
>>> user.save()通过这句话代码,就给他重置了密码。但总不能每次都找上我,然后进入后台重置一下密码吧。
好吧,是时候写找回密码的功能了。
首先,分析一下这个功能怎么构架。初步想了一下,有3种方式:
1、发送密码到注册用的邮箱;
2、发送一个重置密码的链接到邮箱;
3、发送一个验证码到邮箱,然后用户填写新密码可以直接修改。
由于Django的密码是被加密和加盐的。直接得到明文的密码比较困难。第2种和第3种方案其实没多大区别。由于第3种开发步骤和使用环节比较少。所以采用第3种方案。
接着,再进一步想一个详细的方案。
发送一个验证码到注册使用的邮箱是为了确认用户的身份。用户就可以用这个验证码去修改密码。那么就需要一个地方存储一下这个验证码,以便后台确认修改密码的时候,检查验证码是否正确。
保存验证码有两种方式,一种是sessions,一种是保存到数据库中。sessions不是很安全,验证码要求的保密程度比较高。所以还是创建一个模型和用户表关联,用于存储生成的验证码。
大致流程是:
1、在重置密码的页面,用户填写了邮箱和新密码之后。
2、点击“获取验证码”的按钮,就发送验证码到填写的邮箱中。
3、用户收到验证码之后,再复制填回重置密码的页面中。确定提交即可。
看看具体实现的代码。注意:我网站的用户认证系统是Django自身的用户。
创建应用user_ex(这个我不用python manage.py startapp user_ex这种方式创建,因为前面开发的时候有这个文件夹。里面的文档结构都是按照app的结构来处理的。所以我只需要创建models.py文件和migrations文件夹即可)
最后,不要忘记在settings.py添加user_ex应用和更新数据库。
到了上代码的时候了,先看看models.py
#coding:utf-8 from django.db import models from django.contrib.auth.models import User class User_ex(models.Model): """User models ex""" user = models.ForeignKey(User) #和User关联的外键 valid_code = models.CharField(max_length = 24) #验证码 valid_time = models.DateTimeField(auto_now = True) #验证码有效时间 def __unicode__(self): return u'%s' % (self.valid_code)
为了方便验证数据,我把重置密码的页面做成一个form表单,对应django也写了一个form表单模型:
#coding:utf-8
from django import forms
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
from user_ex.models import User_ex
from django.utils import timezone
class ForgetPwdForm(forms.Form):
"""Forget the password and try to find it"""
email = forms.EmailField(label=u'用户邮箱',
widget=forms.EmailInput(attrs={'class':'form-control', 'id':'email','placeholder':u'请输入您注册时用的邮箱'}))
pwd_1 = forms.CharField(label=u'新的密码', max_length=36,
widget=forms.PasswordInput(attrs={'class':'form-control', 'id':'pwd_1','placeholder':u'请输入6-36位的密码'}))
pwd_2 = forms.CharField(label=u'再输一遍', max_length=36,
widget=forms.PasswordInput(attrs={'class':'form-control', 'id':'pwd_2','placeholder':u'重复新的密码确保正确'}))
check_code = forms.CharField(label=u' 验证码',
widget=forms.TextInput(attrs={'class':'form-control', 'id':'check_code','placeholder':u'输入验证码'}))
#验证邮箱是否存在
def clean_email(self):
email = self.cleaned_data.get('email')
users = User.objects.filter(email = email)
if users.count() == 0:
raise ValidationError(u'该邮箱没有被注册,请检查')
return email
#验证两个新密码是否一致
def clean_pwd_2(self):
pwd_1 = self.cleaned_data.get('pwd_1')
pwd_2 = self.cleaned_data.get('pwd_2')
if pwd_1 != pwd_2:
raise ValidationError(u'两次输入的密码不一致,再输入一次吧')
return pwd_2
#验证验证码是否正确
def clean_check_code(self):
try:
#获取对应的用户
email = self.cleaned_data.get('email')
check_code = self.cleaned_data.get('check_code')
user = User.objects.get(email = email)
#获取对应的用户拓展信息,验证验证码
user_ex = User_ex.objects.filter(user = user)
if user_ex.count > 0:
user_ex = user_ex[0]
else:
raise ValidationError(u'未获取验证码')
if user_ex.valid_code != check_code:
raise ValidationError(u'验证码不正确')
#验证有效期
now = timezone.now() #用这个回去当前时间
create = user_ex.valid_time
#两个datetime相减,得到datetime.timedelta类型
td = now - create
if td.seconds/60 >= 10:
raise ValidationError(u'验证码失效')
return check_code
except Exception as e:
raise ValidationError(u'验证码不正确或失效')该表单可以验证邮箱、验证码和验证码有效期。有效期是为了安全期间而设定的。
模型和表单模型都有了,进一步写一下处理方法。这里需要两个处理方法:一是表单的处理方法,一个是获取验证码的处理方法。
#coding:utf-8
from django.http import HttpResponse
from django.shortcuts import render_to_response, render
from django.contrib.auth import logout,authenticate,login
from django.contrib.auth.models import User
from django.core.mail import EmailMultiAlternatives
import json, time, datetime, random, string
from .forms import ForgetPwdForm
from user_ex.models import User_ex
from django.utils import timezone
def password_lost(request):
"""forgot password deals"""
data = {}
data['form_title'] = u'重置密码'
data['submit_name'] = u' 确定 '
if request.method == 'POST':
#表单提交
form = ForgetPwdForm(request.POST)
#验证是否合法
if form.is_valid():
#修改数据库
email = form.cleaned_data['email']
pwd = form.cleaned_data['pwd_2']
user = User.objects.get(email = email)
user.set_password(pwd)
user.save()
#删除验证码
ex = User_ex.objects.filter(user=user)
if ex.count() > 0:
ex.delete()
#重新登录
user = authenticate(username=user.username, password=pwd)
if user is not None:
login(request, user)
#页面提示
data['goto_url'] = reverse('user_info')
data['goto_time'] = 3000
data['goto_page'] = True
data['message'] = u'重置密码成功,请牢记新密码'
return render_to_response('message.html',data)
else:
#正常加载
form = ForgetPwdForm()
data['form'] = form
return render(request, 'user/pwd_forget.html', data)
def get_email_code(request):
"""get email code"""
email = request.GET.get('email', '')
code = ''.join(random.sample(string.digits + string.letters, 6))
data = {}
data['success'] = False
data['message'] = ''
try:
#检查邮箱
users = User.objects.filter(email = email)
if len(users)==0:
data['success'] = False
data['message'] = u'此邮箱未注册'
raise Exception, data['message']
user = users[0]
#检查短时间内是否有生成过验证码
user_ex = User_ex.objects.filter(user = user)
if len(user_ex)>0:
user_ex = user_ex[0]
#两个datetime相减,得到datetime.timedelta类型
create_time = user_ex.valid_time
td = timezone.now() - create_time
if td.seconds < 60:
data['message'] = u'1分钟内发送过一次验证码'
raise Exception, data['message']
else:
#没有则新建
user_ex = User_ex(user = user)
#写入数据库
user_ex.valid_code = code
user_ex.valid_time = timezone.now()
user_ex.save()
#发送邮件
subject=u'[yshblog.com]激活您的帐号'
message=u"""
<h2>杨仕航的博客(<a href='http://yshblog.com/' target=_blank>yshblog.com</a>)<h2><br />
<p>重置密码的验证码(有效期10分钟):%s</p>
<p><br/>(请保管好您的验证码)</p>
""" % code
send_to=[email]
fail_silently=True #发送异常不报错
msg=EmailMultiAlternatives(subject=subject,body=message,to=send_to)
msg.attach_alternative(message, "text/html")
msg.send(fail_silently)
data['success'] = True
data['message'] = 'OK'
except Exception as e:
pass
finally:
return HttpResponse(json.dumps(data), content_type="application/json")其中message.html模版是显示消息的,可以参考我的网站搭建(第13天) 用户认证:前言博文的末尾。
这里的获取验证码处理方法返回的是json数据。因为前端页面打算放置一个按钮,用ajax方式提交请求。
前端页面稍微复杂一点,需要计时。获取验证码之后,倒计时1分钟。避免获取频率过高。
我写的这个前端页面是继承了一个基础页面。基础页面已经引用了jQuery库。
{% extends "base.html" %}
{% block title %}杨仕航的博客{% endblock %}
{% block content %}
{#通用表单页面#}
<div class="row">
<div class="col-md-6 col-md-offset-3" >
<div id="panel_form" class="panel panel-default">
<div class="panel-body">
<h3 class="form_title">{{form_title}}</h3>
<form class="main_form" method='post'>
{%csrf_token%}
{% for field in form %}
{# 区分是否是hidden字段 #}
{% if field.is_hidden %}
{{ field }}
{% else %}
<div class="input-group">
<label class="input-group-addon" for="{{ field.id_for_label }}">
{{ field.label }}
</label>
{{ field }}
</div>
{# 错误提示信息 #}
<p class="text-danger text-right">
{{ field.errors.as_text }}
</p>
{%endif%}
{%endfor%}
<div class="text-right form_btn">
<a class="btn btn-primary" id="btn_code">获取验证码</a>
<input class="btn btn-primary" id="btn_submit" type="submit" value="{{submit_name}}"/>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_footer %}
{#获取验证码#}
<script type="text/javascript">
function time_run(time_check){
time_check -= 1;
$("#btn_code").text("("+time_check+")重新获取");
if(time_check>0){
//setTimeout('time_run('+time_check+')', 1000);
setTimeout(function(){time_run(time_check)}, 1000);
}else{
$("#btn_code").text("获取验证码");
$("#btn_code").removeClass("disabled");
}
}
$(document).ready(function() {
$("#btn_code").click(function(){
//获取填写的邮箱,并检查
var email_tip = $(".text-danger")[0];
var email = document.getElementById("email").value;
email_tip.innerText = '';
var myreg = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;
if(!myreg.test(email)){
email_tip.innerText = '* 请正确地填写邮箱'
return false;
}
//发送验证码
$.ajax({
type:"GET",
url:"{% url 'get_email_code' %}?email=" + email,
cache:false,
dataType:'json',
success:function(result){
if(result['success']){
//提示
var code_tip = $(".text-danger")[3];
code_tip.innerText = '已经发送验证码到您填写的邮箱中'
//禁用按钮
$("#btn_code").addClass("disabled");
//倒计时
time_run(60);
}else{
alert(result['message']);
}
},
error:function(XMLHttpRequest, textStatus, errorThrown){
alert("获取出错,稍后再试一下吧。")
}
});
});
});
</script>
<style type="text/css">
.form_title{
margin-bottom: 1em;
padding-bottom: 0.5em;
border-bottom: 1px #ccc solid;
}
.main_form div, .form_btn .btn{
margin-top:1em;
}
#panel_form{
margin-top: 2em;
margin-bottom: 3em;
}
</style>
{% endblock %}倒计时部分,你可以看成是一个递归。每次调用都把变量减去1,直到控制变量为0时才跳出递归。
最后,再加上url路由设置,和登录页面加上对应的链接即可。
url(r'password_lost', 'user_ex.views.password_lost', name='password_lost'), url(r'get_email_code', 'user_ex.views.get_email_code', name='get_email_code'),
最终效果如下:
