From fdf6b39c783601d249fc4097ac3e86a031a9ae6c Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 12:31:28 +0800 Subject: [PATCH 01/19] add message to mq --- judge/judger_controller/tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/judge/judger_controller/tasks.py b/judge/judger_controller/tasks.py index ceb3f4e..8382deb 100644 --- a/judge/judger_controller/tasks.py +++ b/judge/judger_controller/tasks.py @@ -38,4 +38,5 @@ def judge(submission_id, time_limit, memory_limit, test_case_id): conn.commit() conn.close() r = redis.Redis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) - r.decr("judge_queue_length") \ No newline at end of file + r.decr("judge_queue_length") + r.lpush("queue", submission_id) From 46d2ea35c2e6884eac21232078d29c14ae0f1647 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 12:55:44 +0800 Subject: [PATCH 02/19] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=A2=98=E7=9B=AE?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=BB=93=E6=9E=9C=E8=AE=A1=E6=95=B0=E5=99=A8?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E6=B8=85=E9=9B=B6=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/src/js/app/oj/problem/problem.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/src/js/app/oj/problem/problem.js b/static/src/js/app/oj/problem/problem.js index cb3127a..009929b 100644 --- a/static/src/js/app/oj/problem/problem.js +++ b/static/src/js/app/oj/problem/problem.js @@ -59,6 +59,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirro if(counter++ > 10){ hideLoading(); bsAlert("抱歉,服务器可能出现了故障,请稍后到我的提交列表中查看"); + counter = 0; return; } $.ajax({ @@ -73,6 +74,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirro setTimeout(getResult, 1000); } else { + counter = 0; hideLoading(); $("#result").html(getResultHtml(data.data)); } From c12c227ee9bb32e15aa9e7ee673d7c2ce518875a Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 12:56:22 +0800 Subject: [PATCH 03/19] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20redis=20=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=98=9F=E5=88=97=E6=9D=A5=E4=BC=A0=E9=80=92=E9=A2=98?= =?UTF-8?q?=E7=9B=AE=E7=9A=84=E7=BB=93=E6=9E=9C=E3=80=82=E4=BB=8E=E8=80=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A2=98=E7=9B=AE=20ac=20=E5=92=8C=20ts=20?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- judge/judger_controller/tasks.py | 2 +- mq/__init__.py | 1 + mq/models.py | 1 + mq/scripts/__init__.py | 1 + mq/scripts/info.py | 37 ++++++++++++++++++++++++++++++++ oj/settings.py | 3 +++ submission/views.py | 12 ----------- 7 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 mq/__init__.py create mode 100644 mq/models.py create mode 100644 mq/scripts/__init__.py create mode 100644 mq/scripts/info.py diff --git a/judge/judger_controller/tasks.py b/judge/judger_controller/tasks.py index 8382deb..cc3d7d5 100644 --- a/judge/judger_controller/tasks.py +++ b/judge/judger_controller/tasks.py @@ -1,5 +1,5 @@ # coding=utf-8 -import datetime +import json import redis import MySQLdb import subprocess diff --git a/mq/__init__.py b/mq/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/mq/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/mq/models.py b/mq/models.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/mq/models.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/mq/scripts/__init__.py b/mq/scripts/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/mq/scripts/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/mq/scripts/info.py b/mq/scripts/info.py new file mode 100644 index 0000000..a4fa50e --- /dev/null +++ b/mq/scripts/info.py @@ -0,0 +1,37 @@ +# coding=utf-8 +import json +import redis +from judge.judger_controller.settings import redis_config +from judge.judger.result import result +from submission.models import Submission +from problem.models import Problem + + +class MessageQueue(object): + def __init__(self): + self.conn = redis.StrictRedis(host=redis_config["host"], port=redis_config["port"], db=redis_config["db"]) + self.queue = 'queue' + + def listen_task(self): + while True: + submission_id = self.conn.blpop(self.queue, 0)[1] + print submission_id + try: + submission = Submission.objects.get(id=submission_id) + except Submission.DoesNotExist: + print "error 1" + pass + + if submission.result == result["accepted"]: + # 更新题目的 ac 计数器 + try: + problem = Problem.objects.get(id=submission.problem_id) + problem.total_accepted_number += 1 + problem.save() + except Problem.DoesNotExist: + print "error 2" + pass + + +print "mq running" +MessageQueue().listen_task() diff --git a/oj/settings.py b/oj/settings.py index 8d012d0..a8d1570 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -52,7 +52,10 @@ INSTALLED_APPS = ( 'problem', 'admin', 'submission', + 'mq', + + 'django_extensions', 'rest_framework', 'rest_framework_swagger', ) diff --git a/submission/views.py b/submission/views.py index 26e8d3a..e92716c 100644 --- a/submission/views.py +++ b/submission/views.py @@ -60,18 +60,6 @@ class SubmissionAPIView(APIView): submission = Submission.objects.get(id=submission_id, user_id=request.user.id) except Submission.DoesNotExist: return error_response(u"提交不存在") - # 标记这个submission 已经被统计 - if not submission.is_counted: - submission.is_counted = True - submission.save() - if submission.result == result["accepted"]: - # 更新题目的 ac 计数器 - try: - problem = Problem.objects.get(id=submission.problem_id) - problem.total_accepted_number += 1 - problem.save() - except Problem.DoesNotExist: - pass response_data = {"result": submission.result} if submission.result == 0: response_data["accepted_answer_time"] = submission.accepted_answer_time From 40ed90885f022770c3b8c76a35a5c9c155c7690a Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 14:12:58 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=20log=20?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oj/settings.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/oj/settings.py b/oj/settings.py index a8d1570..d1c10fd 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -129,10 +129,16 @@ LOGGING = { # 日志格式 }, 'handlers': { - 'file_handler': { + 'django_error': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', - 'filename': LOG_PATH + 'info.log', + 'filename': LOG_PATH + 'django.log', + 'formatter': 'standard' + }, + 'app_info': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': LOG_PATH + 'app_info.log', 'formatter': 'standard' }, 'console': { @@ -142,13 +148,13 @@ LOGGING = { } }, 'loggers': { - 'info_logger': { - 'handlers': ['file_handler', "console"], + 'app_info_logger': { + 'handlers': ['app_info', "console"], 'level': 'DEBUG', 'propagate': True }, 'django.request': { - 'handlers': ['file_handler', 'console'], + 'handlers': ['django_error', 'console'], 'level': 'DEBUG', 'propagate': True, }, From f86ebd8ba32b8ff578e3a8c33ec87e900e50ca80 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 14:26:32 +0800 Subject: [PATCH 05/19] =?UTF-8?q?mq=20=E5=A2=9E=E5=8A=A0=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mq/scripts/info.py | 12 +++++++----- oj/settings.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mq/scripts/info.py b/mq/scripts/info.py index a4fa50e..b6482ed 100644 --- a/mq/scripts/info.py +++ b/mq/scripts/info.py @@ -1,11 +1,13 @@ # coding=utf-8 -import json +import logging import redis from judge.judger_controller.settings import redis_config from judge.judger.result import result from submission.models import Submission from problem.models import Problem +logger = logging.getLogger("app_info") + class MessageQueue(object): def __init__(self): @@ -15,11 +17,11 @@ class MessageQueue(object): def listen_task(self): while True: submission_id = self.conn.blpop(self.queue, 0)[1] - print submission_id + logger.debug("receive submission_id: " + submission_id) try: submission = Submission.objects.get(id=submission_id) except Submission.DoesNotExist: - print "error 1" + logger.warning("Submission does not exist, submission_id: " + submission_id) pass if submission.result == result["accepted"]: @@ -29,9 +31,9 @@ class MessageQueue(object): problem.total_accepted_number += 1 problem.save() except Problem.DoesNotExist: - print "error 2" + logger.warning("Submission problem does not exist, submission_id: " + submission_id) pass -print "mq running" +logger.debug("Start message queue") MessageQueue().listen_task() diff --git a/oj/settings.py b/oj/settings.py index ce699b4..5703566 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -148,7 +148,7 @@ LOGGING = { } }, 'loggers': { - 'app_info_logger': { + 'app_info': { 'handlers': ['app_info', "console"], 'level': 'DEBUG', 'propagate': True From 0bf84d1c4047ecfd74c526ffd5777d256a3e4b6a Mon Sep 17 00:00:00 2001 From: hohoTT <609029365@qq.com> Date: Sat, 22 Aug 2015 16:08:39 +0800 Subject: [PATCH 06/19] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=89=8D=E5=8F=B0?= =?UTF-8?q?=E6=AF=94=E8=B5=9B=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contest/views.py | 49 +++++++++++- oj/urls.py | 3 + template/oj/contest/contest_list.html | 106 ++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 template/oj/contest/contest_list.html diff --git a/contest/views.py b/contest/views.py index d00bee5..0f0cf70 100644 --- a/contest/views.py +++ b/contest/views.py @@ -1,15 +1,18 @@ # coding=utf-8 import json +import datetime from django.shortcuts import render from django.db import IntegrityError from django.utils import dateparse from django.db.models import Q +from django.core.paginator import Paginator from rest_framework.views import APIView from utils.shortcuts import (serializer_invalid_response, error_response, success_response, paginate, rand_str, error_page) from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN from group.models import Group +from announcement.models import Announcement from .models import Contest, ContestProblem from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer, @@ -17,7 +20,11 @@ from .serializers import (CreateContestSerializer, ContestSerializer, EditContes def contest_page(request, contest_id): - pass + try: + contest = Contest.objects.get(id=contest_id, visible=True) + except Contest.DoesNotExist: + return error_page(request, u"比赛不存在") + return render(request, "oj/contest/problems.html", {"contest": contest}) class ContestAdminAPIView(APIView): @@ -220,4 +227,42 @@ class ContestProblemAdminAPIView(APIView): contest_problem = contest_problem.filter(Q(title__contains=keyword) | Q(description__contains=keyword)) - return paginate(request, contest_problem, ContestProblemSerializer) \ No newline at end of file + return paginate(request, contest_problem, ContestProblemSerializer) + + +def contest_list_page(request, page=1): + # 正常情况 + contests = Contest.objects.filter(visible=True) + + # 搜索的情况 + keyword = request.GET.get("keyword", None) + if keyword: + contests = contests.filter(title__contains=keyword) + + paginator = Paginator(contests, 20) + try: + current_page = paginator.page(int(page)) + except Exception: + return error_page(request, u"不存在的页码") + + previous_page = next_page = None + + try: + previous_page = current_page.previous_page_number() + except Exception: + pass + + try: + next_page = current_page.next_page_number() + except Exception: + pass + + # 右侧的公告列表 + announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time") + # 系统当前时间 + now = datetime.datetime.now() + return render(request, "oj/contest/contest_list.html", + {"problems": current_page, "page": int(page), + "previous_page": previous_page, "next_page": next_page, + "keyword": keyword, "announcements": announcements, + "now": now}) diff --git a/oj/urls.py b/oj/urls.py index 6dc7606..106b966 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -40,12 +40,15 @@ urlpatterns = [ url(r'^api/admin/contest/$', ContestAdminAPIView.as_view(), name="contest_admin_api"), url(r'^api/admin/user/$', UserAdminAPIView.as_view(), name="user_admin_api"), url(r'^problem/(?P\d+)/$', "problem.views.problem_page", name="problem_page"), + url(r'^contest/(?P\d+)/$', "contest.views.contest_page", name="contest_page"), url(r'^announcement/(?P\d+)/$', "announcement.views.announcement_page", name="announcement_page"), url(r'^admin/contest/$', TemplateView.as_view(template_name="admin/contest/add_contest.html"), name="add_contest_page"), url(r'^problems/$', "problem.views.problem_list_page", name="problem_list_page"), url(r'^problems/(?P\d+)/$', "problem.views.problem_list_page", name="problem_list_page"), + url(r'^contests/$', "contest.views.contest_list_page", name="contest_list_page"), + url(r'^contests/(?P\d+)/$', "contest.views.contest_list_page", name="contest_list_page"), url(r'^admin/template/(?P\w+)/(?P\w+).html$', AdminTemplateView.as_view(), name="admin_template"), url(r'^api/admin/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"), diff --git a/template/oj/contest/contest_list.html b/template/oj/contest/contest_list.html new file mode 100644 index 0000000..3233a6e --- /dev/null +++ b/template/oj/contest/contest_list.html @@ -0,0 +1,106 @@ +{% extends "oj_base.html" %} +{% block body %} + {% load problem %} +
+
+
+
+
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + {% for item in contests %} + + + + + + {% ifequal item.mode 0 %} + + {% endifequal %} + {% ifequal item.mode 1 %} + + {% endifequal %} + {% ifequal item.mode 2 %} + + {% endifequal %} + + {% ifequal item.contest_type 0 %} + + {% endifequal %} + {% ifequal item.contest_type 1 %} + + {% endifequal %} + {% ifequal item.contest_type 2 %} + + {% endifequal %} + + {% if now < item_start_time %} + + {% elif item.start_time <= now and now <= item_end_time %} + + {% else %} + + {% endif %} + + {% endfor %} + +
#比赛名称开始时间比赛模式比赛类型状态
{{ item.id }}{{ item.title }}{{ item.start_time }}acm模式AC数量模式AC总分排名模式小组赛公开赛公开赛(密码保护)比赛还未开始比赛正在进行比赛已结束
+ +
+
+ +
+
+
+

+ + 公告 +

+
+ {% for item in announcements %} + {{ forloop.counter }}.  {{ item.title }} +
+ {% endfor %} +
+
+
+
+
+{% endblock %} + +{% block js_block %} + +{% endblock %} \ No newline at end of file From 48d48a0f3040115b75c9fc3d9f0126c7d25b2896 Mon Sep 17 00:00:00 2001 From: hohoTT <609029365@qq.com> Date: Sat, 22 Aug 2015 20:42:21 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=AF=94?= =?UTF-8?q?=E8=B5=9B=E5=88=97=E8=A1=A8=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contest/views.py | 12 +++-- .../oj/announcement/_announcement_panel.html | 12 +++++ template/oj/contest/contest_list.html | 51 +++++-------------- template/oj/problem/problem_list.html | 13 +---- utils/templatetags/contest.py | 29 +++++++++++ 5 files changed, 64 insertions(+), 53 deletions(-) create mode 100644 template/oj/announcement/_announcement_panel.html create mode 100644 utils/templatetags/contest.py diff --git a/contest/views.py b/contest/views.py index 0f0cf70..cabfff9 100644 --- a/contest/views.py +++ b/contest/views.py @@ -1,6 +1,7 @@ # coding=utf-8 import json import datetime +from django.utils.timezone import localtime from django.shortcuts import render from django.db import IntegrityError from django.utils import dateparse @@ -209,7 +210,7 @@ class ContestProblemAdminAPIView(APIView): """ 比赛题目分页json api接口 --- - response_serializer: ProblemSerializer + response_serializer: ContestProblemSerializer """ contest_problem_id = request.GET.get("contest_problem_id", None) if contest_problem_id: @@ -239,6 +240,11 @@ def contest_list_page(request, page=1): if keyword: contests = contests.filter(title__contains=keyword) + # 筛选我能参加的比赛 + join = request.GET.get("join", None) + if join: + contests = Contest.objects.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())) + paginator = Paginator(contests, 20) try: current_page = paginator.page(int(page)) @@ -262,7 +268,7 @@ def contest_list_page(request, page=1): # 系统当前时间 now = datetime.datetime.now() return render(request, "oj/contest/contest_list.html", - {"problems": current_page, "page": int(page), + {"contests": current_page, "page": int(page), "previous_page": previous_page, "next_page": next_page, "keyword": keyword, "announcements": announcements, - "now": now}) + "join": join, "now": now}) diff --git a/template/oj/announcement/_announcement_panel.html b/template/oj/announcement/_announcement_panel.html new file mode 100644 index 0000000..f17a9ce --- /dev/null +++ b/template/oj/announcement/_announcement_panel.html @@ -0,0 +1,12 @@ +
+
+

+ + 公告 +

+
+ {% for item in announcements %} +

{{ forloop.counter }}.  {{ item.title }}

+ {% endfor %} +
+
\ No newline at end of file diff --git a/template/oj/contest/contest_list.html b/template/oj/contest/contest_list.html index 3233a6e..1689cee 100644 --- a/template/oj/contest/contest_list.html +++ b/template/oj/contest/contest_list.html @@ -1,6 +1,6 @@ {% extends "oj_base.html" %} {% block body %} - {% load problem %} + {% load contest %}
@@ -18,11 +18,9 @@ - - @@ -30,19 +28,9 @@ {% for item in contests %} - - {% ifequal item.mode 0 %} - - {% endifequal %} - {% ifequal item.mode 1 %} - - {% endifequal %} - {% ifequal item.mode 2 %} - - {% endifequal %} {% ifequal item.contest_type 0 %} @@ -54,27 +42,26 @@ {% endifequal %} - {% if now < item_start_time %} - - {% elif item.start_time <= now and now <= item_end_time %} - - {% else %} - - {% endif %} + {% endfor %}
# 比赛名称 开始时间比赛模式 比赛类型 状态
{{ item.id }} {{ item.title }} {{ item.start_time }}acm模式AC数量模式AC总分排名模式小组赛公开赛(密码保护)比赛还未开始比赛正在进行比赛已结束{{ item|contest_status }}
+
+ +
-
-
-

- - 公告 -

-
- {% for item in announcements %} - {{ forloop.counter }}.  {{ item.title }} -
- {% endfor %} -
-
+ {% include "oj/announcement/_announcement_panel.html" %}
{% endblock %} + {% block js_block %} - {% endblock %} \ No newline at end of file diff --git a/template/oj/problem/problem_list.html b/template/oj/problem/problem_list.html index 073cfe7..21833a7 100644 --- a/template/oj/problem/problem_list.html +++ b/template/oj/problem/problem_list.html @@ -55,18 +55,7 @@
-
-
-

- - 公告 -

-
- {% for item in announcements %} - {{ forloop.counter }}.  {{ item.title }} - {% endfor %} -
-
+ {% include "oj/announcement/_announcement_panel.html" %}

diff --git a/utils/templatetags/contest.py b/utils/templatetags/contest.py new file mode 100644 index 0000000..818d7f6 --- /dev/null +++ b/utils/templatetags/contest.py @@ -0,0 +1,29 @@ +# coding=utf-8 +import datetime +from django.utils.timezone import localtime + + +def get_contest_status(contest): + now = datetime.datetime.now() + if localtime(contest.start_time).replace(tzinfo=None) > now: + return "没有开始" + if localtime(contest.end_time).replace(tzinfo=None) < now: + return "已经结束" + return "正在进行" + + +def get_contest_status_color(contest): + now = datetime.datetime.now() + if localtime(contest.start_time).replace(tzinfo=None) > now: + return "info" + if localtime(contest.end_time).replace(tzinfo=None) < now: + return "warning" + return "success" + + +from django import template + +register = template.Library() +register.filter("contest_status", get_contest_status) +register.filter("contest_status_color", get_contest_status_color) + From 1fe35bd6e0ed529156141e3eaf8ca3c3dd25c8c0 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 20:46:52 +0800 Subject: [PATCH 08/19] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E4=BA=86=E5=8D=95?= =?UTF-8?q?=E4=B8=AA=E6=AF=94=E8=B5=9B=E7=9A=84=E8=AF=A6=E6=83=85=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contest/models.py | 4 +- contest/serializers.py | 5 ++ contest/views.py | 66 +++++++++++++++++-- oj/urls.py | 4 +- .../src/js/app/oj/contest/contest_password.js | 24 +++++++ template/oj/contest/_contest_header.html | 38 +++++++++++ template/oj/contest/contest_index.html | 21 ++++++ template/oj/contest/contest_no_privilege.html | 26 ++++++++ 8 files changed, 178 insertions(+), 10 deletions(-) create mode 100644 static/src/js/app/oj/contest/contest_password.js create mode 100644 template/oj/contest/_contest_header.html create mode 100644 template/oj/contest/contest_index.html create mode 100644 template/oj/contest/contest_no_privilege.html diff --git a/contest/models.py b/contest/models.py index 0ece5ff..3611c3a 100644 --- a/contest/models.py +++ b/contest/models.py @@ -9,19 +9,17 @@ from group.models import Group class Contest(models.Model): title = models.CharField(max_length=40, unique=True) description = models.TextField() - # 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式,2 即为按照 ac 的题目的总分排名模式 + # 比赛模式:0 即为是acm模式,1 即为是按照总的 ac 题目数量排名模式 mode = models.IntegerField() # 是否显示排名结果 show_rank = models.BooleanField() # 是否显示别人的提交记录 show_user_submission = models.BooleanField() - # 只能超级管理员创建公开赛,管理员只能创建小组内部的比赛 # 如果这一项不为空,即为有密码的公开赛,没有密码的可以为小组赛或者是公开赛(此时用比赛的类型来表示) password = models.CharField(max_length=30, blank=True, null=True) # 比赛的类型: 0 即为是小组赛,1 即为是无密码的公开赛,2 即为是有密码的公开赛 contest_type = models.IntegerField() - # 开始时间 start_time = models.DateTimeField() # 结束时间 diff --git a/contest/serializers.py b/contest/serializers.py index dd9f370..23d8474 100644 --- a/contest/serializers.py +++ b/contest/serializers.py @@ -103,3 +103,8 @@ class EditContestProblemSerializer(serializers.Serializer): sort_index = serializers.CharField(max_length=30) +class ContestPasswordVerifySerializer(serializers.Serializer): + contest_id = serializers.IntegerField() + password = serializers.CharField(max_length=30) + + diff --git a/contest/views.py b/contest/views.py index d00bee5..34271b1 100644 --- a/contest/views.py +++ b/contest/views.py @@ -9,15 +9,13 @@ from utils.shortcuts import (serializer_invalid_response, error_response, success_response, paginate, rand_str, error_page) from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN +from account.decorators import login_required from group.models import Group from .models import Contest, ContestProblem from .serializers import (CreateContestSerializer, ContestSerializer, EditContestSerializer, - CreateContestProblemSerializer, ContestProblemSerializer, EditContestProblemSerializer) - - -def contest_page(request, contest_id): - pass + CreateContestProblemSerializer, ContestProblemSerializer, + EditContestProblemSerializer, ContestPasswordVerifySerializer) class ContestAdminAPIView(APIView): @@ -220,4 +218,60 @@ class ContestProblemAdminAPIView(APIView): contest_problem = contest_problem.filter(Q(title__contains=keyword) | Q(description__contains=keyword)) - return paginate(request, contest_problem, ContestProblemSerializer) \ No newline at end of file + return paginate(request, contest_problem, ContestProblemSerializer) + + +class ContestPasswordVerifyAPIView(APIView): + @login_required + def post(self, request): + serializer = ContestPasswordVerifySerializer(data=request.data) + if serializer.is_valid(): + data = request.data + try: + contest = Contest.objects.get(id=data["contest_id"], contest_type=2) + except Contest.DoesNotExist: + return error_response(u"密码错误") + + if data["password"] != contest.password: + return error_response(u" 密码错误") + else: + print request.session.get("contests", None) + if "contests" not in request.session: + request.session["contests"] = [] + request.session["contests"].append(int(data["contest_id"])) + print request.session["contests"] + + return success_response(True) + else: + return serializer_invalid_response(serializer) + + +def check_user_contest_permission(request, contest): + # 有密码的公开赛 + if contest.contest_type == 2: + # 没有输入过密码 + if contest.id not in request.session.get("contests", []): + return {"result": False, "reason": "password_protect"} + + # 指定小组参加的 + if contest.contest_type == 0: + if not contest.groups.filter(id__in=request.user.group_set.all()).exists(): + return {"result": False, "reason": "limited_group"} + return {"result": True} + + +@login_required +def contest_page(request, contest_id): + print request.session.get("contests", None) + try: + contest = Contest.objects.get(id=contest_id) + except Contest.DoesNotExist: + return error_page(request, u"比赛不存在") + + Contest.objects.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())) + + result = check_user_contest_permission(request, contest) + if not result["result"]: + return render(request, "oj/contest/contest_no_privilege.html", {"contenst": contest, "reason": result["reason"]}) + + return render(request, "oj/contest/contest_index.html", {"contest": contest}) \ No newline at end of file diff --git a/oj/urls.py b/oj/urls.py index 6dc7606..2f66950 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -8,7 +8,7 @@ from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterA UserAdminAPIView, UserInfoAPIView) from announcement.views import AnnouncementAdminAPIView -from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView +from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView, ContestPasswordVerifyAPIView from group.views import (GroupAdminAPIView, GroupMemberAdminAPIView, JoinGroupAPIView, JoinGroupRequestAdminAPIView) @@ -64,5 +64,7 @@ urlpatterns = [ url(r'^api/submission/$', SubmissionAPIView.as_view(), name="submission_api"), url(r'^api/admin/submission/$', SubmissionAdminAPIView.as_view(), name="submission_admin_api_view"), url(r'^api/admin/monitor/$', QueueLengthMonitorAPIView.as_view(), name="queue_length_monitor_api"), + url(r'^contest/(?P\d+)/$', "contest.views.contest_page", name="contest_page"), + url(r'^api/contest/password/$', ContestPasswordVerifyAPIView.as_view(), name="contest_password_verify_api"), ] diff --git a/static/src/js/app/oj/contest/contest_password.js b/static/src/js/app/oj/contest/contest_password.js new file mode 100644 index 0000000..24d785f --- /dev/null +++ b/static/src/js/app/oj/contest/contest_password.js @@ -0,0 +1,24 @@ +require(["jquery", "bsAlert", "csrfToken"], function($, bsAlert, csrfTokenHeader){ + $("#contest-password-btn").click(function(){ + var password = $("#contest-password").val(); + if(!password){ + bsAlert("密码不能为空!"); + return; + } + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/contest/password/", + data: {password: password, contest_id: location.href.split("/")[4]}, + method: "post", + dataType: "json", + success: function(data){ + if(!data.code){ + location.reload(); + } + else{ + bsAlert(data.data); + } + } + }) + }) +}); \ No newline at end of file diff --git a/template/oj/contest/_contest_header.html b/template/oj/contest/_contest_header.html new file mode 100644 index 0000000..62892ce --- /dev/null +++ b/template/oj/contest/_contest_header.html @@ -0,0 +1,38 @@ +{% load contest %} +

{{ contest.title }}

+ +
+
+ + + + + + + + + + + + + + + + {% ifequal contest.contest_type 0 %} + + {% endifequal %} + {% ifequal contest.contest_type 1 %} + + {% endifequal %} + {% ifequal contest.contest_type 2 %} + + {% endifequal %} + + + + +
开始时间结束时间状态比赛类型创建者
{{ contest.start_time }}{{ contest.end_time }}{{ contest|contest_status }}小组赛公开赛公开赛(密码保护){{ contest.created_by.username }}
+
+
{{ contest.description|safe }}
+
+

\ No newline at end of file diff --git a/template/oj/contest/contest_index.html b/template/oj/contest/contest_index.html new file mode 100644 index 0000000..7d32910 --- /dev/null +++ b/template/oj/contest/contest_index.html @@ -0,0 +1,21 @@ +{% extends 'oj_base.html' %} + +{% block body %} +
+ + {% include "oj/contest/_contest_header.html" %} +
+{% endblock %} \ No newline at end of file diff --git a/template/oj/contest/contest_no_privilege.html b/template/oj/contest/contest_no_privilege.html new file mode 100644 index 0000000..12c7fca --- /dev/null +++ b/template/oj/contest/contest_no_privilege.html @@ -0,0 +1,26 @@ +{% extends 'oj_base.html' %} + +{% block body %} +
+ + {% include "oj/contest/_contest_header.html" %} + {% ifequal reason "password_protect" %} +
+
+ + +
+ +
+ {% else %} + + {% endifequal %} +
+{% endblock %} +{% block js_block %} + +{% endblock %} \ No newline at end of file From c2a2035e1339a856c7f2797d94d2ec5ba2c02e79 Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 20:54:32 +0800 Subject: [PATCH 09/19] fix typo --- contest/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contest/views.py b/contest/views.py index 4ddff2c..283f23e 100644 --- a/contest/views.py +++ b/contest/views.py @@ -267,7 +267,6 @@ def check_user_contest_permission(request, contest): @login_required def contest_page(request, contest_id): - print request.session.get("contests", None) try: contest = Contest.objects.get(id=contest_id) except Contest.DoesNotExist: @@ -277,7 +276,7 @@ def contest_page(request, contest_id): result = check_user_contest_permission(request, contest) if not result["result"]: - return render(request, "oj/contest/contest_no_privilege.html", {"contenst": contest, "reason": result["reason"]}) + return render(request, "oj/contest/contest_no_privilege.html", {"contest": contest, "reason": result["reason"]}) return render(request, "oj/contest/contest_index.html", {"contest": contest}) From 44f9ea862c322bb2a100fde5f1bb7423dc7076eb Mon Sep 17 00:00:00 2001 From: virusdefender <1670873886@qq.com> Date: Sat, 22 Aug 2015 21:03:00 +0800 Subject: [PATCH 10/19] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=A0=8F=E7=9A=84=20url=EF=BC=9B=E4=BF=AE=E5=A4=8D=E6=88=91?= =?UTF-8?q?=E7=9A=84=E6=8F=90=E4=BA=A4=E9=A1=BA=E5=BA=8F=E9=A1=BA=E5=BA=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- submission/views.py | 4 +- .../oj/submission/my_submissions_list.html | 110 ++++++++---------- template/oj_base.html | 2 +- 3 files changed, 54 insertions(+), 62 deletions(-) diff --git a/submission/views.py b/submission/views.py index 792660a..ab8fb71 100644 --- a/submission/views.py +++ b/submission/views.py @@ -117,7 +117,7 @@ class SubmissionAdminAPIView(APIView): @login_required def my_submission_list_page(request, page=1): submissions = Submission.objects.filter(user_id=request.user.id). \ - values("id", "result", "create_time", "accepted_answer_time", "language") + values("id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time") paginator = Paginator(submissions, 20) try: current_page = paginator.page(int(page)) @@ -135,4 +135,4 @@ def my_submission_list_page(request, page=1): return render(request, "oj/submission/my_submissions_list.html", {"submissions": current_page, "page": int(page), - "previous_page": previous_page, "next_page": next_page, "startId":int(page)*20-20}) + "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20}) diff --git a/template/oj/submission/my_submissions_list.html b/template/oj/submission/my_submissions_list.html index c6fcacb..4b939ec 100644 --- a/template/oj/submission/my_submissions_list.html +++ b/template/oj/submission/my_submissions_list.html @@ -2,65 +2,57 @@ {% block body %} -{% load submission %} -
- + {% load submission %} +
+ {% if submissions %} + + + + + + + + + + + + {% for item in submissions %} + + + + + + + + {% endfor %} -
- {% if submissions %} -
#提交时间结果运行时间语言
+ {{ forloop.counter |add:start_id }}{{ item.create_time }}{{ item.result|translate_result }} + {% if item.accepted_answer_time %} + {{ item.accepted_answer_time }}ms + {% else %} + -- + {% endif %} + + {{ item.language|translate_language }} +
- - - - - - - - - - - {% for item in submissions %} - - - - - +
#提交时间结果运行时间语言
- {{ forloop.counter |add:startId }}{{ item.create_time }}{{ item.result|translate_result }} - {% if item.accepted_answer_time %} - {{ item.accepted_answer_time }}ms - {% else %} - -- +
+ {% else %} +

你还没有提交记录!

+ {% endif %} +
+ {% if next_page %} + + {% endif %} + + +
{% endblock %} diff --git a/template/oj_base.html b/template/oj_base.html index 082cad5..61b887a 100644 --- a/template/oj_base.html +++ b/template/oj_base.html @@ -43,7 +43,7 @@ ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/static/src/js/lib/formValidation/base.js b/static/src/js/lib/formValidation/base.js deleted file mode 100755 index a49bbc7..0000000 --- a/static/src/js/lib/formValidation/base.js +++ /dev/null @@ -1,2290 +0,0 @@ -/** - * FormValidation (http://formvalidation.io) - * The best jQuery plugin to validate form fields. Support Bootstrap, Foundation, Pure, SemanticUI, UIKit frameworks - * - * @author https://twitter.com/nghuuphuoc - * @copyright (c) 2013 - 2015 Nguyen Huu Phuoc - * @license http://formvalidation.io/license/ - */ - -(function(root, factory) { - - "use strict"; - - // AMD module is defined - if (typeof define === "function" && define.amd) { - define("base", ["jquery"], factory); - } else { - // planted over the root! - root.FormValidation = factory(root.jQuery); - } - -}(this, function ($) { - - // Register the namespace - var FormValidation = { - AddOn: {}, // Add-ons - Framework: {}, // Supported frameworks - I18n: {}, // i18n - Validator: {}, // Available validators - Helper: {} // Helper object - }; - - if (typeof jQuery === 'undefined') { - throw new Error('FormValidation requires jQuery'); - } - - // identify jquery version - (function($) { - var version = $.fn.jquery.split(' ')[0].split('.'); - if ((+version[0] < 2 && +version[1] < 9) || (+version[0] === 1 && +version[1] === 9 && +version[2] < 1)) { - throw new Error('FormValidation requires jQuery version 1.9.1 or higher'); - } - }(jQuery)); - - (function($) { - // TODO: Remove backward compatibility in v0.7.0 - /** - * Constructor - * - * @param {jQuery|String} form The form element or selector - * @param {Object} options The options - * @param {String} [namespace] The optional namespace which is used for data-{namespace}-xxx attributes and internal data. - * Currently, it's used to support backward version - * @constructor - */ - FormValidation.Base = function(form, options, namespace) { - this.$form = $(form); - this.options = $.extend({}, $.fn.formValidation.DEFAULT_OPTIONS, options); - this._namespace = namespace || 'fv'; - - this.$invalidFields = $([]); // Array of invalid fields - this.$submitButton = null; // The submit button which is clicked to submit form - this.$hiddenButton = null; - - // Validating status - this.STATUS_NOT_VALIDATED = 'NOT_VALIDATED'; - this.STATUS_VALIDATING = 'VALIDATING'; - this.STATUS_INVALID = 'INVALID'; - this.STATUS_VALID = 'VALID'; - - // Determine the event that is fired when user change the field value - // Most modern browsers supports input event except IE 7, 8. - // IE 9 supports input event but the event is still not fired if I press the backspace key. - // Get IE version - // https://gist.github.com/padolsey/527683/#comment-7595 - var ieVersion = (function() { - var v = 3, div = document.createElement('div'), a = div.all || []; - while (div.innerHTML = '', a[0]) {} - return v > 4 ? v : !v; - }()); - - var el = document.createElement('div'); - this._changeEvent = (ieVersion === 9 || !('oninput' in el)) ? 'keyup' : 'input'; - - // The flag to indicate that the form is ready to submit when a remote/callback validator returns - this._submitIfValid = null; - - // Field elements - this._cacheFields = {}; - - this._init(); - }; - - FormValidation.Base.prototype = { - constructor: FormValidation.Base, - - /** - * Check if the number of characters of field value exceed the threshold or not - * - * @param {jQuery} $field The field element - * @returns {Boolean} - */ - _exceedThreshold: function($field) { - var ns = this._namespace, - field = $field.attr('data-' + ns + '-field'), - threshold = this.options.fields[field].threshold || this.options.threshold; - if (!threshold) { - return true; - } - var cannotType = $.inArray($field.attr('type'), ['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit']) !== -1; - return (cannotType || $field.val().length >= threshold); - }, - - /** - * Init form - */ - _init: function() { - var that = this, - ns = this._namespace, - options = { - addOns: {}, - autoFocus: this.$form.attr('data-' + ns + '-autofocus'), - button: { - selector: this.$form.attr('data-' + ns + '-button-selector') || this.$form.attr('data-' + ns + '-submitbuttons'), // Support backward - disabled: this.$form.attr('data-' + ns + '-button-disabled') - }, - control: { - valid: this.$form.attr('data-' + ns + '-control-valid'), - invalid: this.$form.attr('data-' + ns + '-control-invalid') - }, - err: { - clazz: this.$form.attr('data-' + ns + '-err-clazz'), - container: this.$form.attr('data-' + ns + '-err-container') || this.$form.attr('data-' + ns + '-container'), // Support backward - parent: this.$form.attr('data-' + ns + '-err-parent') - }, - events: { - formInit: this.$form.attr('data-' + ns + '-events-form-init'), - formError: this.$form.attr('data-' + ns + '-events-form-error'), - formSuccess: this.$form.attr('data-' + ns + '-events-form-success'), - fieldAdded: this.$form.attr('data-' + ns + '-events-field-added'), - fieldRemoved: this.$form.attr('data-' + ns + '-events-field-removed'), - fieldInit: this.$form.attr('data-' + ns + '-events-field-init'), - fieldError: this.$form.attr('data-' + ns + '-events-field-error'), - fieldSuccess: this.$form.attr('data-' + ns + '-events-field-success'), - fieldStatus: this.$form.attr('data-' + ns + '-events-field-status'), - localeChanged: this.$form.attr('data-' + ns + '-events-locale-changed'), - validatorError: this.$form.attr('data-' + ns + '-events-validator-error'), - validatorSuccess: this.$form.attr('data-' + ns + '-events-validator-success') - }, - excluded: this.$form.attr('data-' + ns + '-excluded'), - icon: { - valid: this.$form.attr('data-' + ns + '-icon-valid') || this.$form.attr('data-' + ns + '-feedbackicons-valid'), // Support backward - invalid: this.$form.attr('data-' + ns + '-icon-invalid') || this.$form.attr('data-' + ns + '-feedbackicons-invalid'), // Support backward - validating: this.$form.attr('data-' + ns + '-icon-validating') || this.$form.attr('data-' + ns + '-feedbackicons-validating'), // Support backward - feedback: this.$form.attr('data-' + ns + '-icon-feedback') - }, - live: this.$form.attr('data-' + ns + '-live'), - locale: this.$form.attr('data-' + ns + '-locale'), - message: this.$form.attr('data-' + ns + '-message'), - onError: this.$form.attr('data-' + ns + '-onerror'), - onSuccess: this.$form.attr('data-' + ns + '-onsuccess'), - row: { - selector: this.$form.attr('data-' + ns + '-row-selector') || this.$form.attr('data-' + ns + '-group'), // Support backward - valid: this.$form.attr('data-' + ns + '-row-valid'), - invalid: this.$form.attr('data-' + ns + '-row-invalid'), - feedback: this.$form.attr('data-' + ns + '-row-feedback') - }, - threshold: this.$form.attr('data-' + ns + '-threshold'), - trigger: this.$form.attr('data-' + ns + '-trigger'), - verbose: this.$form.attr('data-' + ns + '-verbose'), - fields: {} - }; - - this.$form - // Disable client side validation in HTML 5 - .attr('novalidate', 'novalidate') - .addClass(this.options.elementClass) - // Disable the default submission first - .on('submit.' + ns, function(e) { - e.preventDefault(); - that.validate(); - }) - .on('click.' + ns, this.options.button.selector, function() { - that.$submitButton = $(this); - // The user just click the submit button - that._submitIfValid = true; - }) - // Find all fields which have either "name" or "data-{namespace}-field" attribute - .find('[name], [data-' + ns + '-field]') - .each(function() { - var $field = $(this), - field = $field.attr('name') || $field.attr('data-' + ns + '-field'), - opts = that._parseOptions($field); - if (opts) { - $field.attr('data-' + ns + '-field', field); - options.fields[field] = $.extend({}, opts, options.fields[field]); - } - }); - - this.options = $.extend(true, this.options, options); - - // Normalize the err.parent option - if ('string' === typeof this.options.err.parent) { - this.options.err.parent = new RegExp(this.options.err.parent); - } - - // Support backward - if (this.options.container) { - this.options.err.container = this.options.container; - delete this.options.container; - } - if (this.options.feedbackIcons) { - this.options.icon = $.extend(true, this.options.icon, this.options.feedbackIcons); - delete this.options.feedbackIcons; - } - if (this.options.group) { - this.options.row.selector = this.options.group; - delete this.options.group; - } - if (this.options.submitButtons) { - this.options.button.selector = this.options.submitButtons; - delete this.options.submitButtons; - } - - // If the locale is not found, reset it to default one - if (!FormValidation.I18n[this.options.locale]) { - this.options.locale = $.fn.formValidation.DEFAULT_OPTIONS.locale; - } - - // Parse the add-on options from HTML attributes - this.options = $.extend(true, this.options, { addOns: this._parseAddOnOptions() }); - - // When pressing Enter on any field in the form, the first submit button will do its job. - // The form then will be submitted. - // I create a first hidden submit button - this.$hiddenButton = $('