diff --git a/account/serializers.py b/account/serializers.py index 9a43457..179d28d 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -25,7 +25,6 @@ class UserRegisterSerializer(serializers.Serializer): class UserChangePasswordSerializer(serializers.Serializer): - username = serializers.CharField(max_length=30) old_password = serializers.CharField() new_password = serializers.CharField(max_length=30, min_length=6) diff --git a/account/tests.py b/account/tests.py index a75d2c5..bbdf1e9 100644 --- a/account/tests.py +++ b/account/tests.py @@ -123,22 +123,13 @@ class UserChangePasswordAPITest(APITestCase): user = User.objects.create(username="test") user.set_password("aaabbb") user.save() + self.client.login(username="test",password="aaabbb") def test_error_old_password(self): - data = {"username": "test", "old_password": "aaaccc", "new_password": "aaaddd"} + data = {"old_password": "aaaccc", "new_password": "aaaddd"} response = self.client.post(self.url, data=data) self.assertEqual(response.data, {"code": 1, "data": u"密码不正确,请重新修改!"}) - def test_invalid_data_format(self): - data = {"old_password": "aaa", "new_password": "aaaddd"} - response = self.client.post(self.url, data=data) - self.assertEqual(response.data["code"], 1) - - def test_username_does_not_exist(self): - data = {"username": "test1", "old_password": "aaabbb", "new_password": "aaaddd"} - response = self.client.post(self.url, data=data) - self.assertEqual(response.data["code"], 1) - def test_success_change_password(self): data = {"username": "test", "old_password": "aaabbb", "new_password": "aaaccc"} response = self.client.post(self.url, data=data) @@ -369,3 +360,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) diff --git a/account/views.py b/account/views.py index f2c6ca3..1da8967 100644 --- a/account/views.py +++ b/account/views.py @@ -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 @@ -34,6 +35,10 @@ class UserLoginAPIView(APIView): else: return serializer_invalid_response(serializer) +@login_required +def logout(request): + auth.logout(request) + return http.HttpResponseRedirect("/") class UserRegisterAPIView(APIView): def post(self, request): @@ -64,6 +69,7 @@ class UserRegisterAPIView(APIView): class UserChangePasswordAPIView(APIView): + @login_required def post(self, request): """ 用户修改密码json api接口 @@ -73,7 +79,8 @@ class UserChangePasswordAPIView(APIView): serializer = UserChangePasswordSerializer(data=request.data) if serializer.is_valid(): data = serializer.data - user = auth.authenticate(username=data["username"], password=data["old_password"]) + username = request.user.username + user = auth.authenticate(username=username, password=data["old_password"]) if user: user.set_password(data["new_password"]) user.save() diff --git a/contest/serializers.py b/contest/serializers.py index a9f33ee..65b2fe7 100644 --- a/contest/serializers.py +++ b/contest/serializers.py @@ -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 diff --git a/contest/tests.py b/contest/tests.py index 58c9127..052829f 100644 --- a/contest/tests.py +++ b/contest/tests.py @@ -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): diff --git a/contest/views.py b/contest/views.py index 91aa8f7..60334f3 100644 --- a/contest/views.py +++ b/contest/views.py @@ -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) diff --git a/contest_submission/tests.py b/contest_submission/tests.py index 7ce503c..b1eace1 100644 --- a/contest_submission/tests.py +++ b/contest_submission/tests.py @@ -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) diff --git a/contest_submission/views.py b/contest_submission/views.py index 0dba7cb..003ad8a 100644 --- a/contest_submission/views.py +++ b/contest_submission/views.py @@ -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}) @@ -112,4 +114,38 @@ def contest_problem_submissions_list_page(request, contest_id, page=1): return render(request, "oj/contest/submissions_list.html", {"submissions": current_page, "page": int(page), "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20, - "contest": contest}) \ No newline at end of file + "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) diff --git a/group/migrations/0005_joingrouprequest_accepted.py b/group/migrations/0005_joingrouprequest_accepted.py new file mode 100644 index 0000000..1091ce0 --- /dev/null +++ b/group/migrations/0005_joingrouprequest_accepted.py @@ -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), + ), + ] diff --git a/group/models.py b/group/models.py index b7f49c1..b399246 100644 --- a/group/models.py +++ b/group/models.py @@ -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" diff --git a/group/serializers.py b/group/serializers.py index 065baf9..402ea5b 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -21,7 +21,7 @@ class EditGroupSerializer(serializers.Serializer): class CreateJoinGroupRequestSerializer(serializers.Serializer): group_id = serializers.IntegerField() - message = serializers.CharField(max_length=30) + message = serializers.CharField(max_length=30, required=False) class JoinGroupRequestSerializer(serializers.ModelSerializer): diff --git a/group/tests.py b/group/tests.py index f62dfa5..208720d 100644 --- a/group/tests.py +++ b/group/tests.py @@ -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) diff --git a/group/views.py b/group/views.py index ff1cbde..2929706 100644 --- a/group/views.py +++ b/group/views.py @@ -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): @@ -26,7 +29,7 @@ class GroupAPIViewBase(object): else: group = Group.objects.get(id=group_id, visible=True, admin=request.user) return group - + def get_groups(self, request): """ 如果是超级管理员,就返回全部的小组 @@ -113,8 +116,8 @@ class GroupAdminAPIView(APIView, GroupAPIViewBase): elif request.GET.get("admin_id", None): groups = groups.filter(admin__id=request.GET["admin_id"]) return paginate(request, groups, GroupSerializer) - - + + class GroupMemberAdminAPIView(APIView, GroupAPIViewBase): def get(self, request): """ @@ -129,9 +132,9 @@ class GroupMemberAdminAPIView(APIView, GroupAPIViewBase): group = self.get_group(request, group_id) except Group.DoesNotExist: return error_response(u"小组不存在") - + return paginate(request, UserGroupRelation.objects.filter(group=group), GroupMemberSerializer) - + def put(self, request): """ 删除小组成员的api接口 @@ -180,6 +183,8 @@ class JoinGroupAPIView(APIView): else: return error_response(u"你已经是小组成员了") elif group.join_group_setting == 1: + if not data["message"]: + return error_response(u"message : 该字段是必填项。") try: JoinGroupRequest.objects.get(user=request.user, group=group, status=False) return error_response(u"你已经提交过申请了,请等待审核") @@ -190,7 +195,7 @@ class JoinGroupAPIView(APIView): return error_response(u"该小组不允许任何人加入") else: return serializer_invalid_response(serializer) - + def get(self, request): """ 搜索小组的api,需要传递keyword参数 @@ -233,9 +238,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 +250,70 @@ 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}) diff --git a/oj/local_settings.py b/oj/local_settings.py index da1a672..0b7f642 100644 --- a/oj/local_settings.py +++ b/oj/local_settings.py @@ -5,7 +5,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 下面是需要自己修改的 -LOG_PATH = "LOG/" +LOG_PATH = "log/" # 注意这是web 服务器访问的地址,判题端访问的地址不一定一样,因为可能不在一台机器上 DATABASES = { @@ -18,7 +18,7 @@ DATABASES = { 'NAME': 'oj_submission', 'ENGINE': 'django.db.backends.mysql', 'HOST': "121.42.32.129", - 'POST': 3306, + 'PORT': 3306, 'USER': 'root', 'PASSWORD': 'mypwd' } @@ -29,5 +29,5 @@ DEBUG = True # 同理 这是 web 服务器的上传路径 TEST_CASE_DIR = os.path.join(BASE_DIR, 'test_case/') -DATABASE_ROUTERS = ['oj.db_router.DBRouter'] +ALLOWED_HOSTS = [] diff --git a/oj/server_settings.py b/oj/server_settings.py index 9bad579..badfef6 100644 --- a/oj/server_settings.py +++ b/oj/server_settings.py @@ -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.3, + 'HOST': '127.0.0.1', + 'PORT': 3306, + 'USER': 'root', + 'PASSWORD': 'mypwd' + }, + 'submission': { + 'NAME': 'oj_submission', + 'ENGINE': 'django.db.backends.mysql', + 'HOST': "127.0.0.1", + 'PORT': 3306, + 'USER': 'root', + 'PASSWORD': 'mypwd' + } +} + +DEBUG = True + +# 同理 这是 web 服务器的上传路径 +TEST_CASE_DIR = '/root/test_case/' + +ALLOWED_HOSTS = ['*'] + diff --git a/oj/settings.py b/oj/settings.py index cf4dafc..376060f 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -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 @@ -170,4 +168,6 @@ LOGGING = { REST_FRAMEWORK = { 'TEST_REQUEST_DEFAULT_FORMAT': 'json' -} \ No newline at end of file +} + +DATABASE_ROUTERS = ['oj.db_router.DBRouter'] \ No newline at end of file diff --git a/oj/urls.py b/oj/urls.py index adb7ef0..648c849 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -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\d+)/problem/(?P\d+)/$', "contest.views.contest_problem_page", @@ -87,7 +89,6 @@ urlpatterns = [ url(r'^contest/(?P\d+)/$', "contest.views.contest_page", name="contest_page"), - url(r'^problem/(?P\d+)/$', "problem.views.problem_page", name="problem_page"), url(r'^problem/(?P\d+)/$', "problem.views.problem_page", name="problem_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"), @@ -99,6 +100,11 @@ urlpatterns = [ url(r'^submissions/$', "submission.views.my_submission_list_page", name="my_submission_list_page"), url(r'^submissions/(?P\d+)/$', "submission.views.my_submission_list_page", name="my_submission_list_page"), - url(r'^contest/(?P\d+)/rank/$', "contest.views.contest_rank_page", name="contest_rank_page") + url(r'^contest/(?P\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\d+)/$', "group.views.group_list_page", name="group_list_page"), + url(r'^group/(?P\d+)/$', "group.views.group_page", name="group_page"), + url(r'^group/(?P\d+)/applications/$', "group.views.application_list_page", name="group_application_page"), + url(r'^group/application/(?P\d+)/$', "group.views.application_page", name="group_application") ] diff --git a/problem/views.py b/problem/views.py index a295ad1..b73085f 100644 --- a/problem/views.py +++ b/problem/views.py @@ -226,7 +226,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) diff --git a/static/src/css/admin.css b/static/src/css/admin.css index f7e7581..fb9c9f2 100644 --- a/static/src/css/admin.css +++ b/static/src/css/admin.css @@ -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; +} diff --git a/static/src/js/app/admin/admin.js b/static/src/js/app/admin/admin.js index bd0d51e..e3e8545 100644 --- a/static/src/js/app/admin/admin.js +++ b/static/src/js/app/admin/admin.js @@ -72,6 +72,7 @@ define("admin", ["jquery", "avalon"], function ($, avalon) { var vm = avalon.define({ $id: "admin", template_url: "template/" + hash + ".html", + username: "", groupId: -1, problemId: -1, adminNavList: [], @@ -93,6 +94,7 @@ define("admin", ["jquery", "avalon"], function ($, avalon) { dataType: "json", success: function(data){ if(!data.code){ + vm.username = data.data.username; if (data.data.admin_type == 2){ vm.adminNavList = superAdminNav; } @@ -133,11 +135,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 () { diff --git a/static/src/js/app/admin/announcement/announcement.js b/static/src/js/app/admin/announcement/announcement.js index 7dd2051..4c67ef2 100644 --- a/static/src/js/app/admin/announcement/announcement.js +++ b/static/src/js/app/admin/announcement/announcement.js @@ -1,163 +1,163 @@ 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", - //通用变量 - announcementList: [], // 公告列表数据项 - previousPage: 0, // 之前的页数 - nextPage: 0, // 之后的页数 - page: 1, // 当前页数 - editingAnnouncementId: 0, // 正在编辑的公告的ID, 为零说明未在编辑 - totalPage: 1, // 总页数 - showVisibleOnly: false, //仅显示可见公告 - // 编辑 - newTitle: "", - announcementVisible: 0, - showGlobalViewRadio: true, - isGlobal: true, - allGroups: [], - getState: function (el) { //获取公告当前状态,显示 - if (el.visible) - return "可见"; - else - return "隐藏"; - }, - getNext: function () { - if (!vm.nextPage) - return; - getPageData(vm.page + 1); - }, - getPrevious: function () { - if (!vm.previousPage) - return; - getPageData(vm.page - 1); - }, - getBtnClass: function (btnType) { - if (btnType == "next") { - return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled"; - } - else { - return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled"; - } - - }, - editAnnouncement: function (announcement) { - vm.newTitle = announcement.title; - editAnnouncementEditor.setValue(announcement.content); - vm.announcementVisible = announcement.visible; - if (vm.editingAnnouncementId == announcement.id) + var vm = avalon.define({ + $id: "announcement", + //通用变量 + announcementList: [], // 公告列表数据项 + previousPage: 0, // 之前的页数 + nextPage: 0, // 之后的页数 + page: 1, // 当前页数 + editingAnnouncementId: 0, // 正在编辑的公告的ID, 为零说明未在编辑 + totalPage: 1, // 总页数 + showVisibleOnly: false, //仅显示可见公告 + // 编辑 + newTitle: "", + announcementVisible: 0, + showGlobalViewRadio: true, + isGlobal: true, + allGroups: [], + getNext: function () { + if (!vm.nextPage) + return; + getPageData(vm.page + 1); + }, + getPrevious: function () { + if (!vm.previousPage) + return; + getPageData(vm.page - 1); + }, + getBtnClass: function (btnType) { + if (btnType == "next") { + return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled"; + } + else { + return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled"; + } + }, + editAnnouncement: function (announcement) { + vm.newTitle = announcement.title; + editAnnouncementEditor.setValue(announcement.content); + vm.announcementVisible = announcement.visible; + if (vm.editingAnnouncementId == announcement.id) + vm.editingAnnouncementId = 0; + else + vm.editingAnnouncementId = announcement.id; + vm.isGlobal = announcement.is_global; + for (var i = 0; i < announcement.groups.length; i++) { + for (var j = 0; j < vm.allGroups.length; j++) { + if (announcement.groups[i] == vm.allGroups[j].id) { + vm.allGroups[j].isSelected = true; + } + } + } + editAnnouncementEditor.focus(); + }, + cancelEdit: function () { vm.editingAnnouncementId = 0; - else - vm.editingAnnouncementId = announcement.id; - vm.isGlobal = announcement.is_global; - for (var i = 0; i < announcement.groups.length; i++) { - for (var j = 0; j < vm.allGroups.length; j++) { - if (announcement.groups[i] == vm.allGroups[j].id) { - vm.allGroups[j].isSelected = true; + }, + submitChange: function () { + var title = vm.newTitle; + var content = editAnnouncementEditor.getValue(); + + if (content == "" || title == "") { + bsAlert("标题和内容都不能为空"); + return false; + } + + var selectedGroups = []; + if (!vm.isGlobal) { + for (var i = 0; i < vm.allGroups.length; i++) { + if (vm.allGroups[i].isSelected) { + selectedGroups.push(vm.allGroups[i].id); + } } } - } - editAnnouncementEditor.focus(); - }, - cancelEdit: function () { - vm.editingAnnouncementId = 0; - }, - submitChange: function () { - var title = vm.newTitle; - var content = editAnnouncementEditor.getValue(); - if (content == "" || title == "") { - bsAlert("标题和内容都不能为空"); - return false; - } - - var selectedGroups = []; - if (!vm.isGlobal) { - for (var i = 0; i < vm.allGroups.length; i++) { - if (vm.allGroups[i].isSelected) { - selectedGroups.push(vm.allGroups[i].id); - } + if (!vm.isGlobal && !selectedGroups.length) { + bsAlert("请至少选择一个小组"); + return false; } - } - if (!vm.isGlobal && !selectedGroups.length) { - bsAlert("请至少选择一个小组"); - return false; - } - - $.ajax({ - beforeSend: csrfTokenHeader, - url: "/api/admin/announcement/", - contentType: "application/json", - dataType: "json", - method: "put", - data: JSON.stringify({ - id: vm.editingAnnouncementId, - title: title, - content: content, - visible: vm.announcementVisible, - is_global: vm.isGlobal, - groups: selectedGroups - }), - success: function (data) { - if (!data.code) { - bsAlert("修改成功"); - vm.editingAnnouncementId = 0; - getPageData(1); + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/admin/announcement/", + contentType: "application/json", + dataType: "json", + method: "put", + data: JSON.stringify({ + id: vm.editingAnnouncementId, + title: title, + content: content, + visible: vm.announcementVisible, + is_global: vm.isGlobal, + groups: selectedGroups + }), + success: function (data) { + if (!data.code) { + bsAlert("修改成功"); + vm.editingAnnouncementId = 0; + getPageData(1); + } + else { + bsAlert(data.data); + } } - else { - bsAlert(data.data); - } - } - }); + }); - } - }); - vm.$watch("showVisibleOnly", function () { - getPageData(1); - }); + } + }); + vm.$watch("showVisibleOnly", function () { + getPageData(1); + }); + } getPageData(1); - $.ajax({ - url: "/api/admin/group/", - method: "get", - dataType: "json", - success: function (data) { - if (!data.code) { - if (!data.data.length) { - bsAlert("您的用户权限只能创建组内公告,但是您还没有创建过小组"); - return; - } - for (var i = 0; i < data.data.length; i++) { - var item = data.data[i]; - item["isSelected"] = false; - vm.allGroups.push(item); - } - } - else { - bsAlert(data.data); - } - } - }); - $.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", + dataType: "json", + success: function (data) { + if (!data.code) { + if (!data.data.length) { + if (admin_type != 2) + bsAlert("您的用户权限只能创建组内公告,但是您还没有创建过小组"); + return; + } + for (var i = 0; i < data.data.length; i++) { + var item = data.data[i]; + item["isSelected"] = false; + vm.allGroups.push(item); + } + } + else { + bsAlert(data.data); + } + } + }); } }); diff --git a/static/src/js/app/admin/contest/addContest.js b/static/src/js/app/admin/contest/addContest.js index 437fbdf..a87e4e2 100644 --- a/static/src/js/app/admin/contest/addContest.js +++ b/static/src/js/app/admin/contest/addContest.js @@ -43,7 +43,6 @@ require(["jquery", "avalon", "editor", "uploader", "bsAlert", "csrfToken", "date contentType: "application/json", data: JSON.stringify(ajaxData), method: "post", - contentType: "application/json", success: function (data) { if (!data.code) { bsAlert("添加成功!将转到比赛列表页以便为比赛添加问题(注意比赛当前状态为:隐藏)"); @@ -131,7 +130,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++) { diff --git a/static/src/js/app/admin/contest/contestList.js b/static/src/js/app/admin/contest/contestList.js index 9c1ba71..c920cd6 100644 --- a/static/src/js/app/admin/contest/contestList.js +++ b/static/src/js/app/admin/contest/contestList.js @@ -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/", diff --git a/static/src/js/app/admin/contest/submissionList.js b/static/src/js/app/admin/contest/submissionList.js new file mode 100644 index 0000000..34fd591 --- /dev/null +++ b/static/src/js/app/admin/contest/submissionList.js @@ -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(); +}); \ No newline at end of file diff --git a/static/src/js/app/admin/group/group.js b/static/src/js/app/admin/group/group.js index 470f9ca..425e4bb 100644 --- a/static/src/js/app/admin/group/group.js +++ b/static/src/js/app/admin/group/group.js @@ -1,51 +1,57 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) { - avalon.ready(function () { - avalon.vmodels.group = null; - var vm = avalon.define({ - $id: "group", - //通用变量 - groupList: [], // 用户列表数据项 - previousPage: 0, // 之前的页数 - nextPage: 0, // 之后的页数 - page: 1, // 当前页数 - totalPage: 1, // 总页数 - keyword: "", + //avalon.vmodels.group = null; + if (avalon.vmodels.group) { + var vm = avalon.vmodels.group; + } + else { - getNext: function () { - if (!vm.nextPage) - return; - getPageData(vm.page + 1); - }, - getPrevious: function () { - if (!vm.previousPage) - return; - getPageData(vm.page - 1); - }, - getBtnClass: function (btnType) { - if (btnType == "next") { - return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled"; - } - else { - return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled"; - } + var vm = avalon.define({ + $id: "group", + //通用变量 + groupList: [], // 用户列表数据项 + previousPage: 0, // 之前的页数 + nextPage: 0, // 之后的页数 + page: 1, // 当前页数 + totalPage: 1, // 总页数 + keyword: "", + + getNext: function () { + if (!vm.nextPage) + return; + getPageData(vm.page + 1); + }, + getPrevious: function () { + if (!vm.previousPage) + return; + getPageData(vm.page - 1); + }, + getBtnClass: function (btnType) { + if (btnType == "next") { + return vm.nextPage ? "btn btn-primary" : "btn btn-primary disabled"; + } + else { + return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled"; + } + + }, + search: function(){ + getPageData(1); + }, + getGroupSettingString: function (setting) { + return {0: "允许任何人加入", 1: "提交请求后管理员审核", 2: "不允许任何人加入"}[setting] + }, + showGroupDetailPage: function (groupId) { + vm.$fire("up!showGroupDetailPage", groupId); + } + }); + } - }, - search: function(){ - getPageData(1); - }, - getGroupSettingString: function (setting) { - return {0: "允许任何人加入", 1: "提交请求后管理员审核", 2: "不允许任何人加入"}[setting] - }, - showGroupDetailPage: function (groupId) { - 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({ diff --git a/static/src/js/app/admin/group/groupDetail.js b/static/src/js/app/admin/group/groupDetail.js index cddc755..c06f9ce 100644 --- a/static/src/js/app/admin/group/groupDetail.js +++ b/static/src/js/app/admin/group/groupDetail.js @@ -3,55 +3,60 @@ require(["jquery", "avalon", "csrfToken", "bsAlert", "validator"], function ($, // avalon:定义模式 group_list avalon.ready(function () { - avalon.vmodels.groupDetail = null; - var vm = avalon.define({ - $id: "groupDetail", - //通用变量 - memberList: [], - previousPage: 0, - nextPage: 0, - page: 1, - totalPage: 1, - name: "", - description: "", - checkedSetting: "0", - 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"; - } - }, + if (avalon.vmodels.groupDetail) { + var vm = avalon.vmodels.groupDetail; + } + else { + var vm = avalon.define({ + $id: "groupDetail", + //通用变量 + memberList: [], + previousPage: 0, + nextPage: 0, + page: 1, + totalPage: 1, + name: "", + description: "", + checkedSetting: "0", - removeMember: function (relation) { - $.ajax({ - beforeSend: csrfTokenHeader, - url: "/api/admin/group_member/", - method: "put", - data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}), - contentType: "application/json", - success: function (data) { - vm.memberList.remove(relation); - bsAlert(data.data); + 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"; } - }) - }, - showGroupListPage: function () { - vm.$fire("up!showGroupListPage"); - } - }); + else { + return vm.previousPage ? "btn btn-primary" : "btn btn-primary disabled"; + } + }, + + removeMember: function (relation) { + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/admin/group_member/", + method: "put", + data: JSON.stringify({group_id: relation.group, members: [relation.user.id]}), + contentType: "application/json", + success: function (data) { + vm.memberList.remove(relation); + bsAlert(data.data); + } + }) + }, + showGroupListPage: function () { + vm.$fire("up!showGroupListPage"); + } + }); + } avalon.scan(); getPageData(1); diff --git a/static/src/js/app/admin/group/joinGroupRequestList.js b/static/src/js/app/admin/group/joinGroupRequestList.js index e565aad..b178a80 100644 --- a/static/src/js/app/admin/group/joinGroupRequestList.js +++ b/static/src/js/app/admin/group/joinGroupRequestList.js @@ -1,51 +1,57 @@ -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; - var vm = avalon.define({ - $id: "requestList", - //通用变量 - requestList: [], // 列表数据项 - previousPage: 0, // 之前的页数 - nextPage: 0, // 之后的页数 - page: 1, // 当前页数 - totalPage: 1, // 总页数 - 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) { - getPageData(page_index); - }, - processRequest: function(request, status){ - $.ajax({ - beforeSend: csrfTokenHeader, - url: "/api/admin/join_group_request/", - method: "put", - data: {request_id: request.id, status: status}, - success: function(data){ - vm.requestList.remove(request); - bsAlert(data.data); + if (avalon.vmodels.requestList) { + var vm = avalon.vmodels.requestList; + } + else { + + var vm = avalon.define({ + $id: "requestList", + //通用变量 + requestList: [], // 列表数据项 + previousPage: 0, // 之前的页数 + nextPage: 0, // 之后的页数 + page: 1, // 当前页数 + totalPage: 1, // 总页数 + + 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) { + getPageData(page_index); + }, + processRequest: function(request, status){ + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/admin/join_group_request/", + method: "put", + data: {request_id: request.id, status: status}, + success: function(data){ + vm.requestList.remove(request); + bsAlert(data.data); + } + }) + } + }); + } 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); - } - } - }) - });*/ }); }); \ No newline at end of file diff --git a/static/src/js/app/admin/problem/problem.js b/static/src/js/app/admin/problem/problem.js index 2632ebf..9fe56df 100644 --- a/static/src/js/app/admin/problem/problem.js +++ b/static/src/js/app/admin/problem/problem.js @@ -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", diff --git a/static/src/js/app/admin/problem/submissionList.js b/static/src/js/app/admin/problem/submissionList.js index 405cffa..1af2e6c 100644 --- a/static/src/js/app/admin/problem/submissionList.js +++ b/static/src/js/app/admin/problem/submissionList.js @@ -1,53 +1,59 @@ require(["jquery", "avalon", "csrfToken", "bsAlert"], function ($, avalon, csrfTokenHeader, bsAlert) { avalon.ready(function () { - avalon.vmodels.submissionList = null; - var vm = avalon.define({ - $id: "submissionList", - 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) { - getPageData(page_index); - }, - showSubmissionDetailPage: function (submissionId) { - }, - showProblemListPage: function(){ - vm.$fire("up!showProblemListPage"); - } - }); + if (avalon.vmodels.submissionList){ + var vm = avalon.vmodels.submissionList; + } + else { + + var vm = avalon.define({ + $id: "submissionList", + 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) { + getPageData(page_index); + }, + showSubmissionDetailPage: function (submissionId) { + + }, + showProblemListPage: function(){ + vm.$fire("up!showProblemListPage"); + } + }); + } getPageData(1); diff --git a/static/src/js/app/admin/user/userList.js b/static/src/js/app/admin/user/userList.js index 3f80c89..d2949f0 100644 --- a/static/src/js/app/admin/user/userList.js +++ b/static/src/js/app/admin/user/userList.js @@ -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; } diff --git a/static/src/js/app/oj/account/change_password.js b/static/src/js/app/oj/account/change_password.js index ba88f5f..608114f 100644 --- a/static/src/js/app/oj/account/change_password.js +++ b/static/src/js/app/oj/account/change_password.js @@ -2,13 +2,12 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c $('form').validator().on('submit', function (e) { e.preventDefault(); - var username = $("#username").val(); var newPassword = $("#new_password ").val(); var password = $("#password").val(); $.ajax({ beforeSend: csrfTokenHeader, url: "/api/change_password/", - data: {username: username, new_password: newPassword, old_password: password}, + data: {new_password: newPassword, old_password: password}, dataType: "json", method: "post", success: function (data) { diff --git a/static/src/js/app/oj/group/group.js b/static/src/js/app/oj/group/group.js new file mode 100644 index 0000000..c36448e --- /dev/null +++ b/static/src/js/app/oj/group/group.js @@ -0,0 +1,30 @@ +require(["jquery", "csrfToken", "bsAlert"], function ($, csrfTokenHeader, bsAlert) { + $("#sendApplication").click(function (){ + var message; + if ($("#applyMessage").length) { + message = $("#applyMessage").val(); + if (!message) + bsAlert("提交失败,请填写申请信息!"); + return false; + } + + var groupId = window.location.pathname.split("/")[2]; + 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("申请已提交!"); + } + } + }) + }) +}) diff --git a/submission/tests.py b/submission/tests.py index e745bde..0d374ee 100644 --- a/submission/tests.py +++ b/submission/tests.py @@ -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 - - - - - - - diff --git a/submission/views.py b/submission/views.py index 17c1615..0925e13 100644 --- a/submission/views.py +++ b/submission/views.py @@ -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}) \ No newline at end of file + "previous_page": previous_page, "next_page": next_page, "start_id": int(page) * 20 - 20, + "announcements": announcements, "filter":filter}) \ No newline at end of file diff --git a/template/src/admin/admin.html b/template/src/admin/admin.html index 1179182..67b9163 100644 --- a/template/src/admin/admin.html +++ b/template/src/admin/admin.html @@ -15,7 +15,7 @@ - + + {% else %} +

当前没有合适的比赛,你可以尝试到小组列表申请加入一些小组,以便参加小组内部的比赛

+ {% endif %} -
+
{% include "oj/announcement/_announcement_panel.html" %}
diff --git a/template/src/oj/contest/contest_problems_list.html b/template/src/oj/contest/contest_problems_list.html index 2a863dd..0289592 100644 --- a/template/src/oj/contest/contest_problems_list.html +++ b/template/src/oj/contest/contest_problems_list.html @@ -26,7 +26,7 @@ -
+
@@ -55,7 +55,7 @@ -
+
{% include "oj/announcement/_announcement_panel.html" %}
diff --git a/template/src/oj/contest/contest_rank.html b/template/src/oj/contest/contest_rank.html index 823b021..7d17134 100644 --- a/template/src/oj/contest/contest_rank.html +++ b/template/src/oj/contest/contest_rank.html @@ -20,6 +20,7 @@
+ {% if result %}
@@ -49,6 +50,9 @@ {% endfor %}
+ {% else %} +

还没有结果

+ {% endif %}
diff --git a/template/src/oj/group/group.html b/template/src/oj/group/group.html new file mode 100644 index 0000000..4fe3a00 --- /dev/null +++ b/template/src/oj/group/group.html @@ -0,0 +1,47 @@ +{% extends 'oj_base.html' %} + +{% block body %} +
+ +

{{ group.name }}

+ +

发布时间 : {{ group.create_time }}   + 创建者 : {{ group.admin }} +

+ +
+
+ + +

{{ group.description|safe }}

+
+
+
+
+ {% if group.join_group_setting %} +
+ + + +
+ {% endif %} +
+ +
+
+
+{% endblock %} +{% block js_block %} + +{% endblock %} \ No newline at end of file diff --git a/template/src/oj/group/group_list.html b/template/src/oj/group/group_list.html new file mode 100644 index 0000000..d499e1a --- /dev/null +++ b/template/src/oj/group/group_list.html @@ -0,0 +1,71 @@ +{% extends "oj_base.html" %} +{% block body %} + {% load problem %} +
+
+
+
+
+ +
+ + +
+ +
+
+
+ + + + + + + + + + + + {% for item in groups %} + + + + + + + + {% endfor %} + +
#名称加入方式创建者创建时间
{{ item.id }}{{ item.name }} + {% if item.join_group_setting %} + 需要申请 + {% else %} + 无需申请 + {% endif %} + {{ item.admin }}{{ item.create_time }}
+ +
+
+ +
+ {% include "oj/announcement/_announcement_panel.html" %} +
+
+
+{% endblock %} diff --git a/template/src/oj/group/my_application.html b/template/src/oj/group/my_application.html new file mode 100644 index 0000000..71ba5cb --- /dev/null +++ b/template/src/oj/group/my_application.html @@ -0,0 +1,26 @@ +{% extends 'oj_base.html' %} + +{% block body %} + +
+ + +

{{ application.message|safe }}

+ + {% if application.status %} + {% if application.accepted %} +

管理员接受了你的请求

+ {% else %} +

管理员拒绝了你的请求

+ {% endif %} + {% else %} +

待审核

+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/template/src/oj/group/my_application_list.html b/template/src/oj/group/my_application_list.html new file mode 100644 index 0000000..f2b276c --- /dev/null +++ b/template/src/oj/group/my_application_list.html @@ -0,0 +1,44 @@ +{% extends 'oj_base.html' %} + +{% block body %} + +
+ + {% if applications %} + + + + + + + + + + {% for item in applications %} + + + + {% if item.status %} + {% if item.accepted %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + + {% endfor %} + + +
#提交时间结果
{{ forloop.counter }}{{ item.create_time }}通过拒绝未处理
+ {% else %} +

你还没有申请该小组

+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/template/src/oj/index.html b/template/src/oj/index.html index 520f753..80aaa0d 100644 --- a/template/src/oj/index.html +++ b/template/src/oj/index.html @@ -8,7 +8,8 @@