mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 08:23:20 +00:00
commit
0c52704a72
23
.gitignore
vendored
23
.gitignore
vendored
@ -54,21 +54,18 @@ db.db
|
|||||||
#*.out
|
#*.out
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
.DS_Store
|
.DS_Store
|
||||||
log/
|
|
||||||
static/release/css
|
|
||||||
static/release/js
|
|
||||||
static/release/img
|
|
||||||
static/src/upload_image/*
|
|
||||||
build.txt
|
build.txt
|
||||||
tmp/
|
tmp/
|
||||||
test_case/
|
|
||||||
release/
|
|
||||||
upload/
|
|
||||||
custom_settings.py
|
custom_settings.py
|
||||||
docker-compose.yml
|
|
||||||
*.zip
|
*.zip
|
||||||
rsyncd.passwd
|
|
||||||
|
|
||||||
node_modules/
|
data/log/*
|
||||||
update.sh
|
!data/log/.gitkeep
|
||||||
ssh.sh
|
data/test_case/*
|
||||||
|
!data/test_case/.gitkeep
|
||||||
|
data/ssl/*
|
||||||
|
!data/ssl/.gitkeep
|
||||||
|
data/public/upload/*
|
||||||
|
!data/public/upload/.gitkeep
|
||||||
|
data/public/avatar/*
|
||||||
|
!data/public/avatar/default.png
|
||||||
|
@ -9,11 +9,9 @@ before_install:
|
|||||||
- docker run -it -d -e POSTGRES_DB=onlinejudge -e POSTGRES_USER=onlinejudge -e POSTGRES_PASSWORD=onlinejudge -p 127.0.0.1:5433:5432 postgres:10
|
- docker run -it -d -e POSTGRES_DB=onlinejudge -e POSTGRES_USER=onlinejudge -e POSTGRES_PASSWORD=onlinejudge -p 127.0.0.1:5433:5432 postgres:10
|
||||||
install:
|
install:
|
||||||
- pip install -r deploy/requirements.txt
|
- pip install -r deploy/requirements.txt
|
||||||
- mkdir log test_case upload
|
|
||||||
- cp oj/custom_settings.example.py oj/custom_settings.py
|
- cp oj/custom_settings.example.py oj/custom_settings.py
|
||||||
- echo "SECRET_KEY=\"`cat /dev/urandom | head -1 | md5sum | head -c 32`\"" >> oj/custom_settings.py
|
- echo "SECRET_KEY=\"`cat /dev/urandom | head -1 | md5sum | head -c 32`\"" >> oj/custom_settings.py
|
||||||
- python manage.py migrate
|
- python manage.py migrate
|
||||||
- python manage.py initadmin
|
|
||||||
script:
|
script:
|
||||||
- docker ps -a
|
- docker ps -a
|
||||||
- flake8 .
|
- flake8 .
|
||||||
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
FROM python:3.6-alpine3.6
|
||||||
|
|
||||||
|
ENV OJ_ENV production
|
||||||
|
|
||||||
|
ADD . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
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 && \
|
||||||
|
apk del build-base --purge
|
||||||
|
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
|
@ -3,6 +3,18 @@ from django.utils.timezone import now
|
|||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
from utils.api import JSONResponse
|
from utils.api import JSONResponse
|
||||||
|
from account.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class APITokenAuthMiddleware(MiddlewareMixin):
|
||||||
|
def process_request(self, request):
|
||||||
|
appkey = request.META.get("HTTP_APPKEY")
|
||||||
|
if appkey:
|
||||||
|
try:
|
||||||
|
request.user = User.objects.get(open_api_appkey=appkey, open_api=True, is_disabled=False)
|
||||||
|
request.csrf_processing_done = True
|
||||||
|
except User.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SessionRecordMiddleware(MiddlewareMixin):
|
class SessionRecordMiddleware(MiddlewareMixin):
|
||||||
|
@ -40,8 +40,7 @@ class GenerateUserSerializer(serializers.Serializer):
|
|||||||
suffix = serializers.CharField(max_length=16, allow_blank=True)
|
suffix = serializers.CharField(max_length=16, allow_blank=True)
|
||||||
number_from = serializers.IntegerField()
|
number_from = serializers.IntegerField()
|
||||||
number_to = serializers.IntegerField()
|
number_to = serializers.IntegerField()
|
||||||
default_email = serializers.CharField(max_length=64)
|
password_length = serializers.IntegerField(max_value=16, default=8)
|
||||||
password_length = serializers.IntegerField(required=False, max_value=16)
|
|
||||||
|
|
||||||
|
|
||||||
class ImportUserSeralizer(serializers.Serializer):
|
class ImportUserSeralizer(serializers.Serializer):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@ -557,20 +558,25 @@ class AdminUserTest(APITestCase):
|
|||||||
|
|
||||||
def test_import_users(self):
|
def test_import_users(self):
|
||||||
data = {"users": [["user1", "pass1", "eami1@e.com"],
|
data = {"users": [["user1", "pass1", "eami1@e.com"],
|
||||||
["user1", "pass1", "eami1@e.com"],
|
["user2", "pass3", "eamil3@e.com"]]
|
||||||
["user2", "pass2"], ["user3", "pass3", "eamil3@e.com"]]
|
|
||||||
}
|
}
|
||||||
resp = self.client.post(self.url, data)
|
resp = self.client.post(self.url, data)
|
||||||
self.assertSuccess(resp)
|
self.assertSuccess(resp)
|
||||||
self.assertDictEqual(resp.data["data"], {"omitted_count": 1,
|
|
||||||
"created_count": 2,
|
|
||||||
"get_count": 1})
|
|
||||||
# successfully created 2 users
|
# successfully created 2 users
|
||||||
self.assertEqual(User.objects.all().count(), 4)
|
self.assertEqual(User.objects.all().count(), 4)
|
||||||
|
|
||||||
|
def test_import_duplicate_user(self):
|
||||||
|
data = {"users": [["user1", "pass1", "eami1@e.com"],
|
||||||
|
["user1", "pass1", "eami1@e.com"]]
|
||||||
|
}
|
||||||
|
resp = self.client.post(self.url, data)
|
||||||
|
self.assertFailed(resp, "DETAIL: Key (username)=(user1) already exists.")
|
||||||
|
# no user is created
|
||||||
|
self.assertEqual(User.objects.all().count(), 2)
|
||||||
|
|
||||||
def test_delete_users(self):
|
def test_delete_users(self):
|
||||||
self.test_import_users()
|
self.test_import_users()
|
||||||
user_ids = User.objects.filter(username__in=["user1", "user3"]).values_list("id", flat=True)
|
user_ids = User.objects.filter(username__in=["user1", "user2"]).values_list("id", flat=True)
|
||||||
user_ids = ",".join([str(id) for id in user_ids])
|
user_ids = ",".join([str(id) for id in user_ids])
|
||||||
resp = self.client.delete(self.url + "?id=" + user_ids)
|
resp = self.client.delete(self.url + "?id=" + user_ids)
|
||||||
self.assertSuccess(resp)
|
self.assertSuccess(resp)
|
||||||
@ -605,6 +611,19 @@ class GenerateUserAPITest(APITestCase):
|
|||||||
resp = self.client.post(self.url, data=self.data)
|
resp = self.client.post(self.url, data=self.data)
|
||||||
self.assertSuccess(resp)
|
self.assertSuccess(resp)
|
||||||
mock_workbook.assert_called()
|
mock_workbook.assert_called()
|
||||||
data = resp.data["data"]
|
|
||||||
self.assertEqual(data["created_count"], 6)
|
|
||||||
self.assertEqual(data["get_count"], 0)
|
class OpenAPIAppkeyAPITest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = self.create_super_admin()
|
||||||
|
self.url = self.reverse("open_api_appkey_api")
|
||||||
|
|
||||||
|
def test_reset_appkey(self):
|
||||||
|
resp = self.client.post(self.url, data={})
|
||||||
|
self.assertFailed(resp)
|
||||||
|
|
||||||
|
self.user.open_api = True
|
||||||
|
self.user.save()
|
||||||
|
resp = self.client.post(self.url, data={})
|
||||||
|
self.assertSuccess(resp)
|
||||||
|
self.assertEqual(resp.data["data"]["appkey"], User.objects.get(username=self.user.username).open_api_appkey)
|
||||||
|
@ -5,7 +5,7 @@ from ..views.oj import (ApplyResetPasswordAPI, ResetPasswordAPI,
|
|||||||
UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck,
|
UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck,
|
||||||
AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI,
|
AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI,
|
||||||
UserRankAPI, CheckTFARequiredAPI, SessionManagementAPI,
|
UserRankAPI, CheckTFARequiredAPI, SessionManagementAPI,
|
||||||
ProfileProblemDisplayIDRefreshAPI)
|
ProfileProblemDisplayIDRefreshAPI, OpenAPIAppkeyAPI)
|
||||||
|
|
||||||
from utils.captcha.views import CaptchaAPIView
|
from utils.captcha.views import CaptchaAPIView
|
||||||
|
|
||||||
@ -25,5 +25,6 @@ urlpatterns = [
|
|||||||
url(r"^tfa_required/?$", CheckTFARequiredAPI.as_view(), name="tfa_required_check"),
|
url(r"^tfa_required/?$", CheckTFARequiredAPI.as_view(), name="tfa_required_check"),
|
||||||
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api"),
|
url(r"^two_factor_auth/?$", TwoFactorAuthAPI.as_view(), name="two_factor_auth_api"),
|
||||||
url(r"^user_rank/?$", UserRankAPI.as_view(), name="user_rank_api"),
|
url(r"^user_rank/?$", UserRankAPI.as_view(), name="user_rank_api"),
|
||||||
url(r"^sessions/?$", SessionManagementAPI.as_view(), name="session_management_api")
|
url(r"^sessions/?$", SessionManagementAPI.as_view(), name="session_management_api"),
|
||||||
|
url(r"^open_api_appkey/?$", OpenAPIAppkeyAPI.as_view(), name="open_api_appkey_api"),
|
||||||
]
|
]
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import xlsxwriter
|
import xlsxwriter
|
||||||
|
|
||||||
|
from django.db import transaction, IntegrityError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
|
||||||
from submission.models import Submission
|
from submission.models import Submission
|
||||||
from utils.api import APIView, validate_serializer
|
from utils.api import APIView, validate_serializer
|
||||||
@ -18,26 +21,27 @@ class UserAdminAPI(APIView):
|
|||||||
@validate_serializer(ImportUserSeralizer)
|
@validate_serializer(ImportUserSeralizer)
|
||||||
@super_admin_required
|
@super_admin_required
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
Generate user
|
||||||
|
"""
|
||||||
data = request.data["users"]
|
data = request.data["users"]
|
||||||
omitted_count = created_count = get_count = 0
|
|
||||||
|
user_list = []
|
||||||
for user_data in data:
|
for user_data in data:
|
||||||
if len(user_data) != 3 or len(user_data[0]) > 32:
|
if len(user_data) != 3 or len(user_data[0]) > 32:
|
||||||
omitted_count += 1
|
return self.error(f"Error occurred while processing data '{user_data}'")
|
||||||
continue
|
user_list.append(User(username=user_data[0], password=make_password(user_data[1]), email=user_data[2]))
|
||||||
user, created = User.objects.get_or_create(username=user_data[0])
|
|
||||||
user.set_password(user_data[1])
|
try:
|
||||||
user.email = user_data[2]
|
with transaction.atomic():
|
||||||
user.save()
|
ret = User.objects.bulk_create(user_list)
|
||||||
if created:
|
UserProfile.objects.bulk_create([UserProfile(user=user) for user in ret])
|
||||||
UserProfile.objects.create(user=user)
|
return self.success()
|
||||||
created_count += 1
|
except IntegrityError as e:
|
||||||
else:
|
# Extract detail from exception message
|
||||||
get_count += 1
|
# duplicate key value violates unique constraint "user_username_key"
|
||||||
return self.success({
|
# DETAIL: Key (username)=(root11) already exists.
|
||||||
"omitted_count": omitted_count,
|
return self.error(str(e).split("\n")[1])
|
||||||
"created_count": created_count,
|
|
||||||
"get_count": get_count
|
|
||||||
})
|
|
||||||
|
|
||||||
@validate_serializer(EditUserSerializer)
|
@validate_serializer(EditUserSerializer)
|
||||||
@super_admin_required
|
@super_admin_required
|
||||||
@ -146,7 +150,7 @@ class GenerateUserAPI(APIView):
|
|||||||
file_id = request.GET.get("file_id")
|
file_id = request.GET.get("file_id")
|
||||||
if not file_id:
|
if not file_id:
|
||||||
return self.error("Invalid Parameter, file_id is required")
|
return self.error("Invalid Parameter, file_id is required")
|
||||||
if not re.match(r"[a-zA-Z0-9]+", file_id):
|
if not re.match(r"^[a-zA-Z0-9]+$", file_id):
|
||||||
return self.error("Illegal file_id")
|
return self.error("Illegal file_id")
|
||||||
file_path = f"/tmp/{file_id}.xlsx"
|
file_path = f"/tmp/{file_id}.xlsx"
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
@ -169,9 +173,6 @@ class GenerateUserAPI(APIView):
|
|||||||
if data["number_from"] > data["number_to"]:
|
if data["number_from"] > data["number_to"]:
|
||||||
return self.error("Start number must be lower than end number")
|
return self.error("Start number must be lower than end number")
|
||||||
|
|
||||||
password_length = data.get("password_length", 8)
|
|
||||||
default_email = data.get("default_email")
|
|
||||||
|
|
||||||
file_id = rand_str(8)
|
file_id = rand_str(8)
|
||||||
filename = f"/tmp/{file_id}.xlsx"
|
filename = f"/tmp/{file_id}.xlsx"
|
||||||
workbook = xlsxwriter.Workbook(filename)
|
workbook = xlsxwriter.Workbook(filename)
|
||||||
@ -180,26 +181,27 @@ class GenerateUserAPI(APIView):
|
|||||||
worksheet.write("A1", "Username")
|
worksheet.write("A1", "Username")
|
||||||
worksheet.write("B1", "Password")
|
worksheet.write("B1", "Password")
|
||||||
i = 1
|
i = 1
|
||||||
created_count = 0
|
|
||||||
get_count = 0
|
user_list = []
|
||||||
for number in range(data["number_from"], data["number_to"] + 1):
|
for number in range(data["number_from"], data["number_to"] + 1):
|
||||||
username = f"{data['prefix']}{number}{data['suffix']}"
|
raw_password = rand_str(data["password_length"])
|
||||||
password = rand_str(password_length)
|
user = User(username=f"{data['prefix']}{number}{data['suffix']}", password=make_password(raw_password))
|
||||||
user, created = User.objects.get_or_create(username=username)
|
user.raw_password = raw_password
|
||||||
user.email = default_email
|
user_list.append(user)
|
||||||
user.set_password(password)
|
|
||||||
user.save()
|
try:
|
||||||
if created:
|
with transaction.atomic():
|
||||||
UserProfile.objects.create(user=user)
|
|
||||||
created_count += 1
|
ret = User.objects.bulk_create(user_list)
|
||||||
else:
|
UserProfile.objects.bulk_create([UserProfile(user=user) for user in ret])
|
||||||
get_count += 1
|
for item in user_list:
|
||||||
worksheet.write_string(i, 0, username)
|
worksheet.write_string(i, 0, item.username)
|
||||||
worksheet.write_string(i, 1, password)
|
worksheet.write_string(i, 1, item.raw_password)
|
||||||
i += 1
|
i += 1
|
||||||
workbook.close()
|
workbook.close()
|
||||||
return self.success({
|
return self.success({"file_id": file_id})
|
||||||
"file_id": file_id,
|
except IntegrityError as e:
|
||||||
"created_count": created_count,
|
# Extract detail from exception message
|
||||||
"get_count": get_count
|
# duplicate key value violates unique constraint "user_username_key"
|
||||||
})
|
# DETAIL: Key (username)=(root11) already exists.
|
||||||
|
return self.error(str(e).split("\n")[1])
|
||||||
|
@ -401,3 +401,15 @@ class ProfileProblemDisplayIDRefreshAPI(APIView):
|
|||||||
v["_id"] = id_map[k]
|
v["_id"] = id_map[k]
|
||||||
profile.save(update_fields=["acm_problems_status", "oi_problems_status"])
|
profile.save(update_fields=["acm_problems_status", "oi_problems_status"])
|
||||||
return self.success()
|
return self.success()
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAPIAppkeyAPI(APIView):
|
||||||
|
@login_required
|
||||||
|
def post(self, request):
|
||||||
|
user = request.user
|
||||||
|
if not user.open_api:
|
||||||
|
return self.error("Permission denied")
|
||||||
|
api_appkey = rand_str()
|
||||||
|
user.open_api_appkey = api_appkey
|
||||||
|
user.save()
|
||||||
|
return self.success({"appkey": api_appkey})
|
||||||
|
0
data/log/.gitkeep
Normal file
0
data/log/.gitkeep
Normal file
BIN
data/public/avatar/default.png
Normal file
BIN
data/public/avatar/default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
0
data/public/upload/.gitkeep
Normal file
0
data/public/upload/.gitkeep
Normal file
0
data/ssl/.gitkeep
Normal file
0
data/ssl/.gitkeep
Normal file
0
data/test_case/.gitkeep
Normal file
0
data/test_case/.gitkeep
Normal file
@ -1,13 +0,0 @@
|
|||||||
FROM python:3.6-alpine3.6
|
|
||||||
|
|
||||||
ENV OJ_ENV production
|
|
||||||
RUN apk add --no-cache supervisor jpeg-dev zlib-dev postgresql-dev freetype-dev
|
|
||||||
|
|
||||||
ADD requirements.txt /tmp
|
|
||||||
RUN apk add --no-cache build-base && \
|
|
||||||
pip install --no-cache-dir -r /tmp/requirements.txt -i https://pypi.doubanio.com/simple && \
|
|
||||||
apk del build-base --purge
|
|
||||||
|
|
||||||
VOLUME [ "/app" ]
|
|
||||||
|
|
||||||
CMD sh /app/deploy/run.sh
|
|
20
deploy/nginx/common.conf
Normal file
20
deploy/nginx/common.conf
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
location /public {
|
||||||
|
root /data;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /admin {
|
||||||
|
root /app/dist/admin;
|
||||||
|
try_files $uri $uri/ /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /app/dist;
|
||||||
|
try_files $uri $uri/ /index.html =404;
|
||||||
|
}
|
57
deploy/nginx/nginx.conf
Normal file
57
deploy/nginx/nginx.conf
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
user nobody;
|
||||||
|
daemon off;
|
||||||
|
pid /tmp/nginx.pid;
|
||||||
|
worker_processes auto;
|
||||||
|
pcre_jit on;
|
||||||
|
error_log /data/log/nginx_error.log warn;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
server_tokens off;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
sendfile on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_types application/javascript text/css;
|
||||||
|
client_body_temp_path /tmp 1 2;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /data/log/nginx_access.log main;
|
||||||
|
|
||||||
|
upstream backend {
|
||||||
|
server 127.0.0.1:8080;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8000 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
include common.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 1443 ssl http2 default_server;
|
||||||
|
server_name _;
|
||||||
|
ssl_certificate /data/ssl/server.crt;
|
||||||
|
ssl_certificate_key /data/ssl/server.key;
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
|
||||||
|
include common.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,39 +1,33 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
BASE=/app
|
APP=/app
|
||||||
|
DATA=/data
|
||||||
|
|
||||||
if [ ! -f "$BASE/custom_settings.py" ]; then
|
if [ ! -f "$APP/oj/custom_settings.py" ]; then
|
||||||
echo SECRET_KEY=\"$(cat /dev/urandom | head -1 | md5sum | head -c 32)\" >> /app/oj/custom_settings.py
|
echo SECRET_KEY=\"$(cat /dev/urandom | head -1 | md5sum | head -c 32)\" >> $APP/oj/custom_settings.py
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "$BASE/log" ]; then
|
mkdir -p $DATA/log $DATA/ssl $DATA/test_case $DATA/public/upload $DATA/public/avatar
|
||||||
mkdir -p $BASE/log
|
|
||||||
|
SSL="$DATA/ssl"
|
||||||
|
if [ ! -f "$SSL/server.key" ]; then
|
||||||
|
openssl req -x509 -newkey rsa:2048 -keyout "$SSL/server.key" -out "$SSL/server.crt" -days 1000 \
|
||||||
|
-subj "/C=CN/ST=Beijing/L=Beijing/O=Beijing OnlineJudge Technology Co., Ltd./OU=Service Infrastructure Department/CN=`hostname`" -nodes
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd $BASE
|
cd $APP
|
||||||
find . -name "*.pyc" -delete
|
|
||||||
|
|
||||||
# wait for postgresql start
|
|
||||||
sleep 6
|
|
||||||
|
|
||||||
n=0
|
n=0
|
||||||
while [ $n -lt 3 ]
|
while [ $n -lt 5 ]
|
||||||
do
|
do
|
||||||
python manage.py migrate
|
python manage.py migrate --no-input &&
|
||||||
if [ $? -ne 0 ]; then
|
python manage.py inituser --username=root --password=rootroot --action=create_super_admin &&
|
||||||
echo "Can't start server, try again in 3 seconds.."
|
break
|
||||||
sleep 3
|
n=$(($n+1))
|
||||||
let "n+=1"
|
echo "Failed to migrate, going to retry..."
|
||||||
continue
|
sleep 8
|
||||||
fi
|
|
||||||
python manage.py initinstall
|
|
||||||
break
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ $n -eq 3 ]; then
|
cp data/public/avatar/default.png /data/public/avatar
|
||||||
echo "Can't start server, please check log file for details."
|
chown -R nobody:nogroup $DATA $APP/dist
|
||||||
exit 1
|
exec supervisord -c /app/deploy/supervisord.conf
|
||||||
fi
|
|
||||||
|
|
||||||
chown -R nobody:nogroup /data/log /data/test_case /data/avatar /data/upload
|
|
||||||
exec supervisord -c /app/deploy/supervisor.conf
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[supervisord]
|
[supervisord]
|
||||||
logfile=/app/log/supervisord.log
|
logfile=/data/log/supervisord.log
|
||||||
logfile_maxbytes=10MB
|
logfile_maxbytes=10MB
|
||||||
logfile_backups=10
|
logfile_backups=10
|
||||||
loglevel=info
|
loglevel=info
|
||||||
@ -7,12 +7,30 @@ pidfile=/tmp/supervisord.pid
|
|||||||
nodaemon=true
|
nodaemon=true
|
||||||
childlogdir=/data/log/
|
childlogdir=/data/log/
|
||||||
|
|
||||||
|
[inet_http_server]
|
||||||
|
port=127.0.0.1:9005
|
||||||
|
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
|
||||||
|
|
||||||
[supervisorctl]
|
[supervisorctl]
|
||||||
serverurl=unix:///tmp/supervisor.sock
|
serverurl=http://127.0.0.1:9005
|
||||||
|
|
||||||
|
[program:nginx]
|
||||||
|
command=nginx -c /app/deploy/nginx/nginx.conf
|
||||||
|
directory=/app/
|
||||||
|
stdout_logfile=/data/log/nginx.log
|
||||||
|
stderr_logfile=/data/log/nginx.log
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startsecs=5
|
||||||
|
stopwaitsecs = 5
|
||||||
|
killasgroup=true
|
||||||
|
|
||||||
[program:gunicorn]
|
[program:gunicorn]
|
||||||
command=sh -c "gunicorn oj.wsgi --user nobody -b 0.0.0.0:8080 --reload -w `grep -c ^processor /proc/cpuinfo`"
|
command=sh -c "gunicorn oj.wsgi --user nobody -b 127.0.0.1:8080 --reload -w `grep -c ^processor /proc/cpuinfo`"
|
||||||
directory=/app/
|
directory=/app/
|
||||||
|
user=nobody
|
||||||
stdout_logfile=/data/log/gunicorn.log
|
stdout_logfile=/data/log/gunicorn.log
|
||||||
stderr_logfile=/data/log/gunicorn.log
|
stderr_logfile=/data/log/gunicorn.log
|
||||||
autostart=true
|
autostart=true
|
@ -14,6 +14,7 @@ from contest.models import ContestRuleType, ACMContestRank, OIContestRank, Conte
|
|||||||
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 problem.models import Problem, ProblemRuleType
|
from problem.models import Problem, ProblemRuleType
|
||||||
|
from problem.utils import parse_problem_template
|
||||||
from submission.models import JudgeStatus, Submission
|
from submission.models import JudgeStatus, Submission
|
||||||
from utils.cache import cache
|
from utils.cache import cache
|
||||||
from utils.constants import CacheKey
|
from utils.constants import CacheKey
|
||||||
@ -123,16 +124,24 @@ class JudgeDispatcher(DispatcherBase):
|
|||||||
cache.lpush(CacheKey.waiting_queue, json.dumps(data))
|
cache.lpush(CacheKey.waiting_queue, json.dumps(data))
|
||||||
return
|
return
|
||||||
|
|
||||||
sub_config = list(filter(lambda item: self.submission.language == item["name"], languages))[0]
|
language = self.submission.language
|
||||||
|
sub_config = list(filter(lambda item: language == item["name"], languages))[0]
|
||||||
spj_config = {}
|
spj_config = {}
|
||||||
if self.problem.spj_code:
|
if self.problem.spj_code:
|
||||||
for lang in spj_languages:
|
for lang in spj_languages:
|
||||||
if lang["name"] == self.problem.spj_language:
|
if lang["name"] == self.problem.spj_language:
|
||||||
spj_config = lang["spj"]
|
spj_config = lang["spj"]
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if language in self.problem.template:
|
||||||
|
template = parse_problem_template(self.problem.template[language])
|
||||||
|
code = f"{template['prepend']}\n{self.submission.code}\n{template['append']}"
|
||||||
|
else:
|
||||||
|
code = self.submission.code
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"language_config": sub_config["config"],
|
"language_config": sub_config["config"],
|
||||||
"src": self.submission.code,
|
"src": code,
|
||||||
"max_cpu_time": self.problem.time_limit,
|
"max_cpu_time": self.problem.time_limit,
|
||||||
"max_memory": 1024 * 1024 * self.problem.memory_limit,
|
"max_memory": 1024 * 1024 * self.problem.memory_limit,
|
||||||
"test_case_id": self.problem.test_case_id,
|
"test_case_id": self.problem.test_case_id,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
_c_lang_config = {
|
_c_lang_config = {
|
||||||
"template": """//PREPEND START
|
"template": """//PREPEND BEGIN
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
//PREPEND END
|
//PREPEND END
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ int add(int a, int b) {
|
|||||||
}
|
}
|
||||||
//TEMPLATE END
|
//TEMPLATE END
|
||||||
|
|
||||||
//APPEND START
|
//APPEND BEGIN
|
||||||
int main() {
|
int main() {
|
||||||
printf("%d", add(1, 2));
|
printf("%d", add(1, 2));
|
||||||
return 0;
|
return 0;
|
||||||
@ -48,12 +48,23 @@ _c_lang_spj_config = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_cpp_lang_config = {
|
_cpp_lang_config = {
|
||||||
"template": """/*--PREPEND START--*/
|
"template": """//PREPEND BEGIN
|
||||||
/*--PREPEND END--*/
|
#include <iostream>
|
||||||
/*--TEMPLATE BEGIN--*/
|
//PREPEND END
|
||||||
/*--TEMPLATE END--*/
|
|
||||||
/*--APPEND START--*/
|
//TEMPLATE BEGIN
|
||||||
/*--APPEND END--*/""",
|
int add(int a, int b) {
|
||||||
|
// Please fill this blank
|
||||||
|
return ___________;
|
||||||
|
}
|
||||||
|
//TEMPLATE END
|
||||||
|
|
||||||
|
//APPEND BEGIN
|
||||||
|
int main() {
|
||||||
|
std::cout << add(1, 2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
//APPEND END""",
|
||||||
"compile": {
|
"compile": {
|
||||||
"src_name": "main.cpp",
|
"src_name": "main.cpp",
|
||||||
"exe_name": "main",
|
"exe_name": "main",
|
||||||
|
@ -24,16 +24,4 @@ DEBUG = True
|
|||||||
|
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
TEST_CASE_DIR = "/tmp"
|
DATA_DIR = f"{BASE_DIR}/data"
|
||||||
|
|
||||||
LOG_PATH = f"{BASE_DIR}/log/"
|
|
||||||
|
|
||||||
AVATAR_URI_PREFIX = "/static/avatar"
|
|
||||||
AVATAR_UPLOAD_DIR = f"{BASE_DIR}{AVATAR_URI_PREFIX}"
|
|
||||||
|
|
||||||
UPLOAD_PREFIX = "/static/upload"
|
|
||||||
UPLOAD_DIR = f"{BASE_DIR}{UPLOAD_PREFIX}"
|
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
|
||||||
os.path.join(BASE_DIR, "static"),
|
|
||||||
]
|
|
||||||
|
@ -8,8 +8,8 @@ def get_env(name, default=""):
|
|||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
'HOST': get_env("POSTGRES_HOST", "postgres"),
|
'HOST': get_env("POSTGRES_HOST", "oj-postgres"),
|
||||||
'PORT': get_env("POSTGRES_PORT", "5433"),
|
'PORT': get_env("POSTGRES_PORT", "5432"),
|
||||||
'NAME': get_env("POSTGRES_DB"),
|
'NAME': get_env("POSTGRES_DB"),
|
||||||
'USER': get_env("POSTGRES_USER"),
|
'USER': get_env("POSTGRES_USER"),
|
||||||
'PASSWORD': get_env("POSTGRES_PASSWORD")
|
'PASSWORD': get_env("POSTGRES_PASSWORD")
|
||||||
@ -17,7 +17,7 @@ DATABASES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
REDIS_CONF = {
|
REDIS_CONF = {
|
||||||
"host": get_env("REDIS_HOST", "redis"),
|
"host": get_env("REDIS_HOST", "oj-redis"),
|
||||||
"port": get_env("REDIS_PORT", "6379")
|
"port": get_env("REDIS_PORT", "6379")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,12 +25,4 @@ DEBUG = False
|
|||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
AVATAR_URI_PREFIX = "/static/avatar"
|
DATA_DIR = "/data"
|
||||||
AVATAR_UPLOAD_DIR = "/data/avatar"
|
|
||||||
|
|
||||||
UPLOAD_PREFIX = "/static/upload"
|
|
||||||
UPLOAD_DIR = "/data/upload"
|
|
||||||
|
|
||||||
TEST_CASE_DIR = "/data/test_case"
|
|
||||||
LOG_PATH = "/data/log"
|
|
||||||
DEFAULT_JUDGE_SERVER_SERVICE_URL = "http://judge-server:8080/"
|
|
||||||
|
@ -49,6 +49,7 @@ MIDDLEWARE_CLASSES = (
|
|||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'account.middleware.APITokenAuthMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
@ -109,57 +110,55 @@ USE_TZ = True
|
|||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/public/'
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'account.User'
|
AUTH_USER_MODEL = 'account.User'
|
||||||
|
|
||||||
|
TEST_CASE_DIR = os.path.join(DATA_DIR, "test_case")
|
||||||
|
LOG_PATH = os.path.join(DATA_DIR, "log")
|
||||||
|
|
||||||
|
AVATAR_URI_PREFIX = "/public/avatar"
|
||||||
|
AVATAR_UPLOAD_DIR = f"{DATA_DIR}{AVATAR_URI_PREFIX}"
|
||||||
|
|
||||||
|
UPLOAD_PREFIX = "/public/upload"
|
||||||
|
UPLOAD_DIR = f"{DATA_DIR}{UPLOAD_PREFIX}"
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [os.path.join(DATA_DIR, "public")]
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
'formatters': {
|
'formatters': {
|
||||||
'standard': {
|
'standard': {
|
||||||
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s',
|
'format': '[%(asctime)s] - [%(levelname)s] - [%(name)s:%(lineno)d] - %(message)s',
|
||||||
'datefmt': '%Y-%m-%d %H:%M:%S'
|
'datefmt': '%Y-%m-%d %H:%M:%S'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'handlers': {
|
'handlers': {
|
||||||
'django_error': {
|
'console': {
|
||||||
'level': 'WARNING',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.StreamHandler',
|
||||||
'filename': os.path.join(LOG_PATH, 'django.log'),
|
'formatter': 'standard'
|
||||||
'formatter': 'standard'
|
}
|
||||||
},
|
},
|
||||||
'app_info': {
|
'loggers': {
|
||||||
'level': 'INFO',
|
'django.request': {
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'handlers': ['console'],
|
||||||
'filename': os.path.join(LOG_PATH, 'app_info.log'),
|
'level': 'ERROR',
|
||||||
'formatter': 'standard'
|
'propagate': True,
|
||||||
},
|
},
|
||||||
'console': {
|
'django.db.backends': {
|
||||||
'level': 'DEBUG',
|
'handlers': ['console'],
|
||||||
'class': 'logging.StreamHandler',
|
'level': 'ERROR',
|
||||||
'formatter': 'standard'
|
'propagate': True,
|
||||||
}
|
},
|
||||||
},
|
'': {
|
||||||
'loggers': {
|
'handlers': ['console'],
|
||||||
'django.request': {
|
'level': 'WARNING',
|
||||||
'handlers': ['django_error', 'console'],
|
'propagate': True,
|
||||||
'level': 'WARNING',
|
}
|
||||||
'propagate': True,
|
},
|
||||||
},
|
|
||||||
'django.db.backends': {
|
|
||||||
'handlers': ['django_error', 'console'],
|
|
||||||
'level': 'WARNING',
|
|
||||||
'propagate': True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
app_logger = {
|
|
||||||
'handlers': ['app_info', 'console'],
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'propagate': False
|
|
||||||
}
|
|
||||||
LOGGING["loggers"].update({app: deepcopy(app_logger) for app in LOCAL_APPS})
|
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
||||||
|
@ -4,6 +4,7 @@ from judge.languages import language_names, spj_language_names
|
|||||||
from utils.api import DateTimeTZField, UsernameSerializer, serializers
|
from utils.api import DateTimeTZField, UsernameSerializer, serializers
|
||||||
|
|
||||||
from .models import Problem, ProblemRuleType, ProblemTag
|
from .models import Problem, ProblemRuleType, ProblemTag
|
||||||
|
from .utils import parse_problem_template
|
||||||
|
|
||||||
|
|
||||||
class TestCaseUploadForm(forms.Form):
|
class TestCaseUploadForm(forms.Form):
|
||||||
@ -110,9 +111,18 @@ class ContestProblemAdminSerializer(BaseProblemSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ProblemSerializer(BaseProblemSerializer):
|
class ProblemSerializer(BaseProblemSerializer):
|
||||||
|
template = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_template(self, obj):
|
||||||
|
ret = {}
|
||||||
|
for lang, code in obj.template.items():
|
||||||
|
ret[lang] = parse_problem_template(code)["template"]
|
||||||
|
return ret
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Problem
|
model = Problem
|
||||||
exclude = ("contest", "test_case_score", "test_case_id", "visible", "is_public")
|
exclude = ("contest", "test_case_score", "test_case_id", "visible", "is_public",
|
||||||
|
"template", "spj_code", "spj_version", "spj_compile_ok")
|
||||||
|
|
||||||
|
|
||||||
class ContestProblemSerializer(BaseProblemSerializer):
|
class ContestProblemSerializer(BaseProblemSerializer):
|
||||||
@ -131,3 +141,54 @@ class ContestProblemSafeSerializer(BaseProblemSerializer):
|
|||||||
class ContestProblemMakePublicSerializer(serializers.Serializer):
|
class ContestProblemMakePublicSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
display_id = serializers.CharField(max_length=32)
|
display_id = serializers.CharField(max_length=32)
|
||||||
|
|
||||||
|
|
||||||
|
class ExportProblemSerializer(serializers.ModelSerializer):
|
||||||
|
description = serializers.SerializerMethodField()
|
||||||
|
input_description = serializers.SerializerMethodField()
|
||||||
|
output_description = serializers.SerializerMethodField()
|
||||||
|
test_case_score = serializers.SerializerMethodField()
|
||||||
|
hint = serializers.SerializerMethodField()
|
||||||
|
time_limit = serializers.SerializerMethodField()
|
||||||
|
memory_limit = serializers.SerializerMethodField()
|
||||||
|
spj = serializers.SerializerMethodField()
|
||||||
|
template = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_description(self, obj):
|
||||||
|
return {"format": "html", "value": obj.description}
|
||||||
|
|
||||||
|
def get_input_description(self, obj):
|
||||||
|
return {"format": "html", "value": obj.input_description}
|
||||||
|
|
||||||
|
def get_output_description(self, obj):
|
||||||
|
return {"format": "html", "value": obj.output_description}
|
||||||
|
|
||||||
|
def get_hint(self, obj):
|
||||||
|
return {"format": "html", "value": obj.hint}
|
||||||
|
|
||||||
|
def get_test_case_score(self, obj):
|
||||||
|
return obj.test_case_score if obj.rule_type == ProblemRuleType.OI else []
|
||||||
|
|
||||||
|
def get_time_limit(self, obj):
|
||||||
|
return {"unit": "ms", "value": obj.time_limit}
|
||||||
|
|
||||||
|
def get_memory_limit(self, obj):
|
||||||
|
return {"unit": "MB", "value": obj.memory_limit}
|
||||||
|
|
||||||
|
def get_spj(self, obj):
|
||||||
|
return {"enabled": obj.spj,
|
||||||
|
"code": obj.spj_code if obj.spj else None,
|
||||||
|
"language": obj.spj_language if obj.spj else None}
|
||||||
|
|
||||||
|
def get_template(self, obj):
|
||||||
|
ret = {}
|
||||||
|
for k, v in obj.template.items():
|
||||||
|
ret[k] = parse_problem_template(v)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Problem
|
||||||
|
fields = ("_id", "title", "description",
|
||||||
|
"input_description", "output_description",
|
||||||
|
"test_case_score", "hint", "time_limit", "memory_limit", "samples",
|
||||||
|
"template", "spj", "rule_type", "source", "template")
|
||||||
|
@ -11,10 +11,13 @@ from utils.api.tests import APITestCase
|
|||||||
|
|
||||||
from .models import ProblemTag
|
from .models import ProblemTag
|
||||||
from .models import Problem, ProblemRuleType
|
from .models import Problem, ProblemRuleType
|
||||||
from .views.admin import TestCaseAPI
|
|
||||||
from contest.models import Contest
|
from contest.models import Contest
|
||||||
from contest.tests import DEFAULT_CONTEST_DATA
|
from contest.tests import DEFAULT_CONTEST_DATA
|
||||||
|
|
||||||
|
from .views.admin import TestCaseAPI
|
||||||
|
from .utils import parse_problem_template
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_PROBLEM_DATA = {"_id": "A-110", "title": "test", "description": "<p>test</p>", "input_description": "test",
|
DEFAULT_PROBLEM_DATA = {"_id": "A-110", "title": "test", "description": "<p>test</p>", "input_description": "test",
|
||||||
"output_description": "test", "time_limit": 1000, "memory_limit": 256, "difficulty": "Low",
|
"output_description": "test", "time_limit": 1000, "memory_limit": 256, "difficulty": "Low",
|
||||||
"visible": True, "tags": ["test"], "languages": ["C", "C++", "Java", "Python2"], "template": {},
|
"visible": True, "tags": ["test"], "languages": ["C", "C++", "Java", "Python2"], "template": {},
|
||||||
@ -257,3 +260,44 @@ class ContestProblemTest(ProblemCreateTestBase):
|
|||||||
contest.save()
|
contest.save()
|
||||||
resp = self.client.get(self.url + "?contest_id=" + str(self.contest["id"]))
|
resp = self.client.get(self.url + "?contest_id=" + str(self.contest["id"]))
|
||||||
self.assertSuccess(resp)
|
self.assertSuccess(resp)
|
||||||
|
|
||||||
|
|
||||||
|
class ParseProblemTemplateTest(APITestCase):
|
||||||
|
def test_parse(self):
|
||||||
|
template_str = """
|
||||||
|
//PREPEND BEGIN
|
||||||
|
aaa
|
||||||
|
//PREPEND END
|
||||||
|
|
||||||
|
//TEMPLATE BEGIN
|
||||||
|
bbb
|
||||||
|
//TEMPLATE END
|
||||||
|
|
||||||
|
//APPEND BEGIN
|
||||||
|
ccc
|
||||||
|
//APPEND END
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret = parse_problem_template(template_str)
|
||||||
|
self.assertEqual(ret["prepend"], "aaa\n")
|
||||||
|
self.assertEqual(ret["template"], "bbb\n")
|
||||||
|
self.assertEqual(ret["append"], "ccc\n")
|
||||||
|
|
||||||
|
def test_parse1(self):
|
||||||
|
template_str = """
|
||||||
|
//PREPEND BEGIN
|
||||||
|
aaa
|
||||||
|
//PREPEND END
|
||||||
|
|
||||||
|
//APPEND BEGIN
|
||||||
|
ccc
|
||||||
|
//APPEND END
|
||||||
|
//APPEND BEGIN
|
||||||
|
ddd
|
||||||
|
//APPEND END
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret = parse_problem_template(template_str)
|
||||||
|
self.assertEqual(ret["prepend"], "aaa\n")
|
||||||
|
self.assertEqual(ret["template"], "")
|
||||||
|
self.assertEqual(ret["append"], "ccc\n")
|
||||||
|
10
problem/utils.py
Normal file
10
problem/utils.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def parse_problem_template(template_str):
|
||||||
|
prepend = re.findall("//PREPEND BEGIN\n([\s\S]+?)//PREPEND END", template_str)
|
||||||
|
template = re.findall("//TEMPLATE BEGIN\n([\s\S]+?)//TEMPLATE END", template_str)
|
||||||
|
append = re.findall("//APPEND BEGIN\n([\s\S]+?)//APPEND END", template_str)
|
||||||
|
return {"prepend": prepend[0] if prepend else "",
|
||||||
|
"template": template[0] if template else "",
|
||||||
|
"append": append[0] if append else ""}
|
@ -1,38 +0,0 @@
|
|||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
from account.models import AdminType, ProblemPermission, User, UserProfile
|
|
||||||
from utils.shortcuts import rand_str # NOQA
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
try:
|
|
||||||
admin = User.objects.get(username="root")
|
|
||||||
if admin.admin_type == AdminType.SUPER_ADMIN:
|
|
||||||
self.stdout.write(self.style.WARNING("Super admin user 'root' already exists, "
|
|
||||||
"would you like to reset it's password?\n"
|
|
||||||
"Input yes to confirm: "))
|
|
||||||
if input() == "yes":
|
|
||||||
rand_password = "rootroot"
|
|
||||||
admin.save()
|
|
||||||
self.stdout.write(self.style.SUCCESS("Successfully created super admin user password.\n"
|
|
||||||
"Username: root\nPassword: %s\n"
|
|
||||||
"Remember to change password and turn on two factors auth "
|
|
||||||
"after installation." % rand_password))
|
|
||||||
else:
|
|
||||||
self.stdout.write(self.style.SUCCESS("Nothing happened"))
|
|
||||||
else:
|
|
||||||
self.stdout.write(self.style.ERROR("User 'root' is not super admin."))
|
|
||||||
except User.DoesNotExist:
|
|
||||||
user = User.objects.create(username="root", email="root@oj.com", admin_type=AdminType.SUPER_ADMIN,
|
|
||||||
problem_permission=ProblemPermission.ALL)
|
|
||||||
# for dev
|
|
||||||
# rand_password = rand_str(length=6)
|
|
||||||
rand_password = "rootroot"
|
|
||||||
user.set_password(rand_password)
|
|
||||||
user.save()
|
|
||||||
UserProfile.objects.create(user=user)
|
|
||||||
self.stdout.write(self.style.SUCCESS("Successfully created super admin user.\n"
|
|
||||||
"Username: root\nPassword: %s\n"
|
|
||||||
"Remember to change password and turn on two factors auth "
|
|
||||||
"after installation." % rand_password))
|
|
@ -1,17 +0,0 @@
|
|||||||
import os
|
|
||||||
from account.models import User
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
if User.objects.exists():
|
|
||||||
self.stdout.write(self.style.WARNING("Nothing happened\n"))
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
if os.system("python manage.py initadmin") != 0:
|
|
||||||
self.stdout.write(self.style.ERROR("Failed to execute command 'initadmin'"))
|
|
||||||
exit(1)
|
|
||||||
self.stdout.write(self.style.SUCCESS("Done"))
|
|
||||||
except Exception as e:
|
|
||||||
self.stdout.write(self.style.ERROR("Failed to initialize, error: " + str(e)))
|
|
44
utils/management/commands/inituser.py
Normal file
44
utils/management/commands/inituser.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from account.models import AdminType, ProblemPermission, User, UserProfile
|
||||||
|
from utils.shortcuts import rand_str # NOQA
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("--username", type=str)
|
||||||
|
parser.add_argument("--password", type=str)
|
||||||
|
parser.add_argument("--action", type=str)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
username = options["username"]
|
||||||
|
password = options["password"]
|
||||||
|
action = options["action"]
|
||||||
|
|
||||||
|
if not(username and password and action):
|
||||||
|
self.stdout.write(self.style.ERROR("Invalid args"))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if action == "create_super_admin":
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"User {username} exists, operation ignored"))
|
||||||
|
exit()
|
||||||
|
|
||||||
|
user = User.objects.create(username=username, admin_type=AdminType.SUPER_ADMIN,
|
||||||
|
problem_permission=ProblemPermission.ALL)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
UserProfile.objects.create(user=user)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS("User created"))
|
||||||
|
elif action == "reset":
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Password is rested"))
|
||||||
|
except User.DoesNotExist:
|
||||||
|
self.stdout.write(self.style.ERROR(f"User {username} doesnot exist, operation ignored"))
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid action")
|
Loading…
Reference in New Issue
Block a user