checkpoint
This commit is contained in:
parent
1307c16ec1
commit
807c52bf2b
@ -8,3 +8,4 @@ from Main.models.settask import SetTask
|
|||||||
from Main.models.solution import Solution
|
from Main.models.solution import Solution
|
||||||
from Main.models.language import Language
|
from Main.models.language import Language
|
||||||
from Main.models.extrafile import ExtraFile
|
from Main.models.extrafile import ExtraFile
|
||||||
|
from Main.models.progress import Progress
|
||||||
|
@ -1,27 +1,50 @@
|
|||||||
from os import remove
|
from os import remove
|
||||||
from os.path import join, exists
|
from os.path import join, exists
|
||||||
|
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from Sprint.settings import DATA_ROOT
|
from Sprint.settings import DATA_ROOT
|
||||||
|
|
||||||
|
|
||||||
class ExtraFile(models.Model):
|
class ExtraFile(models.Model):
|
||||||
task = models.ForeignKey('Task', on_delete=models.CASCADE)
|
task = models.ForeignKey("Task", on_delete=models.CASCADE)
|
||||||
filename = models.TextField()
|
filename = models.TextField()
|
||||||
is_test = models.BooleanField(null=True)
|
is_test = models.BooleanField(null=True)
|
||||||
|
is_sample = models.BooleanField(null=True)
|
||||||
readable = models.BooleanField(null=True)
|
readable = models.BooleanField(null=True)
|
||||||
test_number = models.IntegerField(null=True)
|
test_number = models.IntegerField(null=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self):
|
def path(self):
|
||||||
return join(DATA_ROOT, 'extra_files', str(self.id))
|
return join(DATA_ROOT, "extra_files", str(self.id))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_be_sample(self):
|
||||||
|
return (
|
||||||
|
self.is_test
|
||||||
|
and not self.filename.endswith(".a")
|
||||||
|
and len(
|
||||||
|
ExtraFile.objects.filter(task=self.task, filename=self.filename + ".a")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
return open(self.path, 'r').read()
|
return open(self.path, "r").read()
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if exists(self.path):
|
if exists(self.path):
|
||||||
remove(self.path)
|
remove(self.path)
|
||||||
|
if self.is_test and self.filename.endswith('.a'):
|
||||||
|
try:
|
||||||
|
ef = ExtraFile.objects.get(task=self.task, filename=self.filename.rstrip('.a'), is_test=True)
|
||||||
|
ef.is_sample = False
|
||||||
|
ef.save()
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
pass
|
||||||
super().delete(using=using, keep_parents=keep_parents)
|
super().delete(using=using, keep_parents=keep_parents)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def answer(self):
|
||||||
|
return ExtraFile.objects.get(task=self.task, is_test=True, filename=self.filename + '.a')
|
@ -7,6 +7,7 @@ class Language(models.Model):
|
|||||||
file_type = models.TextField(null=True)
|
file_type = models.TextField(null=True)
|
||||||
logo = models.ImageField(upload_to="logos", null=True)
|
logo = models.ImageField(upload_to="logos", null=True)
|
||||||
image = models.TextField(default='ubuntu')
|
image = models.TextField(default='ubuntu')
|
||||||
|
highlight = models.TextField(default='plaintext')
|
||||||
opened = models.BooleanField(default=False)
|
opened = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
35
Main/models/progress.py
Normal file
35
Main/models/progress.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from Main.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
class Progress(models.Model):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||||
|
start_time = models.DateTimeField(default=timezone.now)
|
||||||
|
finished_time = models.DateTimeField(null=True)
|
||||||
|
score = models.IntegerField(default=0)
|
||||||
|
finished = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time(self):
|
||||||
|
if not self.finished:
|
||||||
|
self.finished_time = timezone.now()
|
||||||
|
return self.finished_time - self.start_time
|
||||||
|
|
||||||
|
def increment_rating(self):
|
||||||
|
if self.task.creator == self.user:
|
||||||
|
return
|
||||||
|
delta = timedelta(minutes=self.task.time_estimation)
|
||||||
|
self.score = int(delta / self.time * 100)
|
||||||
|
self.save()
|
||||||
|
self.user.userinfo.rating += self.score
|
||||||
|
self.user.userinfo.save()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def by_solution(solution):
|
||||||
|
return Progress.objects.get(task=solution.task, user=solution.user)
|
@ -1,8 +1,20 @@
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
class Set(models.Model):
|
class Set(models.Model):
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
public = models.BooleanField(default=False)
|
public = models.BooleanField(default=False)
|
||||||
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||||
|
opened = models.BooleanField(default=False)
|
||||||
|
start_time = models.DateTimeField(default=timezone.now)
|
||||||
|
end_time = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
return (
|
||||||
|
self.opened
|
||||||
|
and (self.start_time is None or timezone.now() >= self.start_time)
|
||||||
|
and (self.end_time is None or timezone.now() <= self.end_time)
|
||||||
|
)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
from os import mkdir
|
from os import mkdir, walk
|
||||||
from os.path import join, exists
|
from os.path import join, exists
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
@ -24,6 +25,30 @@ class Solution(models.Model):
|
|||||||
rmtree(self.directory)
|
rmtree(self.directory)
|
||||||
super().delete(using=using, keep_parents=keep_parents)
|
super().delete(using=using, keep_parents=keep_parents)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
data = []
|
||||||
|
for path, _, files in walk(self.directory):
|
||||||
|
if path.startswith(self.testing_directory):
|
||||||
|
continue
|
||||||
|
for file in files:
|
||||||
|
try:
|
||||||
|
entity = {
|
||||||
|
'filename': file,
|
||||||
|
'text': open(join(path, file), 'r').read()
|
||||||
|
}
|
||||||
|
end = file.split('.')[-1]
|
||||||
|
try:
|
||||||
|
highlight = 'language-' + Language.objects.get(file_type=end).highlight
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
highlight = 'nohighlight'
|
||||||
|
entity['highlight'] = highlight
|
||||||
|
data.append(entity)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
data.sort(key=lambda x: x['filename'])
|
||||||
|
return data
|
||||||
|
|
||||||
def create_dirs(self):
|
def create_dirs(self):
|
||||||
mkdir(self.directory)
|
mkdir(self.directory)
|
||||||
mkdir(self.testing_directory)
|
mkdir(self.testing_directory)
|
||||||
|
@ -12,6 +12,7 @@ class Task(models.Model):
|
|||||||
output_format = models.TextField(default="")
|
output_format = models.TextField(default="")
|
||||||
specifications = models.TextField(default="")
|
specifications = models.TextField(default="")
|
||||||
time_limit = models.IntegerField(default=10000)
|
time_limit = models.IntegerField(default=10000)
|
||||||
|
time_estimation = models.IntegerField(default=5)
|
||||||
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -24,3 +25,25 @@ class Task(models.Model):
|
|||||||
@property
|
@property
|
||||||
def tests(self):
|
def tests(self):
|
||||||
return ExtraFile.objects.filter(task=self, is_test=True)
|
return ExtraFile.objects.filter(task=self, is_test=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def samples(self):
|
||||||
|
data = []
|
||||||
|
for test in self.tests.order_by('test_number'):
|
||||||
|
if test.is_sample and test.readable:
|
||||||
|
data.append({
|
||||||
|
'input': test.text,
|
||||||
|
'output': test.answer.text
|
||||||
|
})
|
||||||
|
count = 1
|
||||||
|
for entity in data:
|
||||||
|
entity["num"] = count
|
||||||
|
count += 1
|
||||||
|
return data
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
from Main.models.progress import Progress
|
||||||
|
for progress in Progress.objects.filter(task=self):
|
||||||
|
progress.user.userinfo.rating -= progress.score
|
||||||
|
progress.user.userinfo.save()
|
||||||
|
super().delete(using=using, keep_parents=keep_parents)
|
||||||
|
@ -17,6 +17,8 @@ class UserInfo(models.Model):
|
|||||||
profile_picture = models.ImageField(upload_to="profile_pictures", null=True)
|
profile_picture = models.ImageField(upload_to="profile_pictures", null=True)
|
||||||
rating = models.IntegerField(default=0)
|
rating = models.IntegerField(default=0)
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
|
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
|
||||||
|
telegram_chat_id = models.TextField(default="")
|
||||||
|
notification_solution_result = models.BooleanField(default=False)
|
||||||
|
|
||||||
def _append_task(self, task, tasks):
|
def _append_task(self, task, tasks):
|
||||||
if task.creator == self.user or task.public:
|
if task.creator == self.user or task.public:
|
||||||
|
@ -38,3 +38,11 @@ class AccountView(BaseView):
|
|||||||
self.request.user.save()
|
self.request.user.save()
|
||||||
login(self.request, self.request.user)
|
login(self.request, self.request.user)
|
||||||
return "/account?error_message=Пароль успешно установлен"
|
return "/account?error_message=Пароль успешно установлен"
|
||||||
|
|
||||||
|
def post_notifications(self):
|
||||||
|
self.request.user.userinfo.telegram_chat_id = self.request.POST['chat_id']
|
||||||
|
for attr in dir(self.request.user.userinfo):
|
||||||
|
if attr.startswith('notification'):
|
||||||
|
setattr(self.request.user.userinfo, attr, attr in self.request.POST.keys())
|
||||||
|
self.request.user.userinfo.save()
|
||||||
|
return '/account'
|
||||||
|
@ -12,8 +12,7 @@ class RegisterView(BaseView):
|
|||||||
self.context["error_message"] = self.request.GET.get("error_message", "")
|
self.context["error_message"] = self.request.GET.get("error_message", "")
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
data = {**self.request.POST}
|
data = self.request.POST
|
||||||
data["password"] = data["password"].strip()
|
|
||||||
if len(data["password"]) < 8:
|
if len(data["password"]) < 8:
|
||||||
return "/register?error_message=Пароль слишком слабый"
|
return "/register?error_message=Пароль слишком слабый"
|
||||||
if data["password"] != data["repeat_password"]:
|
if data["password"] != data["repeat_password"]:
|
||||||
|
10
Main/views/SolutionView.py
Normal file
10
Main/views/SolutionView.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from SprintLib.BaseView import BaseView, AccessError
|
||||||
|
|
||||||
|
|
||||||
|
class SolutionView(BaseView):
|
||||||
|
view_file = 'solution.html'
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def pre_handle(self):
|
||||||
|
if self.entities.solution.user != self.request.user:
|
||||||
|
raise AccessError()
|
17
Main/views/TaskRuntimeView.py
Normal file
17
Main/views/TaskRuntimeView.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
from Main.models import Progress
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class TaskRuntimeView(BaseView):
|
||||||
|
view_file = 'task_runtime.html'
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
progress = Progress.objects.get(task=self.entities.task, user=self.request.user)
|
||||||
|
self.context['progress'] = progress
|
||||||
|
if 'render' in self.request.GET.keys():
|
||||||
|
return
|
||||||
|
if progress.finished:
|
||||||
|
return HttpResponse('done')
|
@ -1,7 +1,7 @@
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from Main.models import ExtraFile
|
from Main.models import ExtraFile, Progress
|
||||||
from SprintLib.BaseView import BaseView, AccessError
|
from SprintLib.BaseView import BaseView, AccessError
|
||||||
|
|
||||||
|
|
||||||
@ -12,6 +12,10 @@ class TaskSettingsView(BaseView):
|
|||||||
def pre_handle(self):
|
def pre_handle(self):
|
||||||
if self.entities.task not in self.request.user.userinfo.available_tasks:
|
if self.entities.task not in self.request.user.userinfo.available_tasks:
|
||||||
raise AccessError()
|
raise AccessError()
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
for progress in Progress.objects.filter(task=self.entities.task, finished=False):
|
||||||
|
progress.start_time = timezone.now()
|
||||||
|
progress.save()
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
self.context['error_message'] = self.request.GET.get('error_message', '')
|
self.context['error_message'] = self.request.GET.get('error_message', '')
|
||||||
@ -29,8 +33,10 @@ class TaskSettingsView(BaseView):
|
|||||||
name = filename.strip('.a')
|
name = filename.strip('.a')
|
||||||
if not name.isnumeric():
|
if not name.isnumeric():
|
||||||
return f'/admin/task?task_id={self.entities.task.id}&error_message=Формат файла не соответствует тесту'
|
return f'/admin/task?task_id={self.entities.task.id}&error_message=Формат файла не соответствует тесту'
|
||||||
ef, created = ExtraFile.objects.get_or_create(task=self.entities.task, is_test=True, test_number=int(name))
|
ef, created = ExtraFile.objects.get_or_create(task=self.entities.task, is_test=True, filename=filename)
|
||||||
if not created:
|
if not created:
|
||||||
|
ef.is_sample = False
|
||||||
|
ef.save()
|
||||||
return f'/admin/task?task_id={self.entities.task.id}'
|
return f'/admin/task?task_id={self.entities.task.id}'
|
||||||
if ef is None or created is None:
|
if ef is None or created is None:
|
||||||
ef, created = ExtraFile.objects.get_or_create(
|
ef, created = ExtraFile.objects.get_or_create(
|
||||||
@ -78,3 +84,11 @@ class TaskSettingsView(BaseView):
|
|||||||
|
|
||||||
def post_create_test(self):
|
def post_create_test(self):
|
||||||
return self._create(True)
|
return self._create(True)
|
||||||
|
|
||||||
|
def post_save_test(self):
|
||||||
|
ef = ExtraFile.objects.get(id=self.request.POST['test_id'])
|
||||||
|
with open(ef.path, 'w') as fs:
|
||||||
|
fs.write(self.request.POST['text'])
|
||||||
|
ef.is_sample = 'is_sample' in self.request.POST.keys()
|
||||||
|
ef.save()
|
||||||
|
return f'/admin/task?task_id={self.entities.task.id}'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from Main.models import Solution
|
from Main.models import Solution, Progress
|
||||||
from Main.tasks import start_testing
|
from Main.tasks import start_testing
|
||||||
from SprintLib.BaseView import BaseView, Language
|
from SprintLib.BaseView import BaseView, Language
|
||||||
from SprintLib.testers import *
|
from SprintLib.testers import *
|
||||||
@ -12,6 +12,8 @@ class TaskView(BaseView):
|
|||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
self.context['languages'] = Language.objects.filter(opened=True).order_by('name')
|
self.context['languages'] = Language.objects.filter(opened=True).order_by('name')
|
||||||
|
progress, _ = Progress.objects.get_or_create(user=self.request.user, task=self.entities.task)
|
||||||
|
self.context['progress'] = progress
|
||||||
|
|
||||||
def pre_handle(self):
|
def pre_handle(self):
|
||||||
if self.request.method == 'GET':
|
if self.request.method == 'GET':
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from Main.models import Task
|
from Main.models import Task
|
||||||
from SprintLib.BaseView import BaseView
|
from SprintLib.BaseView import BaseView
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
|
|
||||||
class TasksView(BaseView):
|
class TasksView(BaseView):
|
||||||
|
@ -9,3 +9,5 @@ from Main.views.RatingView import RatingView
|
|||||||
from Main.views.SetsView import SetsView
|
from Main.views.SetsView import SetsView
|
||||||
from Main.views.TaskView import TaskView
|
from Main.views.TaskView import TaskView
|
||||||
from Main.views.SolutionsTableView import SolutionsTableView
|
from Main.views.SolutionsTableView import SolutionsTableView
|
||||||
|
from Main.views.TaskRuntimeView import TaskRuntimeView
|
||||||
|
from Main.views.SolutionView import SolutionView
|
||||||
|
@ -14,7 +14,9 @@ urlpatterns = [
|
|||||||
path("admin/task", TaskSettingsView.as_view()),
|
path("admin/task", TaskSettingsView.as_view()),
|
||||||
path("sets", SetsView.as_view()),
|
path("sets", SetsView.as_view()),
|
||||||
path("task", TaskView.as_view()),
|
path("task", TaskView.as_view()),
|
||||||
|
path("solution", SolutionView.as_view()),
|
||||||
path("solutions_table", SolutionsTableView.as_view()),
|
path("solutions_table", SolutionsTableView.as_view()),
|
||||||
|
path("task_runtime", TaskRuntimeView.as_view()),
|
||||||
path("", MainView.as_view()),
|
path("", MainView.as_view()),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDaemon:
|
class BaseDaemon:
|
||||||
|
@ -4,8 +4,10 @@ from shutil import copyfile, rmtree
|
|||||||
from subprocess import call, TimeoutExpired
|
from subprocess import call, TimeoutExpired
|
||||||
|
|
||||||
from Main.models import ExtraFile
|
from Main.models import ExtraFile
|
||||||
|
from Main.models.progress import Progress
|
||||||
from Sprint.settings import CONSTS
|
from Sprint.settings import CONSTS
|
||||||
from SprintLib.utils import copy_content
|
from SprintLib.utils import copy_content
|
||||||
|
from bot import bot
|
||||||
|
|
||||||
|
|
||||||
class TestException(Exception):
|
class TestException(Exception):
|
||||||
@ -68,6 +70,12 @@ class BaseTester:
|
|||||||
self.test(test.filename)
|
self.test(test.filename)
|
||||||
self.after_test()
|
self.after_test()
|
||||||
self.solution.result = CONSTS["ok_status"]
|
self.solution.result = CONSTS["ok_status"]
|
||||||
|
progress = Progress.objects.get(user=self.solution.user, task=self.solution.task)
|
||||||
|
if progress.finished_time is None:
|
||||||
|
progress.finished_time = self.solution.time_sent
|
||||||
|
progress.finished = True
|
||||||
|
progress.save()
|
||||||
|
progress.increment_rating()
|
||||||
except TestException as e:
|
except TestException as e:
|
||||||
self.solution.result = str(e)
|
self.solution.result = str(e)
|
||||||
except TimeoutExpired:
|
except TimeoutExpired:
|
||||||
@ -78,3 +86,11 @@ class BaseTester:
|
|||||||
self.solution.save()
|
self.solution.save()
|
||||||
call(f"docker rm --force solution_{self.solution.id}", shell=True)
|
call(f"docker rm --force solution_{self.solution.id}", shell=True)
|
||||||
rmtree(self.solution.testing_directory)
|
rmtree(self.solution.testing_directory)
|
||||||
|
self.solution.user.userinfo.refresh_from_db()
|
||||||
|
if self.solution.user.userinfo.notification_solution_result:
|
||||||
|
bot.send_message(self.solution.user.userinfo.telegram_chat_id,
|
||||||
|
f'Задача: {self.solution.task.name}\n'
|
||||||
|
f'Результат: {self.solution.result}\n'
|
||||||
|
f'Очки решения: {Progress.by_solution(self.solution).score}\n'
|
||||||
|
f'Текущий рейтинг: {self.solution.user.userinfo.rating}',
|
||||||
|
parse_mode='html')
|
||||||
|
16
bot.py
Normal file
16
bot.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import telebot
|
||||||
|
|
||||||
|
|
||||||
|
bot = telebot.TeleBot("1994460106:AAGrGsCZjF6DVG_T-zycELuVfxnWw8x7UyU")
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(commands=["start"])
|
||||||
|
@bot.message_handler(content_types=["text"])
|
||||||
|
def do_action(message):
|
||||||
|
bot.send_message(chat_id=message.chat.id, text=f"ID чата: {message.chat.id}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('bot is starting')
|
||||||
|
bot.polling()
|
||||||
|
print('bot failed')
|
6
daemons/bot.py
Normal file
6
daemons/bot.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from SprintLib.BaseDaemon import BaseDaemon
|
||||||
|
|
||||||
|
|
||||||
|
class Daemon(BaseDaemon):
|
||||||
|
def command(self):
|
||||||
|
return "python bot.py"
|
@ -3,4 +3,4 @@ from SprintLib.BaseDaemon import BaseDaemon
|
|||||||
|
|
||||||
class Daemon(BaseDaemon):
|
class Daemon(BaseDaemon):
|
||||||
def command(self):
|
def command(self):
|
||||||
return "python manage.py runserver"
|
return "python manage.py runserver 0.0.0.0:80"
|
||||||
|
@ -87,4 +87,25 @@
|
|||||||
<button type="submit" class="btn btn-light">Сменить пароль</button>
|
<button type="submit" class="btn btn-light">Сменить пароль</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if owner %}
|
||||||
|
<hr><hr>
|
||||||
|
<h2>Уведомления</h2>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="notifications">
|
||||||
|
<input type="text" name="chat_id" value="{{ user.userinfo.telegram_chat_id }}" placeholder="telegram chat id"> <a class="btn btn-link" target="_blank" rel="noopener noreferrer" href="https://t.me/sprint_notifications_bot">Бот</a>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 200px;">
|
||||||
|
Результаты решений
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="notification_solution_result" {% if user.userinfo.notification_solution_result %}checked{% endif %}>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button type="submit" class="btn btn-light" style="margin-top: 15px;"><i class="fa fa-save"></i> Сохранить</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -24,6 +24,10 @@
|
|||||||
<script type="text/javascript" id="MathJax-script" async
|
<script type="text/javascript" id="MathJax-script" async
|
||||||
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js">
|
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js">
|
||||||
</script>
|
</script>
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.2.0/build/styles/default.min.css">
|
||||||
|
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.2.0/build/highlight.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
<script type="text/javascript" src={% static "js/scripts.js" %}></script>
|
<script type="text/javascript" src={% static "js/scripts.js" %}></script>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.center {
|
.center {
|
||||||
@ -65,6 +69,8 @@
|
|||||||
}
|
}
|
||||||
.button-right {
|
.button-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
right:10%;
|
||||||
}
|
}
|
||||||
.task-settings-input {
|
.task-settings-input {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
@ -74,9 +80,10 @@
|
|||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 700px){
|
@media screen and (max-width: 1200px){
|
||||||
.header-button {
|
.header-button {
|
||||||
width:50px;
|
width:140px;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{% block styles %}{% endblock %}
|
{% block styles %}{% endblock %}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
onclick="window.location.href='/rating'">
|
onclick="window.location.href='/rating'">
|
||||||
<i class="fa fa-arrow-up"></i> Рейтинг
|
<i class="fa fa-arrow-up"></i> Рейтинг
|
||||||
</button>
|
</button>
|
||||||
<div class="button-right" style="margin-top: -35px;">
|
<div class="button-right">
|
||||||
<button class="btn btn-light header-button"
|
<button class="btn btn-light header-button"
|
||||||
onclick="window.location.href='/account'">
|
onclick="window.location.href='/account'">
|
||||||
<i class="fa fa-user"></i> Аккаунт
|
<i class="fa fa-user"></i> Аккаунт
|
||||||
|
46
templates/solution.html
Normal file
46
templates/solution.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends 'base_main.html' %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h4>
|
||||||
|
<table class="table" style="width: 30%;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Id решения
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ solution.id }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Задача
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/task?task_id={{ solution.task.id }}">{{ solution.task.name }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Язык
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img src="{{ solution.language.logo.url }}" width="30px" height="30px">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Результат
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-{% if solution.result == in_queue_status %}secondary{% else %}{% if solution.result == ok_status %}success{% else %}{% if solution.result == testing_status %}info{% else %}danger{% endif %}{% endif %}{% endif %}">{% if solution.result == testing_status %}<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="circle-notch" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width: 20px;" class="svg-inline--fa fa-circle-notch fa-w-16 fa-spin fa-lg"><path fill="currentColor" d="M288 39.056v16.659c0 10.804 7.281 20.159 17.686 23.066C383.204 100.434 440 171.518 440 256c0 101.689-82.295 184-184 184-101.689 0-184-82.295-184-184 0-84.47 56.786-155.564 134.312-177.219C216.719 75.874 224 66.517 224 55.712V39.064c0-15.709-14.834-27.153-30.046-23.234C86.603 43.482 7.394 141.206 8.003 257.332c.72 137.052 111.477 246.956 248.531 246.667C393.255 503.711 504 392.788 504 256c0-115.633-79.14-212.779-186.211-240.236C302.678 11.889 288 23.456 288 39.056z" class=""></path></svg> {% endif %}{{ solution.result }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</h4>
|
||||||
|
<h4>Файлы решения</h4>
|
||||||
|
{% for entity in solution.files %}
|
||||||
|
<h5>{{ entity.filename }}</h5>
|
||||||
|
<pre><code class="{{ entity.highlight }}" style="border: 1px solid black;">{{ entity.text }}</code></pre>
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
@ -1,7 +1,7 @@
|
|||||||
{% for solution in solutions %}
|
{% for solution in solutions %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<b><a href="">{{ solution.id }}</a></b>
|
<b><a href="/solution?solution_id={{ solution.id }}">{{ solution.id }}</a></b>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ solution.time_sent }}
|
{{ solution.time_sent }}
|
||||||
|
@ -19,24 +19,31 @@
|
|||||||
}
|
}
|
||||||
function doPoll() {
|
function doPoll() {
|
||||||
jQuery.get('/solutions_table?task_id={{ task.id }}', function(data) {
|
jQuery.get('/solutions_table?task_id={{ task.id }}', function(data) {
|
||||||
if (data == 'done') {
|
jQuery.get('/task_runtime?task_id={{ task.id }}', function(data1) {
|
||||||
return
|
if (data == 'done' && data1 == 'done')
|
||||||
}
|
return
|
||||||
else {
|
if (data != 'done') {
|
||||||
document.getElementById('solutions').innerHTML = data;
|
document.getElementById('solutions').innerHTML = data;
|
||||||
|
}
|
||||||
|
if (data1 != 'done') {
|
||||||
|
document.getElementById('runtime').innerHTML = data1;
|
||||||
|
}
|
||||||
setTimeout(function() {doPoll()}, 2000);
|
setTimeout(function() {doPoll()}, 2000);
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
jQuery.get('/solutions_table?task_id={{ task.id }}&render=true', function(data) {
|
jQuery.get('/solutions_table?id={{ task.id }}&render=true', function(data) {
|
||||||
document.getElementById('solutions').innerHTML = data;
|
document.getElementById('solutions').innerHTML = data;
|
||||||
})
|
})
|
||||||
|
jQuery.get('/task_runtime?id={{ task.id }}&render=true', function(data) {
|
||||||
|
document.getElementById('runtime').innerHTML = data;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block onload %}doPoll(){% endblock %}
|
{% block onload %}doPoll(){% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h2>{{ task.name }}</h2>
|
<div id="runtime"></div>
|
||||||
{% if task.legend %}
|
{% if task.legend %}
|
||||||
<h4>Легенда</h4>
|
<h4>Легенда</h4>
|
||||||
{% autoescape off %}
|
{% autoescape off %}
|
||||||
@ -64,6 +71,40 @@
|
|||||||
{{ task.specifications }}
|
{{ task.specifications }}
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
<hr>
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
{% if task.samples %}
|
||||||
|
<h4 style="">Примеры</h4>
|
||||||
|
{% for sample in task.samples %}
|
||||||
|
<h5>Пример {{ sample.num }}</h5>
|
||||||
|
<b>
|
||||||
|
<table style="width: 100%">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Входные данные
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
Выходные данные
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</b>
|
||||||
|
<hr>
|
||||||
|
<table style="width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 50%; vertical-align: top;">
|
||||||
|
<pre>
|
||||||
|
{{ sample.input }}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
<td style="width: 50%; vertical-align: top;">
|
||||||
|
<pre>
|
||||||
|
{{ sample.output }}
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>Отправить решение</h2>
|
<h2>Отправить решение</h2>
|
||||||
<table style="margin-bottom: 10px;">
|
<table style="margin-bottom: 10px;">
|
||||||
|
1
templates/task_runtime.html
Normal file
1
templates/task_runtime.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h2>{{ task.name }}<span style="margin-left: 15px;" class="badge badge-{% if progress.finished %}success{% else %}danger{% endif %}">{{ progress.time }}</span></h2>
|
@ -74,6 +74,14 @@
|
|||||||
<input type="text" name="time_limit" value="{{ task.time_limit }}" class="task-settings-input">
|
<input type="text" name="time_limit" value="{{ task.time_limit }}" class="task-settings-input">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Оценка времени решения (мин)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="time_estimation" value="{{ task.time_estimation }}" class="task-settings-input">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<button type="submit" class="btn btn-light" style="margin-top: 15px;"><i class="fa fa-save"></i> Сохранить</button>
|
<button type="submit" class="btn btn-light" style="margin-top: 15px;"><i class="fa fa-save"></i> Сохранить</button>
|
||||||
</form>
|
</form>
|
||||||
@ -93,8 +101,40 @@
|
|||||||
<td>
|
<td>
|
||||||
{% for test in task.tests %}
|
{% for test in task.tests %}
|
||||||
<div id="file_{{ test.id }}">
|
<div id="file_{{ test.id }}">
|
||||||
<i class="fa fa-file"></i> <button class="btn btn-link" {% if not test.readable %}style="color: red;"{% endif %}>{{ test.filename }}</button><button class="btn btn-link" style="color: black;" onclick="deleteFile({{ test.id }});"><i class="fa fa-times"></i> </button><br>
|
<i class="fa fa-file"></i> <button class="btn btn-link" {% if not test.readable %}style="color: red;" {% else %}data-toggle="modal" data-target="#filesModalLong{{ test.id }}"{% endif %}>{{ test.filename }}</button><button class="btn btn-link" style="color: black;" onclick="deleteFile({{ test.id }});"><i class="fa fa-times"></i> </button><br>
|
||||||
|
{% if test.readable %}
|
||||||
|
<form method="POST">{% csrf_token %}
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade bd-example-modal-lg" id="filesModalLong{{ test.id }}" tabindex="-1" role="dialog" aria-labelledby="filesModalLongTitle{{ test.id }}" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="filesModalLongTitle{{ test.id }}">{{ test.filename }}</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<textarea cols="82" rows="30" name="text">{{ test.text }}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
{% if test.can_be_sample %}Использовать как пример <input type="checkbox" name="is_sample" {% if test.is_sample %}checked{% endif %}>{% endif %}
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal"><i class="fa fa-times"></i> Close</button>
|
||||||
|
<input name="action" value="save_test" type="hidden">
|
||||||
|
<input name="test_id" value="{{ test.id }}" type="hidden">
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="fa fa-save"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<input type="file" style="display: none;" form="form_test_upload" onchange="this.form.submit();" class="btn form-control-file" id="test-upload" value="Выбрать файл" name="file">
|
<input type="file" style="display: none;" form="form_test_upload" onchange="this.form.submit();" class="btn form-control-file" id="test-upload" value="Выбрать файл" name="file">
|
||||||
<label for="test-upload" class="btn btn-primary"><i class="fa fa-upload"></i> Загрузить тесты</label><button style="margin-left: 10px; margin-top: -8px;" class="btn btn-success" data-toggle="modal" data-target="#exampleModalLongnewtest" onclick="setActionCreate('create_test');"><i class="fa fa-plus"></i></button>
|
<label for="test-upload" class="btn btn-primary"><i class="fa fa-upload"></i> Загрузить тесты</label><button style="margin-left: 10px; margin-top: -8px;" class="btn btn-success" data-toggle="modal" data-target="#exampleModalLongnewtest" onclick="setActionCreate('create_test');"><i class="fa fa-plus"></i></button>
|
||||||
|
Loading…
Reference in New Issue
Block a user