Accept Merge Request #310 增加找回密码的功能 : (dev-s -> master)

Merge Request: 增加找回密码的功能
Created By: @virusdefender
Accepted By: @virusdefender
URL: https://coding.net/u/virusdefender/p/qduoj/git/merge/310
This commit is contained in:
virusdefender
2015-12-07 17:53:37 +08:00
13 changed files with 224 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)
@@ -353,4 +363,14 @@ class SSOAPIView(APIView):
token = rand_str() token = rand_str()
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

@@ -515,6 +515,10 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
values("id", "contest_id", "problem_id", "result", "create_time", values("id", "contest_id", "problem_id", "result", "create_time",
"accepted_answer_time", "language", "user_id").order_by("-create_time") "accepted_answer_time", "language", "user_id").order_by("-create_time")
# 如果比赛已经开始,就不再显示之前测试题目的提交
if contest.status != CONTEST_NOT_START:
submissions = submissions.filter(create_time__gte=contest.start_time)
user_id = request.GET.get("user_id", None) user_id = request.GET.get("user_id", None)
if user_id: if user_id:
submissions = submissions.filter(user_id=request.GET.get("user_id")) submissions = submissions.filter(user_id=request.GET.get("user_id"))

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

@@ -1,6 +1,9 @@
<div ms-controller="add_contest"> <div ms-controller="add_contest">
<form id="add-contest-form"> <form id="add-contest-form">
<div class="col-md-9"> <div class="col-md-9">
<div class="alert alert-warning" role="alert">
<p>注意!管理员在测试比赛题目的时候请务必保持比赛状态为没有开始,这时只有管理员可以正常查看和提交题目,而且不会产生排名。</p>
</div>
<div class="col-md-12"> <div class="col-md-12">
<label>比赛名称</label> <label>比赛名称</label>
</div> </div>

View File

@@ -6,6 +6,9 @@
aria-hidden="true">&larr;</span> 返回</a></li> aria-hidden="true">&larr;</span> 返回</a></li>
</ul> </ul>
</nav> </nav>
<div class="alert alert-warning" role="alert">
<p>注意!管理员在测试比赛题目的时候请务必保持比赛状态为没有开始,这时只有管理员可以正常查看和提交题目,而且不会产生排名。</p>
</div>
<div class="col-md-12"> <div class="col-md-12">
<label>比赛名称</label> <label>比赛名称</label>
</div> </div>

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">