checkpoint
This commit is contained in:
@@ -8,3 +8,4 @@ from Main.models.settask import SetTask
|
||||
from Main.models.solution import Solution
|
||||
from Main.models.language import Language
|
||||
from Main.models.extrafile import ExtraFile
|
||||
from Main.models.progress import Progress
|
||||
|
@@ -1,27 +1,50 @@
|
||||
from os import remove
|
||||
from os.path import join, exists
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
|
||||
from Sprint.settings import DATA_ROOT
|
||||
|
||||
|
||||
class ExtraFile(models.Model):
|
||||
task = models.ForeignKey('Task', on_delete=models.CASCADE)
|
||||
task = models.ForeignKey("Task", on_delete=models.CASCADE)
|
||||
filename = models.TextField()
|
||||
is_test = models.BooleanField(null=True)
|
||||
is_sample = models.BooleanField(null=True)
|
||||
readable = models.BooleanField(null=True)
|
||||
test_number = models.IntegerField(null=True)
|
||||
|
||||
@property
|
||||
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
|
||||
def text(self):
|
||||
return open(self.path, 'r').read()
|
||||
return open(self.path, "r").read()
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if exists(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)
|
||||
|
||||
@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)
|
||||
logo = models.ImageField(upload_to="logos", null=True)
|
||||
image = models.TextField(default='ubuntu')
|
||||
highlight = models.TextField(default='plaintext')
|
||||
opened = models.BooleanField(default=False)
|
||||
|
||||
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.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Set(models.Model):
|
||||
name = models.TextField()
|
||||
public = models.BooleanField(default=False)
|
||||
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 shutil import rmtree
|
||||
from subprocess import call
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
@@ -24,6 +25,30 @@ class Solution(models.Model):
|
||||
rmtree(self.directory)
|
||||
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):
|
||||
mkdir(self.directory)
|
||||
mkdir(self.testing_directory)
|
||||
|
@@ -12,6 +12,7 @@ class Task(models.Model):
|
||||
output_format = models.TextField(default="")
|
||||
specifications = models.TextField(default="")
|
||||
time_limit = models.IntegerField(default=10000)
|
||||
time_estimation = models.IntegerField(default=5)
|
||||
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||
|
||||
def __str__(self):
|
||||
@@ -24,3 +25,25 @@ class Task(models.Model):
|
||||
@property
|
||||
def tests(self):
|
||||
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)
|
||||
rating = models.IntegerField(default=0)
|
||||
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):
|
||||
if task.creator == self.user or task.public:
|
||||
|
@@ -38,3 +38,11 @@ class AccountView(BaseView):
|
||||
self.request.user.save()
|
||||
login(self.request, self.request.user)
|
||||
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", "")
|
||||
|
||||
def post(self):
|
||||
data = {**self.request.POST}
|
||||
data["password"] = data["password"].strip()
|
||||
data = self.request.POST
|
||||
if len(data["password"]) < 8:
|
||||
return "/register?error_message=Пароль слишком слабый"
|
||||
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.utils import timezone
|
||||
|
||||
from Main.models import ExtraFile
|
||||
from Main.models import ExtraFile, Progress
|
||||
from SprintLib.BaseView import BaseView, AccessError
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ class TaskSettingsView(BaseView):
|
||||
def pre_handle(self):
|
||||
if self.entities.task not in self.request.user.userinfo.available_tasks:
|
||||
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):
|
||||
self.context['error_message'] = self.request.GET.get('error_message', '')
|
||||
@@ -29,8 +33,10 @@ class TaskSettingsView(BaseView):
|
||||
name = filename.strip('.a')
|
||||
if not name.isnumeric():
|
||||
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:
|
||||
ef.is_sample = False
|
||||
ef.save()
|
||||
return f'/admin/task?task_id={self.entities.task.id}'
|
||||
if ef is None or created is None:
|
||||
ef, created = ExtraFile.objects.get_or_create(
|
||||
@@ -78,3 +84,11 @@ class TaskSettingsView(BaseView):
|
||||
|
||||
def post_create_test(self):
|
||||
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 Main.models import Solution
|
||||
from Main.models import Solution, Progress
|
||||
from Main.tasks import start_testing
|
||||
from SprintLib.BaseView import BaseView, Language
|
||||
from SprintLib.testers import *
|
||||
@@ -12,6 +12,8 @@ class TaskView(BaseView):
|
||||
|
||||
def get(self):
|
||||
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):
|
||||
if self.request.method == 'GET':
|
||||
|
@@ -1,6 +1,5 @@
|
||||
from Main.models import Task
|
||||
from SprintLib.BaseView import BaseView
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
class TasksView(BaseView):
|
||||
|
@@ -9,3 +9,5 @@ from Main.views.RatingView import RatingView
|
||||
from Main.views.SetsView import SetsView
|
||||
from Main.views.TaskView import TaskView
|
||||
from Main.views.SolutionsTableView import SolutionsTableView
|
||||
from Main.views.TaskRuntimeView import TaskRuntimeView
|
||||
from Main.views.SolutionView import SolutionView
|
||||
|
Reference in New Issue
Block a user