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
*.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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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