From 8454b3679243e72e351b104e3e10349bc028e30c Mon Sep 17 00:00:00 2001 From: virusdefender Date: Thu, 4 Jan 2018 19:27:41 +0800 Subject: [PATCH 1/7] fix permission --- account/decorators.py | 17 ++++++++--------- conf/views.py | 6 +++--- contest/views/admin.py | 28 ++++++++++----------------- problem/views/admin.py | 43 +++++++++++++++++++----------------------- utils/api/api.py | 12 ++++++++++++ 5 files changed, 52 insertions(+), 54 deletions(-) diff --git a/account/decorators.py b/account/decorators.py index 839893f..36a2b44 100644 --- a/account/decorators.py +++ b/account/decorators.py @@ -1,10 +1,7 @@ import functools - -from utils.api import JSONResponse - -from .models import ProblemPermission - from contest.models import Contest, ContestType, ContestStatus, ContestRuleType +from utils.api import JSONResponse, APIError +from .models import ProblemPermission class BasePermissionDecorator(object): @@ -90,8 +87,7 @@ def check_contest_permission(check_type="details"): if not user.is_authenticated(): return self.error("Please login first.") # password error - if ("accessible_contests" not in request.session) or \ - (self.contest.id not in request.session["accessible_contests"]): + if self.contest.id not in request.session.get("accessible_contests", []): return self.error("Password is required.") # 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 func(*args, **kwargs) - return _check_permission - 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") diff --git a/conf/views.py b/conf/views.py index 972312d..5e82645 100644 --- a/conf/views.py +++ b/conf/views.py @@ -30,14 +30,14 @@ class SMTPAPI(APIView): smtp.pop("password") return self.success(smtp) - @validate_serializer(CreateSMTPConfigSerializer) @super_admin_required + @validate_serializer(CreateSMTPConfigSerializer) def post(self, request): SysOptions.smtp_config = request.data return self.success() - @validate_serializer(EditSMTPConfigSerializer) @super_admin_required + @validate_serializer(EditSMTPConfigSerializer) def put(self, request): smtp = SysOptions.smtp_config data = request.data @@ -81,8 +81,8 @@ class WebsiteConfigAPI(APIView): "website_footer", "allow_register", "submission_list_show_all"]} return self.success(ret) - @validate_serializer(CreateEditWebsiteConfigSerializer) @super_admin_required + @validate_serializer(CreateEditWebsiteConfigSerializer) def post(self, request): for k, v in request.data.items(): if k == "website_footer": diff --git a/contest/views/admin.py b/contest/views/admin.py index d4066b5..a44e9da 100644 --- a/contest/views/admin.py +++ b/contest/views/admin.py @@ -5,7 +5,7 @@ from utils.api import APIView, validate_serializer from utils.cache import cache 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 ..serializers import (ContestAnnouncementSerializer, ContestAdminSerializer, CreateConetestSeriaizer, CreateContestAnnouncementSerializer, @@ -37,8 +37,7 @@ class ContestAPI(APIView): data = request.data try: contest = Contest.objects.get(id=data.pop("id")) - if request.user.is_admin() and contest.created_by != request.user: - return self.error("Contest does not exist") + ensure_created_by(contest, request.user) except Contest.DoesNotExist: return self.error("Contest does not exist") data["start_time"] = dateutil.parser.parse(data["start_time"]) @@ -66,20 +65,18 @@ class ContestAPI(APIView): if contest_id: try: contest = Contest.objects.get(id=contest_id) - if request.user.is_admin() and contest.created_by != request.user: - return self.error("Contest does not exist") + ensure_created_by(contest, request.user) return self.success(ContestAdminSerializer(contest).data) except Contest.DoesNotExist: return self.error("Contest does not exist") 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") if 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)) @@ -92,8 +89,7 @@ class ContestAnnouncementAPI(APIView): data = request.data try: contest = Contest.objects.get(id=data.pop("contest_id")) - if request.user.is_admin() and contest.created_by != request.user: - return self.error("Contest does not exist") + ensure_created_by(contest, request.user) data["contest"] = contest data["created_by"] = request.user except Contest.DoesNotExist: @@ -109,8 +105,7 @@ class ContestAnnouncementAPI(APIView): data = request.data try: contest_announcement = ContestAnnouncement.objects.get(id=data.pop("id")) - if request.user.is_admin() and contest_announcement.created_by != request.user: - return self.error("Contest announcement does not exist") + ensure_created_by(contest_announcement, request.user) except ContestAnnouncement.DoesNotExist: return self.error("Contest announcement does not exist") for k, v in data.items(): @@ -139,15 +134,14 @@ class ContestAnnouncementAPI(APIView): if contest_announcement_id: try: contest_announcement = ContestAnnouncement.objects.get(id=contest_announcement_id) - if request.user.is_admin() and contest_announcement.created_by != request.user: - return self.error("Contest announcement does not exist") + ensure_created_by(contest_announcement, request.user) return self.success(ContestAnnouncementSerializer(contest_announcement).data) except ContestAnnouncement.DoesNotExist: return self.error("Contest announcement does not exist") contest_id = request.GET.get("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) if request.user.is_admin(): 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"]) return self.success(results) - @validate_serializer(ACMContesHelperSerializer) @check_contest_permission(check_type="ranks") + @validate_serializer(ACMContesHelperSerializer) def put(self, request): data = request.data - if not request.user.is_contest_admin(self.contest): - return self.error("You are not contest admin") try: rank = ACMContestRank.objects.get(pk=data["rank_id"]) except ACMContestRank.DoesNotExist: diff --git a/problem/views/admin.py b/problem/views/admin.py index 352aa09..5846e35 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -8,7 +8,7 @@ from wsgiref.util import FileWrapper from django.conf import settings 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 contest.models import Contest, ContestStatus from submission.models import Submission @@ -49,7 +49,6 @@ class TestCaseAPI(CSRFExemptAPIView): else: return sorted(ret, key=natural_sort_key) - @problem_permission_required def get(self, request): problem_id = request.GET.get("problem_id") if not problem_id: @@ -59,6 +58,11 @@ class TestCaseAPI(CSRFExemptAPIView): except Problem.DoesNotExist: 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) if not os.path.isdir(test_case_dir): return self.error("Test case does not exists") @@ -79,7 +83,6 @@ class TestCaseAPI(CSRFExemptAPIView): response["Content-Length"] = os.path.getsize(file_name) return response - @problem_permission_required def post(self, request): form = TestCaseUploadForm(request.POST, request.FILES) if form.is_valid(): @@ -147,7 +150,6 @@ class TestCaseAPI(CSRFExemptAPIView): class CompileSPJAPI(APIView): @validate_serializer(CompileSPJSerializer) - @problem_permission_required def post(self, request): data = request.data spj_version = rand_str(8) @@ -186,11 +188,12 @@ class ProblemBase(APIView): def delete(self, request): id = request.GET.get("id") if not id: - return self.error("Invalid parameter, id is requred") + return self.error("Invalid parameter, id is required") try: - problem = Problem.objects.get(id=id) + problem = Problem.objects.get(id=id, contest_id__isnull=True) except Problem.DoesNotExist: return self.error("Problem does not exists") + ensure_created_by(problem, request.user) if Submission.objects.filter(problem=problem).exists(): return self.error("Can't delete the problem as it has submissions") d = os.path.join(settings.TEST_CASE_DIR, problem.test_case_id) @@ -201,11 +204,10 @@ class ProblemBase(APIView): class ProblemAPI(ProblemBase): - @validate_serializer(CreateProblemSerializer) @problem_permission_required + @validate_serializer(CreateProblemSerializer) def post(self, request): data = request.data - _id = data["_id"] if not _id: return self.error("Display ID is required") @@ -236,8 +238,7 @@ class ProblemAPI(ProblemBase): if problem_id: try: problem = Problem.objects.get(id=problem_id) - if not user.can_mgmt_all_problem() and problem.created_by != user: - return self.error("Problem does not exist") + ensure_created_by(problem, request.user) return self.success(ProblemAdminSerializer(problem).data) except Problem.DoesNotExist: return self.error("Problem does not exist") @@ -256,17 +257,15 @@ class ProblemAPI(ProblemBase): problems = problems.filter(title__contains=keyword) return self.success(self.paginate_data(request, problems, ProblemAdminSerializer)) - @validate_serializer(EditProblemSerializer) @problem_permission_required + @validate_serializer(EditProblemSerializer) def put(self, request): data = request.data problem_id = data.pop("id") - user = request.user try: problem = Problem.objects.get(id=problem_id) - if not user.can_mgmt_all_problem() and problem.created_by != user: - return self.error("Problem does not exist") + ensure_created_by(problem, request.user) except Problem.DoesNotExist: return self.error("Problem does not exist") @@ -300,13 +299,11 @@ class ProblemAPI(ProblemBase): class ContestProblemAPI(ProblemBase): @validate_serializer(CreateContestProblemSerializer) - @problem_permission_required def post(self, request): data = request.data try: contest = Contest.objects.get(id=data.pop("contest_id")) - if request.user.is_admin() and contest.created_by != request.user: - return self.error("Contest does not exist") + ensure_created_by(contest, request.user) except Contest.DoesNotExist: return self.error("Contest does not exist") @@ -345,8 +342,7 @@ class ContestProblemAPI(ProblemBase): if problem_id: try: problem = Problem.objects.get(id=problem_id) - if user.is_admin() and problem.contest.created_by != user: - return self.error("Problem does not exist") + ensure_created_by(problem, user) except Problem.DoesNotExist: return self.error("Problem does not exist") return self.success(ProblemAdminSerializer(problem).data) @@ -366,10 +362,11 @@ class ContestProblemAPI(ProblemBase): @problem_permission_required def put(self, request): data = request.data + user = request.user + try: contest = Contest.objects.get(id=data.pop("contest_id")) - if request.user.is_admin() and contest.created_by != request.user: - return self.error("Contest does not exist") + ensure_created_by(contest, user) except Contest.DoesNotExist: return self.error("Contest does not exist") @@ -377,12 +374,10 @@ class ContestProblemAPI(ProblemBase): return self.error("Invalid rule type") problem_id = data.pop("id") - user = request.user try: problem = Problem.objects.get(id=problem_id) - if not user.can_mgmt_all_problem() and problem.created_by != user: - return self.error("Problem does not exist") + ensure_created_by(problem, user) except Problem.DoesNotExist: return self.error("Problem does not exist") diff --git a/utils/api/api.py b/utils/api/api.py index e33daaf..5b7a231 100644 --- a/utils/api/api.py +++ b/utils/api/api.py @@ -11,6 +11,13 @@ from django.views.generic import View 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): json_request = "application/json" json_response = "application/json;charset=UTF-8" @@ -137,6 +144,11 @@ class APIView(View): return self.error(err="invalid-request", msg=str(e)) try: 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: logger.exception(e) return self.server_error() From 914c4727fc9b52749aa62da0e7edc6880be04c1e Mon Sep 17 00:00:00 2001 From: zema1 Date: Tue, 2 Jan 2018 20:05:33 +0800 Subject: [PATCH 2/7] add new version api --- conf/tests.py | 25 +++++++++++++++++++++++++ conf/urls/admin.py | 2 ++ conf/views.py | 26 +++++++++++++++++++++++++- docs/data.json | 2 +- problem/views/admin.py | 2 +- 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/conf/tests.py b/conf/tests.py index 55f4853..db07e32 100644 --- a/conf/tests.py +++ b/conf/tests.py @@ -6,6 +6,7 @@ from django.utils import timezone from options.options import SysOptions from utils.api.tests import APITestCase from .models import JudgeServer +from .views import CheckNewVersionAPI class SMTPConfigTest(APITestCase): @@ -153,3 +154,27 @@ class TestCasePruneAPITest(APITestCase): resp = self.client.delete(self.url) self.assertSuccess(resp) mocked_delete_one.assert_called_once_with(valid_id) + + +class CheckNewVersionAPITest(APITestCase): + def setUp(self): + self.url = self.reverse("check_new_version_api") + self.create_super_admin() + self.latest_data = {"update": [ + { + "version": "2099-12-25", + "level": 1, + "title": "Update at 2099-12-25", + "details": ["test get", ] + } + ]} + + @mock.patch("conf.views.requests.get", autospec=True) + def test_get_latest_version(self, mocked_requests): + resp = self.client.get(self.url) + self.assertSuccess(resp) + mocked_requests.assert_called_once() + + def test_get_version(self): + version = CheckNewVersionAPI().get_latest_version(self.latest_data) + self.assertListEqual(version, [2099, 12, 25]) diff --git a/conf/urls/admin.py b/conf/urls/admin.py index 5b194b8..7a41637 100644 --- a/conf/urls/admin.py +++ b/conf/urls/admin.py @@ -1,6 +1,7 @@ from django.conf.urls import url from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI, TestCasePruneAPI, SMTPTestAPI +from ..views import CheckNewVersionAPI urlpatterns = [ url(r"^smtp/?$", SMTPAPI.as_view(), name="smtp_admin_api"), @@ -8,4 +9,5 @@ urlpatterns = [ url(r"^website/?$", WebsiteConfigAPI.as_view(), name="website_config_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"^new_version/?$", CheckNewVersionAPI.as_view(), name="check_new_version_api"), ] diff --git a/conf/views.py b/conf/views.py index 5e82645..4925bce 100644 --- a/conf/views.py +++ b/conf/views.py @@ -2,6 +2,9 @@ import os import re import hashlib import shutil +import json +import requests +from requests.exceptions import RequestException from django.utils import timezone from django.conf import settings @@ -156,7 +159,7 @@ class TestCasePruneAPI(APIView): @super_admin_required def get(self, request): """ - return isolated test_case list + return orphan test_case list """ ret_data = [] dir_to_be_removed = self.get_orphan_ids() @@ -190,3 +193,24 @@ class TestCasePruneAPI(APIView): test_case_dir = os.path.join(settings.TEST_CASE_DIR, id) if os.path.isdir(test_case_dir): shutil.rmtree(test_case_dir, ignore_errors=True) + + +class CheckNewVersionAPI(APIView): + @staticmethod + def get_latest_version(data): + return list(map(lambda x: int(x), data["update"][0]["version"].split("-"))) + + def get(self, request): + try: + resp = requests.get("https://raw.githubusercontent.com/QingdaoU/OnlineJudge/master/docs/data.json", + timeout=3) + remote = resp.json() + except (RequestException, ValueError): + return self.success() + with open("docs/data.json", "r") as f: + local = json.load(f) + remote_version = self.get_latest_version(remote) + local_version = self.get_latest_version(local) + if remote_version > local_version: + return self.success(remote["update"][0]) + return self.success() diff --git a/docs/data.json b/docs/data.json index 53d164a..addb2fe 100644 --- a/docs/data.json +++ b/docs/data.json @@ -2,7 +2,7 @@ "update": [ { "version": "2017-12-25", - "level": 1, + "level": "Recommend", "title": "Update at 2017-12-25", "details": [ "Fix some issues under IE/Edge", diff --git a/problem/views/admin.py b/problem/views/admin.py index 5846e35..5e8a880 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -423,7 +423,7 @@ class MakeContestProblemPublicAPIView(APIView): return self.error("Problem does not exist") 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.save() # https://docs.djangoproject.com/en/1.11/topics/db/queries/#copying-model-instances From b388c5dd03df3da209513540c7ed073bf3be71a2 Mon Sep 17 00:00:00 2001 From: zema1 Date: Thu, 4 Jan 2018 11:42:20 +0800 Subject: [PATCH 3/7] add dashboard api --- conf/tests.py | 22 ++++++++++++-------- conf/urls/admin.py | 5 +++-- conf/views.py | 43 +++++++++++++++++++++++++++------------ oj/production_settings.py | 7 +------ utils/shortcuts.py | 5 +++++ 5 files changed, 52 insertions(+), 30 deletions(-) diff --git a/conf/tests.py b/conf/tests.py index db07e32..c6a49af 100644 --- a/conf/tests.py +++ b/conf/tests.py @@ -6,7 +6,6 @@ from django.utils import timezone from options.options import SysOptions from utils.api.tests import APITestCase from .models import JudgeServer -from .views import CheckNewVersionAPI class SMTPConfigTest(APITestCase): @@ -156,9 +155,9 @@ class TestCasePruneAPITest(APITestCase): mocked_delete_one.assert_called_once_with(valid_id) -class CheckNewVersionAPITest(APITestCase): +class ReleaseNoteAPITest(APITestCase): def setUp(self): - self.url = self.reverse("check_new_version_api") + self.url = self.reverse("get_release_notes_api") self.create_super_admin() self.latest_data = {"update": [ { @@ -169,12 +168,17 @@ class CheckNewVersionAPITest(APITestCase): } ]} - @mock.patch("conf.views.requests.get", autospec=True) - def test_get_latest_version(self, mocked_requests): + def test_get_versions(self): resp = self.client.get(self.url) self.assertSuccess(resp) - mocked_requests.assert_called_once() - def test_get_version(self): - version = CheckNewVersionAPI().get_latest_version(self.latest_data) - self.assertListEqual(version, [2099, 12, 25]) + +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) diff --git a/conf/urls/admin.py b/conf/urls/admin.py index 7a41637..8e6d293 100644 --- a/conf/urls/admin.py +++ b/conf/urls/admin.py @@ -1,7 +1,7 @@ from django.conf.urls import url from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI, TestCasePruneAPI, SMTPTestAPI -from ..views import CheckNewVersionAPI +from ..views import ReleaseNotesAPI, DashboardInfoAPI urlpatterns = [ url(r"^smtp/?$", SMTPAPI.as_view(), name="smtp_admin_api"), @@ -9,5 +9,6 @@ urlpatterns = [ url(r"^website/?$", WebsiteConfigAPI.as_view(), name="website_config_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"^new_version/?$", CheckNewVersionAPI.as_view(), name="check_new_version_api"), + url(r"^versions/?$", ReleaseNotesAPI.as_view(), name="get_release_notes_api"), + url(r"^dashboard_info", DashboardInfoAPI.as_view(), name="dashboard_info_api"), ] diff --git a/conf/views.py b/conf/views.py index 4925bce..62c1d35 100644 --- a/conf/views.py +++ b/conf/views.py @@ -3,7 +3,9 @@ import re import hashlib import shutil import json +import pytz import requests +from datetime import datetime from requests.exceptions import RequestException from django.utils import timezone @@ -11,11 +13,14 @@ from django.conf import settings from account.decorators import super_admin_required 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.languages import languages, spj_languages from options.options import SysOptions 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 .models import JudgeServer from .serializers import (CreateEditWebsiteConfigSerializer, @@ -195,22 +200,34 @@ class TestCasePruneAPI(APIView): shutil.rmtree(test_case_dir, ignore_errors=True) -class CheckNewVersionAPI(APIView): - @staticmethod - def get_latest_version(data): - return list(map(lambda x: int(x), data["update"][0]["version"].split("-"))) - +class ReleaseNotesAPI(APIView): def get(self, request): try: resp = requests.get("https://raw.githubusercontent.com/QingdaoU/OnlineJudge/master/docs/data.json", timeout=3) - remote = resp.json() + releases = resp.json() except (RequestException, ValueError): return self.success() with open("docs/data.json", "r") as f: - local = json.load(f) - remote_version = self.get_latest_version(remote) - local_version = self.get_latest_version(local) - if remote_version > local_version: - return self.success(remote["update"][0]) - return self.success() + 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="") + } + }) diff --git a/oj/production_settings.py b/oj/production_settings.py index 026c53a..e1bcde8 100644 --- a/oj/production_settings.py +++ b/oj/production_settings.py @@ -1,9 +1,4 @@ -import os - - -def get_env(name, default=""): - return os.environ.get(name, default) - +from utils.shortcuts import get_env DATABASES = { 'default': { diff --git a/utils/shortcuts.py b/utils/shortcuts.py index 111c31b..ea0d094 100644 --- a/utils/shortcuts.py +++ b/utils/shortcuts.py @@ -1,3 +1,4 @@ +import os import re import datetime import random @@ -76,3 +77,7 @@ def send_email(smtp_config, from_name, to_email, to_name, subject, content): password=smtp_config["password"], port=smtp_config["port"], tls=smtp_config["tls"]) + + +def get_env(name, default=""): + return os.environ.get(name, default) From 0e1d40792fc62a0beb1bcc3f40a3f91099f087bc Mon Sep 17 00:00:00 2001 From: virusdefender Date: Thu, 4 Jan 2018 20:13:50 +0800 Subject: [PATCH 4/7] update release note update sentry settings --- account/serializers.py | 2 +- docs/data.json | 5 +++-- oj/settings.py | 16 +++++----------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index fe54862..e14ab8e 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -86,7 +86,7 @@ class UserProfileSerializer(serializers.ModelSerializer): class EditUserSerializer(serializers.Serializer): id = serializers.IntegerField() 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) email = serializers.EmailField(max_length=64) admin_type = serializers.ChoiceField(choices=(AdminType.REGULAR_USER, AdminType.ADMIN, AdminType.SUPER_ADMIN)) diff --git a/docs/data.json b/docs/data.json index addb2fe..415fa28 100644 --- a/docs/data.json +++ b/docs/data.json @@ -1,14 +1,15 @@ { "update": [ { - "version": "2017-12-25", + "version": "2017-01-04", "level": "Recommend", - "title": "Update at 2017-12-25", + "title": "Update at 2018-01-04", "details": [ "Fix some issues under IE/Edge", "Add backend error reporter", "New email template", "A more flexible throttling function", + "Add admin dashboard (this page)", "Other bugs and enhancements" ] } diff --git a/oj/settings.py b/oj/settings.py index 2cb64ad..055c055 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -12,14 +12,15 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ import os import raven 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 * else: from .dev_settings import * -from .custom_settings import * - BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Applications @@ -128,7 +129,7 @@ UPLOAD_DIR = f"{DATA_DIR}{UPLOAD_PREFIX}" 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 = { 'version': 1, '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_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" - -# 用于限制用户恶意提交大量代码 -TOKEN_BUCKET_DEFAULT_CAPACITY = 10 - -# 单位:每分钟 -TOKEN_BUCKET_FILL_RATE = 2 - RAVEN_CONFIG = { 'dsn': 'https://b200023b8aed4d708fb593c5e0a6ad3d:1fddaba168f84fcf97e0d549faaeaff0@sentry.io/263057' } \ No newline at end of file From 679512e3cc7c48b8f12c9597182564fbfccafa93 Mon Sep 17 00:00:00 2001 From: virusdefender Date: Fri, 5 Jan 2018 09:50:25 +0800 Subject: [PATCH 5/7] fix FORCE_HTTPS add ip_header env --- deploy/entrypoint.sh | 6 ++++++ deploy/nginx/api_proxy.conf | 5 +++++ deploy/nginx/https_redirect.conf | 4 ++++ deploy/nginx/locations.conf | 5 +---- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 deploy/nginx/api_proxy.conf diff --git a/deploy/entrypoint.sh b/deploy/entrypoint.sh index 37bf799..055123c 100755 --- a/deploy/entrypoint.sh +++ b/deploy/entrypoint.sh @@ -31,6 +31,12 @@ else ln -sf https_redirect.conf http_locations.conf 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 if [ ! -z "$STATIC_CDN_HOST" ]; then find . -name index.html -exec sed -i "s/link href=\/static/link href=\/\/$STATIC_CDN_HOST\/static/g" {} \; diff --git a/deploy/nginx/api_proxy.conf b/deploy/nginx/api_proxy.conf new file mode 100644 index 0000000..631103d --- /dev/null +++ b/deploy/nginx/api_proxy.conf @@ -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 ''; \ No newline at end of file diff --git a/deploy/nginx/https_redirect.conf b/deploy/nginx/https_redirect.conf index 71eb6c0..88d8103 100644 --- a/deploy/nginx/https_redirect.conf +++ b/deploy/nginx/https_redirect.conf @@ -1,3 +1,7 @@ +location /api/judge_server_heartbeat { + include api_proxy.conf; +} + location / { return 301 https://$host$request_uri; } \ No newline at end of file diff --git a/deploy/nginx/locations.conf b/deploy/nginx/locations.conf index 1732c7e..a096ffa 100644 --- a/deploy/nginx/locations.conf +++ b/deploy/nginx/locations.conf @@ -3,10 +3,7 @@ location /public { } location /api { - proxy_pass http://backend; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Host $http_host; - client_max_body_size 200M; + include api_proxy.conf; } location /data/ { From b50f5404ff3250a8bacbffc682248edb87a43a55 Mon Sep 17 00:00:00 2001 From: virusdefender Date: Fri, 5 Jan 2018 10:19:54 +0800 Subject: [PATCH 6/7] update replace static cdn host function --- deploy/entrypoint.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deploy/entrypoint.sh b/deploy/entrypoint.sh index 055123c..97d6c62 100755 --- a/deploy/entrypoint.sh +++ b/deploy/entrypoint.sh @@ -39,8 +39,9 @@ fi cd $APP/dist 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/script type=text\/javascript src=\/static/script type=text\/javascript src=\/\/$STATIC_CDN_HOST\/static/g" {} \; + find . -name index.html -exec sed -i "s/__STATIC_CDN_HOST__/\/\/$STATIC_CDN_HOST/g" {} \; +else + find . -name index.html -exec sed -i "s/__STATIC_CDN_HOST__//g" {} \; fi cd $APP From dbce3e443a71c4f29947b1f9a3ef9f0a91e4e0a8 Mon Sep 17 00:00:00 2001 From: virusdefender Date: Sat, 6 Jan 2018 14:26:46 +0800 Subject: [PATCH 7/7] redis retry --- deploy/entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/entrypoint.sh b/deploy/entrypoint.sh index 97d6c62..929e9c0 100755 --- a/deploy/entrypoint.sh +++ b/deploy/entrypoint.sh @@ -51,13 +51,13 @@ while [ $n -lt 5 ] do python manage.py migrate --no-input && 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 n=$(($n+1)) echo "Failed to migrate, going to retry..." sleep 8 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 -exec supervisord -c /app/deploy/supervisord.conf \ No newline at end of file +exec supervisord -c /app/deploy/supervisord.conf