diff --git a/account/decorators.py b/account/decorators.py index 9df97e54..e424e8c4 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -4,6 +4,8 @@ from utils.api import JSONResponse from .models import ProblemPermission +from contest.models import Contest, ContestType, ContestStatus + class BasePermissionDecorator(object): def __init__(self, func): @@ -53,3 +55,42 @@ class problem_permission_required(admin_role_required): if self.request.user.problem_permission == ProblemPermission.NONE: return False return True + + +def check_contest_permission(func): + """ + 只供Class based view 使用,检查用户是否有权进入该contest, + 若通过验证,在view中可通过self.contest获得该contest + """ + @functools.wraps(func) + def _check_permission(*args, **kwargs): + self = args[0] + request = args[1] + user = request.user + if kwargs.get('contest_id'): + contest_id = kwargs.pop('contest_id') + else: + contest_id = request.GET.get('contest_id') + if not contest_id: + return self.error("Parameter contest_id not exist.") + + try: + # use self.contest to avoid query contest again in view. + self.contest = Contest.objects.get(id=contest_id, visible=True) + except Contest.DoesNotExist: + return self.error("Contest %s doesn't exist" % contest_id) + + if self.contest.contest_type == ContestType.PASSWORD_PROTECTED_CONTEST: + # Anonymous + if not user.is_authenticated(): + return self.error("Please login in first.") + # creator + if request.user == self.contest.created_by: + return func(*args, **kwargs) + # password error + if ("contests" not in request.session) or (self.contest.id not in request.session["contests"]): + return self.error("Password is required.") + + return func(*args, **kwargs) + + return _check_permission diff --git a/contest/migrations/0004_auto_20170717_1324.py b/contest/migrations/0004_auto_20170717_1324.py new file mode 100644 index 00000000..6a7aa092 --- /dev/null +++ b/contest/migrations/0004_auto_20170717_1324.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2017-07-17 13:24 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('contest', '0003_auto_20170217_0820'), + ] + + operations = [ + migrations.AlterModelOptions( + name='contest', + options={'ordering': ('create_time',)}, + ), + ] diff --git a/contest/models.py b/contest/models.py index 72e60cbf..dc52f476 100644 --- a/contest/models.py +++ b/contest/models.py @@ -12,9 +12,9 @@ class ContestType(object): class ContestStatus(object): - CONTEST_NOT_START = "Not Started" - CONTEST_ENDED = "Ended" - CONTEST_UNDERWAY = "Underway" + CONTEST_NOT_START = "1" + CONTEST_ENDED = "-1" + CONTEST_UNDERWAY = "0" class ContestRuleType(object): @@ -58,6 +58,7 @@ class Contest(models.Model): class Meta: db_table = "contest" + ordering = ("create_time",) class ContestRank(models.Model): diff --git a/contest/serializers.py b/contest/serializers.py index b886afed..528fff4c 100644 --- a/contest/serializers.py +++ b/contest/serializers.py @@ -2,6 +2,8 @@ from utils.api import DateTimeTZField, UsernameSerializer, serializers from .models import Contest, ContestAnnouncement, ContestRuleType +from problem.serializers import ContestProblemSerializer + class CreateConetestSeriaizer(serializers.Serializer): title = serializers.CharField(max_length=128) @@ -14,6 +16,17 @@ class CreateConetestSeriaizer(serializers.Serializer): real_time_rank = serializers.BooleanField() +class EditConetestSeriaizer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField(max_length=128) + description = serializers.CharField() + start_time = serializers.DateTimeField() + end_time = serializers.DateTimeField() + password = serializers.CharField(allow_blank=True, allow_null=True, max_length=32) + visible = serializers.BooleanField() + real_time_rank = serializers.BooleanField() + + class ContestSerializer(serializers.ModelSerializer): start_time = DateTimeTZField() end_time = DateTimeTZField() @@ -27,16 +40,6 @@ class ContestSerializer(serializers.ModelSerializer): model = Contest exclude = ('password', 'visible') -class EditConetestSeriaizer(serializers.Serializer): - id = serializers.IntegerField() - title = serializers.CharField(max_length=128) - description = serializers.CharField() - start_time = serializers.DateTimeField() - end_time = serializers.DateTimeField() - password = serializers.CharField(allow_blank=True, allow_null=True, max_length=32) - visible = serializers.BooleanField() - real_time_rank = serializers.BooleanField() - class ContestAnnouncementSerializer(serializers.ModelSerializer): created_by = UsernameSerializer() @@ -50,3 +53,8 @@ class CreateContestAnnouncementSerializer(serializers.Serializer): title = serializers.CharField(max_length=128) content = serializers.CharField() contest_id = serializers.IntegerField() + + +class ContestPasswordVerifySerializer(serializers.Serializer): + contest_id = serializers.IntegerField() + password = serializers.CharField(max_length=30, required=True) diff --git a/contest/urls/oj.py b/contest/urls/oj.py index 283bdff9..6c093438 100644 --- a/contest/urls/oj.py +++ b/contest/urls/oj.py @@ -1,9 +1,9 @@ from django.conf.urls import url -from ..views.oj import ContestAnnouncementListAPI, ContestListAPI +from ..views.oj import ContestAnnouncementListAPI, ContestAPI urlpatterns = [ - url(r"^contest/?$", ContestListAPI.as_view(), name="contest_api"), + url(r"^contest/?$", ContestAPI.as_view(), name="contest_api"), url(r"^contest/announcement/?$", ContestAnnouncementListAPI.as_view(), name="contest_announcement_api"), ] diff --git a/contest/views/oj.py b/contest/views/oj.py index 8bff965a..b576fa52 100644 --- a/contest/views/oj.py +++ b/contest/views/oj.py @@ -1,8 +1,9 @@ -from utils.api import APIView +from utils.api import APIView, validate_serializer +from account.decorators import login_required from ..models import ContestAnnouncement, Contest from ..serializers import ContestAnnouncementSerializer -from ..serializers import ContestSerializer +from ..serializers import ContestSerializer, ContestPasswordVerifySerializer class ContestAnnouncementListAPI(APIView): @@ -17,18 +18,39 @@ class ContestAnnouncementListAPI(APIView): return self.success(ContestAnnouncementSerializer(data, many=True).data) -class ContestListAPI(APIView): +class ContestAPI(APIView): def get(self, request): - contest_id = request.GET.get("id") + contest_id = request.GET.get("contest_id") if contest_id: try: contest = Contest.objects.get(id=contest_id, visible=True) return self.success(ContestSerializer(contest).data) except Contest.DoesNotExist: - return self.error("Contest Doesn't exist.") + return self.error("Contest doesn't exist.") contests = Contest.objects.filter(visible=True) keyword = request.GET.get("keyword") if keyword: contests = contests.filter(title__contains=keyword) return self.success(self.paginate_data(request, contests, ContestSerializer)) + + +class ContestPasswordVerifyAPI(APIView): + @validate_serializer(ContestPasswordVerifySerializer) + @login_required + def get(self, request): + data = request.data + try: + contest = Contest.objects.get(id=data["contest_id"], visible=True, password__isnull=False) + except Contest.DoesNotExist: + return self.error("Contest %s doesn't exist." % data["contest_id"]) + if contest.password != data["password"]: + return self.error("Password doesn't match.") + + # password verify OK. + if "contests" not in request.session: + request.session["contests"] = [] + request.session["contests"].append(int(data["contest_id"])) + # https://docs.djangoproject.com/en/dev/topics/http/sessions/#when-sessions-are-saved + request.session.modified = True + return self.success(True) diff --git a/judge/languages.py b/judge/languages.py index 6002760a..1c2fd302 100644 --- a/judge/languages.py +++ b/judge/languages.py @@ -99,7 +99,7 @@ _java_lang_config = { "compile_command": "/usr/bin/javac {src_path} -d {exe_dir} -encoding UTF8" }, "run": { - "command": "/usr/bin/java -cp {exe_dir} -Xss1M -XX:MaxPermSize=16M -XX:PermSize=8M -Xms16M -Xmx{max_memory}k " + "command": "/usr/bin/java -cp {exe_dir} -Xss1M -Xms16M -Xmx{max_memory}k " "-Djava.security.manager -Djava.security.policy==/etc/java_policy -Djava.awt.headless=true Main", "seccomp_rule": None, "env": ["MALLOC_ARENA_MAX=1"] diff --git a/problem/serializers.py b/problem/serializers.py index 0425f3de..ad391c19 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -4,6 +4,7 @@ from judge.languages import language_names, spj_language_names from utils.api import DateTimeTZField, UsernameSerializer, serializers from .models import Problem, ProblemRuleType, ProblemTag +from .models import ContestProblem class TestCaseUploadForm(forms.Form): @@ -85,3 +86,17 @@ class ProblemSerializer(serializers.ModelSerializer): class Meta: model = Problem + + +class ContestProblemSerializer(serializers.ModelSerializer): + samples = serializers.JSONField() + test_case_score = serializers.JSONField() + languages = serializers.JSONField() + template = serializers.JSONField() + tags = serializers.SlugRelatedField(many=True, slug_field="name", read_only=True) + create_time = DateTimeTZField() + last_update_time = DateTimeTZField() + created_by = UsernameSerializer() + + class Meta: + model = ContestProblem \ No newline at end of file diff --git a/problem/urls/oj.py b/problem/urls/oj.py index 5e7e6bea..74503022 100644 --- a/problem/urls/oj.py +++ b/problem/urls/oj.py @@ -1,8 +1,9 @@ from django.conf.urls import url -from ..views.oj import ProblemTagAPI, ProblemAPI +from ..views.oj import ProblemTagAPI, ProblemAPI, ContestProblemAPI urlpatterns = [ url(r"^problem/tags/?$", ProblemTagAPI.as_view(), name="problem_tag_list_api"), url(r"^problems/?$", ProblemAPI.as_view(), name="problem_list_api"), + url(r"^contest_problems/?$", ContestProblemAPI.as_view(), name="contest_problem_api"), ] diff --git a/problem/views/oj.py b/problem/views/oj.py index a6875206..4ad6488c 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -1,8 +1,9 @@ from django.db.models import Q from utils.api import APIView - -from ..models import ProblemTag, Problem +from account.decorators import login_required, check_contest_permission +from ..models import ProblemTag, Problem, ContestProblem from ..serializers import ProblemSerializer, TagSerializer +from ..serializers import ContestProblemSerializer class ProblemTagAPI(APIView): @@ -42,3 +43,10 @@ class ProblemAPI(APIView): problems = problems.filter(difficulty=difficulty_rank) return self.success(self.paginate_data(request, problems, ProblemSerializer)) + + +class ContestProblemAPI(APIView): + @check_contest_permission + def get(self, request): + contest_problems = ContestProblem.objects.filter(contest=self.contest, visible=True) + return self.success(ContestProblemSerializer(contest_problems, many=True).data) diff --git a/submission/migrations/0004_auto_20170717_1324.py b/submission/migrations/0004_auto_20170717_1324.py new file mode 100644 index 00000000..ca355ae6 --- /dev/null +++ b/submission/migrations/0004_auto_20170717_1324.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2017-07-17 13:24 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submission', '0003_auto_20170704_1243'), + ] + + operations = [ + migrations.AlterModelOptions( + name='submission', + options={'ordering': ('-created_time',)}, + ), + ] diff --git a/submission/models.py b/submission/models.py index bc445ecb..7e8c06f7 100644 --- a/submission/models.py +++ b/submission/models.py @@ -42,6 +42,7 @@ class Submission(models.Model): class Meta: db_table = "submission" + ordering = ("-created_time",) def __str__(self): return self.id diff --git a/utils/api/_serializers.py b/utils/api/_serializers.py index f841f036..816845af 100644 --- a/utils/api/_serializers.py +++ b/utils/api/_serializers.py @@ -4,7 +4,7 @@ from rest_framework import serializers class DateTimeTZField(serializers.DateTimeField): def to_representation(self, value): - # self.format = "%Y-%-m-%d %H:%M:%S %Z" + # self.format = "%Y-%m-%d %H:%M:%S %Z" value = timezone.localtime(value) return super(DateTimeTZField, self).to_representation(value)