Merge pull request #118 from QingdaoU/zemal_dev
This commit is contained in:
@@ -1,10 +1,7 @@
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
from utils.api import JSONResponse
|
|
||||||
|
|
||||||
from .models import ProblemPermission
|
|
||||||
|
|
||||||
from contest.models import Contest, ContestType, ContestStatus, ContestRuleType
|
from contest.models import Contest, ContestType, ContestStatus, ContestRuleType
|
||||||
|
from utils.api import JSONResponse, APIError
|
||||||
|
from .models import ProblemPermission
|
||||||
|
|
||||||
|
|
||||||
class BasePermissionDecorator(object):
|
class BasePermissionDecorator(object):
|
||||||
@@ -90,8 +87,7 @@ def check_contest_permission(check_type="details"):
|
|||||||
if not user.is_authenticated():
|
if not user.is_authenticated():
|
||||||
return self.error("Please login first.")
|
return self.error("Please login first.")
|
||||||
# password error
|
# password error
|
||||||
if ("accessible_contests" not in request.session) or \
|
if self.contest.id not in request.session.get("accessible_contests", []):
|
||||||
(self.contest.id not in request.session["accessible_contests"]):
|
|
||||||
return self.error("Password is required.")
|
return self.error("Password is required.")
|
||||||
|
|
||||||
# regular user get contest problems, ranks etc. before contest started
|
# regular user get contest problems, ranks etc. before contest started
|
||||||
@@ -104,7 +100,10 @@ def check_contest_permission(check_type="details"):
|
|||||||
return self.error(f"No permission to get {check_type}")
|
return self.error(f"No permission to get {check_type}")
|
||||||
|
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
return _check_permission
|
return _check_permission
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_created_by(obj, user):
|
||||||
|
if not user.is_admin_role() or (user.is_admin() and obj.created_by != user):
|
||||||
|
raise APIError(msg=f"{obj.__class__.__name__} does not exist")
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class UserProfileSerializer(serializers.ModelSerializer):
|
|||||||
class EditUserSerializer(serializers.Serializer):
|
class EditUserSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
username = serializers.CharField(max_length=32)
|
username = serializers.CharField(max_length=32)
|
||||||
real_name = serializers.CharField(max_length=32, allow_blank=True)
|
real_name = serializers.CharField(max_length=32, allow_blank=True, allow_null=True)
|
||||||
password = serializers.CharField(min_length=6, allow_blank=True, required=False, default=None)
|
password = serializers.CharField(min_length=6, allow_blank=True, required=False, default=None)
|
||||||
email = serializers.EmailField(max_length=64)
|
email = serializers.EmailField(max_length=64)
|
||||||
admin_type = serializers.ChoiceField(choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN))
|
admin_type = serializers.ChoiceField(choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN))
|
||||||
|
|||||||
@@ -153,3 +153,32 @@ class TestCasePruneAPITest(APITestCase):
|
|||||||
resp = self.client.delete(self.url)
|
resp = self.client.delete(self.url)
|
||||||
self.assertSuccess(resp)
|
self.assertSuccess(resp)
|
||||||
mocked_delete_one.assert_called_once_with(valid_id)
|
mocked_delete_one.assert_called_once_with(valid_id)
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseNoteAPITest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.url = self.reverse("get_release_notes_api")
|
||||||
|
self.create_super_admin()
|
||||||
|
self.latest_data = {"update": [
|
||||||
|
{
|
||||||
|
"version": "2099-12-25",
|
||||||
|
"level": 1,
|
||||||
|
"title": "Update at 2099-12-25",
|
||||||
|
"details": ["test get", ]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
|
||||||
|
def test_get_versions(self):
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
self.assertSuccess(resp)
|
||||||
|
|
||||||
|
|
||||||
|
class DashboardInfoAPITest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.url = self.reverse("dashboard_info_api")
|
||||||
|
self.create_admin()
|
||||||
|
|
||||||
|
def test_get_info(self):
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
self.assertSuccess(resp)
|
||||||
|
self.assertEqual(resp.data["data"]["user_count"], 1)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI, TestCasePruneAPI, SMTPTestAPI
|
from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI, TestCasePruneAPI, SMTPTestAPI
|
||||||
|
from ..views import ReleaseNotesAPI, DashboardInfoAPI
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^smtp/?$", SMTPAPI.as_view(), name="smtp_admin_api"),
|
url(r"^smtp/?$", SMTPAPI.as_view(), name="smtp_admin_api"),
|
||||||
@@ -8,4 +9,6 @@ urlpatterns = [
|
|||||||
url(r"^website/?$", WebsiteConfigAPI.as_view(), name="website_config_api"),
|
url(r"^website/?$", WebsiteConfigAPI.as_view(), name="website_config_api"),
|
||||||
url(r"^judge_server/?$", JudgeServerAPI.as_view(), name="judge_server_api"),
|
url(r"^judge_server/?$", JudgeServerAPI.as_view(), name="judge_server_api"),
|
||||||
url(r"^prune_test_case/?$", TestCasePruneAPI.as_view(), name="prune_test_case_api"),
|
url(r"^prune_test_case/?$", TestCasePruneAPI.as_view(), name="prune_test_case_api"),
|
||||||
|
url(r"^versions/?$", ReleaseNotesAPI.as_view(), name="get_release_notes_api"),
|
||||||
|
url(r"^dashboard_info", DashboardInfoAPI.as_view(), name="dashboard_info_api"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,17 +2,25 @@ import os
|
|||||||
import re
|
import re
|
||||||
import hashlib
|
import hashlib
|
||||||
import shutil
|
import shutil
|
||||||
|
import json
|
||||||
|
import pytz
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from account.decorators import super_admin_required
|
from account.decorators import super_admin_required
|
||||||
from problem.models import Problem
|
from problem.models import Problem
|
||||||
|
from account.models import User
|
||||||
|
from submission.models import Submission
|
||||||
|
from contest.models import Contest
|
||||||
from judge.dispatcher import process_pending_task
|
from judge.dispatcher import process_pending_task
|
||||||
from judge.languages import languages, spj_languages
|
from judge.languages import languages, spj_languages
|
||||||
from options.options import SysOptions
|
from options.options import SysOptions
|
||||||
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
||||||
from utils.shortcuts import send_email
|
from utils.shortcuts import send_email, get_env
|
||||||
from utils.xss_filter import XSSHtml
|
from utils.xss_filter import XSSHtml
|
||||||
from .models import JudgeServer
|
from .models import JudgeServer
|
||||||
from .serializers import (CreateEditWebsiteConfigSerializer,
|
from .serializers import (CreateEditWebsiteConfigSerializer,
|
||||||
@@ -30,14 +38,14 @@ class SMTPAPI(APIView):
|
|||||||
smtp.pop("password")
|
smtp.pop("password")
|
||||||
return self.success(smtp)
|
return self.success(smtp)
|
||||||
|
|
||||||
@validate_serializer(CreateSMTPConfigSerializer)
|
|
||||||
@super_admin_required
|
@super_admin_required
|
||||||
|
@validate_serializer(CreateSMTPConfigSerializer)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
SysOptions.smtp_config = request.data
|
SysOptions.smtp_config = request.data
|
||||||
return self.success()
|
return self.success()
|
||||||
|
|
||||||
@validate_serializer(EditSMTPConfigSerializer)
|
|
||||||
@super_admin_required
|
@super_admin_required
|
||||||
|
@validate_serializer(EditSMTPConfigSerializer)
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
smtp = SysOptions.smtp_config
|
smtp = SysOptions.smtp_config
|
||||||
data = request.data
|
data = request.data
|
||||||
@@ -81,8 +89,8 @@ class WebsiteConfigAPI(APIView):
|
|||||||
"website_footer", "allow_register", "submission_list_show_all"]}
|
"website_footer", "allow_register", "submission_list_show_all"]}
|
||||||
return self.success(ret)
|
return self.success(ret)
|
||||||
|
|
||||||
@validate_serializer(CreateEditWebsiteConfigSerializer)
|
|
||||||
@super_admin_required
|
@super_admin_required
|
||||||
|
@validate_serializer(CreateEditWebsiteConfigSerializer)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
for k, v in request.data.items():
|
for k, v in request.data.items():
|
||||||
if k == "website_footer":
|
if k == "website_footer":
|
||||||
@@ -156,7 +164,7 @@ class TestCasePruneAPI(APIView):
|
|||||||
@super_admin_required
|
@super_admin_required
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
return isolated test_case list
|
return orphan test_case list
|
||||||
"""
|
"""
|
||||||
ret_data = []
|
ret_data = []
|
||||||
dir_to_be_removed = self.get_orphan_ids()
|
dir_to_be_removed = self.get_orphan_ids()
|
||||||
@@ -190,3 +198,36 @@ class TestCasePruneAPI(APIView):
|
|||||||
test_case_dir = os.path.join(settings.TEST_CASE_DIR, id)
|
test_case_dir = os.path.join(settings.TEST_CASE_DIR, id)
|
||||||
if os.path.isdir(test_case_dir):
|
if os.path.isdir(test_case_dir):
|
||||||
shutil.rmtree(test_case_dir, ignore_errors=True)
|
shutil.rmtree(test_case_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseNotesAPI(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
try:
|
||||||
|
resp = requests.get("https://raw.githubusercontent.com/QingdaoU/OnlineJudge/master/docs/data.json",
|
||||||
|
timeout=3)
|
||||||
|
releases = resp.json()
|
||||||
|
except (RequestException, ValueError):
|
||||||
|
return self.success()
|
||||||
|
with open("docs/data.json", "r") as f:
|
||||||
|
local_version = json.load(f)["update"][0]["version"]
|
||||||
|
releases["local_version"] = local_version
|
||||||
|
return self.success(releases)
|
||||||
|
|
||||||
|
|
||||||
|
class DashboardInfoAPI(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
today = datetime.today()
|
||||||
|
today_submission_count = Submission.objects.filter(
|
||||||
|
create_time__gte=datetime(today.year, today.month, today.day, 0, 0, tzinfo=pytz.UTC)).count()
|
||||||
|
recent_contest_count = Contest.objects.exclude(end_time__lt=timezone.now()).count()
|
||||||
|
judge_server_count = len(list(filter(lambda x: x.status == "normal", JudgeServer.objects.all())))
|
||||||
|
return self.success({
|
||||||
|
"user_count": User.objects.count(),
|
||||||
|
"recent_contest_count": recent_contest_count,
|
||||||
|
"today_submission_count": today_submission_count,
|
||||||
|
"judge_server_count": judge_server_count,
|
||||||
|
"env": {
|
||||||
|
"FORCE_HTTPS": get_env("FORCE_HTTPS", default=False),
|
||||||
|
"STATIC_CDN_HOST": get_env("STATIC_CDN_HOST", default="")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from utils.api import APIView, validate_serializer
|
|||||||
from utils.cache import cache
|
from utils.cache import cache
|
||||||
from utils.constants import CacheKey
|
from utils.constants import CacheKey
|
||||||
|
|
||||||
from account.decorators import check_contest_permission
|
from account.decorators import check_contest_permission, ensure_created_by
|
||||||
from ..models import Contest, ContestAnnouncement, ACMContestRank
|
from ..models import Contest, ContestAnnouncement, ACMContestRank
|
||||||
from ..serializers import (ContestAnnouncementSerializer, ContestAdminSerializer,
|
from ..serializers import (ContestAnnouncementSerializer, ContestAdminSerializer,
|
||||||
CreateConetestSeriaizer, CreateContestAnnouncementSerializer,
|
CreateConetestSeriaizer, CreateContestAnnouncementSerializer,
|
||||||
@@ -37,8 +37,7 @@ class ContestAPI(APIView):
|
|||||||
data = request.data
|
data = request.data
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.get(id=data.pop("id"))
|
contest = Contest.objects.get(id=data.pop("id"))
|
||||||
if request.user.is_admin() and contest.created_by != request.user:
|
ensure_created_by(contest, request.user)
|
||||||
return self.error("Contest does not exist")
|
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return self.error("Contest does not exist")
|
return self.error("Contest does not exist")
|
||||||
data["start_time"] = dateutil.parser.parse(data["start_time"])
|
data["start_time"] = dateutil.parser.parse(data["start_time"])
|
||||||
@@ -66,20 +65,18 @@ class ContestAPI(APIView):
|
|||||||
if contest_id:
|
if contest_id:
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.get(id=contest_id)
|
contest = Contest.objects.get(id=contest_id)
|
||||||
if request.user.is_admin() and contest.created_by != request.user:
|
ensure_created_by(contest, request.user)
|
||||||
return self.error("Contest does not exist")
|
|
||||||
return self.success(ContestAdminSerializer(contest).data)
|
return self.success(ContestAdminSerializer(contest).data)
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return self.error("Contest does not exist")
|
return self.error("Contest does not exist")
|
||||||
|
|
||||||
contests = Contest.objects.all().order_by("-create_time")
|
contests = Contest.objects.all().order_by("-create_time")
|
||||||
|
if request.user.is_admin():
|
||||||
|
contests = contests.filter(created_by=request.user)
|
||||||
|
|
||||||
keyword = request.GET.get("keyword")
|
keyword = request.GET.get("keyword")
|
||||||
if keyword:
|
if keyword:
|
||||||
contests = contests.filter(title__contains=keyword)
|
contests = contests.filter(title__contains=keyword)
|
||||||
|
|
||||||
if request.user.is_admin():
|
|
||||||
contests = contests.filter(created_by=request.user)
|
|
||||||
return self.success(self.paginate_data(request, contests, ContestAdminSerializer))
|
return self.success(self.paginate_data(request, contests, ContestAdminSerializer))
|
||||||
|
|
||||||
|
|
||||||
@@ -92,8 +89,7 @@ class ContestAnnouncementAPI(APIView):
|
|||||||
data = request.data
|
data = request.data
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.get(id=data.pop("contest_id"))
|
contest = Contest.objects.get(id=data.pop("contest_id"))
|
||||||
if request.user.is_admin() and contest.created_by != request.user:
|
ensure_created_by(contest, request.user)
|
||||||
return self.error("Contest does not exist")
|
|
||||||
data["contest"] = contest
|
data["contest"] = contest
|
||||||
data["created_by"] = request.user
|
data["created_by"] = request.user
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
@@ -109,8 +105,7 @@ class ContestAnnouncementAPI(APIView):
|
|||||||
data = request.data
|
data = request.data
|
||||||
try:
|
try:
|
||||||
contest_announcement = ContestAnnouncement.objects.get(id=data.pop("id"))
|
contest_announcement = ContestAnnouncement.objects.get(id=data.pop("id"))
|
||||||
if request.user.is_admin() and contest_announcement.created_by != request.user:
|
ensure_created_by(contest_announcement, request.user)
|
||||||
return self.error("Contest announcement does not exist")
|
|
||||||
except ContestAnnouncement.DoesNotExist:
|
except ContestAnnouncement.DoesNotExist:
|
||||||
return self.error("Contest announcement does not exist")
|
return self.error("Contest announcement does not exist")
|
||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
@@ -139,15 +134,14 @@ class ContestAnnouncementAPI(APIView):
|
|||||||
if contest_announcement_id:
|
if contest_announcement_id:
|
||||||
try:
|
try:
|
||||||
contest_announcement = ContestAnnouncement.objects.get(id=contest_announcement_id)
|
contest_announcement = ContestAnnouncement.objects.get(id=contest_announcement_id)
|
||||||
if request.user.is_admin() and contest_announcement.created_by != request.user:
|
ensure_created_by(contest_announcement, request.user)
|
||||||
return self.error("Contest announcement does not exist")
|
|
||||||
return self.success(ContestAnnouncementSerializer(contest_announcement).data)
|
return self.success(ContestAnnouncementSerializer(contest_announcement).data)
|
||||||
except ContestAnnouncement.DoesNotExist:
|
except ContestAnnouncement.DoesNotExist:
|
||||||
return self.error("Contest announcement does not exist")
|
return self.error("Contest announcement does not exist")
|
||||||
|
|
||||||
contest_id = request.GET.get("contest_id")
|
contest_id = request.GET.get("contest_id")
|
||||||
if not contest_id:
|
if not contest_id:
|
||||||
return self.error("Paramater error")
|
return self.error("Parameter error")
|
||||||
contest_announcements = ContestAnnouncement.objects.filter(contest_id=contest_id)
|
contest_announcements = ContestAnnouncement.objects.filter(contest_id=contest_id)
|
||||||
if request.user.is_admin():
|
if request.user.is_admin():
|
||||||
contest_announcements = contest_announcements.filter(created_by=request.user)
|
contest_announcements = contest_announcements.filter(created_by=request.user)
|
||||||
@@ -177,12 +171,10 @@ class ACMContestHelper(APIView):
|
|||||||
results.sort(key=lambda x: -x["ac_info"]["ac_time"])
|
results.sort(key=lambda x: -x["ac_info"]["ac_time"])
|
||||||
return self.success(results)
|
return self.success(results)
|
||||||
|
|
||||||
@validate_serializer(ACMContesHelperSerializer)
|
|
||||||
@check_contest_permission(check_type="ranks")
|
@check_contest_permission(check_type="ranks")
|
||||||
|
@validate_serializer(ACMContesHelperSerializer)
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
if not request.user.is_contest_admin(self.contest):
|
|
||||||
return self.error("You are not contest admin")
|
|
||||||
try:
|
try:
|
||||||
rank = ACMContestRank.objects.get(pk=data["rank_id"])
|
rank = ACMContestRank.objects.get(pk=data["rank_id"])
|
||||||
except ACMContestRank.DoesNotExist:
|
except ACMContestRank.DoesNotExist:
|
||||||
|
|||||||
@@ -31,10 +31,17 @@ else
|
|||||||
ln -sf https_redirect.conf http_locations.conf
|
ln -sf https_redirect.conf http_locations.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$LOWER_IP_HEADER" ]; then
|
||||||
|
sed -i "s/__IP_HEADER__/\$http_$LOWER_IP_HEADER/g" api_proxy.conf;
|
||||||
|
else
|
||||||
|
sed -i "s/__IP_HEADER__/\$remote_addr/g" api_proxy.conf;
|
||||||
|
fi
|
||||||
|
|
||||||
cd $APP/dist
|
cd $APP/dist
|
||||||
if [ ! -z "$STATIC_CDN_HOST" ]; then
|
if [ ! -z "$STATIC_CDN_HOST" ]; then
|
||||||
find . -name index.html -exec sed -i "s/link href=\/static/link href=\/\/$STATIC_CDN_HOST\/static/g" {} \;
|
find . -name index.html -exec sed -i "s/__STATIC_CDN_HOST__/\/\/$STATIC_CDN_HOST/g" {} \;
|
||||||
find . -name index.html -exec sed -i "s/script type=text\/javascript src=\/static/script type=text\/javascript src=\/\/$STATIC_CDN_HOST\/static/g" {} \;
|
else
|
||||||
|
find . -name index.html -exec sed -i "s/__STATIC_CDN_HOST__//g" {} \;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd $APP
|
cd $APP
|
||||||
@@ -44,13 +51,13 @@ while [ $n -lt 5 ]
|
|||||||
do
|
do
|
||||||
python manage.py migrate --no-input &&
|
python manage.py migrate --no-input &&
|
||||||
python manage.py inituser --username=root --password=rootroot --action=create_super_admin &&
|
python manage.py inituser --username=root --password=rootroot --action=create_super_admin &&
|
||||||
|
echo "from options.options import SysOptions; SysOptions.judge_server_token='$JUDGE_SERVER_TOKEN'" | python manage.py shell &&
|
||||||
break
|
break
|
||||||
n=$(($n+1))
|
n=$(($n+1))
|
||||||
echo "Failed to migrate, going to retry..."
|
echo "Failed to migrate, going to retry..."
|
||||||
sleep 8
|
sleep 8
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "from options.options import SysOptions; SysOptions.judge_server_token='$JUDGE_SERVER_TOKEN'" | python manage.py shell || exit 1
|
|
||||||
|
|
||||||
chown -R nobody:nogroup $DATA $APP/dist
|
chown -R nobody:nogroup $DATA $APP/dist
|
||||||
exec supervisord -c /app/deploy/supervisord.conf
|
exec supervisord -c /app/deploy/supervisord.conf
|
||||||
|
|||||||
5
deploy/nginx/api_proxy.conf
Normal file
5
deploy/nginx/api_proxy.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
proxy_pass http://backend;
|
||||||
|
proxy_set_header X-Real-IP __IP_HEADER__;
|
||||||
|
proxy_set_header Host $http_host;client_max_body_size 200M;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection '';
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
location /api/judge_server_heartbeat {
|
||||||
|
include api_proxy.conf;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,7 @@ location /public {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /api {
|
location /api {
|
||||||
proxy_pass http://backend;
|
include api_proxy.conf;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
client_max_body_size 200M;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location /data/ {
|
location /data/ {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"update": [
|
"update": [
|
||||||
{
|
{
|
||||||
"version": "2017-12-25",
|
"version": "2017-01-04",
|
||||||
"level": 1,
|
"level": "Recommend",
|
||||||
"title": "Update at 2017-12-25",
|
"title": "Update at 2018-01-04",
|
||||||
"details": [
|
"details": [
|
||||||
"Fix some issues under IE/Edge",
|
"Fix some issues under IE/Edge",
|
||||||
"Add backend error reporter",
|
"Add backend error reporter",
|
||||||
"New email template",
|
"New email template",
|
||||||
"A more flexible throttling function",
|
"A more flexible throttling function",
|
||||||
|
"Add admin dashboard (this page)",
|
||||||
"Other bugs and enhancements"
|
"Other bugs and enhancements"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import os
|
from utils.shortcuts import get_env
|
||||||
|
|
||||||
|
|
||||||
def get_env(name, default=""):
|
|
||||||
return os.environ.get(name, default)
|
|
||||||
|
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
|
|||||||
import os
|
import os
|
||||||
import raven
|
import raven
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from utils.shortcuts import get_env
|
||||||
|
from .custom_settings import *
|
||||||
|
|
||||||
if os.environ.get("OJ_ENV") == "production":
|
production_env = get_env("OJ_ENV", "dev") == "production"
|
||||||
|
if production_env:
|
||||||
from .production_settings import *
|
from .production_settings import *
|
||||||
else:
|
else:
|
||||||
from .dev_settings import *
|
from .dev_settings import *
|
||||||
|
|
||||||
from .custom_settings import *
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
# Applications
|
# Applications
|
||||||
@@ -128,7 +129,7 @@ UPLOAD_DIR = f"{DATA_DIR}{UPLOAD_PREFIX}"
|
|||||||
STATICFILES_DIRS = [os.path.join(DATA_DIR, "public")]
|
STATICFILES_DIRS = [os.path.join(DATA_DIR, "public")]
|
||||||
|
|
||||||
|
|
||||||
LOGGING_HANDLERS = ['console'] if DEBUG else ['console', 'sentry']
|
LOGGING_HANDLERS = ['console', 'sentry'] if production_env else ['console']
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
@@ -204,13 +205,6 @@ BROKER_URL = f"{REDIS_URL}/3"
|
|||||||
CELERY_TASK_SOFT_TIME_LIMIT = CELERY_TASK_TIME_LIMIT = 180
|
CELERY_TASK_SOFT_TIME_LIMIT = CELERY_TASK_TIME_LIMIT = 180
|
||||||
CELERY_ACCEPT_CONTENT = ["json"]
|
CELERY_ACCEPT_CONTENT = ["json"]
|
||||||
CELERY_TASK_SERIALIZER = "json"
|
CELERY_TASK_SERIALIZER = "json"
|
||||||
|
|
||||||
# 用于限制用户恶意提交大量代码
|
|
||||||
TOKEN_BUCKET_DEFAULT_CAPACITY = 10
|
|
||||||
|
|
||||||
# 单位:每分钟
|
|
||||||
TOKEN_BUCKET_FILL_RATE = 2
|
|
||||||
|
|
||||||
RAVEN_CONFIG = {
|
RAVEN_CONFIG = {
|
||||||
'dsn': 'https://b200023b8aed4d708fb593c5e0a6ad3d:1fddaba168f84fcf97e0d549faaeaff0@sentry.io/263057'
|
'dsn': 'https://b200023b8aed4d708fb593c5e0a6ad3d:1fddaba168f84fcf97e0d549faaeaff0@sentry.io/263057'
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ from wsgiref.util import FileWrapper
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import StreamingHttpResponse, HttpResponse
|
from django.http import StreamingHttpResponse, HttpResponse
|
||||||
|
|
||||||
from account.decorators import problem_permission_required
|
from account.decorators import problem_permission_required, ensure_created_by
|
||||||
from judge.dispatcher import SPJCompiler
|
from judge.dispatcher import SPJCompiler
|
||||||
from contest.models import Contest, ContestStatus
|
from contest.models import Contest, ContestStatus
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
@@ -49,7 +49,6 @@ class TestCaseAPI(CSRFExemptAPIView):
|
|||||||
else:
|
else:
|
||||||
return sorted(ret, key=natural_sort_key)
|
return sorted(ret, key=natural_sort_key)
|
||||||
|
|
||||||
@problem_permission_required
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
problem_id = request.GET.get("problem_id")
|
problem_id = request.GET.get("problem_id")
|
||||||
if not problem_id:
|
if not problem_id:
|
||||||
@@ -59,6 +58,11 @@ class TestCaseAPI(CSRFExemptAPIView):
|
|||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exists")
|
return self.error("Problem does not exists")
|
||||||
|
|
||||||
|
if problem.contest:
|
||||||
|
ensure_created_by(problem.contest, request.user)
|
||||||
|
else:
|
||||||
|
ensure_created_by(problem, request.user)
|
||||||
|
|
||||||
test_case_dir = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id)
|
test_case_dir = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id)
|
||||||
if not os.path.isdir(test_case_dir):
|
if not os.path.isdir(test_case_dir):
|
||||||
return self.error("Test case does not exists")
|
return self.error("Test case does not exists")
|
||||||
@@ -79,7 +83,6 @@ class TestCaseAPI(CSRFExemptAPIView):
|
|||||||
response["Content-Length"] = os.path.getsize(file_name)
|
response["Content-Length"] = os.path.getsize(file_name)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@problem_permission_required
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
form = TestCaseUploadForm(request.POST, request.FILES)
|
form = TestCaseUploadForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@@ -147,7 +150,6 @@ class TestCaseAPI(CSRFExemptAPIView):
|
|||||||
|
|
||||||
class CompileSPJAPI(APIView):
|
class CompileSPJAPI(APIView):
|
||||||
@validate_serializer(CompileSPJSerializer)
|
@validate_serializer(CompileSPJSerializer)
|
||||||
@problem_permission_required
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
spj_version = rand_str(8)
|
spj_version = rand_str(8)
|
||||||
@@ -186,11 +188,12 @@ class ProblemBase(APIView):
|
|||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
id = request.GET.get("id")
|
id = request.GET.get("id")
|
||||||
if not id:
|
if not id:
|
||||||
return self.error("Invalid parameter, id is requred")
|
return self.error("Invalid parameter, id is required")
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=id)
|
problem = Problem.objects.get(id=id, contest_id__isnull=True)
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exists")
|
return self.error("Problem does not exists")
|
||||||
|
ensure_created_by(problem, request.user)
|
||||||
if Submission.objects.filter(problem=problem).exists():
|
if Submission.objects.filter(problem=problem).exists():
|
||||||
return self.error("Can't delete the problem as it has submissions")
|
return self.error("Can't delete the problem as it has submissions")
|
||||||
d = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id)
|
d = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id)
|
||||||
@@ -201,11 +204,10 @@ class ProblemBase(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class ProblemAPI(ProblemBase):
|
class ProblemAPI(ProblemBase):
|
||||||
@validate_serializer(CreateProblemSerializer)
|
|
||||||
@problem_permission_required
|
@problem_permission_required
|
||||||
|
@validate_serializer(CreateProblemSerializer)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
|
|
||||||
_id = data["_id"]
|
_id = data["_id"]
|
||||||
if not _id:
|
if not _id:
|
||||||
return self.error("Display ID is required")
|
return self.error("Display ID is required")
|
||||||
@@ -236,8 +238,7 @@ class ProblemAPI(ProblemBase):
|
|||||||
if problem_id:
|
if problem_id:
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=problem_id)
|
problem = Problem.objects.get(id=problem_id)
|
||||||
if not user.can_mgmt_all_problem() and problem.created_by != user:
|
ensure_created_by(problem, request.user)
|
||||||
return self.error("Problem does not exist")
|
|
||||||
return self.success(ProblemAdminSerializer(problem).data)
|
return self.success(ProblemAdminSerializer(problem).data)
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
@@ -256,17 +257,15 @@ class ProblemAPI(ProblemBase):
|
|||||||
problems = problems.filter(title__contains=keyword)
|
problems = problems.filter(title__contains=keyword)
|
||||||
return self.success(self.paginate_data(request, problems, ProblemAdminSerializer))
|
return self.success(self.paginate_data(request, problems, ProblemAdminSerializer))
|
||||||
|
|
||||||
@validate_serializer(EditProblemSerializer)
|
|
||||||
@problem_permission_required
|
@problem_permission_required
|
||||||
|
@validate_serializer(EditProblemSerializer)
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
problem_id = data.pop("id")
|
problem_id = data.pop("id")
|
||||||
user = request.user
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=problem_id)
|
problem = Problem.objects.get(id=problem_id)
|
||||||
if not user.can_mgmt_all_problem() and problem.created_by != user:
|
ensure_created_by(problem, request.user)
|
||||||
return self.error("Problem does not exist")
|
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
|
|
||||||
@@ -300,13 +299,11 @@ class ProblemAPI(ProblemBase):
|
|||||||
|
|
||||||
class ContestProblemAPI(ProblemBase):
|
class ContestProblemAPI(ProblemBase):
|
||||||
@validate_serializer(CreateContestProblemSerializer)
|
@validate_serializer(CreateContestProblemSerializer)
|
||||||
@problem_permission_required
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.get(id=data.pop("contest_id"))
|
contest = Contest.objects.get(id=data.pop("contest_id"))
|
||||||
if request.user.is_admin() and contest.created_by != request.user:
|
ensure_created_by(contest, request.user)
|
||||||
return self.error("Contest does not exist")
|
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return self.error("Contest does not exist")
|
return self.error("Contest does not exist")
|
||||||
|
|
||||||
@@ -345,8 +342,7 @@ class ContestProblemAPI(ProblemBase):
|
|||||||
if problem_id:
|
if problem_id:
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=problem_id)
|
problem = Problem.objects.get(id=problem_id)
|
||||||
if user.is_admin() and problem.contest.created_by != user:
|
ensure_created_by(problem, user)
|
||||||
return self.error("Problem does not exist")
|
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
return self.success(ProblemAdminSerializer(problem).data)
|
return self.success(ProblemAdminSerializer(problem).data)
|
||||||
@@ -366,10 +362,11 @@ class ContestProblemAPI(ProblemBase):
|
|||||||
@problem_permission_required
|
@problem_permission_required
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
data = request.data
|
data = request.data
|
||||||
|
user = request.user
|
||||||
|
|
||||||
try:
|
try:
|
||||||
contest = Contest.objects.get(id=data.pop("contest_id"))
|
contest = Contest.objects.get(id=data.pop("contest_id"))
|
||||||
if request.user.is_admin() and contest.created_by != request.user:
|
ensure_created_by(contest, user)
|
||||||
return self.error("Contest does not exist")
|
|
||||||
except Contest.DoesNotExist:
|
except Contest.DoesNotExist:
|
||||||
return self.error("Contest does not exist")
|
return self.error("Contest does not exist")
|
||||||
|
|
||||||
@@ -377,12 +374,10 @@ class ContestProblemAPI(ProblemBase):
|
|||||||
return self.error("Invalid rule type")
|
return self.error("Invalid rule type")
|
||||||
|
|
||||||
problem_id = data.pop("id")
|
problem_id = data.pop("id")
|
||||||
user = request.user
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
problem = Problem.objects.get(id=problem_id)
|
problem = Problem.objects.get(id=problem_id)
|
||||||
if not user.can_mgmt_all_problem() and problem.created_by != user:
|
ensure_created_by(problem, user)
|
||||||
return self.error("Problem does not exist")
|
|
||||||
except Problem.DoesNotExist:
|
except Problem.DoesNotExist:
|
||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
|
|
||||||
@@ -428,7 +423,7 @@ class MakeContestProblemPublicAPIView(APIView):
|
|||||||
return self.error("Problem does not exist")
|
return self.error("Problem does not exist")
|
||||||
|
|
||||||
if not problem.contest or problem.is_public:
|
if not problem.contest or problem.is_public:
|
||||||
return self.error("Alreay be a public problem")
|
return self.error("Already be a public problem")
|
||||||
problem.is_public = True
|
problem.is_public = True
|
||||||
problem.save()
|
problem.save()
|
||||||
# https://docs.djangoproject.com/en/1.11/topics/db/queries/#copying-model-instances
|
# https://docs.djangoproject.com/en/1.11/topics/db/queries/#copying-model-instances
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ from django.views.generic import View
|
|||||||
logger = logging.getLogger("")
|
logger = logging.getLogger("")
|
||||||
|
|
||||||
|
|
||||||
|
class APIError(Exception):
|
||||||
|
def __init__(self, msg, err=None):
|
||||||
|
self.err = err
|
||||||
|
self.msg = msg
|
||||||
|
super().__init__(err, msg)
|
||||||
|
|
||||||
|
|
||||||
class ContentType(object):
|
class ContentType(object):
|
||||||
json_request = "application/json"
|
json_request = "application/json"
|
||||||
json_response = "application/json;charset=UTF-8"
|
json_response = "application/json;charset=UTF-8"
|
||||||
@@ -137,6 +144,11 @@ class APIView(View):
|
|||||||
return self.error(err="invalid-request", msg=str(e))
|
return self.error(err="invalid-request", msg=str(e))
|
||||||
try:
|
try:
|
||||||
return super(APIView, self).dispatch(request, *args, **kwargs)
|
return super(APIView, self).dispatch(request, *args, **kwargs)
|
||||||
|
except APIError as e:
|
||||||
|
ret = {"msg": e.msg}
|
||||||
|
if e.err:
|
||||||
|
ret["err"] = e.err
|
||||||
|
return self.error(**ret)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
return self.server_error()
|
return self.server_error()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import random
|
import random
|
||||||
@@ -76,3 +77,7 @@ def send_email(smtp_config, from_name, to_email, to_name, subject, content):
|
|||||||
password=smtp_config["password"],
|
password=smtp_config["password"],
|
||||||
port=smtp_config["port"],
|
port=smtp_config["port"],
|
||||||
tls=smtp_config["tls"])
|
tls=smtp_config["tls"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_env(name, default=""):
|
||||||
|
return os.environ.get(name, default)
|
||||||
|
|||||||
Reference in New Issue
Block a user