Merge pull request #95 from QingdaoU/virusdefender_dev

code review
This commit is contained in:
zema1 2017-11-28 16:15:37 +08:00 committed by GitHub
commit 0c52704a72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 486 additions and 252 deletions

23
.gitignore vendored
View File

@ -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

View File

@ -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
View 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

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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"),
] ]

View File

@ -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])

View File

@ -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
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

0
data/ssl/.gitkeep Normal file
View File

0
data/test_case/.gitkeep Normal file
View File

View 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
View 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
View 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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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",

View File

@ -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"),
]

View File

@ -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/"

View File

@ -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',

View File

@ -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")

View File

@ -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
View 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 ""}

View File

@ -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))

View File

@ -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)))

View 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")