mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 00:13:18 +00:00
commit
0c52704a72
23
.gitignore
vendored
23
.gitignore
vendored
@ -54,21 +54,18 @@ db.db
|
||||
#*.out
|
||||
*.sqlite3
|
||||
.DS_Store
|
||||
log/
|
||||
static/release/css
|
||||
static/release/js
|
||||
static/release/img
|
||||
static/src/upload_image/*
|
||||
build.txt
|
||||
tmp/
|
||||
test_case/
|
||||
release/
|
||||
upload/
|
||||
custom_settings.py
|
||||
docker-compose.yml
|
||||
*.zip
|
||||
rsyncd.passwd
|
||||
|
||||
node_modules/
|
||||
update.sh
|
||||
ssh.sh
|
||||
data/log/*
|
||||
!data/log/.gitkeep
|
||||
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
|
||||
install:
|
||||
- pip install -r deploy/requirements.txt
|
||||
- mkdir log test_case upload
|
||||
- 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
|
||||
- python manage.py migrate
|
||||
- python manage.py initadmin
|
||||
script:
|
||||
- docker ps -a
|
||||
- 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 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):
|
||||
|
@ -40,8 +40,7 @@ class GenerateUserSerializer(serializers.Serializer):
|
||||
suffix = serializers.CharField(max_length=16, allow_blank=True)
|
||||
number_from = serializers.IntegerField()
|
||||
number_to = serializers.IntegerField()
|
||||
default_email = serializers.CharField(max_length=64)
|
||||
password_length = serializers.IntegerField(required=False, max_value=16)
|
||||
password_length = serializers.IntegerField(max_value=16, default=8)
|
||||
|
||||
|
||||
class ImportUserSeralizer(serializers.Serializer):
|
||||
|
@ -1,4 +1,5 @@
|
||||
import time
|
||||
|
||||
from unittest import mock
|
||||
from datetime import timedelta
|
||||
from copy import deepcopy
|
||||
@ -557,20 +558,25 @@ class AdminUserTest(APITestCase):
|
||||
|
||||
def test_import_users(self):
|
||||
data = {"users": [["user1", "pass1", "eami1@e.com"],
|
||||
["user1", "pass1", "eami1@e.com"],
|
||||
["user2", "pass2"], ["user3", "pass3", "eamil3@e.com"]]
|
||||
["user2", "pass3", "eamil3@e.com"]]
|
||||
}
|
||||
resp = self.client.post(self.url, data)
|
||||
self.assertSuccess(resp)
|
||||
self.assertDictEqual(resp.data["data"], {"omitted_count": 1,
|
||||
"created_count": 2,
|
||||
"get_count": 1})
|
||||
# successfully created 2 users
|
||||
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):
|
||||
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])
|
||||
resp = self.client.delete(self.url + "?id=" + user_ids)
|
||||
self.assertSuccess(resp)
|
||||
@ -605,6 +611,19 @@ class GenerateUserAPITest(APITestCase):
|
||||
resp = self.client.post(self.url, data=self.data)
|
||||
self.assertSuccess(resp)
|
||||
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,
|
||||
AvatarUploadAPI, TwoFactorAuthAPI, UserProfileAPI,
|
||||
UserRankAPI, CheckTFARequiredAPI, SessionManagementAPI,
|
||||
ProfileProblemDisplayIDRefreshAPI)
|
||||
ProfileProblemDisplayIDRefreshAPI, OpenAPIAppkeyAPI)
|
||||
|
||||
from utils.captcha.views import CaptchaAPIView
|
||||
|
||||
@ -25,5 +25,6 @@ urlpatterns = [
|
||||
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"^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 re
|
||||
import xlsxwriter
|
||||
|
||||
from django.db import transaction, IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
from submission.models import Submission
|
||||
from utils.api import APIView, validate_serializer
|
||||
@ -18,26 +21,27 @@ class UserAdminAPI(APIView):
|
||||
@validate_serializer(ImportUserSeralizer)
|
||||
@super_admin_required
|
||||
def post(self, request):
|
||||
"""
|
||||
Generate user
|
||||
"""
|
||||
data = request.data["users"]
|
||||
omitted_count = created_count = get_count = 0
|
||||
|
||||
user_list = []
|
||||
for user_data in data:
|
||||
if len(user_data) != 3 or len(user_data[0]) > 32:
|
||||
omitted_count += 1
|
||||
continue
|
||||
user, created = User.objects.get_or_create(username=user_data[0])
|
||||
user.set_password(user_data[1])
|
||||
user.email = user_data[2]
|
||||
user.save()
|
||||
if created:
|
||||
UserProfile.objects.create(user=user)
|
||||
created_count += 1
|
||||
else:
|
||||
get_count += 1
|
||||
return self.success({
|
||||
"omitted_count": omitted_count,
|
||||
"created_count": created_count,
|
||||
"get_count": get_count
|
||||
})
|
||||
return self.error(f"Error occurred while processing data '{user_data}'")
|
||||
user_list.append(User(username=user_data[0], password=make_password(user_data[1]), email=user_data[2]))
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
ret = User.objects.bulk_create(user_list)
|
||||
UserProfile.objects.bulk_create([UserProfile(user=user) for user in ret])
|
||||
return self.success()
|
||||
except IntegrityError as e:
|
||||
# Extract detail from exception message
|
||||
# duplicate key value violates unique constraint "user_username_key"
|
||||
# DETAIL: Key (username)=(root11) already exists.
|
||||
return self.error(str(e).split("\n")[1])
|
||||
|
||||
@validate_serializer(EditUserSerializer)
|
||||
@super_admin_required
|
||||
@ -146,7 +150,7 @@ class GenerateUserAPI(APIView):
|
||||
file_id = request.GET.get("file_id")
|
||||
if not file_id:
|
||||
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")
|
||||
file_path = f"/tmp/{file_id}.xlsx"
|
||||
if not os.path.isfile(file_path):
|
||||
@ -169,9 +173,6 @@ class GenerateUserAPI(APIView):
|
||||
if data["number_from"] > data["number_to"]:
|
||||
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)
|
||||
filename = f"/tmp/{file_id}.xlsx"
|
||||
workbook = xlsxwriter.Workbook(filename)
|
||||
@ -180,26 +181,27 @@ class GenerateUserAPI(APIView):
|
||||
worksheet.write("A1", "Username")
|
||||
worksheet.write("B1", "Password")
|
||||
i = 1
|
||||
created_count = 0
|
||||
get_count = 0
|
||||
|
||||
user_list = []
|
||||
for number in range(data["number_from"], data["number_to"] + 1):
|
||||
username = f"{data['prefix']}{number}{data['suffix']}"
|
||||
password = rand_str(password_length)
|
||||
user, created = User.objects.get_or_create(username=username)
|
||||
user.email = default_email
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
if created:
|
||||
UserProfile.objects.create(user=user)
|
||||
created_count += 1
|
||||
else:
|
||||
get_count += 1
|
||||
worksheet.write_string(i, 0, username)
|
||||
worksheet.write_string(i, 1, password)
|
||||
i += 1
|
||||
workbook.close()
|
||||
return self.success({
|
||||
"file_id": file_id,
|
||||
"created_count": created_count,
|
||||
"get_count": get_count
|
||||
})
|
||||
raw_password = rand_str(data["password_length"])
|
||||
user = User(username=f"{data['prefix']}{number}{data['suffix']}", password=make_password(raw_password))
|
||||
user.raw_password = raw_password
|
||||
user_list.append(user)
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
|
||||
ret = User.objects.bulk_create(user_list)
|
||||
UserProfile.objects.bulk_create([UserProfile(user=user) for user in ret])
|
||||
for item in user_list:
|
||||
worksheet.write_string(i, 0, item.username)
|
||||
worksheet.write_string(i, 1, item.raw_password)
|
||||
i += 1
|
||||
workbook.close()
|
||||
return self.success({"file_id": file_id})
|
||||
except IntegrityError as e:
|
||||
# Extract detail from exception message
|
||||
# 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]
|
||||
profile.save(update_fields=["acm_problems_status", "oi_problems_status"])
|
||||
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
|
||||
|
||||
BASE=/app
|
||||
APP=/app
|
||||
DATA=/data
|
||||
|
||||
if [ ! -f "$BASE/custom_settings.py" ]; then
|
||||
echo SECRET_KEY=\"$(cat /dev/urandom | head -1 | md5sum | head -c 32)\" >> /app/oj/custom_settings.py
|
||||
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
|
||||
fi
|
||||
|
||||
if [ ! -d "$BASE/log" ]; then
|
||||
mkdir -p $BASE/log
|
||||
mkdir -p $DATA/log $DATA/ssl $DATA/test_case $DATA/public/upload $DATA/public/avatar
|
||||
|
||||
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
|
||||
|
||||
cd $BASE
|
||||
find . -name "*.pyc" -delete
|
||||
|
||||
# wait for postgresql start
|
||||
sleep 6
|
||||
cd $APP
|
||||
|
||||
n=0
|
||||
while [ $n -lt 3 ]
|
||||
while [ $n -lt 5 ]
|
||||
do
|
||||
python manage.py migrate
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Can't start server, try again in 3 seconds.."
|
||||
sleep 3
|
||||
let "n+=1"
|
||||
continue
|
||||
fi
|
||||
python manage.py initinstall
|
||||
break
|
||||
python manage.py migrate --no-input &&
|
||||
python manage.py inituser --username=root --password=rootroot --action=create_super_admin &&
|
||||
break
|
||||
n=$(($n+1))
|
||||
echo "Failed to migrate, going to retry..."
|
||||
sleep 8
|
||||
done
|
||||
|
||||
if [ $n -eq 3 ]; then
|
||||
echo "Can't start server, please check log file for details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chown -R nobody:nogroup /data/log /data/test_case /data/avatar /data/upload
|
||||
exec supervisord -c /app/deploy/supervisor.conf
|
||||
cp data/public/avatar/default.png /data/public/avatar
|
||||
chown -R nobody:nogroup $DATA $APP/dist
|
||||
exec supervisord -c /app/deploy/supervisord.conf
|
||||
|
@ -1,5 +1,5 @@
|
||||
[supervisord]
|
||||
logfile=/app/log/supervisord.log
|
||||
logfile=/data/log/supervisord.log
|
||||
logfile_maxbytes=10MB
|
||||
logfile_backups=10
|
||||
loglevel=info
|
||||
@ -7,12 +7,30 @@ pidfile=/tmp/supervisord.pid
|
||||
nodaemon=true
|
||||
childlogdir=/data/log/
|
||||
|
||||
[inet_http_server]
|
||||
port=127.0.0.1:9005
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[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]
|
||||
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/
|
||||
user=nobody
|
||||
stdout_logfile=/data/log/gunicorn.log
|
||||
stderr_logfile=/data/log/gunicorn.log
|
||||
autostart=true
|
@ -14,6 +14,7 @@ from contest.models import ContestRuleType, ACMContestRank, OIContestRank, Conte
|
||||
from judge.languages import languages, spj_languages
|
||||
from options.options import SysOptions
|
||||
from problem.models import Problem, ProblemRuleType
|
||||
from problem.utils import parse_problem_template
|
||||
from submission.models import JudgeStatus, Submission
|
||||
from utils.cache import cache
|
||||
from utils.constants import CacheKey
|
||||
@ -123,16 +124,24 @@ class JudgeDispatcher(DispatcherBase):
|
||||
cache.lpush(CacheKey.waiting_queue, json.dumps(data))
|
||||
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 = {}
|
||||
if self.problem.spj_code:
|
||||
for lang in spj_languages:
|
||||
if lang["name"] == self.problem.spj_language:
|
||||
spj_config = lang["spj"]
|
||||
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 = {
|
||||
"language_config": sub_config["config"],
|
||||
"src": self.submission.code,
|
||||
"src": code,
|
||||
"max_cpu_time": self.problem.time_limit,
|
||||
"max_memory": 1024 * 1024 * self.problem.memory_limit,
|
||||
"test_case_id": self.problem.test_case_id,
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
_c_lang_config = {
|
||||
"template": """//PREPEND START
|
||||
"template": """//PREPEND BEGIN
|
||||
#include <stdio.h>
|
||||
//PREPEND END
|
||||
|
||||
@ -12,7 +12,7 @@ int add(int a, int b) {
|
||||
}
|
||||
//TEMPLATE END
|
||||
|
||||
//APPEND START
|
||||
//APPEND BEGIN
|
||||
int main() {
|
||||
printf("%d", add(1, 2));
|
||||
return 0;
|
||||
@ -48,12 +48,23 @@ _c_lang_spj_config = {
|
||||
}
|
||||
|
||||
_cpp_lang_config = {
|
||||
"template": """/*--PREPEND START--*/
|
||||
/*--PREPEND END--*/
|
||||
/*--TEMPLATE BEGIN--*/
|
||||
/*--TEMPLATE END--*/
|
||||
/*--APPEND START--*/
|
||||
/*--APPEND END--*/""",
|
||||
"template": """//PREPEND BEGIN
|
||||
#include <iostream>
|
||||
//PREPEND END
|
||||
|
||||
//TEMPLATE BEGIN
|
||||
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": {
|
||||
"src_name": "main.cpp",
|
||||
"exe_name": "main",
|
||||
|
@ -24,16 +24,4 @@ DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
TEST_CASE_DIR = "/tmp"
|
||||
|
||||
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"),
|
||||
]
|
||||
DATA_DIR = f"{BASE_DIR}/data"
|
||||
|
@ -8,8 +8,8 @@ def get_env(name, default=""):
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'HOST': get_env("POSTGRES_HOST", "postgres"),
|
||||
'PORT': get_env("POSTGRES_PORT", "5433"),
|
||||
'HOST': get_env("POSTGRES_HOST", "oj-postgres"),
|
||||
'PORT': get_env("POSTGRES_PORT", "5432"),
|
||||
'NAME': get_env("POSTGRES_DB"),
|
||||
'USER': get_env("POSTGRES_USER"),
|
||||
'PASSWORD': get_env("POSTGRES_PASSWORD")
|
||||
@ -17,7 +17,7 @@ DATABASES = {
|
||||
}
|
||||
|
||||
REDIS_CONF = {
|
||||
"host": get_env("REDIS_HOST", "redis"),
|
||||
"host": get_env("REDIS_HOST", "oj-redis"),
|
||||
"port": get_env("REDIS_PORT", "6379")
|
||||
}
|
||||
|
||||
@ -25,12 +25,4 @@ DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
AVATAR_URI_PREFIX = "/static/avatar"
|
||||
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/"
|
||||
DATA_DIR = "/data"
|
||||
|
@ -49,6 +49,7 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'account.middleware.APITokenAuthMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
@ -109,57 +110,55 @@ USE_TZ = True
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_URL = '/public/'
|
||||
|
||||
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 = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'standard': {
|
||||
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'django_error': {
|
||||
'level': 'WARNING',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': os.path.join(LOG_PATH, 'django.log'),
|
||||
'formatter': 'standard'
|
||||
},
|
||||
'app_info': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': os.path.join(LOG_PATH, 'app_info.log'),
|
||||
'formatter': 'standard'
|
||||
},
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'standard'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['django_error', 'console'],
|
||||
'level': 'WARNING',
|
||||
'propagate': True,
|
||||
},
|
||||
'django.db.backends': {
|
||||
'handlers': ['django_error', 'console'],
|
||||
'level': 'WARNING',
|
||||
'propagate': True,
|
||||
},
|
||||
},
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'standard': {
|
||||
'format': '[%(asctime)s] - [%(levelname)s] - [%(name)s:%(lineno)d] - %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'standard'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['console'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
'django.db.backends': {
|
||||
'handlers': ['console'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
'': {
|
||||
'handlers': ['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 = {
|
||||
'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 .models import Problem, ProblemRuleType, ProblemTag
|
||||
from .utils import parse_problem_template
|
||||
|
||||
|
||||
class TestCaseUploadForm(forms.Form):
|
||||
@ -110,9 +111,18 @@ class ContestProblemAdminSerializer(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:
|
||||
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):
|
||||
@ -131,3 +141,54 @@ class ContestProblemSafeSerializer(BaseProblemSerializer):
|
||||
class ContestProblemMakePublicSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
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 Problem, ProblemRuleType
|
||||
from .views.admin import TestCaseAPI
|
||||
from contest.models import Contest
|
||||
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",
|
||||
"output_description": "test", "time_limit": 1000, "memory_limit": 256, "difficulty": "Low",
|
||||
"visible": True, "tags": ["test"], "languages": ["C", "C++", "Java", "Python2"], "template": {},
|
||||
@ -257,3 +260,44 @@ class ContestProblemTest(ProblemCreateTestBase):
|
||||
contest.save()
|
||||
resp = self.client.get(self.url + "?contest_id=" + str(self.contest["id"]))
|
||||
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