关于本站
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'),
最终效果如下: