独立完成一个在线网站记录之忘记密码的处理

用户忘记密码是一个比较复杂的功能,首先点击忘记密码需要跳转到一个界面,接下来就是提示已经发送了邮件,在邮件中带一个链接,直接跳转到设置新密码的界面,设置的密码符合条件之后,然后跳转到登录界面以使用户成功登录。

目前已经将忘记密码的前端界面添加进了templates中,接下来添加忘记密码的视图函数。视图函数中首先对用户的登录名进行判断是否在数据库中存在,使用filter()如果没存在在数据库中,就返回一个错误。

遇到一个坑,仿照注册的页面去做忘记密码的页面,在修改url,添加静态文件,添加一个忘记密码的表单,添加对应的视图函数之后,进入忘记密码的页面后验证码还是没有显示出来。
错误就出在了在使用render函数的时候没有将新添加的忘记密码的表单实例化之后传入前端html界面,原来的时候是这样:

1
2
3
4
class ForgetPasswordView(View):
def get(self, request):
forgetpwd_form = ForgetPwdForm()
return render(request, "forgetpwd.html")

我先实例化了一个表单,接下来没有传到前端界面里,这样前端的html文件接收不到表单信息。

接下来做出如下修改:

1
2
3
4
class ForgetPasswordView(View):
def get(self, request):
forgetpwd_form = ForgetPwdForm()
return render(request, "forgetpwd.html", {"forgetpwd_form": forgetpwd_form })

这样把表单信息传进去了前端的界面,在跳转到忘记密码界面就能看到验证码图片了。

接下来继续写逻辑,首先对输入的邮箱进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class RegisterView(View):
def get(self, request):
register_form = RegisterForm()
return render(request, "register.html", {'register_form':register_form})
def post(self, request):
register_form = RegisterForm(request.POST)
if register_form.is_valid():
user_name = request.POST.get("email", "")
if UserProfile.objects.filter(email = user_name):
return render(request, "register.html", {"register_form": register_form },{"msg": "用户已经存在"})
pass_word = request.POST.get("password", "")
user_profile = UserProfile()
user_profile.username = user_name
user_profile.email = user_name
user_profile.is_active = False
user_profile.password = make_password(pass_word)
user_profile.save()
send_register_email(user_name, "register")
return render(request, "login.html")
else:
return render(request, "register.html", {"register_form": register_form })

下面是发送邮件的逻辑,这里用到了Django自带的发送邮件的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 这就是Django自带的函数,用来发送邮件
from django.core.mail import send_mail
# 这是python自带的函数,用于生成随机数
from random import Random
# 从user应用中import进入验证的模型
from users.models import EmailVerifyRecord
# 在setting中import进入默认的发送邮箱
from mymooc.settings import DEFAULT_FROM_EMAIL
def send_email(email, send_type):
email_record = EmailVerifyRecord()
code = general_random_str(16)
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save()
if send_type == "register":
email_title = "在线学习网站的注册激活链接"
email_body = "请点击下面的链接激活你的账户:http://127.0.0.1:8000/active/{0}".format(code)
elif send_type == "forget_password":
email_title = "在线学习网站密码重置链接"
email_body = "请点击下面的链接重置密码:http://127.0.0.1:8000/resetpwd/{0}".format(code)
send_status = send_mail(email_title, email_body, DEFAULT_FROM_EMAIL, [email])
if send_status:
pass

在发送邮件的时候用到了一个随机生成的16位的验证码,接下来把生成随机验证码的代码也贴上:

1
2
3
4
5
6
7
8
9
10
from random import Random
def general_random_str(randomlength):
str = ""
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
length = len(chars) - 1
random = Random()
for i in range(randomlength):
str+=chars[random.randint(0, length)]
return str

ok,现在点击忘记密码,可以正常的发送邮件,还能通过邮件跳转到重置密码的页面,现在的任务就是继续添加在提交请求之后的视图函数。

现在有一个问题就是每次提交重置密码的表单之后汇报错,说post的参数中多了一个active_code,这是为什么呢?怎么解决呢?

按照教程上说的添加了ModifyPasswordView之后,我们遇到了错误

1
2
3
4
5
6
Request Method: POST
Request URL: http://127.0.0.1:8000/modifypwd/
Django Version: 1.9
Exception Type: DoesNotExist
Exception Value:
UserProfile matching query does not exist.

这里出现了UserProfile不匹配的问题。该怎么解决呢?

一步步地调试。

找到了问题,是这样,我们在重置密码的时候,email是跟着POST请求传进来的我们要在前端页面的输入中获取这个,原来的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#在view文件中
if resetpwd_form.is_valid():
password1 = request.POST.get("password1","")
password2 = request.POST.get("password2", "")
email = request.POST.get("email", "")
if password1 == password2:
user = UserProfile()
user = UserProfile.objects.get(email=email)
user.password = make_password(password1)
user.save()
return render(request, "login.html")
else:
return render(request, "password_reset.html", {"resetpwd_form": resetpwd_form}, {"msg": "两次密码不一致"})
# 重置密码的前端页面中
<input type="hidden" value="{{ email }}">

这里的makepassword()函数需要说明下,因为在Django后台中password是已密文的形式存储的,所以我们要把从前端表单中传过来的信息存储到数据库中的时候要对密码进行加密。

1
from django.contrib.auth.hashers import make_password

makepassword()的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def make_password(password, salt=None, hasher='default'):
"""
Turn a plain-text password into a hash for database storage
Same as encode() but generates a new random salt.
If password is None then a concatenation of
UNUSABLE_PASSWORD_PREFIX and a random string will be returned
which disallows logins. Additional random string reduces chances
of gaining access to staff or superuser accounts.
See ticket #20079 for more info.
"""
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
hasher = get_hasher(hasher)
if not salt:
salt = hasher.salt()
return hasher.encode(password, salt)

这里对密码进行加密的方法我会自己专门写一篇文章,分析下Django中怎样对前端表单中提交的数据进行加密存储到数据库,怎样核对前端传来的明文和数据库中的密文是否对应。

我们要在前端的POST请求中获得email的值,并且根据email的值去数据库中寻找对应的用户的信息。

在前端页面中我们将email的input设置成hidden属性,并且将这个值获取到,但是没有name属性,这样的话,视图函数根据name = email去寻找值得时候,就会发现找不到,进而没有办法确定是哪一个用户要修改密码,所以会报错。

解决方法很简单,就是给这个input添加上name属性,这样后端的view函数就能顺利的取到email的值,这样就解决了问题。

修改如下:

1
<input type="hidden" name="email" value="{{ email }}">

目前还存在的问题就是在注册界面和找回密码界面没有办法在输入错误的时候用红框显示哪一个输入项出错误了。也没能将用户的输入的信息回填回去。

这个问题就是后端没有成功的传消息给前端的文件,也就是对render()函数理解不对,

1
{% for key,error in forgetpwd_form.errors.items %}{{ error }}{% endfor %}

还出现了一个很奇怪的问题,具体看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ForgetPasswordView(View):
def get(self, request):
forgetpwd_form = ForgetPwdForm()
return render(request, "forgetpwd.html", {"forgetpwd_form": forgetpwd_form })
def post(self, request):
forgetpwd_form = ForgetPwdForm(request.POST)
if forgetpwd_form.is_valid():
user_name = request.POST.get("email", "")
if UserProfile.objects.filter(email = user_name):
send_email(user_name, "forget_password")
return render(request, "send_successfully.html", {"forgetpwd_form": forgetpwd_form })
else:
return render(request, "forgetpwd.html", {"forgetpwd_form": forgetpwd_form, "msg": "该邮箱未注册账户"})
else:
return render(request, "forgetpwd.html", {"forgetpwd_form": forgetpwd_form })

上面的代码是忘记密码的视图函数,也就是后台逻辑,第一个else语句,也就是这句:

1
2
else:
return render(request, "forgetpwd.html", {"forgetpwd_form": forgetpwd_form, "msg": "该邮箱未注册账户"})

处理的逻辑是当用户输入的邮箱合法,但是没有用来注册过账户,所以会停留在忘记密码的页面,并且给出错误提示,上面这样写是没错误的,但是如果改成这样:

1
return render(request, "forgetpwd.html", {"forgetpwd_form": forgetpwd_form}, { "msg": "该邮箱未注册账户" })

也就是在render()函数中传入两个参数,就会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
AttributeError at /forget/
'dict' object has no attribute 'push'
Request Method: POST
Request URL: http://localhost:8000/forget/
Django Version: 1.9
Exception Type: AttributeError
Exception Value:
'dict' object has no attribute 'push'
Exception Location: /home/peter/mymooc/mymoocvenv/lib/python3.5/site-packages/django/template/engine.py in render_to_string, line 243
Python Executable: /home/peter/mymooc/mymoocvenv/bin/python
Python Version: 3.5.2
Python Path:
['/home/peter/mymooc/apps',
'/home/peter/mymooc',
'/root/.venvburrito/lib/python2.7/site-packages',
'/root/.venvburrito/lib/python2.7/site-packages/setuptools-28.8.0-py2.7.egg',
'/root/.venvburrito/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg',
'/home/peter/mymooc/mymoocvenv/lib/python35.zip',
'/home/peter/mymooc/mymoocvenv/lib/python3.5',
'/home/peter/mymooc/mymoocvenv/lib/python3.5/plat-x86_64-linux-gnu',
'/home/peter/mymooc/mymoocvenv/lib/python3.5/lib-dynload',
'/usr/lib/python3.5',
'/usr/lib/python3.5/plat-x86_64-linux-gnu',
'/home/peter/mymooc/mymoocvenv/lib/python3.5/site-packages']
Server time: 星期四, 27 四月 2017 10:58:52 +0800

这里涉及到render函数的参数都有什么,以及render函数具体做了什么。我会专门写篇文章来讨论render函数。

现在存在一个问题就是用户可以无限制的发送找回密码的验证邮件,这就是一个问题。值得好好地思考下。需要设置验证邮件的过期时间,这个自己上网搜索。

算是留一个问题自己探索,未完待续哦。