diff --git a/.gitignore b/.gitignore index 3a8cb907..dd8c244c 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ db.db *.rdb #*.out *.sqlite3 +.python-version .DS_Store build.txt tmp/ @@ -69,3 +70,4 @@ data/public/upload/* !data/public/upload/.gitkeep data/public/avatar/* !data/public/avatar/default.png + diff --git a/.python-version b/.python-version deleted file mode 100644 index b7276283..00000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.6.2 diff --git a/Dockerfile b/Dockerfile index 9e3f69c4..61950c02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,8 @@ ENV OJ_ENV production ADD . /app WORKDIR /app +HEALTHCHECK --interval=5s --retries=3 CMD python2 /app/deploy/health_check.py + RUN printf "https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/community/\nhttps://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/main/" > /etc/apk/repositories && \ apk add --update --no-cache build-base nginx openssl curl unzip supervisor jpeg-dev zlib-dev postgresql-dev freetype-dev && \ pip install --no-cache-dir -r /app/deploy/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple && \ @@ -12,4 +14,5 @@ RUN printf "https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.6/community/\nhttps:/ RUN curl -L $(curl -s https://api.github.com/repos/QingdaoU/OnlineJudgeFE/releases/latest | grep /dist.zip | cut -d '"' -f 4) -o dist.zip && \ unzip dist.zip && \ rm dist.zip + CMD sh /app/deploy/run.sh diff --git a/README-CN.md b/README-CN.md index f21330f6..55043a0d 100644 --- a/README-CN.md +++ b/README-CN.md @@ -30,7 +30,11 @@ ## 安装 -文档: [https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0](https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0) +请根据此进行安装: [https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0](https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0) + +## 文档 + +[http://docs.onlinejudge.me/](http://docs.onlinejudge.me/) ## 截图 @@ -64,6 +68,11 @@ Rankings 中可以控制图表和菜单的显隐。 ![create-contest](https://user-images.githubusercontent.com/20637881/33372514-428ab922-d539-11e7-8f68-da55dedf3ad3.png) + +## 浏览器支持 + +Modern browsers(chrome, firefox) 和 Internet Explorer 10+. + ## 特别感谢 + 所有为本项目做出贡献的人 diff --git a/README.md b/README.md index fb26ed13..fae5cb87 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,10 @@ The main modules are open source: Follow me: [https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0](https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0) +## Documents + +[http://docs.onlinejudge.me/](http://docs.onlinejudge.me/) + ## Screenshots ### Frontend: @@ -64,6 +68,10 @@ You can control the menu and chart status in rankings. ![create-contest](https://user-images.githubusercontent.com/20637881/33372514-428ab922-d539-11e7-8f68-da55dedf3ad3.png) +## Browser Support + +Modern browsers(chrome, firefox) 和 Internet Explorer 10+. + ## Special Thanks + I'd appreciate a github star if you find this helpful diff --git a/account/serializers.py b/account/serializers.py index ca1d9d47..bd252c7a 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,6 +1,6 @@ from django import forms -from utils.api import DateTimeTZField, serializers, UsernameSerializer +from utils.api import serializers, UsernameSerializer from .models import AdminType, ProblemPermission, User, UserProfile @@ -50,8 +50,6 @@ class ImportUserSeralizer(serializers.Serializer): class UserAdminSerializer(serializers.ModelSerializer): real_name = serializers.SerializerMethodField() - create_time = DateTimeTZField() - last_login = DateTimeTZField() class Meta: model = User @@ -63,9 +61,6 @@ class UserAdminSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer): - create_time = DateTimeTZField() - last_login = DateTimeTZField() - class Meta: model = User fields = ["id", "username", "email", "admin_type", "problem_permission", @@ -74,22 +69,12 @@ class UserSerializer(serializers.ModelSerializer): class UserProfileSerializer(serializers.ModelSerializer): user = UserSerializer() - acm_problems_status = serializers.JSONField() - oi_problems_status = serializers.JSONField() class Meta: model = UserProfile fields = "__all__" -class UserInfoSerializer(serializers.ModelSerializer): - acm_problems_status = serializers.JSONField() - oi_problems_status = serializers.JSONField() - - class Meta: - model = UserProfile - - class EditUserSerializer(serializers.Serializer): id = serializers.IntegerField() username = serializers.CharField(max_length=32) @@ -105,12 +90,12 @@ class EditUserSerializer(serializers.Serializer): class EditUserProfileSerializer(serializers.Serializer): real_name = serializers.CharField(max_length=32, allow_null=True, required=False) - avatar = serializers.CharField(max_length=256, allow_null=True, allow_blank=True, required=False) - blog = serializers.URLField(max_length=256, allow_null=True, allow_blank=True, required=False) - mood = serializers.CharField(max_length=256, allow_null=True, allow_blank=True, required=False) - github = serializers.CharField(max_length=64, allow_null=True, allow_blank=True, required=False) - school = serializers.CharField(max_length=64, allow_null=True, allow_blank=True, required=False) - major = serializers.CharField(max_length=64, allow_null=True, allow_blank=True, required=False) + avatar = serializers.CharField(max_length=256, allow_blank=True, required=False) + blog = serializers.URLField(max_length=256, allow_blank=True, required=False) + mood = serializers.CharField(max_length=256, allow_blank=True, required=False) + github = serializers.CharField(max_length=64, allow_blank=True, required=False) + school = serializers.CharField(max_length=64, allow_blank=True, required=False) + major = serializers.CharField(max_length=64, allow_blank=True, required=False) class ApplyResetPasswordSerializer(serializers.Serializer): @@ -142,3 +127,4 @@ class RankInfoSerializer(serializers.ModelSerializer): class Meta: model = UserProfile + fields = "__all__" diff --git a/announcement/serializers.py b/announcement/serializers.py index b660a615..1edd1d9c 100644 --- a/announcement/serializers.py +++ b/announcement/serializers.py @@ -1,5 +1,5 @@ from utils.api import serializers -from utils.api._serializers import DateTimeTZField, UsernameSerializer +from utils.api._serializers import UsernameSerializer from .models import Announcement @@ -11,12 +11,11 @@ class CreateAnnouncementSerializer(serializers.Serializer): class AnnouncementSerializer(serializers.ModelSerializer): - create_time = DateTimeTZField() - last_update_time = DateTimeTZField() created_by = UsernameSerializer() class Meta: model = Announcement + fields = "__all__" class EditAnnouncementSerializer(serializers.Serializer): diff --git a/conf/serializers.py b/conf/serializers.py index 7f0cf575..3c741b27 100644 --- a/conf/serializers.py +++ b/conf/serializers.py @@ -1,4 +1,4 @@ -from utils.api import DateTimeTZField, serializers +from utils.api import serializers from .models import JudgeServer @@ -29,8 +29,6 @@ class CreateEditWebsiteConfigSerializer(serializers.Serializer): class JudgeServerSerializer(serializers.ModelSerializer): - create_time = DateTimeTZField() - last_heartbeat = DateTimeTZField() status = serializers.CharField() class Meta: diff --git a/contest/serializers.py b/contest/serializers.py index f66cced4..9e425787 100644 --- a/contest/serializers.py +++ b/contest/serializers.py @@ -1,4 +1,4 @@ -from utils.api import DateTimeTZField, UsernameSerializer, serializers +from utils.api import UsernameSerializer, serializers from .models import Contest, ContestAnnouncement, ContestRuleType from .models import ACMContestRank, OIContestRank @@ -29,16 +29,13 @@ class EditConetestSeriaizer(serializers.Serializer): class ContestAdminSerializer(serializers.ModelSerializer): - start_time = DateTimeTZField() - end_time = DateTimeTZField() - create_time = DateTimeTZField() - last_update_time = DateTimeTZField() created_by = UsernameSerializer() status = serializers.CharField() contest_type = serializers.CharField() class Meta: model = Contest + fields = "__all__" class ContestSerializer(ContestAdminSerializer): @@ -49,10 +46,10 @@ class ContestSerializer(ContestAdminSerializer): class ContestAnnouncementSerializer(serializers.ModelSerializer): created_by = UsernameSerializer() - create_time = DateTimeTZField() class Meta: model = ContestAnnouncement + fields = "__all__" class CreateContestAnnouncementSerializer(serializers.Serializer): @@ -76,10 +73,10 @@ class ContestPasswordVerifySerializer(serializers.Serializer): class ACMContestRankSerializer(serializers.ModelSerializer): user = serializers.SerializerMethodField() - submission_info = serializers.JSONField() class Meta: model = ACMContestRank + fields = "__all__" def __init__(self, *args, **kwargs): self.is_admin_role = kwargs.pop("is_admin_role", False) @@ -91,10 +88,10 @@ class ACMContestRankSerializer(serializers.ModelSerializer): class OIContestRankSerializer(serializers.ModelSerializer): user = serializers.SerializerMethodField() - submission_info = serializers.JSONField() class Meta: model = OIContestRank + fields = "__all__" def __init__(self, *args, **kwargs): self.is_admin_role = kwargs.pop("is_admin_role", False) diff --git a/contest/tests.py b/contest/tests.py index 965ee8d2..8a21977d 100644 --- a/contest/tests.py +++ b/contest/tests.py @@ -3,7 +3,6 @@ from datetime import datetime, timedelta from django.utils import timezone -from utils.api._serializers import DateTimeTZField from utils.api.tests import APITestCase from .models import ContestAnnouncement, ContestRuleType, Contest @@ -44,10 +43,9 @@ class ContestAdminAPITest(APITestCase): response = self.client.put(self.url, data=data) self.assertSuccess(response) response_data = response.data["data"] - datetime_tz_field = DateTimeTZField() for k in data.keys(): if isinstance(data[k], datetime): - data[k] = datetime_tz_field.to_representation(data[k]) + continue self.assertEqual(response_data[k], data[k]) def test_get_contests(self): diff --git a/deploy/health_check.py b/deploy/health_check.py new file mode 100644 index 00000000..a12e6158 --- /dev/null +++ b/deploy/health_check.py @@ -0,0 +1,11 @@ +import xmlrpclib + +if __name__ == "__main__": + try: + server = xmlrpclib.Server('http://localhost:9005/RPC2') + info = server.supervisor.getAllProcessInfo() + error_states = list(filter(lambda x: x["state"] != 20, info)) + exit(len(error_states)) + except Exception as e: + print(e.with_traceback()) + exit(1) diff --git a/deploy/test_case_rsync/Dockerfile b/deploy/test_case_rsync/Dockerfile new file mode 100644 index 00000000..e20a116e --- /dev/null +++ b/deploy/test_case_rsync/Dockerfile @@ -0,0 +1,8 @@ +FROM alpine:3.6 + +RUN apk add --update --no-cache rsync + +ADD ./run.sh /tmp/run.sh +ADD ./rsyncd.conf /etc/rsyncd.conf + +CMD /bin/sh /tmp/run.sh \ No newline at end of file diff --git a/deploy/test_case_rsync/docker-compose.yml b/deploy/test_case_rsync/docker-compose.yml new file mode 100644 index 00000000..105dc47a --- /dev/null +++ b/deploy/test_case_rsync/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3" +services: + oj-rsync-master: + image: oj_rsync + container_name: oj-rsync + volumes: + - $PWD/data/backend/test_case:/test_case:ro + - $PWD/data/rsync_master:/log + environment: + - RSYNC_MODE=master + - RSYNC_USER=ojrsync + - RSYNC_PASSWORD=CHANGE_THIS_PASSWORD + ports: + - "0.0.0.0:873:873" + + oj-rsync-slave: + image: oj-rsync + volumes: + - $PWD/test_case:/test_case + - $PWD/rsync_slave:/log + environment: + - RSYNC_MODE=slave + - RSYNC_USER=ojrsync + - RSYNC_PASSWORD=CHANGE_THIS_PASSWORD diff --git a/deploy/test_case_rsync/rsyncd.conf b/deploy/test_case_rsync/rsyncd.conf new file mode 100644 index 00000000..6ff66250 --- /dev/null +++ b/deploy/test_case_rsync/rsyncd.conf @@ -0,0 +1,12 @@ +port = 873 +uid = root +gid = root +use chroot = yes +read only = yes +log file = /log/rsyncd.log + +[testcase] +path = /test_case/ +list = yes +auth users = ojrsync +secrets file = /etc/rsyncd.passwd diff --git a/deploy/test_case_rsync/run.sh b/deploy/test_case_rsync/run.sh new file mode 100644 index 00000000..c86b5713 --- /dev/null +++ b/deploy/test_case_rsync/run.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env sh + +slave_runner() +{ + while true + do + rsync -avzP --delete --progress --password-file=/etc/rsync_slave.passwd $RSYNC_USER@$RSYNC_MASTER_ADDR::testcase /test_case >> /log/rsync_slave.log + sleep 5 + done +} + +master_runner() +{ + rsync --daemon --config=/etc/rsyncd.conf + while true + do + sleep 60 + done +} + +if [ "$RSYNC_MODE" = "master" ]; then + if [ ! -f "/etc/rsyncd.passwd" ]; then + echo "$RSYNC_USER:$RSYNC_PASSWORD" > /etc/rsyncd.passwd + fi + chmod 600 /etc/rsyncd.passwd + master_runner +else + if [ ! -f "/etc/rsync_slave.passwd" ]; then + echo "$RSYNC_PASSWORD" > /etc/rsync_slave.passwd + fi + chmod 600 /etc/rsync_slave.passwd + slave_runner +fi diff --git a/problem/serializers.py b/problem/serializers.py index 46b2e222..aee5721e 100644 --- a/problem/serializers.py +++ b/problem/serializers.py @@ -1,7 +1,7 @@ from django import forms from judge.languages import language_names, spj_language_names -from utils.api import DateTimeTZField, UsernameSerializer, serializers +from utils.api import UsernameSerializer, serializers from .models import Problem, ProblemRuleType, ProblemTag from .utils import parse_problem_template @@ -87,15 +87,8 @@ class CompileSPJSerializer(serializers.Serializer): class BaseProblemSerializer(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() - statistic_info = serializers.JSONField() class ProblemAdminSerializer(BaseProblemSerializer): @@ -104,12 +97,6 @@ class ProblemAdminSerializer(BaseProblemSerializer): fields = "__all__" -class ContestProblemAdminSerializer(BaseProblemSerializer): - class Meta: - model = Problem - fields = "__all__" - - class ProblemSerializer(BaseProblemSerializer): template = serializers.SerializerMethodField() @@ -121,21 +108,16 @@ class ProblemSerializer(BaseProblemSerializer): class Meta: model = Problem - exclude = ("contest", "test_case_score", "test_case_id", "visible", "is_public", - "template", "spj_code", "spj_version", "spj_compile_ok") + exclude = ("test_case_score", "test_case_id", "visible", "is_public", + "spj_code", "spj_version", "spj_compile_ok") -class ContestProblemSerializer(BaseProblemSerializer): +class ProblemSafeSerializer(BaseProblemSerializer): class Meta: model = Problem - exclude = ("test_case_score", "test_case_id", "visible", "is_public", "difficulty") - - -class ContestProblemSafeSerializer(BaseProblemSerializer): - class Meta: - model = Problem - exclude = ("test_case_score", "test_case_id", "visible", "is_public", "difficulty", - "submission_number", "accepted_number", "statistic_info") + exclude = ("test_case_score", "test_case_id", "visible", "is_public", + "spj_code", "spj_version", "spj_compile_ok", + "difficulty", "submission_number", "accepted_number", "statistic_info") class ContestProblemMakePublicSerializer(serializers.Serializer): diff --git a/problem/views/admin.py b/problem/views/admin.py index fd5d7a1b..3116fe27 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -16,7 +16,7 @@ from utils.api import APIView, CSRFExemptAPIView, validate_serializer from utils.shortcuts import rand_str, natural_sort_key from ..models import Problem, ProblemRuleType, ProblemTag -from ..serializers import (CreateContestProblemSerializer, ContestProblemAdminSerializer, CompileSPJSerializer, +from ..serializers import (CreateContestProblemSerializer, CompileSPJSerializer, CreateProblemSerializer, EditProblemSerializer, EditContestProblemSerializer, ProblemAdminSerializer, TestCaseUploadForm, ContestProblemMakePublicSerializer) @@ -320,7 +320,7 @@ class ContestProblemAPI(ProblemBase): except ProblemTag.DoesNotExist: tag = ProblemTag.objects.create(name=item) problem.tags.add(tag) - return self.success(ContestProblemAdminSerializer(problem).data) + return self.success(ProblemAdminSerializer(problem).data) @problem_permission_required def get(self, request): @@ -345,7 +345,7 @@ class ContestProblemAPI(ProblemBase): keyword = request.GET.get("keyword") if keyword: problems = problems.filter(title__contains=keyword) - return self.success(self.paginate_data(request, problems, ContestProblemAdminSerializer)) + return self.success(self.paginate_data(request, problems, ProblemAdminSerializer)) @validate_serializer(EditContestProblemSerializer) @problem_permission_required diff --git a/problem/views/oj.py b/problem/views/oj.py index 57df2dd7..ed39dda9 100644 --- a/problem/views/oj.py +++ b/problem/views/oj.py @@ -3,8 +3,7 @@ from django.db.models import Q, Count from utils.api import APIView from account.decorators import check_contest_permission from ..models import ProblemTag, Problem, ProblemRuleType -from ..serializers import ProblemSerializer, TagSerializer -from ..serializers import ContestProblemSerializer, ContestProblemSafeSerializer +from ..serializers import ProblemSerializer, TagSerializer, ProblemSafeSerializer from contest.models import ContestRuleType @@ -102,16 +101,16 @@ class ContestProblemAPI(APIView): except Problem.DoesNotExist: return self.error("Problem does not exist.") if self.contest.problem_details_permission(request.user): - problem_data = ContestProblemSerializer(problem).data + problem_data = ProblemSerializer(problem).data self._add_problem_status(request, [problem_data, ]) else: - problem_data = ContestProblemSafeSerializer(problem).data + problem_data = ProblemSafeSerializer(problem).data return self.success(problem_data) contest_problems = Problem.objects.select_related("created_by").filter(contest=self.contest, visible=True) if self.contest.problem_details_permission(request.user): - data = ContestProblemSerializer(contest_problems, many=True).data + data = ProblemSerializer(contest_problems, many=True).data self._add_problem_status(request, data) else: - data = ContestProblemSafeSerializer(contest_problems, many=True).data + data = ProblemSafeSerializer(contest_problems, many=True).data return self.success(data) diff --git a/submission/serializers.py b/submission/serializers.py index 2b67ab83..9ad65c81 100644 --- a/submission/serializers.py +++ b/submission/serializers.py @@ -17,17 +17,15 @@ class ShareSubmissionSerializer(serializers.Serializer): class SubmissionModelSerializer(serializers.ModelSerializer): - info = serializers.JSONField() - statistic_info = serializers.JSONField() class Meta: model = Submission + fields = "__all__" # 不显示submission info的serializer, 用于ACM rule_type class SubmissionSafeModelSerializer(serializers.ModelSerializer): problem = serializers.SlugRelatedField(read_only=True, slug_field="_id") - statistic_info = serializers.JSONField() class Meta: model = Submission @@ -36,7 +34,6 @@ class SubmissionSafeModelSerializer(serializers.ModelSerializer): class SubmissionListSerializer(serializers.ModelSerializer): problem = serializers.SlugRelatedField(read_only=True, slug_field="_id") - statistic_info = serializers.JSONField() show_link = serializers.SerializerMethodField() def __init__(self, *args, **kwargs): diff --git a/utils/api/_serializers.py b/utils/api/_serializers.py index 35ef3ae3..b45b987c 100644 --- a/utils/api/_serializers.py +++ b/utils/api/_serializers.py @@ -1,12 +1,6 @@ from rest_framework import serializers -class DateTimeTZField(serializers.DateTimeField): - def to_representation(self, value): - # value = timezone.localtime(value) - return super(DateTimeTZField, self).to_representation(value) - - class UsernameSerializer(serializers.Serializer): id = serializers.IntegerField() username = serializers.CharField()