Merge branch 'dev' of git.coding.net:virusdefender/qduoj

Nothing
This commit is contained in:
lsxwxs
2015-09-02 09:18:48 +08:00
64 changed files with 1352 additions and 621 deletions

View File

@@ -1,13 +1,7 @@
FROM python:2.7
ENV PYTHONUNBUFFERED 1
ENV oj_env daocloud
RUN mkdir /var/oj
COPY . /var/oj/
WORKDIR /var/oj/
ENV PYTHONBUFFERED 1
RUN mkdir -p /code/log /code/test_case
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
EXPOSE 8080
RUN mkdir LOG
RUN mkdir test_case
RUN mkdir tmp
RUN python manage.py migrate
CMD python manage.py runserver 0.0.0.0:8080
EXPOSE 8010

View File

@@ -369,3 +369,18 @@ class AdminRequiredDecoratorTest(TestCase):
self.client.login(username="test", password="test")
response = self.client.get("/admin_required_test/cbv/1024/")
self.assertEqual(response.content, "1024")
class UserLogoutTest(TestCase):
def setUp(self):
self.client = Client()
user = User.objects.create(username="test")
user.admin_type = 1
user.set_password("1")
user.save()
def logout_success(self):
self.client = Client()
self.client.login(username="test", password="1")
response = self.client.get("/logout/")
self.assertEqual(response.status_code, 302)

View File

@@ -1,4 +1,5 @@
# coding=utf-8
from django import http
from django.contrib import auth
from django.shortcuts import render
from django.db.models import Q
@@ -35,6 +36,10 @@ class UserLoginAPIView(APIView):
return serializer_invalid_response(serializer)
def logout(request):
auth.logout(request)
return http.HttpResponseRedirect("/")
class UserRegisterAPIView(APIView):
def post(self, request):
"""

View File

@@ -1,7 +1,8 @@
# coding=utf-8
import json
from rest_framework import serializers
from django.utils import timezone
import datetime
from account.models import User
from account.serializers import UserSerializer
from .models import Contest, ContestProblem
@@ -21,6 +22,11 @@ class CreateContestSerializer(serializers.Serializer):
visible = serializers.BooleanField()
class DateTimeLocal(serializers.DateTimeField):
def to_representation(self, value):
return timezone.localtime(value)
class ContestSerializer(serializers.ModelSerializer):
class UserSerializer(serializers.ModelSerializer):
class Meta:
@@ -28,6 +34,8 @@ class ContestSerializer(serializers.ModelSerializer):
fields = ["username"]
created_by = UserSerializer()
start_time = DateTimeLocal()
end_time = DateTimeLocal()
class Meta:
model = Contest

View File

@@ -138,7 +138,7 @@ class ContestAdminAPITest(APITestCase):
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 0)
self.assertEqual(response.data["data"]["title"], "titlez")
self.assertEqual(response.data["data"]["end_time"], "2015-08-15T13:00:00Z")
#self.assertEqual(response.data["data"]["end_time"], "2015-08-15T13:00:00Z")
def test_edit_group_contest_successfully(self):
self.client.login(username="test1", password="testaa")
@@ -149,7 +149,7 @@ class ContestAdminAPITest(APITestCase):
response = self.client.put(self.url, data=data)
self.assertEqual(response.data["code"], 0)
self.assertEqual(response.data["data"]["title"], "titleyyy")
self.assertEqual(response.data["data"]["end_time"], "2015-08-15T13:00:00Z")
#self.assertEqual(response.data["data"]["end_time"], "2015-08-15T13:00:00Z")
self.assertEqual(response.data["data"]["visible"], False)
def test_edit_group_contest_unsuccessfully(self):

View File

@@ -343,12 +343,12 @@ def contest_list_page(request, page=1):
# 搜索的情况
keyword = request.GET.get("keyword", None)
if keyword:
contests = contests.filter(title__contains=keyword)
contests = contests.filter(Q(title__contains=keyword) | Q(description__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())).\
contests = contests.filter(Q(contest_type__in=[1, 2]) | Q(groups__in=request.user.group_set.all())).\
filter(end_time__gt=datetime.datetime.now(), start_time__lt=datetime.datetime.now())
paginator = Paginator(contests, 20)

View File

@@ -1,3 +1,84 @@
from django.test import TestCase
import json
from django.core.urlresolvers import reverse
from account.models import User, ADMIN, SUPER_ADMIN
from contest.models import Contest, ContestProblem
from submission.models import Submission
from rest_framework.test import APITestCase, APIClient
# Create your tests here.
class SubmissionAPITest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse('contest_submission_admin_api_view')
self.userA = User.objects.create(username="test1", admin_type=ADMIN)
self.userA.set_password("testaa")
self.userA.save()
self.userS = User.objects.create(username="test2", admin_type=SUPER_ADMIN)
self.userS.set_password("testbb")
self.userS.save()
self.global_contest = Contest.objects.create(title="titlex", description="descriptionx", mode=1,
contest_type=2, show_rank=True, show_user_submission=True,
start_time="2015-08-15T10:00:00.000Z",
end_time="2015-08-15T12:00:00.000Z",
password="aacc", created_by=self.userS
)
self.problem = ContestProblem.objects.create(title="title1",
description="description1",
input_description="input1_description",
output_description="output1_description",
test_case_id="1",
sort_index="1",
samples=json.dumps([{"input": "1 1", "output": "2"}]),
time_limit=100,
memory_limit=1000,
hint="hint1",
contest=self.global_contest,
created_by=self.userS)
self.submission = Submission.objects.create(user_id=self.userA.id,
language=1,
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
problem_id=self.problem.id)
self.submissionS = Submission.objects.create(user_id=self.userS.id,
language=2,
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
problem_id=self.problem.id)
def test_submission_contest_does_not_exist(self):
self.client.login(username="test2", password="testbb")
response = self.client.get(self.url + "?contest_id=99")
self.assertEqual(response.data["code"], 1)
def test_submission_contest_parameter_error(self):
self.client.login(username="test2", password="testbb")
response = self.client.get(self.url)
self.assertEqual(response.data["code"], 1)
def test_submission_access_denied(self):
self.client.login(username="test1", password="testaa")
response = self.client.get(self.url + "?problem_id=" + str(self.problem.id))
self.assertEqual(response.data["code"], 1)
def test_submission_access_denied_with_contest_id(self):
self.client.login(username="test1", password="testaa")
response = self.client.get(self.url + "?contest_id=" + str(self.global_contest.id))
self.assertEqual(response.data["code"], 1)
def test_get_submission_successfully(self):
self.client.login(username="test2", password="testbb")
response = self.client.get(
self.url + "?contest_id=" + str(self.global_contest.id) + "&problem_id=" + str(self.problem.id))
self.assertEqual(response.data["code"], 0)
def test_get_submission_successfully_problem(self):
self.client.login(username="test2", password="testbb")
response = self.client.get(self.url + "?problem_id=" + str(self.problem.id))
self.assertEqual(response.data["code"], 0)
def test_get_submission_problem_do_not_exist(self):
self.client.login(username="test2", password="testbb")
response = self.client.get(self.url + "?problem_id=9999")
self.assertEqual(response.data["code"], 1)

View File

@@ -21,6 +21,7 @@ from utils.shortcuts import serializer_invalid_response, error_response, success
from submission.models import Submission
from .serializers import CreateContestSubmissionSerializer
from submission.serializers import SubmissionSerializer
class ContestSubmissionAPIView(APIView):
@@ -76,7 +77,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 Problem.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/contest/my_submissions_list.html",
{"submissions": submissions, "problem": contest_problem})
@@ -113,3 +115,37 @@ def contest_problem_submissions_list_page(request, contest_id, page=1):
{"submissions": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
"contest": contest})
class ContestSubmissionAdminAPIView(APIView):
def get(self, request):
"""
查询比赛提交,单个比赛题目提交的adminAPI
---
response_serializer: SubmissionSerializer
"""
problem_id = request.GET.get("problem_id", None)
contest_id = request.GET.get("contest_id", None)
if contest_id:
try:
contest = Contest.objects.get(pk=contest_id)
except Contest.DoesNotExist:
return error_response(u"比赛不存在!")
if request.user.admin_type != SUPER_ADMIN and contest.created_by != request.user:
return error_response(u"您无权查看该信息!")
submissions = Submission.objects.filter(contest_id=contest_id).order_by("-create_time")
else:
if problem_id:
try:
contest_problem = ContestProblem.objects.get(pk=problem_id)
except ContestProblem.DoesNotExist:
return error_response(u"问题不存在!")
if request.user.admin_type != SUPER_ADMIN and contest_problem.contest.created_by != request.user:
return error_response(u"您无权查看该信息!")
submissions = Submission.objects.filter(contest_id=contest_problem.contest_id).order_by("-create_time")
else:
return error_response(u"参数错误!")
if problem_id:
submissions = submissions.filter(problem_id=problem_id)
return paginate(request, submissions, SubmissionSerializer)

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('group', '0004_merge'),
]
operations = [
migrations.AddField(
model_name='joingrouprequest',
name='accepted',
field=models.BooleanField(default=False),
),
]

View File

@@ -36,6 +36,6 @@ class JoinGroupRequest(models.Model):
create_time = models.DateTimeField(auto_now_add=True)
# 是否处理
status = models.BooleanField(default=False)
accepted = models.BooleanField(default=False)
class Meta:
db_table = "join_group_request"

View File

@@ -8,6 +8,8 @@ from rest_framework.test import APITestCase, APIClient
from account.models import User, REGULAR_USER, ADMIN, SUPER_ADMIN
from group.models import Group, UserGroupRelation, JoinGroupRequest
from django.test import TestCase, Client
class GroupAPITest(APITestCase):
pass
@@ -148,7 +150,7 @@ class JoinGroupAPITest(APITestCase):
def setUp(self):
self.client = APIClient()
self.url = reverse('group_join_admin_api')
self.url = reverse('group_join_api')
self.user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
self.user.set_password("testaa")
self.user.save()
@@ -242,7 +244,7 @@ class JoinGroupRequestAdminAPITest(APITestCase):
self.assertEqual(JoinGroupRequest.objects.get(id=self.request.id).status, True)
def test_join_group_successfully(self):
data = {"request_id": self.request.id, "status": True}
data = {"request_id": self.request.id, "status": True, "": True}
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 0, "data": u"加入成功"})
UserGroupRelation.objects.get(group=self.group, user=self.user1)
@@ -254,3 +256,51 @@ class JoinGroupRequestAdminAPITest(APITestCase):
response = self.client.put(self.url, data=data)
self.assertEqual(response.data, {"code": 1, "data": u"加入失败,已经在本小组内"})
class GroupListPageTest(TestCase):
def setUp(self):
self.client = Client()
self.url = reverse('group_list_page')
self.url_with_argument = reverse('group_page', kwargs={"page": 1})
self.user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
self.user.set_password("testaa")
self.user.save()
self.group = Group.objects.create(name="group1",
description="description1",
# 0是公开 1是需要申请后加入 2是不允许任何人加入
join_group_setting=1,
admin=User.objects.get(username="test"))
def get_group_list_page_successful(self):
self.client.login(username="test", password="testaa")
response = self.client.get(self.url)
self.assertEqual(response.status_coed, 200)
def get_group_list_page_successful_with_keyword(self):
self.client.login(username="test", password="testaa")
response = self.client.get(self.url + "?keyword=gro")
self.assertEqual(response.status_coed, 200)
def get_group_list_page_successful_with_page_argument(self):
self.client.login(username="test", password="testaa")
response = self.client.get(self.url_with_argument + "?keyword=gro")
self.assertEqual(response.status_coed, 200)
class GroupPageTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create(username="test", admin_type=SUPER_ADMIN)
self.user.set_password("testaa")
self.user.save()
self.group = Group.objects.create(name="group1",
description="description1",
# 0是公开 1是需要申请后加入 2是不允许任何人加入
join_group_setting=1,
admin=User.objects.get(username="test"))
self.url = reverse('group_page', kwargs={"group_id": self.group.id})
def get_group_list_page_successful(self):
self.client.login(username="test", password="testaa")
response = self.client.get(self.url)
self.assertEqual(response.status_coed, 200)

View File

@@ -4,7 +4,7 @@ from django.db import IntegrityError
from rest_framework.views import APIView
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate
from utils.shortcuts import error_response, serializer_invalid_response, success_response, paginate, error_page
from account.models import REGULAR_USER, ADMIN, SUPER_ADMIN
from account.decorators import login_required
@@ -13,6 +13,9 @@ from .serializers import (CreateGroupSerializer, EditGroupSerializer,
CreateJoinGroupRequestSerializer, GroupSerializer,
GroupMemberSerializer, EditGroupMemberSerializer,
JoinGroupRequestSerializer, PutJoinGroupRequestSerializer)
from announcement.models import Announcement
from django.core.paginator import Paginator
from django.db.models import Q
class GroupAPIViewBase(object):
@@ -233,9 +236,10 @@ class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
join_request.status = True
join_request.save()
if data["status"]:
if join_group(join_request.user, join_request.group):
join_request.accepted = True
join_request.save()
return success_response(u"加入成功")
else:
return error_response(u"加入失败,已经在本小组内")
@@ -244,3 +248,68 @@ class JoinGroupRequestAdminAPIView(APIView, GroupAPIViewBase):
else:
return serializer_invalid_response(serializer)
@login_required
def group_list_page(request, page=1):
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
groups = Group.objects.filter(visible=True, join_group_setting__lte=2)
# 搜索的情况
keyword = request.GET.get("keyword", None)
if keyword:
groups = groups.filter(Q(name__contains=keyword) | Q(description__contains=keyword))
paginator = Paginator(groups, 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
next_page = None
try:
next_page = current_page.next_page_number()
except Exception:
pass
return render(request, "oj/group/group_list.html", {
"groups": groups, "announcements": announcements,
"contests": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page,
"keyword": keyword, "announcements": announcements,
})
@login_required
def group_page(request, group_id):
try:
group = Group.objects.get(id=group_id, visible=True)
except Group.DoesNotExist:
return error_page(request, u"小组不存在")
return render(request, "oj/group/group.html", {"group": group})
@login_required
def application_list_page(request, group_id):
try:
group = Group.objects.get(id=group_id, visible=True)
except Group.DoesNotExist:
return error_page(request, u"小组不存在")
applications = JoinGroupRequest.objects.filter(user=request.user, group=group)
return render(request, "oj/group/my_application_list.html",
{"group": group, "applications": applications})
@login_required
def application_page(request, request_id):
try:
application = JoinGroupRequest.objects.get(user=request.user, pk=request_id)
except JoinGroupRequest.DoesNotExist:
return error_page(request, u"申请不存在")
return render(request, "oj/group/my_application.html",
{"application": application})

19
judge/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM ubuntu:14.04
MAINTAINER virusdefender<qduliyang@outlook.com>
RUN mkdir /var/install/
WORKDIR /var/install/
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install software-properties-common python-software-properties
RUN add-apt-repository -y ppa:webupd8team/java
RUN apt-get update
RUN apt-get -y install python gcc g++ rake pkg-config git make autoconf automake libtool python-pip python2.7-mysqldb
RUN echo debconf shared/accepted-oracle-license-v1-1 select true | sudo debconf-set-selections
RUN echo debconf shared/accepted-oracle-license-v1-1 seen true | sudo debconf-set-selections
RUN apt-get install -y oracle-java7-installer
RUN apt-get -y install libseccomp-dev
RUN git clone https://github.com/quark-zju/lrun.git
RUN cd lrun && make install
RUN mkdir -p /var/judger/run/ && mkdir /var/judger/test_case/ && mkdir /var/judger/code/
RUN chmod -R 777 /var/judger/run/
WORKDIR /var/judger/code/

View File

@@ -1,4 +1,5 @@
# coding=utf-8
# 这个redis 是 celery 使用的,包括存储队列信息还有部分统计信息
redis_config = {
"host": "121.42.32.129",
"port": 6379,
@@ -6,17 +7,21 @@ redis_config = {
}
# 判题的 docker 容器的配置参数
docker_config = {
"image_name": " a7673b55d263",
"image_name": "3da0e526934e",
"docker_path": "docker",
"shell": True
}
test_case_dir = "/root/test_case/"
source_code_dir = "/root/"
# 测试用例的路径,是主机上的实际路径
test_case_dir = "/var/mnt/source/test_case/"
# 源代码路径,也就是 manage.py 所在的实际路径
source_code_dir = "/var/mnt/source/OnlineJudge/"
# 存储提交信息的数据库,是 celery 使用的,与 oj.settings/local_settings 等区分,那是 web 服务器访问的地址
submission_db = {
"host": "127.0.0.1",
"port": 3306,

View File

@@ -1,19 +0,0 @@
# coding=utf-8
import os
LOG_PATH = "LOG/"
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'CONN_MAX_AGE': 1,
}
}
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

View File

@@ -5,22 +5,23 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 下面是需要自己修改的
LOG_PATH = "LOG/"
LOG_PATH = "log/"
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'CONN_MAX_AGE': 0.3,
},
# submission 的 name 和 engine 请勿修改,其他代码会用到
'submission': {
'NAME': 'oj_submission',
'ENGINE': 'django.db.backends.mysql',
'HOST': "121.42.32.129",
'POST': 3306,
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'mypwd'
'PASSWORD': 'mypwd',
'CONN_MAX_AGE': 0.1,
}
}
@@ -29,5 +30,4 @@ DEBUG = True
# 同理 这是 web 服务器的上传路径
TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/')
DATABASE_ROUTERS = ['oj.db_router.DBRouter']
ALLOWED_HOSTS = []

View File

@@ -1 +1,37 @@
# coding=utf-8
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 下面是需要自己修改的
LOG_PATH = "/var/log/oj/"
# 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "oj",
'CONN_MAX_AGE': 0.1,
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'mypwd'
},
'submission': {
'NAME': 'oj_submission',
'ENGINE': 'django.db.backends.mysql',
'CONN_MAX_AGE': 0.1,
'HOST': "127.0.0.1",
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'mypwd'
}
}
DEBUG = True
# 同理 这是 web 服务器的上传路径
TEST_CASE_DIR = '/root/test_case/'
ALLOWED_HOSTS = ['*']

View File

@@ -14,15 +14,13 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
# todo 判断运行环境
# 判断运行环境
ENV = os.environ.get("oj_env", "local")
if ENV == "local":
from .local_settings import *
elif ENV == "server":
from .server_settings import *
elif ENV == "daocloud":
from .daocloud_settings import *
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -33,7 +31,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'hzfp^8mbgapc&x%$#xv)0=t8s7_ilingw(q3!@h&2fty6v6fxz'
ALLOWED_HOSTS = []
# Application definition
@@ -115,10 +113,6 @@ STATIC_URL = '/static/'
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static/src/"),)
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, "template/src"),
)
AUTH_USER_MODEL = 'account.User'
LOGGING = {
@@ -171,3 +165,5 @@ LOGGING = {
REST_FRAMEWORK = {
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}
DATABASE_ROUTERS = ['oj.db_router.DBRouter']

View File

@@ -1,11 +1,11 @@
# coding=utf-8
from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic import TemplateView
from account.views import (UserLoginAPIView, UsernameCheckAPIView, UserRegisterAPIView,
UserChangePasswordAPIView, EmailCheckAPIView,
UserAdminAPIView, UserInfoAPIView)
from announcement.views import AnnouncementAdminAPIView
from contest.views import ContestAdminAPIView, ContestProblemAdminAPIView, ContestPasswordVerifyAPIView
@@ -17,7 +17,7 @@ from admin.views import AdminTemplateView
from problem.views import TestCaseUploadAPIView, ProblemTagAdminAPIView, ProblemAdminAPIView
from submission.views import SubmissionAPIView, SubmissionAdminAPIView
from contest_submission.views import ContestSubmissionAPIView
from contest_submission.views import ContestSubmissionAPIView, ContestSubmissionAdminAPIView
from monitor.views import QueueLengthMonitorAPIView
from contest_submission.views import contest_problem_my_submissions_list_page
@@ -34,6 +34,7 @@ urlpatterns = [
name="admin_template"),
url(r'^login/$', TemplateView.as_view(template_name="oj/account/login.html"), name="user_login_page"),
url(r'^logout/$', "account.views.logout", name="user_logout_api"),
url(r'^register/$', TemplateView.as_view(template_name="oj/account/register.html"),
name="user_register_page"),
url(r'^change_password/$', TemplateView.as_view(template_name="oj/account/change_password.html"),
@@ -50,13 +51,14 @@ urlpatterns = [
url(r'^api/contest/password/$', ContestPasswordVerifyAPIView.as_view(), name="contest_password_verify_api"),
url(r'^api/contest/submission/$', ContestSubmissionAPIView.as_view(), name="contest_submission_api"),
url(r'^api/submission/$', SubmissionAPIView.as_view(), name="submission_api"),
url(r'^api/group_join/$', JoinGroupAPIView.as_view(), name="group_join_api"),
url(r'^api/admin/announcement/$', AnnouncementAdminAPIView.as_view(), name="announcement_admin_api"),
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'^api/admin/group/$', GroupAdminAPIView.as_view(), name="group_admin_api"),
url(r'^api/admin/group_member/$', GroupMemberAdminAPIView.as_view(), name="group_member_admin_api"),
url(r'^api/admin/group_join/$', JoinGroupAPIView.as_view(), name="group_join_admin_api"),
url(r'^api/admin/problem/$', ProblemAdminAPIView.as_view(), name="problem_admin_api"),
url(r'^api/admin/contest_problem/$', ContestProblemAdminAPIView.as_view(), name="contest_problem_admin_api"),
url(r'^api/admin/test_case_upload/$', TestCaseUploadAPIView.as_view(), name="test_case_upload_api"),
@@ -65,7 +67,7 @@ urlpatterns = [
name="join_group_request_admin_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'^api/admin/contest_submission/$', ContestSubmissionAdminAPIView.as_view(), name="contest_submission_admin_api_view"),
url(r'^contest/(?P<contest_id>\d+)/problem/(?P<contest_problem_id>\d+)/$', "contest.views.contest_problem_page",
@@ -87,7 +89,6 @@ urlpatterns = [
url(r'^contest/(?P<contest_id>\d+)/$', "contest.views.contest_page", name="contest_page"),
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
url(r'^problem/(?P<problem_id>\d+)/$', "problem.views.problem_page", name="problem_page"),
url(r'^problems/$', "problem.views.problem_list_page", name="problem_list_page"),
url(r'^problems/(?P<page>\d+)/$', "problem.views.problem_list_page", name="problem_list_page"),
@@ -99,6 +100,11 @@ urlpatterns = [
url(r'^submissions/$', "submission.views.my_submission_list_page", name="my_submission_list_page"),
url(r'^submissions/(?P<page>\d+)/$', "submission.views.my_submission_list_page", name="my_submission_list_page"),
url(r'^contest/(?P<contest_id>\d+)/rank/$', "contest.views.contest_rank_page", name="contest_rank_page")
url(r'^contest/(?P<contest_id>\d+)/rank/$', "contest.views.contest_rank_page", name="contest_rank_page"),
url(r'^groups/$', "group.views.group_list_page", name="group_list_page"),
url(r'^groups/(?P<page>\d+)/$', "group.views.group_list_page", name="group_list_page"),
url(r'^group/(?P<group_id>\d+)/$', "group.views.group_page", name="group_page"),
url(r'^group/(?P<group_id>\d+)/applications/$', "group.views.application_list_page", name="group_application_page"),
url(r'^group/application/(?P<request_id>\d+)/$', "group.views.application_page", name="group_application")
]

View File

@@ -26,8 +26,8 @@ class CreateProblemSerializer(serializers.Serializer):
samples = ProblemSampleSerializer()
test_case_id = serializers.CharField(max_length=40)
source = serializers.CharField(max_length=30, required=False, default=None)
time_limit = serializers.IntegerField()
memory_limit = serializers.IntegerField()
time_limit = serializers.IntegerField(min_value=1)
memory_limit = serializers.IntegerField(min_value=1)
difficulty = serializers.IntegerField()
tags = serializers.ListField(child=serializers.CharField(max_length=10))
hint = serializers.CharField(max_length=3000, allow_blank=True)
@@ -61,8 +61,8 @@ class EditProblemSerializer(serializers.Serializer):
output_description = serializers.CharField(max_length=10000)
test_case_id = serializers.CharField(max_length=40)
source = serializers.CharField(max_length=30)
time_limit = serializers.IntegerField()
memory_limit = serializers.IntegerField()
time_limit = serializers.IntegerField(min_value=1)
memory_limit = serializers.IntegerField(min_value=1)
difficulty = serializers.IntegerField()
tags = serializers.ListField(child=serializers.CharField(max_length=20))
samples = ProblemSampleSerializer()

View File

@@ -222,7 +222,7 @@ def problem_list_page(request, page=1):
# 搜索的情况
keyword = request.GET.get("keyword", None)
if keyword:
problems = problems.filter(title__contains=keyword)
problems = problems.filter(Q(title__contains=keyword) | Q(description__contains=keyword))
# 按照标签筛选
tag_text = request.GET.get("tag", None)

View File

@@ -8,3 +8,4 @@ celery
gunicorn
coverage
django-extensions
supervisor

View File

@@ -55,3 +55,12 @@
list-style-type: none;
margin: 5px;
}
.error-info {
color: #dd4b39;
font-family:
Arial,Helvetica,sans-serif;
font-size: 13px;
line-height: 1.4;
font-weight: 600;
}

View File

@@ -133,11 +133,17 @@ define("admin", ["jquery", "avalon"], function ($, avalon) {
vm.template_url = "template/contest/edit_problem.html";
});
vm.$watch("showContestListPage", function (problemId) {
vm.problemId = problemId;
vm.$watch("showContestListPage", function () {
vm.template_url = "template/contest/contest_list.html";
});
vm.$watch("showContestSubmissionPage", function (problemId, contestId, contestMode) {
vm.$problemId = problemId;
vm.$contestId = contestId;
vm.$contestMode = contestMode
vm.template_url = "template/contest/submission_list.html";
});
avalon.scan();
window.onhashchange = function () {

View File

@@ -1,10 +1,14 @@
require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
function ($, avalon, csrfTokenHeader, bsAlert, editor) {
avalon.ready(function () {
avalon.vmodels.announcement = null;
var createAnnouncementEditor = editor("#create-announcement-editor");
var editAnnouncementEditor = editor("#edit-announcement-editor");
if (avalon.vmodels.announcement){
var vm = avalon.vmodels.announcement;
announcementList = [];
}
else {
var vm = avalon.define({
$id: "announcement",
@@ -22,12 +26,6 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
showGlobalViewRadio: true,
isGlobal: true,
allGroups: [],
getState: function (el) { //获取公告当前状态,显示
if (el.visible)
return "可见";
else
return "隐藏";
},
getNext: function () {
if (!vm.nextPage)
return;
@@ -45,7 +43,6 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
else {
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
}
},
editAnnouncement: function (announcement) {
vm.newTitle = announcement.title;
@@ -122,9 +119,23 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
vm.$watch("showVisibleOnly", function () {
getPageData(1);
});
}
getPageData(1);
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
var admin_type = data.data.admin_type;
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
}
$.ajax({
url: "/api/admin/group/",
method: "get",
@@ -132,6 +143,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
success: function (data) {
if (!data.code) {
if (!data.data.length) {
if (admin_type != 2)
bsAlert("您的用户权限只能创建组内公告,但是您还没有创建过小组");
return;
}
@@ -146,18 +158,6 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "validator"],
}
}
});
$.ajax({
url: "/api/user/",
method: "get",
dataType: "json",
success: function (data) {
if (!data.code) {
if (data.data.admin_type == 1) {
vm.isGlobal = false;
vm.showGlobalViewRadio = false;
}
}
}
});

View File

@@ -131,7 +131,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date
success: function (data) {
if (!data.code) {
if (!data.data.length) {
bsAlert("您的用户权限只能创建组内比赛,但是您还没有创建过小组");
return;
}
for (var i = 0; i < data.data.length; i++) {

View File

@@ -234,15 +234,17 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
vm.groupList[vm.choseGroupList[groupIndex].index].chose = false;
vm.choseGroupList.remove(vm.choseGroupList[groupIndex]);
},
add_problem: function () {
addProblem: function () {
vm.$fire("up!showContestProblemPage", 0, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
},
showProblemEditor: function(el) {
showProblemEditPage: function(el) {
vm.$fire("up!showContestProblemPage", el.id, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
},
getYesOrNo: function(yORn) {
if (yORn) return "是";
return "否";
showSubmissionPage: function(el) {
var problemId = 0
if (el)
problemId = el.id;
vm.$fire("up!showContestSubmissionPage", problemId, vm.contestList[vm.editingProblemContestIndex-1].id, vm.editMode);
}
});
vm.$watch("showVisibleOnly", function() {
@@ -289,6 +291,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "editor", "datetimePicker",
}
});
}
// Get group list
$.ajax({ // Get current user type
url: "/api/user/",

View File

@@ -0,0 +1,88 @@
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
avalon.ready(function () {
if (avalon.vmodels.contestSubmissionList){
var vm = avalon.vmodels.contestSubmissionList;
}
else {
var vm = avalon.define({
$id: "contestSubmissionList",
submissionList: [],
previousPage: 0,
nextPage: 0,
page: 1,
totalPage: 1,
results : {
0: "Accepted",
1: "Runtime Error",
2: "Time Limit Exceeded",
3: "Memory Limit Exceeded",
4: "Compile Error",
5: "Format Error",
6: "Wrong Answer",
7: "System Error",
8: "Waiting"
},
getNext: function () {
if (!vm.nextPage)
return;
getPageData(vm.page + 1);
},
getPrevious: function () {
if (!vm.previousPage)
return;
getPageData(vm.page - 1);
},
getBtnClass: function (btn) {
if (btn == "next") {
return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled";
}
else {
return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled";
}
},
getPage: function (page_index) {
if (!page_index)
var page_index = vm.page;
getPageData(page_index);
},
showSubmissionDetailPage: function (submissionId) {
},
goBack: function(check){
vm.$fire("up!showContestListPage");
}
});
}
getPageData(1);
function getPageData(page) {
var url = "/api/admin/contest_submission/?paging=true&page=" + page + "&page_size=10&contest_id=" + avalon.vmodels.admin.$contestId;
if (avalon.vmodels.admin.$problemId)
url += "&problem_id=" + avalon.vmodels.admin.$problemId
$.ajax({
url: url,
dataType: "json",
method: "get",
success: function (data) {
if (!data.code) {
vm.submissionList = data.data.results;
vm.totalPage = data.data.total_page;
vm.previousPage = data.data.previous_page;
vm.nextPage = data.data.next_page;
vm.page = page;
}
else {
bsAlert(data.data);
}
}
});
}
});
avalon.scan();
});

View File

@@ -1,8 +1,12 @@
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
avalon.ready(function () {
avalon.vmodels.group = null;
//avalon.vmodels.group = null;
if (avalon.vmodels.group) {
var vm = avalon.vmodels.group;
}
else {
var vm = avalon.define({
$id: "group",
//通用变量
@@ -42,10 +46,12 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
vm.$fire("up!showGroupDetailPage", groupId);
}
});
}
getPageData(1);
function getPageData(page) {
var url = "/api/admin/group/?paging=true&page=" + page + "&page_size=2";
var url = "/api/admin/group/?paging=true&page=" + page + "&page_size=10";
if (vm.keyword)
url += "&keyword=" + vm.keyword;
$.ajax({

View File

@@ -3,7 +3,11 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
// avalon:定义模式 group_list
avalon.ready(function () {
avalon.vmodels.groupDetail = null;
if (avalon.vmodels.groupDetail) {
var vm = avalon.vmodels.groupDetail;
}
else {
var vm = avalon.define({
$id: "groupDetail",
//通用变量
@@ -52,6 +56,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
vm.$fire("up!showGroupListPage");
}
});
}
avalon.scan();
getPageData(1);

View File

@@ -1,8 +1,13 @@
require(["jquery", "avalon", "csrfToken", "bsAlert", "formValidation"], function ($, avalon, csrfTokenHeader, bsAlert) {
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
// avalon:定义模式 group_list
avalon.ready(function () {
avalon.vmodels.requestList = null;
if (avalon.vmodels.requestList) {
var vm = avalon.vmodels.requestList;
}
else {
var vm = avalon.define({
$id: "requestList",
//通用变量
@@ -46,6 +51,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "formValidation"], function
})
}
});
}
avalon.scan();
getPageData(1);
@@ -72,78 +78,6 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "formValidation"], function
});
}
/*$("#edit_user-form")
.formValidation({
framework: "bootstrap",
fields: {
username: {
validators: {
notEmpty: {
message: "请填写用户名"
},
stringLength: {
min: 3,
max: 30,
message: '用户名长度必须在3到30位之间'
}
}
},
real_name: {
validators: {
notEmpty: {
message: "请填写真实姓名"
}
}
},
email: {
validators: {
notEmpty: {
message: "请填写电子邮箱邮箱地址"
},
emailAddress: {
message: "请填写有效的邮箱地址"
}
}
},
password: {
validators: {
stringLength: {
min: 6,
max: 30,
message: '密码长度必须在6到30位之间'
}
}
}
}
}
).on('success.form.fv', function (e) {
e.preventDefault();
var data = {
username: vm.username,
real_name: vm.real_name,
email: vm.email,
id: vm.id,
admin_type: vm.admin_type
};
if ($("#password").val() !== "")
data.password = $("#password").val();
$.ajax({
beforeSend: csrfHeader,
url: "/api/admin/user/",
data: data,
dataType: "json",
method: "put",
success: function (data) {
if (!data.code) {
bsAlert("提交成功!");
getPageData(1);
$("#password").val("");
} else {
bsAlert(data.data);
}
}
})
});*/
});
});

View File

@@ -13,10 +13,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "tagE
bsAlert("题目描述不能为空!");
return false;
}
if (vm.timeLimit < 1000 || vm.timeLimit > 5000) {
bsAlert("保证时间限制是一个1000-5000的合法整数");
return false;
}
if (vm.samples.length == 0) {
bsAlert("请至少添加一组样例!");
return false;

View File

@@ -3,6 +3,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
avalon.ready(function () {
if(avalon.vmodels.problemList){
vm = avalon.vmodels.problemList;
problemList = [];
}
else {
var vm = avalon.define({
@@ -13,6 +14,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
page: 1,
totalPage: 1,
keyword: "",
showVisibleOnly: false,
getNext: function () {
if (!vm.nextPage)
return;
@@ -41,12 +43,17 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
vm.$fire("up!showProblemSubmissionPage", problemId);
}
});
vm.$watch("showVisibleOnly", function () {
getPageData(1);
});
}
getPageData(1);
function getPageData(page) {
var url = "/api/admin/problem/?paging=true&page=" + page + "&page_size=10";
if (vm.keyword != "")
url += "&keyword=" + vm.keyword;
if (vm.showVisibleOnly)
url += "&visible=true";
$.ajax({
url: url,
dataType: "json",

View File

@@ -1,7 +1,12 @@
require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) {
avalon.ready(function () {
avalon.vmodels.submissionList = null;
if (avalon.vmodels.submissionList){
var vm = avalon.vmodels.submissionList;
}
else {
var vm = avalon.define({
$id: "submissionList",
submissionList: [],
@@ -48,6 +53,7 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfT
vm.$fire("up!showProblemListPage");
}
});
}
getPageData(1);

View File

@@ -3,12 +3,12 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($,
// avalon:定义模式 userList
avalon.ready(function () {
//avalon.vmodels.userList = null;
if (avalon.vmodels.userList) {
var vm = avalon.vmodels.userList;
// initialize avalon object
userList = []; previousPage= 0; nextPage= 0; page = 1;
editingUserId= 0; totalPage = 1; keyword= ""; showAdminOnly= false;
userList = []; //previousPage= 0; nextPage= 0; page = 1;
//editingUserId= 0; totalPage = 1; keyword= ""; showAdminOnly= false;
//user editor fields
username= ""; realName= ""; email= ""; adminType= 0; id= 0;
}

View File

@@ -0,0 +1,25 @@
require(["jquery", "csrfToken", "bsAlert"], function ($, csrfTokenHeader, bsAlert) {
$("#sendApplication").click(function (){
var message = $("#applyMessage").val();
console.log(message);
var groupId = window.location.pathname.split("/")[2];
console.log(groupId);
data = {group_id: groupId,message:message}
$.ajax({
url: "/api/group_join/",
method: "post",
dataType: "json",
beforeSend: csrfTokenHeader,
data: JSON.stringify(data),
contentType: "application/json",
success: function (data) {
if (data.code) {
bsAlert(data.data);
}
else {
bsAlert("申请已提交!");
}
}
})
})
})

View File

@@ -48,7 +48,7 @@ require(["jquery", "codeMirror", "csrfToken", "bsAlert"], function ($, codeMirro
if (!data.result) {
html += "CPU time: " + data.accepted_answer_time + "ms &nbsp;&nbsp;";
}
html += ('<a href="/my_submission/' + submissionId + '/" target="_blank">查看详情</a></div> </div>');
html += ('<a href="/submission/' + submissionId + '/" target="_blank">查看详情</a></div> </div>');
return html;
}

View File

@@ -18,7 +18,6 @@ class SubmissionsListPageTest(TestCase):
self.user.set_password("666666")
self.user.save()
self.user2.save()
# self.client.login(username="gogoing", password="666666")
self.submission = Submission.objects.create(user_id=self.user.id,
language=1,
code='#include "stdio.h"\nint main(){\n\treturn 0;\n}',
@@ -29,6 +28,16 @@ class SubmissionsListPageTest(TestCase):
response = self.client.get('/submissions/1/')
self.assertEqual(response.status_code, 200)
def test_visit_submissionsListPage_successfully_language_filter(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/?language=1')
self.assertEqual(response.status_code, 200)
def test_visit_submissionsListPage_successfully_result_filter(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/?result=1')
self.assertEqual(response.status_code, 200)
def test_visit_submissionsListPage_without_page_successfully(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/')
@@ -41,7 +50,7 @@ class SubmissionsListPageTest(TestCase):
def test_submissionsListPage_page_not_exist(self):
self.client.login(username="gogoing", password="666666")
response = self.client.get('/submissions/5/')
response = self.client.get('/submissions/999/')
self.assertTemplateUsed(response, "utils/error.html")
def test_submissionsListPage_have_no_submission(self):
@@ -137,10 +146,3 @@ class ContestSubmissionAPITest(APITestCase):
data = {"language": 1}
response = self.client.post(self.url, data=data)
pass

View File

@@ -12,15 +12,14 @@ from judge.judger_controller.settings import redis_config
from account.decorators import login_required
from account.models import SUPER_ADMIN
from contest.decorators import check_user_contest_permission
from problem.models import Problem
from contest.models import Contest, ContestProblem
from contest.models import ContestProblem
from utils.shortcuts import serializer_invalid_response, error_response, success_response, error_page, paginate
from .models import Submission
from .serializers import CreateSubmissionSerializer, SubmissionSerializer
from announcement.models import Announcement
class SubmissionAPIView(APIView):
@login_required
@@ -80,8 +79,10 @@ def problem_my_submissions_list_page(request, problem_id):
problem = Problem.objects.get(id=problem_id, visible=True)
except Problem.DoesNotExist:
return error_page(request, u"问题不存在")
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id, contest_id__isnull=True).order_by("-create_time"). \
values("id", "result", "create_time", "accepted_answer_time", "language")
return render(request, "oj/problem/my_submissions_list.html",
{"submissions": submissions, "problem": problem})
@@ -137,8 +138,17 @@ def my_submission_list_page(request, page=1):
"""
我的所有提交的列表页
"""
submissions = Submission.objects.filter(user_id=request.user.id). \
submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True). \
values("id", "result", "create_time", "accepted_answer_time", "language").order_by("-create_time")
language = request.GET.get("language", None)
filter = None
if language:
submissions = submissions.filter(language=int(language))
filter = {"name": "language", "content": language}
result = request.GET.get("result", None)
if result:
submissions = submissions.filter(result=int(result))
filter = {"name": "result", "content": result}
paginator = Paginator(submissions, 20)
try:
current_page = paginator.page(int(page))
@@ -154,6 +164,10 @@ def my_submission_list_page(request, page=1):
except Exception:
pass
# 右侧的公告列表
announcements = Announcement.objects.filter(is_global=True, visible=True).order_by("-create_time")
return render(request, "oj/submission/my_submissions_list.html",
{"submissions": current_page, "page": int(page),
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20})
"previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20,
"announcements": announcements, "filter":filter})

View File

@@ -7,8 +7,8 @@
<th>创建时间</th>
<th>更新时间</th>
<th>创建者</th>
<td>可见范围</td>
<th>状态</th>
<th>类型</th>
<th>可见</th>
<th></th>
</tr>
<tr ms-repeat="announcementList">
@@ -18,7 +18,7 @@
<td>{{ el.last_update_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.created_by.username }}</td>
<td ms-text="el.is_global?'全局可见':'组内可见'"></td>
<td>{{ getState(el)}}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>
<button class="btn-sm btn-info" ms-click="editAnnouncement(el)">编辑</button>
</td>
@@ -38,7 +38,8 @@
<div class="form-group">
<label>标题</label>
<input name="title" type="text" class="form-control" id="newTitle" placeholder="公告标题" value="" ms-duplex="newTitle"></div>
<input name="title" type="text" class="form-control" id="newTitle" placeholder="公告标题" value=""
ms-duplex="newTitle"></div>
<div class="form-group">
<label>内容</label>
<textarea id="edit-announcement-editor"></textarea>
@@ -66,7 +67,7 @@
</div>
</div>
<div class="form-group">
<button ms-click="submitChange()" class="btn btn-primary">提交</button>
<button ms-click="submitChange()" class="btn btn-success">保存修改</button>
&nbsp;&nbsp;
<button ms-click="cancelEdit()" class="btn btn-danger">取消</button>
</div>
@@ -78,12 +79,14 @@
<label>标题</label>
<input name="title" type="text" class="form-control" id="title" placeholder="公告标题"
data-error="请填写公告标题(标题不得超过50字)" maxlength="50" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label>内容</label>
<textarea id="create-announcement-editor" placeholder="公告内容" maxlength="10000" required>
</textarea>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
@@ -91,10 +94,16 @@
<div>
<span ms-if="showGlobalViewRadio">
<input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
<label>
<small><input type="radio" value="true" name="isGlobal" ms-duplex-boolean="isGlobal">全局可见
</small>
</label>
</span>
<span>
<input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
<label>
<small><input type="radio" value="false" name="isGlobal" ms-duplex-boolean="isGlobal">小组内可见
</small>
</label>
</span>
</div>
@@ -106,7 +115,7 @@
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
<button type="submit" class="btn btn-success">发布公告</button>
</div>
</form>
</div>

View File

@@ -18,9 +18,7 @@
<div class="col-md-12">
<div class="form-group">
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="description"></textarea>
<div class="help-block with-errors"></div>
<small ms-visible="description==''" style="color:red">请填写比赛描述</small>
<p class="error-info" ms-visible="description==''">请填写比赛描述</p>
</div>
</div>
<div class="col-md-6">

View File

@@ -15,22 +15,22 @@
<tr>
<th>ID</th>
<th>比赛</th>
<th>公开排名</th>
<th>可见</th>
<th>排名</th>
<th>创建时间</th>
<th>创建者</th>
<th>可见</th>
<th></th>
</tr>
<tr ms-repeat="contestList">
<td>{{ el.id }}</td>
<td>{{ el.title }}</td>
<td>{{ getYesOrNo(el.show_rank) }}</td>
<td>{{ getYesOrNo(el.visible) }}</td>
<td ms-text="el.show_rank?'公开':'不公开'"></td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.created_by.username }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>
<a class="btn btn-info" href="javascript:void(0)" ms-click="showEditContestArea($index+1)">编辑</a>
<a class="btn btn-primary" href="javascript:void(0)" ms-click="showEditProblemArea($index+1)">编辑问</a>
<a class="btn btn-info btn-sm" href="javascript:void(0)" ms-click="showEditContestArea($index+1)">编辑</a>
<a class="btn btn-info btn-sm" href="javascript:void(0)" ms-click="showEditProblemArea($index+1)"></a>
</td>
</tr>
</table>
@@ -63,7 +63,7 @@
<textarea id="editor" placeholder="这里输入内容" autofocus ms-duplex="editDescription"></textarea>
<div class="help-block with-errors"></div>
<small ms-visible="editDescription==''" style="color:red">请填写比赛描述</small>
<p class="error-info" ms-visible="editDescription==''" >请填写比赛描述</p>
</div>
</div>
<div class="col-md-6">
@@ -152,13 +152,14 @@
</div>
</div>
<div class="col-md-12">
<button class="btn btn-primary" type="submit">保存修改</button>
<button class="btn btn-success" type="submit">保存修改</button>
</div>
</form>
</div>
<div class="col-md-12" ms-visible="editingProblemContestIndex">
<label>题目列表</label>
<a href="javascript:void(0)" class="btn btn-success btn-sm" ms-click="add_problem()">添加</a>
<a href="javascript:void(0)" class="btn btn-primary btn-sm" ms-click="addProblem()">添加题目</a>
<a href="javascript:void(0)" class="btn btn-info btn-sm" ms-click="showSubmissionPage()">查看提交</a>
<table class="table table-striped">
<tr>
<th>编号</th>
@@ -172,11 +173,13 @@
<td>{{ el.sort_index }}</td>
<td>{{ el.title }}</td>
<td ms-visible="editMode=='2'">{{ el.score}}</td>
<td>{{ getYesOrNo(el.visible) }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss") }}</td>
<td>
<a href="javascript:void(0)" class="btn-sm btn-info"
ms-click="showProblemEditor(el)">编辑</a>
ms-click="showProblemEditPage(el)">编辑</a>
<a href="javascript:void(0)" class="btn-sm btn-info"
ms-click="showSubmissionPage(el)">提交</a>
</td>
</tr>
</table>

View File

@@ -23,7 +23,7 @@
<div class="form-group col-md-12">
<label>题目描述</label>
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写题目描述</small>
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
</div>
@@ -130,7 +130,7 @@
<textarea id="hint" placeholder="这里输入内容" ms-duplex="hint"></textarea>
</div>
<div class="col-md-12">
<input type="submit" class="btn btn-success btn-lg" value="发布题目" id="submitBtn">
<button type="submit" class="btn btn-success btn-lg">发布题目</button>
</div>
</form>
</div>

View File

@@ -0,0 +1,37 @@
<div ms-controller="contestSubmissionList" class="col-md-9">
<nav>
<ul class="pager">
<li class="previous" ms-click="goBack()"><a href="javascript:void(0)"><span
aria-hidden="true">&larr;</span> 返回</a></li>
</ul>
</nav>
<h1>提交列表</h1>
<a href="javascript:void(0)" class="btn btn-sm btn-primary" ms-click="getPage(1)">
<span class="glyphicon glyphicon-refresh"></span> 刷新
</a>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>创建时间</th>
<th>作者</th>
<td>结果</td>
<td></td>
</tr>
<tr ms-repeat="submissionList">
<td>{{ el.id }}</td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.user }}</td>
<td>{{ results[el.result] }}</td>
<td>
<a class="btn btn-info" ms-attr-href="'/submission/' + el.id + '/'" target="_blank">详情</a>
</td>
</tr>
</table>
<div class="text-right">
页数:{{ page }}/{{ totalPage }}&nbsp;&nbsp;
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>
<button ms-attr-class="getBtnClass('next')" ms-click="getNext">下一页</button>
</div>
</div>
<script src="/static/js/app/admin/contest/submissionList.js"></script>

View File

@@ -11,7 +11,7 @@
<div class="form-group col-md-12">
<label>题目描述</label>
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写题目描述</small>
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
</div>

View File

@@ -17,7 +17,7 @@
<div class="form-group col-md-12">
<label>题目描述</label>
<textarea id="problemDescription" placeholder="这里输入内容(此内容不能为空)" ms-duplex="description"></textarea>
<small ms-visible="description==''" style="color:red">请填写题目描述</small>
<p class="error-info" ms-visible="description==''">请填写题目描述</p>
</div>

View File

@@ -17,7 +17,8 @@
<th>题目</th>
<th>创建时间</th>
<th>作者</th>
<td>通过次数/提交总数</td>
<th>可见</th>
<th>通过次数/提交总数</th>
<td></td>
</tr>
<tr ms-repeat="problemList">
@@ -25,6 +26,7 @@
<td>{{ el.title }}</td>
<td>{{ el.create_time|date("yyyy-MM-dd HH:mm:ss")}}</td>
<td>{{ el.created_by.username }}</td>
<td ms-text="el.visible?'可见':'不可见'"></td>
<td>{{ el.total_accepted_number }}/{{ el.total_submit_number }}</td>
<td>
<button class="btn-sm btn-info" ms-click="showEditProblemPage(el.id)">编辑</button>
@@ -32,6 +34,9 @@
</td>
</tr>
</table>
<div class="form-group">
<label>仅显示可见 <input ms-duplex-checked="showVisibleOnly" type="checkbox"/></label>
</div>
<div class="text-right">
页数:{{ page }}/{{ totalPage }}&nbsp;&nbsp;
<button ms-attr-class="getBtnClass('pre')" ms-click="getPrevious">上一页</button>

View File

@@ -21,7 +21,7 @@
<td>{{ el.user }}</td>
<td>{{ results[el.result] }}</td>
<td>
<a class="btn btn-info" ms-attr-href="'/my_submission/' + el.id + '/'" target="_blank">详情</a>
<a class="btn btn-info" ms-attr-href="'/submission/' + el.id + '/'" target="_blank">详情</a>
</td>
</tr>
</table>

View File

@@ -7,17 +7,23 @@
<form id="login-form">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control input-lg" id="username" name="username" maxlength="30" data-error="请填写用户名" placeholder="用户名" autofocus required>
<input type="text" class="form-control input-lg" id="username" name="username" maxlength="30"
data-error="请填写用户名" placeholder="用户名" autofocus required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control input-lg" id="password" name="password" maxlength="30" data-error="请填写密码" placeholder="密码" required>
<input type="password" class="form-control input-lg" id="password" name="password" maxlength="30"
data-error="请填写密码" placeholder="密码" required>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
<a href="/register/">还没有帐号?点击注册</a>
</form>
</div>
</div>

View File

@@ -5,8 +5,13 @@
公告
</h3></div>
<div class="panel-body">
{% if announcements %}
{% for item in announcements %}
<p>{{ forloop.counter }}.&nbsp;&nbsp;<a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a></p>
<p>{{ forloop.counter }}.&nbsp;&nbsp;<a href="/announcement/{{ item.id }}/" target="_blank">{{ item.title }}</a>
</p>
{% endfor %}
{% else %}
<p>暂无可显示的公告</p>
{% endif %}
</div>
</div>

View File

@@ -3,7 +3,7 @@
{% load contest %}
<div class="container main">
<div class="row">
<div class="col-lg-9">
<div class="col-md-9 col-lg-9">
<div class="row">
<div class="right">
<form class="form-inline" method="get">
@@ -15,6 +15,7 @@
</div>
</div>
<div>
{% if contests %}
<table class="table table-striped">
<thead>
<tr>
@@ -47,6 +48,7 @@
{% endfor %}
</tbody>
</table>
<div class="form-group">
<label>仅显示当前可参加的比赛
<input id="join" type="checkbox" {% if join %}checked{% endif %} onchange="if(this.checked){location.href='/contests/?join=True'}else{location.href='/contests/'}">
@@ -66,10 +68,13 @@
{% endif %}
</ul>
</nav>
{% else %}
<p>当前没有合适的比赛</p>
{% endif %}
</div>
</div>
<div class="col-lg-3">
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>

View File

@@ -26,7 +26,7 @@
</div>
<div class="col-lg-9">
<div class="col-md-9 col-lg-9">
<div>
<table class="table table-striped">
<thead>
@@ -55,7 +55,7 @@
</div>
</div>
<div class="col-lg-3">
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>

View File

@@ -20,6 +20,7 @@
</div>
<div class="row">
<div class="col-lg-12">
{% if result %}
<table class="table table-bordered">
<thead>
<tr>
@@ -49,6 +50,9 @@
{% endfor %}
</tbody>
</table>
{% else %}
<p>还没有结果</p>
{% endif %}
</div>
</div>
</div>

View File

@@ -0,0 +1,40 @@
{% extends 'oj_base.html' %}
{% block body %}
<div class="container main">
<ul class="nav nav-tabs nav-tabs-google">
<li role="presentation" class="active">
<a href="/group/{{ group.id }}/">详细信息</a></li>
<li role="presentation"><a href="/group/{{ group.id }}/applications/">我的申请</a></li>
</ul>
<h2 class="text-center">{{ group.name }}</h2>
<p class="text-muted text-center">发布时间 : {{ group.create_time }}&nbsp;&nbsp;
创建者 : {{ group.admin }}
</p>
<div>
<div class="group-section">
<label class="group-label">描述</label>
<p class="group-detail">{{ group.description|safe }}</p>
</div>
</div>
<hr>
<div>
{% if group.join_group_setting %}
<div class="form-group">
<input id="groupId" value="{{ group.id }" type="hidden">
<label>申请信息</label>
<textarea class="form-control" id="applyMessage" rows="10"></textarea>
</div>
{% endif %}
<div class="form-group">
<button class="btn btn-primary" id="sendApplication">申请加入</button>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="/static/js/app/oj/group/group.js"></script>
{% endblock %}

View File

@@ -0,0 +1,71 @@
{% extends "oj_base.html" %}
{% block body %}
{% load problem %}
<div class="container main">
<div class="row">
<div class="col-md-9 col-lg-9">
<div class="row">
<div class="right">
<form class="form-inline" method="get">
<div class="form-group-sm">
<input name="keyword" class="form-control" placeholder="请输入关键词">
<input type="submit" value="搜索" class="btn btn-primary">
</div>
</form>
</div>
</div>
<div>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>名称</th>
<th>加入方式</th>
<th>创建者</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
{% for item in groups %}
<tr>
<th scope="row"><a href="/group/{{ item.id }}/" target="_blank">{{ item.id }}</a></th>
<td><a href="/group/{{ item.id }}/" target="_blank">{{ item.name }}</a></td>
<td>
{% if item.join_group_setting %}
需要申请
{% else %}
无需申请
{% endif %}
</td>
<td>{{ item.admin }}</td>
<td>{{ item.create_time }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<nav>
<ul class="pager">
{% if previous_page %}
<li class="previous"><a
href="/groups/{{ previous_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}">
<span aria-hidden="true">&larr;</span> 上一页</a>
</li>
{% endif %}
{% if next_page %}
<li class="next">
<a href="/groups/{{ next_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}">
下一页 <span aria-hidden="true">&rarr;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends 'oj_base.html' %}
{% block body %}
<div class="container main">
<ul class="nav nav-tabs nav-tabs-google">
<li role="presentation">
<a href="/group/{{ application.group.id }}/">详细信息</a></li>
<li role="presentation" class="active">
<a href="/group/{{ application.group.id }}/applications/">我的申请</a>
</li>
</ul>
<label>内容</label>
<p>{{ application.message|safe }}</p>
<label>结果</label>
{% if application.status %}
{% if application.accepted %}
<p>管理员接受了你的请求</p>
{% else %}
<p>管理员拒绝了你的请求</p>
{% endif %}
{% else %}
<p>待审核</p>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,44 @@
{% extends 'oj_base.html' %}
{% block body %}
<div class="container main">
<ul class="nav nav-tabs nav-tabs-google">
<li role="presentation">
<a href="/group/{{ group.id }}/">详细信息</a></li>
<li role="presentation" class="active">
<a href="/group/{{ group.id }}/applications/">我的申请</a></li>
</ul>
{% if applications %}
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>提交时间</th>
<th>结果</th>
</tr>
</thead>
<tbody>
{% for item in applications %}
<tr>
<th scope="row"><a href="/group/application/{{ item.id }}/">{{ forloop.counter }}</a></th>
<td>{{ item.create_time }}</td>
{% if item.status %}
{% if item.accepted %}
<td class="alert-success">通过</td>
{% else %}
<td class="alert-danger">拒绝</td>
{% endif %}
{% else %}
<td class="alert-warning">未处理</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>你还没有申请该小组</p>
{% endif %}
</div>
{% endblock %}

View File

@@ -8,7 +8,8 @@
<link rel="stylesheet" type="text/css" href="/static/css/fullpage/jquery.fullPage.css">
<style>
html, textarea, input, option, select, button {
font: 1em "Helvetica Neue", Helvetica, "Lantinghei SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", "STHeiti", "WenQuanYi Micro Hei", SimSun, sans-serif;
font: 1em "Helvetica Neue", Helvetica, "Lantinghei SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑",
"STHeiti", "WenQuanYi Micro Hei", SimSun, sans-serif;
color: #FFF;
}
@@ -84,21 +85,29 @@
loopBottom: true
});
});
</script>
</head>
<body>
<div id="header">
<span id="name">qduoj</span>
<a href="/problems/">题目</a>&nbsp;&nbsp;<a href="/contests/">比赛</a>&nbsp;&nbsp;<a href="#">小组</a>
<a href="/problems/">题目</a>&nbsp;&nbsp;
<a href="/submissions/">提交</a>&nbsp;&nbsp;
<a href="/contests/">比赛</a>&nbsp;&nbsp;
<a href="/groups/">小组</a>&nbsp;&nbsp;
<a href="/about/">关于</a>
</div>
<div id="fullpage">
<div class="section" id="section0">
<div class="index-section-text animated bounceInUp">
<h1>青岛大学在线评测平台</h1>
<h1>青岛大学 Online Judge</h1>
<h3>新面貌,新的开始~</h3>
<h3>面貌,新的开始~</h3>
</div>
<div class="bottom-pointer">↓继续滚动~</div>
</div>
@@ -128,7 +137,7 @@
<div class="section-text">
<img class="icon" id="img3" src="/static/img/index/m.png">
<h1>自由举办小组赛</h1>
<h1>自由举办小组赛(10月上线)</h1>
<h3>内部比赛,日常作业,期末考试,通通搞定</h3>
</div>

View File

@@ -11,22 +11,25 @@
<a href="/problem/{{ problem.id }}/submissions/">我的提交</a></li>
</ul>
{% include "oj/problem/_problem_header.html" %}
{% if submissions %}
<table class="table table-bordered">
<thead>
<tr class="" success>
<th>#</th>
<th>提交时间</th>
<th>结果</th>
<th>运行时间</th>
<th>语言</th>
<th>运行时间</th>
<th>结果</th>
</tr>
</thead>
<tbody>
{% for item in submissions %}
<tr class="{{ item.result|translate_result_class }}">
<tr>
<th scope="row"><a href="/submission/{{ item.id }}/">{{ forloop.counter }}</a></th>
<td>{{ item.create_time }}</td>
<td>{{ item.result|translate_result }}</td>
<td>
{{ item.language|translate_language }}
</td>
<td>
{% if item.accepted_answer_time %}
{{ item.accepted_answer_time }}ms
@@ -34,13 +37,16 @@
--
{% endif %}
</td>
<td>
{{ item.language|translate_language }}
<td class="alert-{{ item.result|translate_result_class }}">
<strong>{{ item.result|translate_result }}</strong>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>你还没有提交该题目</p>
{% endif %}
</div>
{% endblock %}

View File

@@ -23,7 +23,7 @@
<div class="problem-section">
<label class="problem-label">输出</label>
<p class="problem-detail">{{ problem.output_description }}k</p>
<p class="problem-detail">{{ problem.output_description }}</p>
</div>
{% for item in samples %}
<div class="problem-section">

View File

@@ -3,7 +3,7 @@
{% load problem %}
<div class="container main">
<div class="row">
<div class="col-lg-9">
<div class="col-md-9 col-lg-9">
<div class="row">
<div class="right">
<form class="form-inline" method="get">
@@ -54,7 +54,7 @@
</div>
</div>
<div class="col-lg-3">
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
<div class="panel panel-info">
<div class="panel-heading">

View File

@@ -4,24 +4,56 @@
{% load submission %}
<div class="container main">
{% if submissions %}
<table class="table table-bordered">
<div class="col-md-9 col-lg-9">
<table class="table table-striped">
<thead>
<tr class="" success>
<tr>
<th>#</th>
<th>提交时间</th>
<th>结果</th>
<th>
<div class="dropdown">
<a href="#" class="dropdown-toggle" id="languageFilter" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
语言<span class="caret"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="languageFilter">
<li><a href="/submissions/?language=1">C</a></li>
<li><a href="/submissions/?language=2">C++</a></li>
<li><a href="/submissions/?language=3">Java</a></li>
<li><a href="/submissions/">取消筛选</a></li>
</ul>
</div>
</th>
<th>运行时间</th>
<th>语言</th>
<th>
<div class="dropdown">
<a href="#" class="dropdown-toggle" id="resultFilter" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
结果<span class="caret"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="resultFilter">
<li><a href="/submissions/?result=0">Accepted</a></li>
<li><a href="/submissions/?result=6">Wrong Answer</a></li>
<li><a href="/submissions/?result=1">Runtime Error</a></li>
<li><a href="/submissions/?result=2">Time Limit Exceeded</a></li>
<li><a href="/submissions/?result=3">Memory Limit Exceeded</a></li>
<li><a href="/submissions/?result=4">Compile Error</a></li>
<li><a href="/submissions/?result=5">Format Error</a></li>
<li><a href="/submissions/">取消筛选</a></li>
</ul>
</div>
</th>
</tr>
</thead>
<tbody>
{% for item in submissions %}
<tr class="{{ item.result|translate_result_class }}">
<th scope="row"><a href="/my_submission/{{ item.id }}/" id="id_{{ forloop.counter }}">
<tr>
<th scope="row"><a href="/submission/{{ item.id }}/" id="id_{{ forloop.counter }}">
{{ forloop.counter |add:start_id }}</a></th>
<td>{{ item.create_time }}</td>
<td>{{ item.result|translate_result }}</td>
<td>
{{ item.language|translate_language }}
</td>
<td>
{% if item.accepted_answer_time %}
{{ item.accepted_answer_time }}ms
@@ -29,30 +61,35 @@
--
{% endif %}
</td>
<td>
{{ item.language|translate_language }}
<td class="alert-{{ item.result|translate_result_class }}">
<strong>{{ item.result|translate_result }}</strong>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>你还没有提交记录!</p>
{% endif %}
<nav>
<ul class="pager">
{% if previous_page %}
<li class="previous"><a
href="/submissions/{{ previous_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if tag %}?tag={{ tag }}{% endif %}">
href="/submissions/{{ previous_page }}/{% if filter %}?{{ filter.name }}={{ filter.content }}{% endif %}">
<span aria-hidden="true">&larr;</span> 上一页</a></li>
{% endif %}
{% if next_page %}
<li class="next"><a
href="/submissions/{{ next_page }}/{% if keyword %}?keyword={{ keyword }}{% endif %}{% if tag %}?tag={{ tag }}{% endif %}">下一页 <span
href="/submissions/{{ next_page }}/{% if filter %}?{{ filter.name }}={{ filter.content }}{% endif %}">下一页 <span
aria-hidden="true">&rarr;</span></a></li>
{% endif %}
</ul>
</nav>
{% if not submissions %}
<p>你还没有提交记录!</p>
{% endif %}
</div>
<div class="col-md-3 col-lg-3">
{% include "oj/announcement/_announcement_panel.html" %}
</div>
</div>
{% endblock %}

View File

@@ -45,6 +45,7 @@
<li><a href="/problems/">题目</a></li>
<li><a href="/submissions/">提交</a></li>
<li><a href="/contests/">比赛</a></li>
<li><a href="/groups/">小组</a></li>
<li><a href="/about/">关于</a></li>
</ul>
{% if request.user.is_authenticated %}
@@ -52,20 +53,20 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
李扬
{{ request.user.username }}
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">我的提交</a></li>
<li><a href="/submissions/">我的提交</a></li>
<li><a href="#">我的资料</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出</a></li>
<li><a href="/logout/">退出</a></li>
</ul>
</li>
</ul>
{% else %}
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="/login/" class="dropdown-toggle">
<li>
<a href="/login/">
登录
</a>
</li>
@@ -84,6 +85,10 @@
<script src="/static/js/require.js"></script>
<script>
require(["bootstrap"]);
</script>
{% block js_block %}{% endblock %}
<!-- footer begin -->