mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-12-27 23:51:47 +00:00
Add submission module
This commit is contained in:
parent
25dd57bb49
commit
65f9c7f52b
@ -1,9 +1,10 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from ..views.user import (SSOAPI, AvatarUploadAPI, TwoFactorAuthAPI,
|
||||
UserInfoAPI, UserProfileAPI)
|
||||
UserNameAPI, UserInfoAPI, UserProfileAPI)
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^username/?$", UserNameAPI.as_view(), name="user_name_api"),
|
||||
url(r"^user/(?P<username>\w+)/?$", UserInfoAPI.as_view(), name="user_info_api"),
|
||||
url(r"^profile/?$", UserProfileAPI.as_view(), name="user_profile_api"),
|
||||
url(r"^avatar/upload/?$", AvatarUploadAPI.as_view(), name="avatar_upload_api"),
|
||||
|
@ -19,6 +19,24 @@ from ..serializers import (SSOSerializer, TwoFactorAuthCodeSerializer,
|
||||
EditUserProfileSerializer, AvatarUploadForm)
|
||||
|
||||
|
||||
class UserNameAPI(APIView):
|
||||
def get(self, request):
|
||||
"""
|
||||
Return Username to valid login status
|
||||
"""
|
||||
try:
|
||||
user = User.objects.get(id=request.user.id)
|
||||
except User.DoesNotExist:
|
||||
return self.success({
|
||||
"username": "User does not exist",
|
||||
"isLogin": False
|
||||
})
|
||||
return self.success({
|
||||
"username": user.username,
|
||||
"isLogin": True
|
||||
})
|
||||
|
||||
|
||||
class UserInfoAPI(APIView):
|
||||
# @login_required
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
|
@ -45,6 +45,7 @@ INSTALLED_APPS = (
|
||||
'problem',
|
||||
'contest',
|
||||
'utils',
|
||||
'submission',
|
||||
|
||||
'rest_framework',
|
||||
)
|
||||
|
@ -10,5 +10,6 @@ urlpatterns = [
|
||||
url(r"^api/", include("problem.urls.oj")),
|
||||
url(r"^api/admin/", include("problem.urls.admin")),
|
||||
url(r"^api/admin/", include("contest.urls.admin")),
|
||||
url(r"^api/", include("contest.urls.oj"))
|
||||
url(r"^api/", include("contest.urls.oj")),
|
||||
url(r"^api/", include("submission.urls.oj")),
|
||||
]
|
||||
|
@ -7,4 +7,5 @@ celery
|
||||
Envelopes
|
||||
pytz
|
||||
jsonfield
|
||||
qrcode
|
||||
qrcode
|
||||
redis
|
0
submission/__init__.py
Normal file
0
submission/__init__.py
Normal file
39
submission/migrations/0001_initial.py
Normal file
39
submission/migrations/0001_initial.py
Normal file
@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2017-05-08 09:27
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import jsonfield.fields
|
||||
import utils.models
|
||||
import utils.shortcuts
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Submission',
|
||||
fields=[
|
||||
('id', models.CharField(db_index=True, default=utils.shortcuts.rand_str, max_length=32, primary_key=True, serialize=False)),
|
||||
('contest_id', models.IntegerField(db_index=True)),
|
||||
('problem_id', models.IntegerField(db_index=True)),
|
||||
('created_time', models.DateTimeField(auto_now_add=True)),
|
||||
('user_id', models.IntegerField(db_index=True)),
|
||||
('code', utils.models.RichTextField()),
|
||||
('result', models.IntegerField(default=6)),
|
||||
('info', jsonfield.fields.JSONField()),
|
||||
('language', models.CharField(max_length=20)),
|
||||
('shared', models.BooleanField(default=False)),
|
||||
('accepted_time', models.IntegerField(blank=True, null=True)),
|
||||
('accepted_info', jsonfield.fields.JSONField()),
|
||||
],
|
||||
options={
|
||||
'db_table': 'submission',
|
||||
},
|
||||
),
|
||||
]
|
0
submission/migrations/__init__.py
Normal file
0
submission/migrations/__init__.py
Normal file
44
submission/models.py
Normal file
44
submission/models.py
Normal file
@ -0,0 +1,44 @@
|
||||
from django.db import models
|
||||
from jsonfield import JSONField
|
||||
|
||||
# from judge.languages import language_names
|
||||
from utils.models import RichTextField
|
||||
from utils.shortcuts import rand_str
|
||||
|
||||
|
||||
class JudgeStatus:
|
||||
COMPILE_ERROR = -2
|
||||
WRONG_ANSWER = -1
|
||||
ACCEPTED = 0
|
||||
CPU_TIME_LIMIT_EXCEEDED = 1
|
||||
REAL_TIME_LIMIT_EXCEEDED = 2
|
||||
MEMORY_LIMIT_EXCEEDED = 3
|
||||
RUNTIME_ERROR = 4
|
||||
SYSTEM_ERROR = 5
|
||||
PENDING = 6
|
||||
JUDGING = 7
|
||||
# TODO: 部分正确
|
||||
|
||||
|
||||
class Submission(models.Model):
|
||||
id = models.CharField(max_length=32, default=rand_str, primary_key=True, db_index=True)
|
||||
contest_id = models.IntegerField(db_index=True)
|
||||
problem_id = models.IntegerField(db_index=True)
|
||||
created_time = models.DateTimeField(auto_now_add=True)
|
||||
user_id = models.IntegerField(db_index=True)
|
||||
code = RichTextField()
|
||||
result = models.IntegerField(default=JudgeStatus.PENDING)
|
||||
# 判题结果的详细信息
|
||||
info = JSONField()
|
||||
# TODO: choice
|
||||
language = models.CharField(max_length=20)
|
||||
shared = models.BooleanField(default=False)
|
||||
# 题目状态为 Accepted 时才会存储相关info
|
||||
accepted_time = models.IntegerField(blank=True, null=True)
|
||||
accepted_info = JSONField()
|
||||
|
||||
class Meta:
|
||||
db_table = "submission"
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
10
submission/serializers.py
Normal file
10
submission/serializers.py
Normal file
@ -0,0 +1,10 @@
|
||||
from utils.api import serializers
|
||||
|
||||
# from account.models import User
|
||||
# from .models import Submission
|
||||
|
||||
|
||||
class CreateSubmissionSerializer(serializers.Serializer):
|
||||
problem_id = serializers.IntegerField()
|
||||
language = serializers.CharField(max_length=20)
|
||||
code = serializers.CharField(max_length=20000)
|
6
submission/tasks.py
Normal file
6
submission/tasks.py
Normal file
@ -0,0 +1,6 @@
|
||||
from celery import shared_task
|
||||
|
||||
|
||||
@shared_task
|
||||
def _judge(submission_obj, problem_obj):
|
||||
pass
|
0
submission/test.py
Normal file
0
submission/test.py
Normal file
0
submission/urls/__init__.py
Normal file
0
submission/urls/__init__.py
Normal file
0
submission/urls/admin.py
Normal file
0
submission/urls/admin.py
Normal file
9
submission/urls/oj.py
Normal file
9
submission/urls/oj.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from ..views.oj import (SubmissionAPI, SubmissionListAPI)
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^submission/?$", SubmissionAPI.as_view(), name="submissiob_api"),
|
||||
url(r"^submissions/?$", SubmissionListAPI.as_view(), name="submission_list_api"),
|
||||
url(r"^submissions/(?P<page>\d+)/?$", SubmissionListAPI.as_view(), name="submission_list_page_api"),
|
||||
]
|
0
submission/views/__init__.py
Normal file
0
submission/views/__init__.py
Normal file
0
submission/views/admin.py
Normal file
0
submission/views/admin.py
Normal file
172
submission/views/oj.py
Normal file
172
submission/views/oj.py
Normal file
@ -0,0 +1,172 @@
|
||||
import redis
|
||||
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
from account.decorators import login_required
|
||||
from account.models import AdminType, User
|
||||
from problem.models import Problem
|
||||
|
||||
from utils.api import APIView, validate_serializer
|
||||
from utils.shortcuts import build_query_string
|
||||
from utils.throttling import TokenBucket, BucketController
|
||||
|
||||
from ..models import Submission
|
||||
from ..serializers import CreateSubmissionSerializer
|
||||
from ..tasks import _judge
|
||||
|
||||
|
||||
def _submit_code(response, user, problem_id, language, code):
|
||||
controller = BucketController(user_id=user.id,
|
||||
redis_conn=redis.Redis(),
|
||||
default_capacity=30)
|
||||
bucket = TokenBucket(fill_rate=10,
|
||||
capacity=20,
|
||||
last_capacity=controller.last_capacity,
|
||||
last_timestamp=controller.last_timestamp)
|
||||
if bucket.consume():
|
||||
controller.last_capacity -= 1
|
||||
else:
|
||||
return response.error("Please wait %d seconds" % int(bucket.expected_time() + 1))
|
||||
|
||||
try:
|
||||
problem = Problem.objects.get(id=problem_id)
|
||||
except Problem.DoesNotExist:
|
||||
return response.error("Problem not exist")
|
||||
|
||||
submission = Submission.objects.create(user_id=user.id,
|
||||
language=language,
|
||||
code=code,
|
||||
problem_id=problem.id)
|
||||
|
||||
try:
|
||||
# TODO 参数
|
||||
_judge.delay(submission, problem)
|
||||
except Exception as e:
|
||||
return response.error("Failed")
|
||||
|
||||
return response.success({"submission_id": submission.id})
|
||||
|
||||
|
||||
class SubmissionAPI(APIView):
|
||||
@validate_serializer(CreateSubmissionSerializer)
|
||||
@login_required
|
||||
def post(self, request):
|
||||
data = request.data
|
||||
return _submit_code(self, request.user, data["problem_id"], data["language"], data["code"])
|
||||
|
||||
@login_required
|
||||
def get(self, request):
|
||||
submission_id = request.GET.get("submission_id")
|
||||
if not submission_id:
|
||||
return self.error("Parameter error")
|
||||
try:
|
||||
submission = Submission.objects.get(id=submission_id, user_id=request.user.id)
|
||||
except Submission.DoesNotExist:
|
||||
return self.error("Submission not exist")
|
||||
|
||||
response_data = {"result": submission.result}
|
||||
if submission.result == 0:
|
||||
response_data["accepted_answer_time"] = submission.accepted_answer_time
|
||||
return self.success(response_data)
|
||||
|
||||
|
||||
class MyProblemSubmissionListAPI(APIView):
|
||||
"""
|
||||
用户单个题目的全部提交列表
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
problem_id = request.GET.get("problem_id")
|
||||
try:
|
||||
problem = Problem.objects.get(id=problem_id, visible=True)
|
||||
except Problem.DoesNotExist:
|
||||
return self.error("Problem not exist")
|
||||
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, problem_id=problem.id,
|
||||
contest_id__isnull=True). \
|
||||
order_by("-created_time"). \
|
||||
values("id", "result", "created_time", "accepted_time", "language")
|
||||
|
||||
return self.success({"submissions": submissions, "problem": problem})
|
||||
|
||||
|
||||
class SubmissionListAPI(APIView):
|
||||
"""
|
||||
所有提交的列表
|
||||
"""
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
submission_filter = {"my": None, "user_id": None}
|
||||
show_all = False
|
||||
page = kwargs.get("page", 1)
|
||||
|
||||
user_id = request.GET.get("user_id")
|
||||
if user_id and request.user.admin_type == AdminType.SUPER_ADMIN:
|
||||
submission_filter["user_id"] = user_id
|
||||
submissions = Submission.objects.filter(user_id=user_id, contest_id__isnull=True)
|
||||
else:
|
||||
show_all = True
|
||||
if request.GET.get("my") == "true":
|
||||
submission_filter["my"] = "true"
|
||||
show_all = False
|
||||
if show_all:
|
||||
submissions = Submission.objects.filter(contest_id__isnull=True)
|
||||
else:
|
||||
submissions = Submission.objects.filter(user_id=request.user.id, contest_id__isnull=True)
|
||||
|
||||
submissions = submissions.values("id", "user_id", "problem_id", "result", "created_time",
|
||||
"accepted_time", "language").order_by("-created_time")
|
||||
|
||||
language = request.GET.get("language")
|
||||
if language:
|
||||
submissions = submissions.filter(language=language)
|
||||
submission_filter["language"] = language
|
||||
|
||||
result = request.GET.get("result")
|
||||
if result:
|
||||
# TODO: 转换为数字结果
|
||||
submissions = submissions.filter(result=int(result))
|
||||
submission_filter["result"] = result
|
||||
|
||||
paginator = Paginator(submissions, 20)
|
||||
try:
|
||||
submissions = paginator.page(int(page))
|
||||
except Exception:
|
||||
return self.error("Page not exist")
|
||||
|
||||
# Cache
|
||||
cache_result = {"problem": {}, "user": {}}
|
||||
for item in submissions:
|
||||
problem_id = item["problem_id"]
|
||||
if problem_id not in cache_result["problem"]:
|
||||
problem = Problem.objects.get(id=problem_id)
|
||||
cache_result["problem"][problem_id] = problem.title
|
||||
item["title"] = cache_result["problem"][problem_id]
|
||||
|
||||
user_id = item["user_id"]
|
||||
if user_id not in cache_result["user"]:
|
||||
user = User.objects.get(id=user_id)
|
||||
cache_result["user"][user_id] = user
|
||||
item["user"] = cache_result["user"][user_id]
|
||||
|
||||
if item["user_id"] == request.user.id or request.user.admin_type == AdminType.SUPER_ADMIN:
|
||||
item["show_link"] = True
|
||||
else:
|
||||
item["show_link"] = False
|
||||
|
||||
previous_page = next_page = None
|
||||
try:
|
||||
previous_page = submissions.previous_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
next_page = submissions.next_page_number()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return self.success({"submissions": submissions, "page": int(page),
|
||||
"previous_page": previous_page, "next_page": next_page,
|
||||
"start_id": int(page) * 20 - 20,
|
||||
"query": build_query_string(submission_filter),
|
||||
"submission_filter": submission_filter,
|
||||
"show_all": show_all})
|
@ -44,3 +44,17 @@ def rand_str(length=32, type="lower_hex"):
|
||||
return random.choice("123456789abcdef") + get_random_string(length - 1, allowed_chars="0123456789abcdef")
|
||||
else:
|
||||
return random.choice("123456789") + get_random_string(length - 1, allowed_chars="0123456789")
|
||||
|
||||
|
||||
def build_query_string(kv_data, ignore_none=True):
|
||||
# {"a": 1, "b": "test"} -> "?a=1&b=test"
|
||||
query_string = ""
|
||||
for k, v in kv_data.iteritems():
|
||||
if ignore_none is True and kv_data[k] is None:
|
||||
continue
|
||||
if query_string != "":
|
||||
query_string += "&"
|
||||
else:
|
||||
query_string = "?"
|
||||
query_string += (k + "=" + str(v))
|
||||
return query_string
|
||||
|
91
utils/throttling.py
Normal file
91
utils/throttling.py
Normal file
@ -0,0 +1,91 @@
|
||||
from __future__ import print_function
|
||||
import time
|
||||
|
||||
|
||||
class TokenBucket:
|
||||
def __init__(self, fill_rate, capacity, last_capacity, last_timestamp):
|
||||
self.capacity = float(capacity)
|
||||
self._left_tokens = last_capacity
|
||||
self.fill_rate = float(fill_rate)
|
||||
self.timestamp = last_timestamp
|
||||
|
||||
def consume(self, tokens=1):
|
||||
if tokens <= self.tokens:
|
||||
self._left_tokens -= tokens
|
||||
return True
|
||||
return False
|
||||
|
||||
def expected_time(self, tokens=1):
|
||||
_tokens = self.tokens
|
||||
tokens = max(tokens, _tokens)
|
||||
return (tokens - _tokens) / self.fill_rate * 60
|
||||
|
||||
@property
|
||||
def tokens(self):
|
||||
if self._left_tokens < self.capacity:
|
||||
now = time.time()
|
||||
delta = self.fill_rate * ((now - self.timestamp) / 60)
|
||||
self._left_tokens = min(self.capacity, self._left_tokens + delta)
|
||||
self.timestamp = now
|
||||
return self._left_tokens
|
||||
|
||||
|
||||
class BucketController:
|
||||
def __init__(self, user_id, redis_conn, default_capacity):
|
||||
self.user_id = user_id
|
||||
self.default_capacity = default_capacity
|
||||
self.redis = redis_conn
|
||||
self.key = "bucket_" + str(self.user_id)
|
||||
|
||||
@property
|
||||
def last_capacity(self):
|
||||
value = self.redis.hget(self.key, "last_capacity")
|
||||
if value is None:
|
||||
self.last_capacity = self.default_capacity
|
||||
return self.default_capacity
|
||||
return int(value)
|
||||
|
||||
@last_capacity.setter
|
||||
def last_capacity(self, value):
|
||||
self.redis.hset(self.key, "last_capacity", value)
|
||||
|
||||
@property
|
||||
def last_timestamp(self):
|
||||
value = self.redis.hget(self.key, "last_timestamp")
|
||||
if value is None:
|
||||
timestamp = int(time.time())
|
||||
self.last_timestamp = timestamp
|
||||
return timestamp
|
||||
return int(value)
|
||||
|
||||
@last_timestamp.setter
|
||||
def last_timestamp(self, value):
|
||||
self.redis.hset(self.key, "last_timestamp", value)
|
||||
|
||||
|
||||
"""
|
||||
# # Token bucket, to limit submission rate
|
||||
# # Demo
|
||||
|
||||
success = failure = 0
|
||||
current_user_id = 1
|
||||
token_bucket_default_capacity = 50
|
||||
token_bucket_fill_rate = 10
|
||||
for i in range(5000):
|
||||
controller = BucketController(user_id=current_user_id,
|
||||
redis_conn=redis.Redis(),
|
||||
default_capacity=token_bucket_default_capacity)
|
||||
bucket = TokenBucket(fill_rate=token_bucket_fill_rate,
|
||||
capacity=token_bucket_default_capacity,
|
||||
last_capacity=controller.last_capacity,
|
||||
last_timestamp=controller.last_timestamp)
|
||||
time.sleep(0.05)
|
||||
if bucket.consume():
|
||||
success += 1
|
||||
print(i, ": Accepted")
|
||||
controller.last_capacity -= 1
|
||||
else:
|
||||
failure += 1
|
||||
print(i, "Dropped, time left ", bucket.expected_time())
|
||||
print(success, failure)
|
||||
"""
|
Loading…
Reference in New Issue
Block a user