diff --git a/README.md b/README.md index 043cae0..4d9c036 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ demo: https://qduoj.com TODO: + - 将判题服务器改为 rpc 通信 + - 重构消息队列 - 完善测试 - 完善小组功能 - - 后台重构 ![oj_previewindex.png][1] diff --git a/account/middleware.py b/account/middleware.py new file mode 100644 index 0000000..7024225 --- /dev/null +++ b/account/middleware.py @@ -0,0 +1,24 @@ +# coding=utf-8 +import time +import json +import urllib +from django.http import HttpResponseRedirect, HttpResponse +from django.contrib import auth +from utils.shortcuts import error_response, error_page +from .models import ADMIN + + +class SessionSecurityMiddleware(object): + def process_request(self, request): + if request.user.is_authenticated() and request.user.admin_type >= ADMIN: + if "last_activity" in request.session: + # 24个小时没有活动 + if time.time() - request.session["last_activity"] >= 24 * 60 * 60: + auth.logout(request) + if request.is_ajax(): + return HttpResponse(json.dumps({"code": 1, "data": u"请先登录"}), + content_type="application/json") + else: + return HttpResponseRedirect("/login/?__from=" + urllib.quote(request.build_absolute_uri())) + # 更新最后活动日期 + request.session["last_activity"] = time.time() diff --git a/account/migrations/0014_auto_20151110_1037.py b/account/migrations/0014_auto_20151110_1037.py new file mode 100644 index 0000000..288e471 --- /dev/null +++ b/account/migrations/0014_auto_20151110_1037.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0013_userprofile'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='phone_number', + field=models.CharField(max_length=15, null=True, blank=True), + ), + migrations.AddField( + model_name='userprofile', + name='school', + field=models.CharField(max_length=200, null=True, blank=True), + ), + ] diff --git a/account/models.py b/account/models.py index 0fb7858..e08ff6c 100644 --- a/account/models.py +++ b/account/models.py @@ -68,6 +68,9 @@ class UserProfile(models.Model): submissions_number = models.IntegerField(default=0) # JSON字典用来表示该用户的问题的解决状态 1为ac,2为正在进行 problems_status = JSONField(default={}) + phone_number = models.CharField(max_length=15, blank=True, null=True) + school = models.CharField(max_length=200, blank=True, null=True) + class Meta: db_table = "user_profile" diff --git a/account/serializers.py b/account/serializers.py index 5004ee1..0aac07a 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,7 +1,7 @@ # coding=utf-8 from rest_framework import serializers -from .models import User +from .models import User, UserProfile class UserLoginSerializer(serializers.Serializer): @@ -21,6 +21,7 @@ class EmailCheckSerializer(serializers.Serializer): class UserRegisterSerializer(serializers.Serializer): username = serializers.CharField(max_length=30) real_name = serializers.CharField(max_length=30) + school = serializers.CharField(max_length=200) password = serializers.CharField(max_length=30, min_length=6) email = serializers.EmailField(max_length=254) captcha = serializers.CharField(max_length=4, min_length=4) @@ -49,7 +50,6 @@ class EditUserSerializer(serializers.Serializer): class ApplyResetPasswordSerializer(serializers.Serializer): - username = serializers.CharField(max_length=30) email = serializers.EmailField() captcha = serializers.CharField(max_length=4, min_length=4) @@ -61,4 +61,24 @@ class ResetPasswordSerializer(serializers.Serializer): class SSOSerializer(serializers.Serializer): - token = serializers.CharField(max_length=40) \ No newline at end of file + token = serializers.CharField(max_length=40) + + +class EditUserProfileSerializer(serializers.Serializer): + + avatar = serializers.CharField(max_length=50, required=False, default=None) + blog = serializers.URLField(required=False, allow_blank=True, default='') + mood = serializers.CharField(max_length=60, required=False, default='') + hduoj_username = serializers.CharField(max_length=30, required=False, allow_blank=True, default='') + bestcoder_username = serializers.CharField(max_length=30, required=False, allow_blank=True, default='') + codeforces_username = serializers.CharField(max_length=30, required=False, allow_blank=True, default='') + school = serializers.CharField(max_length=200, required=False, allow_blank=True, default='') + phone_number = serializers.CharField(max_length=15, required=False, allow_blank=True, default='') + + +class UserProfileSerializer(serializers.ModelSerializer): + + class Meta: + model = UserProfile + fields = ["avatar", "blog", "mood", "hduoj_username", "bestcoder_username", "codeforces_username", + "rank", "accepted_number", "submissions_number", "problems_status", "phone_number", "school"] diff --git a/account/views.py b/account/views.py index 5b75ce9..2ba5739 100644 --- a/account/views.py +++ b/account/views.py @@ -17,12 +17,14 @@ from utils.captcha import Captcha from mail.tasks import send_email from .decorators import login_required -from .models import User -from .serializers import (UserLoginSerializer, UsernameCheckSerializer, - UserRegisterSerializer, UserChangePasswordSerializer, - EmailCheckSerializer, UserSerializer, EditUserSerializer, +from .models import User, UserProfile + +from .serializers import (UserLoginSerializer, UserRegisterSerializer, + UserChangePasswordSerializer, + UserSerializer, EditUserSerializer, ApplyResetPasswordSerializer, ResetPasswordSerializer, - SSOSerializer) + SSOSerializer, EditUserProfileSerializer, UserProfileSerializer) + from .decorators import super_admin_required @@ -60,10 +62,9 @@ def index_page(request): if not request.user.is_authenticated(): return render(request, "oj/index.html") - try: - if request.META['HTTP_REFERER']: + if request.META.get('HTTP_REFERER') or request.GET.get("index"): return render(request, "oj/index.html") - except KeyError: + else: return http.HttpResponseRedirect('/problems/') @@ -96,6 +97,7 @@ class UserRegisterAPIView(APIView): email=data["email"]) user.set_password(data["password"]) user.save() + UserProfile.objects.create(user=user, school=data["school"]) return success_response(u"注册成功!") else: return serializer_invalid_response(serializer) @@ -146,17 +148,27 @@ class UsernameCheckAPIView(APIView): class EmailCheckAPIView(APIView): 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) if email: try: User.objects.get(email=email) - return Response(status=400) + return Response(status=existed) except Exception: - return Response(status=200) - return Response(status=200) + return Response(status=does_not_existed) + return Response(status=does_not_existed) class UserAdminAPIView(APIView): @@ -225,6 +237,38 @@ class UserInfoAPIView(APIView): return success_response(UserSerializer(request.user).data) +class UserProfileAPIView(APIView): + @login_required + def get(self, request): + """ + 返回这个用户的个人信息 + --- + response_serializer: UserSerializer + """ + return success_response(UserSerializer(request.user).data) + + @login_required + def put(self, request): + serializer = EditUserProfileSerializer(data=request.data) + if serializer.is_valid(): + data = serializer.data + user_profile = request.user.userprofile + if data["avatar"]: + user_profile.avatar = data["avatar"] + else: + user_profile.mood = data["mood"] + user_profile.hduoj_username = data["hduoj_username"] + user_profile.bestcoder_username = data["bestcoder_username"] + user_profile.codeforces_username = data["codeforces_username"] + user_profile.blog = data["blog"] + user_profile.school = data["school"] + user_profile.phone_number = data["phone_number"] + user_profile.save() + return success_response(u"修改成功") + else: + return serializer_invalid_response(serializer) + + class ApplyResetPasswordAPIView(APIView): def post(self, request): """ @@ -239,7 +283,7 @@ class ApplyResetPasswordAPIView(APIView): if not captcha.check(data["captcha"]): return error_response(u"验证码错误") try: - user = User.objects.get(username=data["username"], email=data["email"]) + user = User.objects.get(email=data["email"]) except User.DoesNotExist: return error_response(u"用户不存在") if user.reset_password_token_create_time and (now() - user.reset_password_token_create_time).total_seconds() < 20 * 60: @@ -251,14 +295,14 @@ class ApplyResetPasswordAPIView(APIView): email_template = email_template.replace("{{ username }}", user.username).\ 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"], user.email, user.username, - settings.WEBSITE_INFO["website_name"] + u" 密码找回邮件", + settings.WEBSITE_INFO["website_name"] + u" 登录信息找回邮件", email_template) - return success_response(u"邮件发送成功") + return success_response(u"邮件发送成功,请前往您的邮箱查收") else: return serializer_invalid_response(serializer) @@ -319,4 +363,14 @@ class SSOAPIView(APIView): token = rand_str() request.user.auth_token = token request.user.save() - return render(request, "oj/account/sso.html", {"redirect_url": callback + "?token=" + token, "callback": callback}) \ No newline at end of file + 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}) diff --git a/contest/decorators.py b/contest/decorators.py index 93e24fa..01297f6 100644 --- a/contest/decorators.py +++ b/contest/decorators.py @@ -4,6 +4,7 @@ from functools import wraps from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render +from django.core.urlresolvers import reverse from utils.shortcuts import error_response, error_page @@ -92,7 +93,7 @@ def check_user_contest_permission(func): {"reason": "contest_not_start", "show_tab": False, "contest": contest}) # 比赛已经结束了,只拦截 ajax 的答案提交 - if contest.status == CONTEST_ENDED and request.is_ajax(): + if contest.status == CONTEST_ENDED and request.path == reverse("contest_submission_api") and request.is_ajax(): return error_response(u"比赛已经结束") return func(*args, **kwargs) diff --git a/contest/views.py b/contest/views.py index 03285c9..87378db 100644 --- a/contest/views.py +++ b/contest/views.py @@ -264,7 +264,7 @@ class ContestProblemAdminAPIView(APIView): keyword = request.GET.get("keyword", None) if keyword: contest_problems = contest_problems.filter(Q(title__contains=keyword) | - Q(description__contains=keyword)) + Q(description__contains=keyword)) contest_id = request.GET.get("contest_id", None) if contest_id: contest_problems = contest_problems.filter(contest__id=contest_id).order_by("sort_index") @@ -278,6 +278,8 @@ class MakeContestProblemPublicAPIView(APIView): problem_id = request.data.get("problem_id", -1) try: problem = ContestProblem.objects.get(id=problem_id) + problem.is_public = True + problem.save() except ContestProblem.DoesNotExist: return error_response(u"比赛不存在") Problem.objects.create(title=problem.title, description=problem.description, @@ -288,10 +290,11 @@ class MakeContestProblemPublicAPIView(APIView): hint=problem.hint, created_by=problem.created_by, time_limit=problem.time_limit, memory_limit=problem.memory_limit, visible=False, difficulty=-1, source=problem.contest.title) + problem.is_public = True + problem.save() return success_response(u"创建成功") - class ContestPasswordVerifyAPIView(APIView): @login_required def post(self, request): @@ -374,7 +377,7 @@ def contest_problems_list_page(request, contest_id): 比赛所有题目的列表页 """ contest = Contest.objects.get(id=contest_id) - contest_problems = ContestProblem.objects.filter(contest=contest).select_related("contest").order_by("sort_index") + contest_problems = ContestProblem.objects.filter(contest=contest, visible=True).select_related("contest").order_by("sort_index") return render(request, "oj/contest/contest_problems_list.html", {"contest_problems": contest_problems, "contest": {"id": contest_id}}) @@ -423,16 +426,16 @@ def contest_list_page(request, page=1): @check_user_contest_permission def contest_rank_page(request, contest_id): contest = Contest.objects.get(id=contest_id) - contest_problems = ContestProblem.objects.filter(contest=contest).order_by("sort_index") + contest_problems = ContestProblem.objects.filter(contest=contest, visible=True).order_by("sort_index") r = get_cache_redis() cache_key = str(contest_id) + "_rank_cache" rank = r.get(cache_key) if not rank: - rank = ContestRank.objects.filter(contest_id=contest_id).\ - select_related("user").\ - order_by("-total_ac_number", "total_time").\ + rank = ContestRank.objects.filter(contest_id=contest_id). \ + select_related("user"). \ + order_by("-total_ac_number", "total_time"). \ values("id", "user__id", "user__username", "user__real_name", "contest_id", "submission_info", "total_submission_number", "total_ac_number", "total_time") r.set(cache_key, json.dumps([dict(item) for item in rank])) @@ -459,13 +462,14 @@ def contest_rank_page(request, contest_id): "contest_problems": contest_problems, "paging_info": paging_info, "auto_refresh": request.GET.get("auto_refresh", None) == "true", - "show_real_name": request.GET.get("show_real_name", None) == "true",}) + "show_real_name": request.GET.get("show_real_name", None) == "true", }) class ContestTimeAPIView(APIView): """ 获取比赛开始或者结束的倒计时,返回毫秒数字 """ + def get(self, request): contest_id = request.GET.get("contest_id", -1) try: @@ -490,8 +494,8 @@ def contest_problem_my_submissions_list_page(request, contest_id, contest_proble contest_problem = ContestProblem.objects.get(id=contest_problem_id, visible=True) except ContestProblem.DoesNotExist: return error_page(request, u"比赛问题不存在") - submissions = Submission.objects.filter(user_id=request.user.id, problem_id=contest_problem.id).\ - order_by("-create_time").\ + submissions = Submission.objects.filter(user_id=request.user.id, problem_id=contest_problem.id). \ + order_by("-create_time"). \ values("id", "result", "create_time", "accepted_answer_time", "language") return render(request, "oj/submission/problem_my_submissions_list.html", {"submissions": submissions, "problem": contest_problem}) @@ -507,10 +511,14 @@ def contest_problem_submissions_list_page(request, contest_id, page=1): except Contest.DoesNotExist: return error_page(request, u"比赛不存在") - submissions = Submission.objects.filter(contest_id=contest_id).\ + submissions = Submission.objects.filter(contest_id=contest_id). \ values("id", "contest_id", "problem_id", "result", "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) if user_id: submissions = submissions.filter(user_id=request.GET.get("user_id")) @@ -568,4 +576,4 @@ def contest_problem_submissions_list_page(request, contest_id, page=1): return render(request, "oj/contest/submissions_list.html", {"submissions": current_page, "page": int(page), "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20, - "contest": contest, "filter": filter, "user_id": user_id, "problem_id": problem_id}) \ No newline at end of file + "contest": contest, "filter": filter, "user_id": user_id, "problem_id": problem_id}) diff --git a/oj/settings.py b/oj/settings.py index 2e86e75..83d49ef 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -71,7 +71,8 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', - 'admin.middleware.AdminRequiredMiddleware' + 'admin.middleware.AdminRequiredMiddleware', + 'account.middleware.SessionSecurityMiddleware' ) ROOT_URLCONF = 'oj.urls' diff --git a/oj/urls.py b/oj/urls.py index c4d2e09..d2beba1 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -5,8 +5,8 @@ from django.views.generic import TemplateView from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView, UserChangePasswordAPIView, EmailCheckAPIView, - UserAdminAPIView, UserInfoAPIView, - ApplyResetPasswordAPIView, SSOAPIView) + UserAdminAPIView, UserInfoAPIView, ResetPasswordAPIView, + ApplyResetPasswordAPIView, SSOAPIView, UserProfileAPIView) from announcement.views import AnnouncementAdminAPIView @@ -122,11 +122,14 @@ urlpatterns = [ url(r'^user/(?P.+)/$', "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/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'^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\w+)/$', "account.views.reset_password_page", name="reset_password_page") ] diff --git a/problem/views.py b/problem/views.py index 301aa25..6e2d391 100644 --- a/problem/views.py +++ b/problem/views.py @@ -40,6 +40,7 @@ class ProblemTagAdminAPIView(APIView): """ 获取所有标签的列表 """ + def get(self, request): return success_response(ProblemTagSerializer(ProblemTag.objects.all(), many=True).data) @@ -189,75 +190,63 @@ class TestCaseUploadAPIView(APIView): return error_response(u"解压失败") name_list = test_case_file.namelist() - l = [] - # 如果文件是直接打包的,那么name_list 就是["1.in", "1.out"]这样的 + if len(name_list) == 0: + return error_response(u"压缩包内没有文件") - if "1.in" in name_list and "1.out" in name_list: - for file_name in name_list: - if self._is_legal_test_case_file_name(file_name): - name = file_name.split(".") - # 有了.in 判断对应的.out 在不在 - if name[1] == "in": - if (name[0] + ".out") in name_list: - l.append(file_name) - else: - return error_response(u"测试用例文件不完整,缺少" + name[0] + ".out") - else: - # 有了.out 判断对应的 .in 在不在 - if (name[0] + ".in") in name_list: - l.append(file_name) - else: - return error_response(u"测试用例文件不完整,缺少" + name[0] + ".in") + if len(name_list) % 2 == 1: + return error_response(u"测试用例文件格式错误,文件数目为奇数") - problem_test_dir = rand_str() - test_case_dir = settings.TEST_CASE_DIR + problem_test_dir + "/" + for index in range(1, len(name_list) / 2 + 1): + if not (str(index) + ".in" in name_list and str(index) + ".out" in name_list): + return error_response(u"测试用例文件格式错误,缺少" + str(index) + u".in/.out文件") - # 得到了合法的测试用例文件列表 然后去解压缩 - os.mkdir(test_case_dir) - for name in l: - f = open(test_case_dir + name, "wb") - try: - f.write(test_case_file.read(name).replace("\r\n", "\n")) - except MemoryError: - return error_response(u"单个测试数据体积过大!") - finally: - f.close() - l.sort() + problem_test_dir = rand_str() + test_case_dir = settings.TEST_CASE_DIR + problem_test_dir + "/" - file_info = {"test_case_number": len(l) / 2, "test_cases": {}} + # 得到了合法的测试用例文件列表 然后去解压缩 + os.mkdir(test_case_dir) + for name in name_list: + f = open(test_case_dir + name, "wb") + try: + f.write(test_case_file.read(name).replace("\r\n", "\n")) + except MemoryError: + return error_response(u"单个测试数据体积过大!") + finally: + f.close() + name_list.sort() - # 计算输出文件的md5 - for i in range(len(l) / 2): - md5 = hashlib.md5() - striped_md5 = hashlib.md5() - f = open(test_case_dir + str(i + 1) + ".out", "r") - # 完整文件的md5 - while True: - data = f.read(2 ** 8) - if not data: - break - md5.update(data) + file_info = {"test_case_number": len(name_list) / 2, "test_cases": {}} - # 删除标准输出最后的空格和换行 - # 这时只能一次全部读入了,分块读的话,没办法确定文件结尾 - f.seek(0) - striped_md5.update(f.read().rstrip()) + # 计算输出文件的md5 + for i in range(1, len(name_list) / 2 + 1): + md5 = hashlib.md5() + striped_md5 = hashlib.md5() + f = open(test_case_dir + str(i) + ".out", "r") + # 完整文件的md5 + while True: + data = f.read(2 ** 8) + if not data: + break + md5.update(data) - file_info["test_cases"][str(i + 1)] = {"input_name": str(i + 1) + ".in", - "output_name": str(i + 1) + ".out", - "output_md5": md5.hexdigest(), - "striped_output_md5": striped_md5.hexdigest(), - "input_size": os.path.getsize(test_case_dir + str(i + 1) + ".in"), - "output_size": os.path.getsize(test_case_dir + str(i + 1) + ".out")} - # 写入配置文件 - with open(test_case_dir + "info", "w") as f: - f.write(json.dumps(file_info)) + # 删除标准输出最后的空格和换行 + # 这时只能一次全部读入了,分块读的话,没办法确定文件结尾 + f.seek(0) + striped_md5.update(f.read().rstrip()) - return success_response({"test_case_id": problem_test_dir, - "file_list": file_info["test_cases"]}) - else: - return error_response(u"测试用例压缩文件格式错误,请保证测试用例文件在根目录下直接压缩") + file_info["test_cases"][str(i)] = {"input_name": str(i) + ".in", + "output_name": str(i) + ".out", + "output_md5": md5.hexdigest(), + "striped_output_md5": striped_md5.hexdigest(), + "input_size": os.path.getsize(test_case_dir + str(i) + ".in"), + "output_size": os.path.getsize(test_case_dir + str(i) + ".out")} + # 写入配置文件 + with open(test_case_dir + "info", "w") as f: + f.write(json.dumps(file_info)) + + return success_response({"test_case_id": problem_test_dir, + "file_list": file_info["test_cases"]}) def get(self, request): test_case_id = request.GET.get("test_case_id", None) @@ -273,7 +262,6 @@ class TestCaseUploadAPIView(APIView): return success_response({"file_list": config["test_cases"]}) - def problem_list_page(request, page=1): """ 前台的问题列表 @@ -306,7 +294,7 @@ def problem_list_page(request, page=1): return error_page(request, u"标签不存在") problems = tag.problem_set.all().filter(visible=True) - paginator = Paginator(problems, 20) + paginator = Paginator(problems, 40) try: current_page = paginator.page(int(page)) except Exception: diff --git a/static/src/img/index/bg/bg0.jpg b/static/src/img/index/bg/bg0.jpg new file mode 100644 index 0000000..02c9417 Binary files /dev/null and b/static/src/img/index/bg/bg0.jpg differ diff --git a/static/src/img/index/bg/bg1.jpg b/static/src/img/index/bg/bg1.jpg new file mode 100644 index 0000000..4d4862f Binary files /dev/null and b/static/src/img/index/bg/bg1.jpg differ diff --git a/static/src/img/index/bg/bg2.jpg b/static/src/img/index/bg/bg2.jpg new file mode 100644 index 0000000..aa80557 Binary files /dev/null and b/static/src/img/index/bg/bg2.jpg differ diff --git a/static/src/img/index/bg/bg3.jpg b/static/src/img/index/bg/bg3.jpg new file mode 100644 index 0000000..5c4b58f Binary files /dev/null and b/static/src/img/index/bg/bg3.jpg differ diff --git a/static/src/js/app/admin/contest/editProblem.js b/static/src/js/app/admin/contest/editProblem.js index 4fdb94f..c19a26b 100644 --- a/static/src/js/app/admin/contest/editProblem.js +++ b/static/src/js/app/admin/contest/editProblem.js @@ -1,4 +1,5 @@ -require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagEditor", "validator", "editorComponent"], +require(["jquery", "avalon", "editor", "uploader", "bsAlert", + "csrfToken", "tagEditor", "validator", "editorComponent", "testCaseUploader"], function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) { avalon.ready(function () { @@ -7,7 +8,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE .on('submit', function (e) { if (!e.isDefaultPrevented()) { e.preventDefault(); - if (vm.testCaseId == "") { + if (!avalon.vmodels.testCaseUploader.uploaded) { bsAlert("你还没有上传测试数据!"); return false; } @@ -35,7 +36,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE time_limit: vm.timeLimit, memory_limit: vm.memoryLimit, samples: [], - test_case_id: vm.testCaseId, + test_case_id: avalon.vmodels.testCaseUploader.testCaseId, hint: avalon.vmodels.contestProblemHintEditor.content, visible: vm.visible, contest_id: avalon.vmodels.admin.contestId, @@ -71,6 +72,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE success: function (data) { if (!data.code) { bsAlert(alertContent); + avalon.vmodels.admin.template_url = "template/contest/problem_list.html"; } else { bsAlert(data.data); @@ -97,7 +99,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE outputDescription: "", testCaseId: "", testCaseList: [], - uploadSuccess: false, contestProblemDescriptionEditor: { editorId: "contest-problem-description-editor", @@ -134,38 +135,20 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE }); else { var vm = avalon.vmodels.editProblem; - title = ""; - description = ""; - timeLimit = 1000; - memoryLimit = 128; - samples = []; - hint = ""; - sortIndex = ""; - visible = true; - inputDescription = ""; - outputDescription = ""; - testCaseId = ""; - testCaseList = []; - uploadSuccess = false; + vm.title = ""; + vm.description = ""; + vm.timeLimit = 1000; + vm.memoryLimit = 128; + vm.samples = []; + vm.hint = ""; + vm.sortIndex = ""; + vm.visible = true; + vm.inputDescription = ""; + vm.outputDescription = ""; + vm.testCaseId = ""; + vm.testCaseList = []; } - var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/", function (file, response) { - if (response.code) - bsAlert(response.data); - else { - vm.testCaseId = response.data.test_case_id; - vm.testCaseList = []; - for (var key in response.data.file_list) { - vm.testCaseList.push({ - input: response.data.file_list[key].input_name, - output: response.data.file_list[key].output_name - }) - } - vm.uploadSuccess = true; - bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据"); - } - }); - if (avalon.vmodels.admin.contestProblemStatus == "edit") { $.ajax({ url: "/api/admin/contest_problem/?contest_problem_id=" + avalon.vmodels.admin.problemId, @@ -188,7 +171,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE vm.inputDescription = problem.input_description; vm.outputDescription = problem.output_description; vm.score = problem.score; - vm.testCaseId = problem.test_case_id; + avalon.vmodels.testCaseUploader.setTestCase(problem.test_case_id); vm.samples = []; for (var i = 0; i < problem.samples.length; i++) { vm.samples.push({ @@ -198,26 +181,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE }) } avalon.vmodels.contestProblemHintEditor.content = problem.hint; - $.ajax({ - url: "/api/admin/test_case_upload/?test_case_id=" + vm.testCaseId, - method: "get", - dataType: "json", - success: function (response) { - if (response.code) { - bsAlert(response.data); - } - else { - vm.testCaseList = []; - for (var key in response.data.file_list) { - vm.testCaseList.push({ - input: response.data.file_list[key].input_name, - output: response.data.file_list[key].output_name - }) - } - vm.uploadSuccess = true; - } - } - }) } } }); diff --git a/static/src/js/app/admin/problem/addProblem.js b/static/src/js/app/admin/problem/addProblem.js index e2f9e6f..73254af 100644 --- a/static/src/js/app/admin/problem/addProblem.js +++ b/static/src/js/app/admin/problem/addProblem.js @@ -1,11 +1,12 @@ -require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagEditor", "validator", "jqueryUI", "editorComponent"], +require(["jquery", "avalon", "editor", "uploader", "bsAlert", + "csrfToken", "tagEditor", "validator", "jqueryUI", "editorComponent", "testCaseUploader"], function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) { avalon.ready(function () { $("#add-problem-form").validator() .on('submit', function (e) { if (!e.isDefaultPrevented()) { - if (vm.testCaseId == "") { + if (!avalon.vmodels.testCaseUploader.uploaded) { bsAlert("你还没有上传测试数据!"); return false; } @@ -39,7 +40,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE time_limit: vm.timeLimit, memory_limit: vm.memoryLimit, samples: [], - test_case_id: vm.testCaseId, + test_case_id: avalon.vmodels.testCaseUploader.testCaseId, hint: avalon.vmodels.problemHintEditor.content, source: vm.source, visible: vm.visible, @@ -139,29 +140,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE } }); - var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/", - function (file, response) { - if (response.code) { - vm.uploadProgress = 0; - bsAlert(response.data); - } - else { - vm.testCaseId = response.data.test_case_id; - vm.uploadSuccess = true; - vm.testCaseList = []; - for (var key in response.data.file_list) { - vm.testCaseList.push({ - input: response.data.file_list[key].input_name, - output: response.data.file_list[key].output_name - }) - } - bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据"); - } - }, - function (file, percentage) { - vm.uploadProgress = parseInt(percentage * 100); - }); - var tagAutoCompleteList = []; $.ajax({ diff --git a/static/src/js/app/admin/problem/editProblem.js b/static/src/js/app/admin/problem/editProblem.js index e6dda3b..b144864 100644 --- a/static/src/js/app/admin/problem/editProblem.js +++ b/static/src/js/app/admin/problem/editProblem.js @@ -1,4 +1,5 @@ -require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagEditor", "validator", "jqueryUI", "editorComponent"], +require(["jquery", "avalon", "editor", "uploader", "bsAlert", + "csrfToken", "tagEditor", "validator", "jqueryUI", "editorComponent", "testCaseUploader"], function ($, avalon, editor, uploader, bsAlert, csrfTokenHeader) { avalon.ready(function () { @@ -6,7 +7,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE $("#edit-problem-form").validator() .on('submit', function (e) { if (!e.isDefaultPrevented()) { - if (vm.testCaseId == "") { + if (!avalon.vmodels.testCaseUploader.uploaded) { bsAlert("你还没有上传测试数据!"); return false; } @@ -40,7 +41,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE time_limit: vm.timeLimit, memory_limit: vm.memoryLimit, samples: [], - test_case_id: vm.testCaseId, + test_case_id: avalon.vmodels.testCaseUploader.testCaseId, hint: avalon.vmodels.problemHintEditor.content, source: vm.source, visible: vm.visible, @@ -127,29 +128,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE avalon.vmodels.admin.template_url = "template/problem/problem_list.html"; } }); - var testCaseUploader = uploader("#testCaseFile", "/api/admin/test_case_upload/", - function (file, response) { - if (response.code) { - vm.uploadProgress = 0; - bsAlert(response.data); - } - else { - vm.testCaseId = response.data.test_case_id; - vm.uploadSuccess = true; - vm.testCaseList = []; - for(var key in response.data.file_list){ - vm.testCaseList.push({ - input: response.data.file_list[key].input_name, - output: response.data.file_list[key].output_name - }) - } - bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据"); - } - }, - function (file, percentage) { - vm.uploadProgress = parseInt(percentage * 100); - } - ); $.ajax({ url: "/api/admin/problem/?problem_id=" + avalon.vmodels.admin.problemId, @@ -178,7 +156,7 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE vm.difficulty = problem.difficulty; vm.inputDescription = problem.input_description; vm.outputDescription = problem.output_description; - vm.testCaseId = problem.test_case_id; + avalon.vmodels.testCaseUploader.setTestCase(problem.test_case_id); vm.source = problem.source; var problemTags = problem.tags; $.ajax({ diff --git a/static/src/js/app/oj/account/applyResetPassword.js b/static/src/js/app/oj/account/applyResetPassword.js new file mode 100644 index 0000000..e8d31ed --- /dev/null +++ b/static/src/js/app/oj/account/applyResetPassword.js @@ -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(); + }); +}); \ No newline at end of file diff --git a/static/src/js/app/oj/account/avatar.js b/static/src/js/app/oj/account/avatar.js new file mode 100644 index 0000000..4780e26 --- /dev/null +++ b/static/src/js/app/oj/account/avatar.js @@ -0,0 +1,39 @@ +require(["jquery", "bsAlert", "csrfToken"], function ($, bsAlert, csrfTokenHeader) { + var avatar = ""; + + function changeAvatar(event) { + avatar = $(event.target).attr('src'); + $('#current_avatar').attr('src', avatar); + } + + $('.avatar-item').click(changeAvatar); + + $('#save_avatar').click(function () { + if (avatar) + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/account/userprofile/", + data: { + avatar: avatar + }, + dataType: "json", + method: "put", + success: function (data) { + if (!data.code) { + bsAlert("已保存!"); + } + else { + bsAlert(data.data); + } + }, + error: function () { + bsAlert("额 好像出错了,请刷新页面重试。如还有问题,请填写页面导航栏上的反馈。") + } + + }); + else + bsAlert("请选择一个头像"); + + }); +}); + diff --git a/static/src/js/app/oj/account/register.js b/static/src/js/app/oj/account/register.js index 46402b1..ef23a38 100644 --- a/static/src/js/app/oj/account/register.js +++ b/static/src/js/app/oj/account/register.js @@ -3,13 +3,14 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c if (!e.isDefaultPrevented()) { var username = $("#username").val(); var realName = $("#real_name").val(); + var school = $('#school').val(); var password = $("#password").val(); var email = $("#email").val(); var captcha = $("#captcha").val(); $.ajax({ beforeSend: csrfTokenHeader, url: "/api/register/", - data: {username: username, real_name: realName, password: password, email: email, captcha:captcha}, + data: {username: username, school: school, real_name: realName, password: password, email: email, captcha:captcha}, dataType: "json", method: "post", success: function (data) { diff --git a/static/src/js/app/oj/account/resetPassword.js b/static/src/js/app/oj/account/resetPassword.js new file mode 100644 index 0000000..0f0bb4b --- /dev/null +++ b/static/src/js/app/oj/account/resetPassword.js @@ -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(); + }); +}); \ No newline at end of file diff --git a/static/src/js/app/oj/account/settings.js b/static/src/js/app/oj/account/settings.js new file mode 100644 index 0000000..b4538ee --- /dev/null +++ b/static/src/js/app/oj/account/settings.js @@ -0,0 +1,43 @@ +require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, csrfTokenHeader) { + $('form').validator().on('submit', function (e) { + if (!e.isDefaultPrevented()) { + var phone = $("#phone").val(); + var hduoj_username = $("#hduoj_username").val(); + var bestcoder_username = $("#bestcoder_username").val(); + var codeforces_username = $("#codeforces_username").val(); + var blog = $("#blog").val(); + var mood = $("#mood").val(); + var school = $("#school").val(); + + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/account/userprofile/", + data: { + phone_number: phone, + hduoj_username: hduoj_username, + bestcoder_username: bestcoder_username, + codeforces_username: codeforces_username, + blog: blog, + mood: mood, + school: school + }, + dataType: "json", + method: "put", + success: function (data) { + if (!data.code) { + bsAlert("修改成功"); + } + else{ + bsAlert(data.data); + } + }, + error: function () { + bsAlert("额 好像出错了,请刷新页面重试。如还有问题,请填写页面导航栏上的反馈。") + } + + }); + return false; + } + }); +}); + diff --git a/static/src/js/build.js b/static/src/js/build.js index 9d2a312..beb891e 100644 --- a/static/src/js/build.js +++ b/static/src/js/build.js @@ -26,6 +26,7 @@ // ------ admin web 组件 ---------- pager: "components/pager", editorComponent: "components/editorComponent", + testCaseUploader: "components/testCaseUploader", // ------ 下面写的都不要直接用,而是使用上面的封装版本 ------ //富文本编辑器simditor -> editor @@ -74,6 +75,7 @@ groupDetail_20_pack: "app/admin/group/groupDetail", editContest_21_pack: "app/admin/contest/editContest", group_22_pack: "app/admin/group/group", + settings_23_pack: "app/oj/account/settings" }, shim: { avalon: { @@ -158,7 +160,10 @@ }, { name: "group_22_pack" - } + }, + { + name: "settings_23_pack" + }, ], optimizeCss: "standard", }) \ No newline at end of file diff --git a/static/src/js/components/testCaseUploader.js b/static/src/js/components/testCaseUploader.js new file mode 100644 index 0000000..8a41cc3 --- /dev/null +++ b/static/src/js/components/testCaseUploader.js @@ -0,0 +1,83 @@ +define("testCaseUploader", ["avalon", "uploader", "bsAlert", "jquery"], function(avalon, uploader, bsAlert, $){ + avalon.component("ms:testcaseuploader", { + $template: '
' + + '
' + + '
' + + '' + + '请将所有测试用例打包在一个文件中上传,' + + '所有文件要在压缩包的根目录,' + + '且输入输出文件名要以从1开始连续数字标识要对应例如:' + + '
1.in 1.out 2.in 2.out
' + + '

上传进度%

' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
编号 输入文件名 输出文件名
{{ $index + 1 }}{{ el.input }}{{ el.output }}
' + + '
' + + '
' + + '
' + + '
选择文件
' + + '
' + + '
', + testCaseId: "", + testCaseList: [], + uploaded: false, + uploadProgress: 0, + + setTestCaseId: function(){}, + + $init: function(vm, el){ + vm.setTestCase = function(testCaseId){ + vm.testCaseId = testCaseId; + $.ajax({ + url: "/api/admin/test_case_upload/?test_case_id=" + testCaseId, + method: "get", + success: function(data){ + if(data.code){ + bsAlert("获取测试用例列表失败"); + } + else{ + for(var key in data.data.file_list){ + vm.testCaseList.push({ + input: data.data.file_list[key].input_name, + output: data.data.file_list[key].output_name + }) + } + vm.uploaded = true; + vm.uploadProgress = 100; + } + } + }); + + } + }, + + $ready: function(vm, el){ + el.msRetain = true; + var testCaseUploader = uploader("#testCaseFileSelector", "/api/admin/test_case_upload/", + function (file, response) { + if (response.code) { + bsAlert(response.data); + } + else { + vm.testCaseId = response.data.test_case_id; + vm.uploaded = true; + vm.testCaseList = []; + for(var key in response.data.file_list){ + vm.testCaseList.push({ + input: response.data.file_list[key].input_name, + output: response.data.file_list[key].output_name + }) + } + bsAlert("测试数据添加成功!共添加" + vm.testCaseList.length + "组测试数据"); + } + }, + function (file, percentage) { + vm.uploadProgress = parseInt(percentage * 100); + }); + } + }) +}); \ No newline at end of file diff --git a/static/src/js/config.js b/static/src/js/config.js index f0b1047..e2856f6 100644 --- a/static/src/js/config.js +++ b/static/src/js/config.js @@ -27,6 +27,7 @@ var require = { // ------ admin web 组件 ---------- pager: "components/pager", editorComponent: "components/editorComponent", + testCaseUploader: "components/testCaseUploader", // ------ 下面写的都不要直接用,而是使用上面的封装版本 ------ @@ -76,6 +77,7 @@ var require = { groupDetail_20_pack: "app/admin/group/groupDetail", editContest_21_pack: "app/admin/contest/editContest", group_22_pack: "app/admin/group/group", + settings_23_pack: "app/oj/account/settings" }, shim: { avalon: { diff --git a/template/src/admin/contest/add_contest.html b/template/src/admin/contest/add_contest.html index ce8fd14..010b73e 100644 --- a/template/src/admin/contest/add_contest.html +++ b/template/src/admin/contest/add_contest.html @@ -1,6 +1,9 @@
+
diff --git a/template/src/admin/contest/contest_list.html b/template/src/admin/contest/contest_list.html index 829f6b2..1781eac 100644 --- a/template/src/admin/contest/contest_list.html +++ b/template/src/admin/contest/contest_list.html @@ -41,153 +41,5 @@
- - -
diff --git a/template/src/admin/contest/edit_contest.html b/template/src/admin/contest/edit_contest.html index 2afc849..08f7c09 100644 --- a/template/src/admin/contest/edit_contest.html +++ b/template/src/admin/contest/edit_contest.html @@ -6,6 +6,9 @@ aria-hidden="true">← 返回 +
diff --git a/template/src/admin/contest/edit_problem.html b/template/src/admin/contest/edit_problem.html index f5ef148..14bbd6b 100644 --- a/template/src/admin/contest/edit_problem.html +++ b/template/src/admin/contest/edit_problem.html @@ -96,29 +96,7 @@ -

-
- 请将所有测试用例打包在一个文件中上传,所有文件要在压缩包的根目录,且输入输出文件名要以从1开始连续数字标识要对应例如:
- 1.in 1.out 2.in 2.out -
- - - - - - - - - - - -
编号输入文件名输出文件名
{{ $index + 1 }}{{ el.input }}{{ el.output }}
-
-
-
-
选择文件
-
-
+
diff --git a/template/src/admin/contest/problem_list.html b/template/src/admin/contest/problem_list.html index 0fb852a..a59bb6c 100644 --- a/template/src/admin/contest/problem_list.html +++ b/template/src/admin/contest/problem_list.html @@ -27,7 +27,7 @@ {{ el.total_accepted_number }}/{{ el.total_submit_number }} - + diff --git a/template/src/admin/problem/add_problem.html b/template/src/admin/problem/add_problem.html index 5c6b430..6d96a7b 100644 --- a/template/src/admin/problem/add_problem.html +++ b/template/src/admin/problem/add_problem.html @@ -99,30 +99,7 @@
-

-
- 请将所有测试用例打包在一个文件中上传,所有文件要在压缩包的根目录,且输入输出文件名要以从1开始连续数字标识要对应例如:
- 1.in 1.out 2.in 2.out -
-

上传进度%

- - - - - - - - - - - -
编号输入文件名输出文件名
{{$index + 1}}{{ el.input }}{{ el.output }}
-
-
-
-
选择文件
-
-
+
diff --git a/template/src/admin/problem/edit_problem.html b/template/src/admin/problem/edit_problem.html index 617573e..f9af36b 100644 --- a/template/src/admin/problem/edit_problem.html +++ b/template/src/admin/problem/edit_problem.html @@ -105,30 +105,7 @@
-

-
- 请将所有测试用例打包在一个文件中上传,所有文件要在压缩包的根目录,且输入输出文件名要以从1开始连续数字标识要对应例如:
- 1.in 1.out 2.in 2.out -
-

上传进度%

- - - - - - - - - - - -
编号输入文件名输出文件名
{{ $index + 1 }}{{ el.input }}{{ el.output }}
-
-
-
-
选择文件
-
-
+
diff --git a/template/src/oj/account/apply_reset_password.html b/template/src/oj/account/apply_reset_password.html new file mode 100644 index 0000000..5d65ccc --- /dev/null +++ b/template/src/oj/account/apply_reset_password.html @@ -0,0 +1,40 @@ +{% extends "oj_base.html" %} +{% block title %} + 找回登录信息 +{% endblock %} +{% block body %} +
+
+

找回登录信息


+
+

请输入你注册时使用的邮箱地址,系统将自动向你的邮箱发送一封含有您登录信息的电子邮件, + 你可以看到你的用户名,并可以选择重新设置登录密码,注意为了你的账户安全,重置密码链接仅在30分钟内有效

+
+
+ +
+ + + +
+
+
+    +

+ + +
+
+
+ +
+ +
+
+{% endblock %} +{% block js_block %} + +{% endblock %} \ No newline at end of file diff --git a/template/src/oj/account/avatar.html b/template/src/oj/account/avatar.html index e51d1c0..d551c4a 100644 --- a/template/src/oj/account/avatar.html +++ b/template/src/oj/account/avatar.html @@ -13,28 +13,39 @@
  • 修改密码
  • -
    - -
    -
    -
    -
    - {% for i in "aaaaaaaaaaaaaaaaaaaa" %} -
    -
    - +
    + +
    + +
    +
    +
    +
    + {% for i in "aaaaaaaaaaaaaaaaaaaa" %} +
    +
    + +
    + {% endfor %} + +
    + +
    + + 水果头像由coding.net提供。 +
    +
    +
    - {% endfor %}
    -
    - - 水果头像由coding.net提供。 -
    - - -
    +
    {% endblock %} + +{% block js_block %} + +{% endblock %} diff --git a/template/src/oj/account/change_password.html b/template/src/oj/account/change_password.html index d69357f..9fd1334 100644 --- a/template/src/oj/account/change_password.html +++ b/template/src/oj/account/change_password.html @@ -1,41 +1,61 @@ {% extends "oj_base.html" %} {% block title %} - 用户修改密码 +用户设置 {% endblock %} {% block body %} -
    -
    -

    修改密码

    +
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - -    -

    - -
    -
    -
    - -
    -
    -
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + +    +

    + + +
    +
    +
    + +
    +
    +
    +
    +
    {% endblock %} {% block js_block %} diff --git a/template/src/oj/account/login.html b/template/src/oj/account/login.html index adc5ca9..eadb5cc 100644 --- a/template/src/oj/account/login.html +++ b/template/src/oj/account/login.html @@ -32,6 +32,7 @@
    + 忘记用户名/密码
    还没有帐号?点击注册 diff --git a/template/src/oj/account/register.html b/template/src/oj/account/register.html index a5e95e9..f3b990b 100644 --- a/template/src/oj/account/register.html +++ b/template/src/oj/account/register.html @@ -18,6 +18,11 @@
    +
    + + +
    +
    diff --git a/template/src/oj/account/reset_password.html b/template/src/oj/account/reset_password.html index 71ce692..703ff6f 100644 --- a/template/src/oj/account/reset_password.html +++ b/template/src/oj/account/reset_password.html @@ -1,10 +1,53 @@ - - - - - - - +{% extends "oj_base.html" %} +{% block title %} + 找回登录信息 +{% endblock %} +{% block body %} +
    +
    +

    找回登录信息


    +
    - - \ No newline at end of file +
    +
    + + +
    +
    + + +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    +    +

    + + +
    +
    +
    + +
    +
    +
    +
    +{% endblock %} +{% block js_block %} + +{% endblock %} \ No newline at end of file diff --git a/template/src/oj/account/settings.html b/template/src/oj/account/settings.html index 6f88512..ebfee38 100644 --- a/template/src/oj/account/settings.html +++ b/template/src/oj/account/settings.html @@ -1,21 +1,20 @@ {% extends "oj_base.html" %} {% block title %} - 用户设置 +用户设置 {% endblock %} {% block body %} -
    +
    -
    - -
    +
    + +
    - +
    @@ -23,57 +22,78 @@
    -
    -
    -
    - -
    +
    +
    +
    + + +
    +
    - + +
    - + +
    - + +
    - + + +
    + +
    +
    + + +
    -
    -
    - +
    + +
    -
    +
    +{% endblock %} + +{% block js_block %} + {% endblock %} diff --git a/template/src/oj/account/user_index.html b/template/src/oj/account/user_index.html index 5ab6e44..a829a25 100644 --- a/template/src/oj/account/user_index.html +++ b/template/src/oj/account/user_index.html @@ -19,7 +19,9 @@ {% endifequal %} -

    {{ user.userprofile.mood }}

    + {% if user.userprofile.mood %} +

    {{ user.userprofile.mood }}

    + {% endif %}
    @@ -32,7 +34,7 @@ {% if user.userprofile.hduoj_username %}

    - + {{ user.userprofile.hduoj_username }}

    @@ -61,15 +63,17 @@ {{ user.create_time }}

    + +
    {{ user.userprofile.accepted_number }} AC
    -
    +
    {{ user.userprofile.submissions_number }} Submissions
    diff --git a/template/src/oj/contest/contest_rank.html b/template/src/oj/contest/contest_rank.html index 0236365..25d2735 100644 --- a/template/src/oj/contest/contest_rank.html +++ b/template/src/oj/contest/contest_rank.html @@ -72,24 +72,27 @@ onchange="if(this.checked){location.href=location.href + '&auto_refresh=true'}else{location.href=location.href=location.href.replace('&auto_refresh=true', '')}"> 自动刷新 + 上一页 + + {% endif %} + {% if paging_info.next_page %} + + {% endif %} + + {% else %}

    还没有结果

    + + 自动刷新 {% endif %}
    diff --git a/template/src/oj/index.html b/template/src/oj/index.html index 4a8edd3..0245084 100644 --- a/template/src/oj/index.html +++ b/template/src/oj/index.html @@ -100,6 +100,15 @@ max-width: 300px; } + .section{ + background-size: cover; + } + + #section0{ + background-image: url(/static/img/index/bg/bg3.jpg); + padding: 0 0 0 0; + } + @@ -110,12 +119,21 @@ @@ -172,7 +190,7 @@
    -

    自由举办小组赛(10月上线)

    +

    自由举办小组赛(12月上线)

    内部比赛,日常作业,期末考试,通通搞定

    diff --git a/template/src/oj_base.html b/template/src/oj_base.html index 87497fc..692bce1 100644 --- a/template/src/oj_base.html +++ b/template/src/oj_base.html @@ -61,9 +61,11 @@ diff --git a/template/src/utils/reset_password_email.html b/template/src/utils/reset_password_email.html index f4c8b93..5a0b591 100644 --- a/template/src/utils/reset_password_email.html +++ b/template/src/utils/reset_password_email.html @@ -8,7 +8,7 @@ - {{ website_name }} 密码找回邮件 + {{ website_name }} 登录信息找回 @@ -32,12 +32,12 @@ - 您刚刚在 {{ website_name }} 使用了找回密码功能。 + 您刚刚在 {{ website_name }} 申请了找回登录信息服务。 - 请在60分钟内点击下面链接设置您的新密码: + 请在30分钟内点击下面链接设置您的新密码: @@ -63,7 +63,8 @@ - 如果你没有提出过密码修改申请,请忽略此邮件。有可能是其他用户误填了你的用户名。我们不会对你的帐户进行任何修改。 + 如果您没有提出过该申请,请忽略此邮件。有可能是其他用户误填了您的邮件地址,我们不会对你的帐户进行任何修改。 + 请不要向他人透露本邮件的内容,否则可能会导致您的账号被盗。