Merge branch 'master' into virusdefender-dev

* master:
  修改错误提示措辞
  精简重置密码邮件模板
  添加重置密码以后的跳转到登录页面
  修改一些细节问题
  添加重置密码页面和js
  修改申请重置密码页面及js名称
  添加重置密码api url,调整url
  修改申请找回用户登录信息的api逻辑,没有用户名也可申请
  添加找回用户信息功能,修改邮件模板的一些细节
  去掉申请重置密码的服务中要求填写用户名,因为有很多用户不记得用户名了
  添加重置密码页面的url,并在用户登录页面添加url
  添加重置密码页面的url
  修改检测邮箱api使其可以被重置密码页面使用
This commit is contained in:
virusdefender 2015-12-07 17:54:21 +08:00
commit b09305fdc1
10 changed files with 214 additions and 28 deletions

View File

@ -50,7 +50,6 @@ class EditUserSerializer(serializers.Serializer):
class ApplyResetPasswordSerializer(serializers.Serializer): class ApplyResetPasswordSerializer(serializers.Serializer):
username = serializers.CharField(max_length=30)
email = serializers.EmailField() email = serializers.EmailField()
captcha = serializers.CharField(max_length=4, min_length=4) captcha = serializers.CharField(max_length=4, min_length=4)

View File

@ -148,17 +148,27 @@ class UsernameCheckAPIView(APIView):
class EmailCheckAPIView(APIView): class EmailCheckAPIView(APIView):
def get(self, request): def get(self, request):
""" """
检测邮箱是否存在存在返回状态码400不存在返回200 检测邮箱是否存在用状态码标识结果
--- ---
""" """
#这里是为了适应前端表单验证空间的要求
reset = request.GET.get("reset", None)
#如果reset为true说明该请求是重置密码页面发出的要返回的状态码应正好相反
if reset:
existed = 200
does_not_existed = 400
else:
existed = 400
does_not_existed = 200
email = request.GET.get("email", None) email = request.GET.get("email", None)
if email: if email:
try: try:
User.objects.get(email=email) User.objects.get(email=email)
return Response(status=400) return Response(status=existed)
except Exception: except Exception:
return Response(status=200) return Response(status=does_not_existed)
return Response(status=200) return Response(status=does_not_existed)
class UserAdminAPIView(APIView): class UserAdminAPIView(APIView):
@ -273,7 +283,7 @@ class ApplyResetPasswordAPIView(APIView):
if not captcha.check(data["captcha"]): if not captcha.check(data["captcha"]):
return error_response(u"验证码错误") return error_response(u"验证码错误")
try: try:
user = User.objects.get(username=data["username"], email=data["email"]) user = User.objects.get(email=data["email"])
except User.DoesNotExist: except User.DoesNotExist:
return error_response(u"用户不存在") return error_response(u"用户不存在")
if user.reset_password_token_create_time and (now() - user.reset_password_token_create_time).total_seconds() < 20 * 60: if user.reset_password_token_create_time and (now() - user.reset_password_token_create_time).total_seconds() < 20 * 60:
@ -285,14 +295,14 @@ class ApplyResetPasswordAPIView(APIView):
email_template = email_template.replace("{{ username }}", user.username).\ email_template = email_template.replace("{{ username }}", user.username).\
replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]).\ replace("{{ website_name }}", settings.WEBSITE_INFO["website_name"]).\
replace("{{ link }}", request.scheme + "://" + request.META['HTTP_HOST'] + "/reset_password/?token=" + user.reset_password_token) replace("{{ link }}", request.scheme + "://" + request.META['HTTP_HOST'] + "/reset_password/t/" + user.reset_password_token)
send_email(settings.WEBSITE_INFO["website_name"], send_email(settings.WEBSITE_INFO["website_name"],
user.email, user.email,
user.username, user.username,
settings.WEBSITE_INFO["website_name"] + u" 密码找回邮件", settings.WEBSITE_INFO["website_name"] + u" 登录信息找回邮件",
email_template) email_template)
return success_response(u"邮件发送成功") return success_response(u"邮件发送成功,请前往您的邮箱查收")
else: else:
return serializer_invalid_response(serializer) return serializer_invalid_response(serializer)
@ -354,3 +364,13 @@ class SSOAPIView(APIView):
request.user.auth_token = token request.user.auth_token = token
request.user.save() request.user.save()
return render(request, "oj/account/sso.html", {"redirect_url": callback + "?token=" + token, "callback": callback}) return render(request, "oj/account/sso.html", {"redirect_url": callback + "?token=" + token, "callback": callback})
def reset_password_page(request, token):
try:
user = User.objects.get(reset_password_token=token)
except User.DoesNotExist:
return error_page(request, u"链接已失效")
if (now() - user.reset_password_token_create_time).total_seconds() > 30 * 60:
return error_page(request, u"链接已过期")
return render(request, "oj/account/reset_password.html", {"user": user})

View File

@ -5,7 +5,7 @@ from django.views.generic import TemplateView
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView, from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
UserChangePasswordAPIView, EmailCheckAPIView, UserChangePasswordAPIView, EmailCheckAPIView,
UserAdminAPIView, UserInfoAPIView, UserAdminAPIView, UserInfoAPIView, ResetPasswordAPIView,
ApplyResetPasswordAPIView, SSOAPIView, UserProfileAPIView) ApplyResetPasswordAPIView, SSOAPIView, UserProfileAPIView)
from announcement.views import AnnouncementAdminAPIView from announcement.views import AnnouncementAdminAPIView
@ -122,12 +122,14 @@ urlpatterns = [
url(r'^user/(?P<username>.+)/$', "account.views.user_index_page"), url(r'^user/(?P<username>.+)/$', "account.views.user_index_page"),
url(r'^api/reset_password/$', ApplyResetPasswordAPIView.as_view(), name="apply_reset_password_api"), url(r'^api/apply_reset_password/$', ApplyResetPasswordAPIView.as_view(), name="apply_reset_password_api"),
url(r'^api/reset_password/$', ResetPasswordAPIView.as_view(), name="apply_reset_password_api"),
url(r'^account/settings/$', TemplateView.as_view(template_name="oj/account/settings.html"), name="account_setting_page"), url(r'^account/settings/$', TemplateView.as_view(template_name="oj/account/settings.html"), name="account_setting_page"),
url(r'^account/settings/avatar/$', TemplateView.as_view(template_name="oj/account/avatar.html"), name="avatar_settings_page"), url(r'^account/settings/avatar/$', TemplateView.as_view(template_name="oj/account/avatar.html"), name="avatar_settings_page"),
url(r'^account/sso/$', SSOAPIView.as_view(), name="sso_api"), url(r'^account/sso/$', SSOAPIView.as_view(), name="sso_api"),
url('^api/account/userprofile/$', UserProfileAPIView.as_view(), name="userprofile_api"), url(r'^api/account/userprofile/$', UserProfileAPIView.as_view(), name="userprofile_api"),
url(r'^reset_password/$', TemplateView.as_view(template_name="oj/account/apply_reset_password.html"), name="apply_reset_password_page"),
url(r'^reset_password/t/(?P<token>\w+)/$', "account.views.reset_password_page", name="reset_password_page")
] ]

View File

@ -0,0 +1,39 @@
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
var applied_captcha = false;
$('form').validator().on('submit', function (e) {
if (!e.isDefaultPrevented()) {
var email = $("#email").val();
var captcha = $("#captcha").val();
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/apply_reset_password/",
data: {email: email, captcha: captcha},
dataType: "json",
method: "post",
success: function (data) {
if (!data.code) {
refresh_captcha();
bsAlert(data.data);
}
else {
refresh_captcha();
bsAlert(data.data);
}
},
error: function(){
bsAlert("额 好像出错了,请刷新页面重试。如还有问题,请填写页面导航栏上的反馈。")
}
});
return false;
}
});
function refresh_captcha(){
$("#captcha-img")[0].src = "/captcha/?" + Math.random();
$("#captcha")[0].value = "";
}
$("#captcha-img").click(function(){
refresh_captcha();
});
});

View File

@ -0,0 +1,41 @@
require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) {
var applied_captcha = false;
$('form').validator().on('submit', function (e) {
if (!e.isDefaultPrevented()) {
var index = location.href.indexOf("/t/");
var token = location.href.substr(36+3, 32);
var captcha = $("#captcha").val();
var password = $("#new_password").val();
$.ajax({
beforeSend: csrfTokenHeader,
url: "/api/reset_password/",
data: {password: password, captcha: captcha, token:token},
dataType: "json",
method: "post",
success: function (data) {
if (!data.code) {
refresh_captcha();
bsAlert(data.data);
window.location.href = "/login/";
}
else {
refresh_captcha();
bsAlert(data.data);
}
},
error: function(){
bsAlert("额 好像出错了,请刷新页面重试。如还有问题,请填写页面导航栏上的反馈。")
}
});
return false;
}
});
function refresh_captcha(){
$("#captcha-img")[0].src = "/captcha/?" + Math.random();
$("#captcha")[0].value = "";
}
$("#captcha-img").click(function(){
refresh_captcha();
});
});

View File

@ -0,0 +1,40 @@
{% extends "oj_base.html" %}
{% block title %}
找回登录信息
{% endblock %}
{% block body %}
<div class="container main">
<div class="col-md-6 col-md-offset-3">
<h2 class="text-center">找回登录信息</h2><br>
<div>
<p>请输入你注册时使用的邮箱地址,系统将自动向你的邮箱发送一封含有您登录信息的电子邮件,
你可以看到你的用户名并可以选择重新设置登录密码注意为了你的账户安全重置密码链接仅在30分钟内有效</p>
</div>
<br>
<form id="login-form">
<div class="form-group">
<label for="email">注册电子邮件地址</label>
<input type="email" class="form-control input-lg" id="email" name="email" placeholder="邮箱地址"
data-remote="/api/email_check/?reset=true" data-remote-error="该邮箱未被注册!" data-error="请填写正确的邮箱地址"
required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group" id="captcha-area">
<label for="captcha">验证码</label>&nbsp;&nbsp;<img src="/captcha/" id="captcha-img">
<small><p></p></small>
<input type="text" class="form-control input-lg" id="captcha" name="captcha"
placeholder="验证码" maxlength="4" data-error="请填写验证码" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/account/applyResetPassword.js"></script>
{% endblock %}

View File

@ -36,7 +36,7 @@
<div class="form-group"> <div class="form-group">
<label for="confirm_password">确认密码</label> <label for="confirm_password">确认密码</label>
<input type="password" class="form-control input-lg" id="confirm_password" name="confirm_password" <input type="password" class="form-control input-lg" id="confirm_password" name="confirm_password"
placeholder="确认密码" maxlength="30" data-match="#new_password" data-match-error="两密码不一致" required> placeholder="确认密码" maxlength="30" data-match="#new_password" data-match-error="两密码不一致" required>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>

View File

@ -32,6 +32,7 @@
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-primary">提交</button> <button type="submit" class="btn btn-primary">提交</button>
</div> </div>
<a href="/reset_password/">忘记用户名/密码</a><br>
<a href="/register/">还没有帐号?点击注册</a> <a href="/register/">还没有帐号?点击注册</a>
</form> </form>

View File

@ -1,10 +1,53 @@
<!DOCTYPE html> {% extends "oj_base.html" %}
<html> {% block title %}
<head lang="en"> 找回登录信息
<meta charset="UTF-8"> {% endblock %}
<title></title> {% block body %}
</head> <div class="container main">
<body> <div class="col-md-6 col-md-offset-3">
<h2 class="text-center">找回登录信息</h2><br>
<br>
</body> <form id="login-form">
</html> <div class="form-group">
<label for="email">注册电子邮件地址</label>
<input type="text" class="form-control input-lg" name="email" value="{{ user.email }}" readonly>
</div>
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control input-lg" name="username" value="{{ user.username }}" readonly>
</div>
<div class="form-group">
<label for="new_password">新密码</label>
<input type="password" class="form-control input-lg" id="new_password" name="new_password"
placeholder="新密码" maxlength="30" data-minlength="6" data-error="密码不得少于6位" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="confirm_password">确认密码</label>
<input type="password" class="form-control input-lg" id="confirm_password" name="confirm_password"
placeholder="确认密码" maxlength="30" data-match="#new_password" data-error="请输入确认密码"
data-match-error="两次密码不一致"
required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group" id="captcha-area">
<label for="captcha">验证码</label>&nbsp;&nbsp;<img src="/captcha/" id="captcha-img">
<small><p></p></small>
<input type="text" class="form-control input-lg" id="captcha" name="captcha"
placeholder="验证码" maxlength="4" data-error="请填写验证码" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/account/resetPassword.js"></script>
{% endblock %}

View File

@ -8,7 +8,7 @@
<tbody> <tbody>
<tr height="39" style="background-color:#50a5e6;"> <tr height="39" style="background-color:#50a5e6;">
<td style="padding-left:15px;font-family:'微软雅黑','黑体',arial;"> <td style="padding-left:15px;font-family:'微软雅黑','黑体',arial;">
{{ website_name }} 密码找回邮件 {{ website_name }} 登录信息找回
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -32,12 +32,12 @@
</tr> </tr>
<tr height="30"> <tr height="30">
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;"> <td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;">
您刚刚在 {{ website_name }} 使用了找回密码功能 您刚刚在 {{ website_name }} 申请了找回登录信息服务
</td> </td>
</tr> </tr>
<tr height="30"> <tr height="30">
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;"> <td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:14px;">
请在<span style="color:rgb(255,0,0)">60分钟</span>内点击下面链接设置您的新密码: 请在<span style="color:rgb(255,0,0)">30分钟</span>内点击下面链接设置您的新密码:
</td> </td>
</tr> </tr>
<tr height="60"> <tr height="60">
@ -63,7 +63,8 @@
</tr> </tr>
<tr height="20"> <tr height="20">
<td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:12px;"> <td style="padding-left:55px;padding-right:55px;font-family:'微软雅黑','黑体',arial;font-size:12px;">
如果你没有提出过密码修改申请,请忽略此邮件。有可能是其他用户误填了你的用户名。我们不会对你的帐户进行任何修改。 如果您没有提出过该申请,请忽略此邮件。有可能是其他用户误填了您的邮件地址,我们不会对你的帐户进行任何修改。
请不要向他人透露本邮件的内容,否则可能会导致您的账号被盗。
</td> </td>
</tr> </tr>
<tr height="20"> <tr height="20">