registration implemented
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM python:3.8
|
||||
FROM python:3.7
|
||||
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV DJANGO_SETTINGS_MODULE Sprint.settings
|
||||
|
218
Main/Tester.py
218
Main/Tester.py
@@ -9,11 +9,16 @@ from .main import solution_path
|
||||
|
||||
|
||||
def start_new(host):
|
||||
in_queue = list(Solution.objects.filter(result='IN QUEUE'))
|
||||
in_queue = list(Solution.objects.filter(result="IN QUEUE"))
|
||||
if in_queue:
|
||||
sol = in_queue[0]
|
||||
for s in in_queue:
|
||||
dif = s.task.block.priority * 10 + s.task.priority - sol.task.block.priority * 10 - sol.task.priority
|
||||
dif = (
|
||||
s.task.block.priority * 10
|
||||
+ s.task.priority
|
||||
- sol.task.block.priority * 10
|
||||
- sol.task.priority
|
||||
)
|
||||
if dif > 0:
|
||||
sol = s
|
||||
elif dif == 0 and s.id < sol.id:
|
||||
@@ -22,7 +27,7 @@ def start_new(host):
|
||||
|
||||
|
||||
def is_project(path):
|
||||
return any([x.endswith('.csproj') for x in listdir(path)])
|
||||
return any([x.endswith(".csproj") for x in listdir(path)])
|
||||
|
||||
|
||||
def get_node_value(element):
|
||||
@@ -30,15 +35,16 @@ def get_node_value(element):
|
||||
|
||||
|
||||
def nunit_path(working_dir):
|
||||
return '..{}'.format(sep) * len(working_dir.split(sep)) + 'nunit_console{}nunit3-console.exe'.format(sep)
|
||||
return "..{}".format(sep) * len(
|
||||
working_dir.split(sep)
|
||||
) + "nunit_console{}nunit3-console.exe".format(sep)
|
||||
|
||||
|
||||
class Tester:
|
||||
|
||||
def __init__(self, solution, host):
|
||||
self.solution = solution
|
||||
self.host = host
|
||||
self.working_dir = ''
|
||||
self.working_dir = ""
|
||||
self.files = []
|
||||
|
||||
# функция компиляции
|
||||
@@ -47,24 +53,25 @@ class Tester:
|
||||
# shell('msbuild ' + path + ' /p:Configuration=Debug')
|
||||
|
||||
# решение для Windows
|
||||
cmd = 'dotnet build {} -o {}\\bin\\Debug'.format(path, path)
|
||||
cmd = "dotnet build {} -o {}\\bin\\Debug".format(path, path)
|
||||
with self.solution.log_fs as fs:
|
||||
shell(cmd, fs)
|
||||
|
||||
def build_and_copy(self, path, working_dir):
|
||||
if exists(join(path, 'bin', 'Debug')):
|
||||
rmtree(join(path, 'bin', 'Debug'))
|
||||
if exists(join(path, "bin", "Debug")):
|
||||
rmtree(join(path, "bin", "Debug"))
|
||||
self.build(path)
|
||||
name = basename(path)
|
||||
if not exists(join(path, 'bin', 'Debug')) or not any(
|
||||
x.endswith('.exe') for x in listdir(join(path, 'bin', 'Debug'))):
|
||||
if not exists(join(path, "bin", "Debug")) or not any(
|
||||
x.endswith(".exe") for x in listdir(join(path, "bin", "Debug"))
|
||||
):
|
||||
return False
|
||||
self.files.append(basename(path))
|
||||
for file in listdir(join(path, 'bin', 'Debug')):
|
||||
if exists(join(path, 'bin', 'Debug', file)):
|
||||
for file in listdir(join(path, "bin", "Debug")):
|
||||
if exists(join(path, "bin", "Debug", file)):
|
||||
new_file = join(working_dir, basename(file))
|
||||
try:
|
||||
copyfile(join(path, 'bin', 'Debug', file), new_file)
|
||||
copyfile(join(path, "bin", "Debug", file), new_file)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
@@ -73,110 +80,132 @@ class Tester:
|
||||
|
||||
def push(self):
|
||||
solution = self.solution
|
||||
if solution.result == 'SOLUTION ERROR':
|
||||
if solution.result == "SOLUTION ERROR":
|
||||
return
|
||||
solution.result = 'IN QUEUE'
|
||||
solution.result = "IN QUEUE"
|
||||
solution.save()
|
||||
from Main.models import System
|
||||
if len(Solution.objects.filter(result='TESTING')) < int(System.objects.get(key='queue_size').value):
|
||||
|
||||
if len(Solution.objects.filter(result="TESTING")) < int(
|
||||
System.objects.get(key="queue_size").value
|
||||
):
|
||||
self.test()
|
||||
|
||||
def delete_everything(self):
|
||||
ssp = solution_path(self.solution.path())
|
||||
sln_path = join(ssp, '.idea')
|
||||
sln_path = join(ssp, ".idea")
|
||||
if exists(sln_path):
|
||||
rmtree(sln_path)
|
||||
sln_path = join(ssp, '.vs')
|
||||
sln_path = join(ssp, ".vs")
|
||||
if exists(sln_path):
|
||||
rmtree(sln_path)
|
||||
sln_path = ssp
|
||||
for p in listdir(sln_path):
|
||||
if isdir(join(sln_path, p)):
|
||||
if exists(join(sln_path, p, 'bin')):
|
||||
rmtree(join(sln_path, p, 'bin'))
|
||||
if exists(join(sln_path, p, 'obj')):
|
||||
rmtree(join(sln_path, p, 'obj'))
|
||||
if exists(join(sln_path, p, "bin")):
|
||||
rmtree(join(sln_path, p, "bin"))
|
||||
if exists(join(sln_path, p, "obj")):
|
||||
rmtree(join(sln_path, p, "obj"))
|
||||
if exists(self.working_dir):
|
||||
rmtree(self.working_dir)
|
||||
if exists(join(self.solution.path(), 'solution.zip')):
|
||||
remove(join(self.solution.path(), 'solution.zip'))
|
||||
if exists(join(self.solution.path(), '__MACOSX')):
|
||||
rmtree(join(self.solution.path(), '__MACOSX'))
|
||||
if exists(join(sln_path, '.DS_Store')):
|
||||
remove(join(sln_path, '.DS_Store'))
|
||||
if exists(join(sln_path, 'test_folder')):
|
||||
rmtree(join(sln_path, 'test_folder'))
|
||||
if exists(join(self.solution.path(), "solution.zip")):
|
||||
remove(join(self.solution.path(), "solution.zip"))
|
||||
if exists(join(self.solution.path(), "__MACOSX")):
|
||||
rmtree(join(self.solution.path(), "__MACOSX"))
|
||||
if exists(join(sln_path, ".DS_Store")):
|
||||
remove(join(sln_path, ".DS_Store"))
|
||||
if exists(join(sln_path, "test_folder")):
|
||||
rmtree(join(sln_path, "test_folder"))
|
||||
|
||||
def nunit_testing(self):
|
||||
solution = self.solution
|
||||
with self.solution.log_fs as fs:
|
||||
fs.write(b'Building image\n')
|
||||
shell('docker build -t solution_{} {}'.format(self.solution.id, self.working_dir))
|
||||
fs.write(b"Building image\n")
|
||||
shell(
|
||||
"docker build -t solution_{} {}".format(self.solution.id, self.working_dir)
|
||||
)
|
||||
with self.solution.log_fs as fs:
|
||||
fs.write(b'Image built successfully\n')
|
||||
fs.write(b"Image built successfully\n")
|
||||
|
||||
def execute():
|
||||
with self.solution.log_fs as fs:
|
||||
shell('docker run --name solution_container_{} solution_{}'.format(self.solution.id, self.solution.id),
|
||||
output=fs)
|
||||
shell(
|
||||
"docker run --name solution_container_{} solution_{}".format(
|
||||
self.solution.id, self.solution.id
|
||||
),
|
||||
output=fs,
|
||||
)
|
||||
|
||||
solution.write_log('Running container')
|
||||
solution.write_log("Running container")
|
||||
t = Thread(target=execute)
|
||||
t.start()
|
||||
t.join(self.solution.task.time_limit / 1000)
|
||||
solution.write_log('Running finished')
|
||||
solution.write_log("Running finished")
|
||||
with self.solution.log_fs as fs:
|
||||
shell('docker cp solution_container_{}:/app/TestResults.xml {}'.format(self.solution.id, self.working_dir),
|
||||
fs)
|
||||
shell(
|
||||
"docker cp solution_container_{}:/app/TestResults.xml {}".format(
|
||||
self.solution.id, self.working_dir
|
||||
),
|
||||
fs,
|
||||
)
|
||||
with self.solution.log_fs as fs:
|
||||
shell('docker rm --force solution_container_{}'.format(self.solution.id), fs)
|
||||
shell(
|
||||
"docker rm --force solution_container_{}".format(self.solution.id), fs
|
||||
)
|
||||
with self.solution.log_fs as fs:
|
||||
shell('docker image rm solution_{}'.format(self.solution.id), fs)
|
||||
if not exists(join(self.working_dir, 'TestResults.xml')):
|
||||
self.solution.set_result('Time limit')
|
||||
solution.write_log('Result file not found in container')
|
||||
shell("docker image rm solution_{}".format(self.solution.id), fs)
|
||||
if not exists(join(self.working_dir, "TestResults.xml")):
|
||||
self.solution.set_result("Time limit")
|
||||
solution.write_log("Result file not found in container")
|
||||
return
|
||||
solution.write_log('Result file found in container')
|
||||
solution.write_log("Result file found in container")
|
||||
try:
|
||||
doc = parse(join(self.working_dir, 'TestResults.xml'))
|
||||
res = get_node_value(doc.getElementsByTagName('Passed')) + '/' + get_node_value(
|
||||
doc.getElementsByTagName('Total'))
|
||||
self.solution.details = ''
|
||||
for el in doc.getElementsByTagName('Result'):
|
||||
self.solution.details += '<h5><b>' + get_node_value(el.getElementsByTagName('MethodName')) + '</b></h5>'
|
||||
r = get_node_value(el.getElementsByTagName('Successful'))
|
||||
if r == 'true':
|
||||
doc = parse(join(self.working_dir, "TestResults.xml"))
|
||||
res = (
|
||||
get_node_value(doc.getElementsByTagName("Passed"))
|
||||
+ "/"
|
||||
+ get_node_value(doc.getElementsByTagName("Total"))
|
||||
)
|
||||
self.solution.details = ""
|
||||
for el in doc.getElementsByTagName("Result"):
|
||||
self.solution.details += (
|
||||
"<h5><b>"
|
||||
+ get_node_value(el.getElementsByTagName("MethodName"))
|
||||
+ "</b></h5>"
|
||||
)
|
||||
r = get_node_value(el.getElementsByTagName("Successful"))
|
||||
if r == "true":
|
||||
self.solution.details += '<div style="color: green;">Passed</div>'
|
||||
else:
|
||||
self.solution.details += '<div style="color: red;">Failed</div>'
|
||||
mes = get_node_value(el.getElementsByTagName('Message'))
|
||||
self.solution.details += '<pre>{}</pre>'.format(mes)
|
||||
mes = get_node_value(el.getElementsByTagName("Message"))
|
||||
self.solution.details += "<pre>{}</pre>".format(mes)
|
||||
except:
|
||||
solution.write_log('Unknown error')
|
||||
res = 'TEST ERROR'
|
||||
solution.write_log("Unknown error")
|
||||
res = "TEST ERROR"
|
||||
self.solution.set_result(res)
|
||||
|
||||
def test(self):
|
||||
solution = self.solution
|
||||
solution.result = 'TESTING'
|
||||
solution.result = "TESTING"
|
||||
solution.save()
|
||||
try:
|
||||
if not exists(self.solution.task.tests_path()):
|
||||
with self.solution.log_fs as fs:
|
||||
fs.write(b'No test file found\n')
|
||||
solution.set_result('TEST ERROR')
|
||||
fs.write(b"No test file found\n")
|
||||
solution.set_result("TEST ERROR")
|
||||
solution.save()
|
||||
self.delete_everything()
|
||||
start_new(self.host)
|
||||
return
|
||||
sln_path = solution_path(join(MEDIA_ROOT, 'solutions', str(solution.id)))
|
||||
if sln_path == '':
|
||||
solution.set_result('TEST ERROR')
|
||||
sln_path = solution_path(join(MEDIA_ROOT, "solutions", str(solution.id)))
|
||||
if sln_path == "":
|
||||
solution.set_result("TEST ERROR")
|
||||
solution.save()
|
||||
self.delete_everything()
|
||||
start_new(self.host)
|
||||
return
|
||||
working_dir = join(sln_path, 'test_folder')
|
||||
working_dir = join(sln_path, "test_folder")
|
||||
if exists(working_dir):
|
||||
try:
|
||||
rmtree(working_dir)
|
||||
@@ -184,60 +213,63 @@ class Tester:
|
||||
remove(working_dir)
|
||||
mkdir(working_dir)
|
||||
with self.solution.log_fs as fs:
|
||||
fs.write(b'Testing directory created\n')
|
||||
fs.write(b"Testing directory created\n")
|
||||
for project in listdir(sln_path):
|
||||
solution.write_log('Checking if {} is project'.format(project))
|
||||
solution.write_log("Checking if {} is project".format(project))
|
||||
prj = project
|
||||
project = join(sln_path, project)
|
||||
if isdir(project) and is_project(project) and basename(project) != 'TestsProject':
|
||||
if (
|
||||
isdir(project)
|
||||
and is_project(project)
|
||||
and basename(project) != "TestsProject"
|
||||
):
|
||||
if not self.build_and_copy(project, working_dir):
|
||||
solution.set_result('Compilation error')
|
||||
solution.write_log('Failed to compile project {}'.format(prj))
|
||||
solution.set_result("Compilation error")
|
||||
solution.write_log("Failed to compile project {}".format(prj))
|
||||
solution.save()
|
||||
self.delete_everything()
|
||||
start_new(self.host)
|
||||
return
|
||||
dll_path = solution.task.tests_path()
|
||||
solution.write_log('Copying test file to working directory')
|
||||
copyfile(dll_path, join(working_dir, str(solution.task.id) + '.cs'))
|
||||
solution.write_log('Test file copied')
|
||||
for file in listdir('SprintTest'):
|
||||
solution.write_log("Copying test file to working directory")
|
||||
copyfile(dll_path, join(working_dir, str(solution.task.id) + ".cs"))
|
||||
solution.write_log("Test file copied")
|
||||
for file in listdir("SprintTest"):
|
||||
try:
|
||||
copyfile(join('SprintTest', file), join(working_dir, file))
|
||||
copyfile(join("SprintTest", file), join(working_dir, file))
|
||||
except:
|
||||
pass
|
||||
self.working_dir = working_dir
|
||||
build_tests_cmd = 'csc -out:{} -t:library /r:{} /r:{} /r:{} '.format(join(self.working_dir, 'tests.dll'),
|
||||
join(self.working_dir,
|
||||
'SprintTest.dll'),
|
||||
join(working_dir,
|
||||
'System.Runtime.dll'),
|
||||
join(working_dir,
|
||||
'System.Reflection.dll'))
|
||||
build_tests_cmd = "csc -out:{} -t:library /r:{} /r:{} /r:{} ".format(
|
||||
join(self.working_dir, "tests.dll"),
|
||||
join(self.working_dir, "SprintTest.dll"),
|
||||
join(working_dir, "System.Runtime.dll"),
|
||||
join(working_dir, "System.Reflection.dll"),
|
||||
)
|
||||
for file in self.files:
|
||||
build_tests_cmd += '/r:{}.dll '.format(join(self.working_dir, file))
|
||||
build_tests_cmd += "/r:{}.dll ".format(join(self.working_dir, file))
|
||||
build_tests_cmd += self.solution.task.tests_path()
|
||||
if exists(join(self.working_dir, 'tests.dll')):
|
||||
remove(join(self.working_dir, 'tests.dll'))
|
||||
solution.write_log('Building tests file started')
|
||||
if exists(join(self.working_dir, "tests.dll")):
|
||||
remove(join(self.working_dir, "tests.dll"))
|
||||
solution.write_log("Building tests file started")
|
||||
with self.solution.log_fs as fs:
|
||||
shell(build_tests_cmd, fs)
|
||||
with self.solution.log_fs as fs:
|
||||
fs.write(b'Building tests file finished\n')
|
||||
if exists(join(self.working_dir, 'tests.dll')):
|
||||
fs.write(b"Building tests file finished\n")
|
||||
if exists(join(self.working_dir, "tests.dll")):
|
||||
with self.solution.log_fs as fs:
|
||||
fs.write(b'Got .dll tests file\n')
|
||||
fs.write(b"Got .dll tests file\n")
|
||||
for file in ExtraFile.objects.filter(task=self.solution.task):
|
||||
copyfile(file.path, join(working_dir, file.filename))
|
||||
self.nunit_testing()
|
||||
else:
|
||||
solution.set_result('TEST ERROR')
|
||||
solution.write_log('Failed to compile tests')
|
||||
solution.set_result("TEST ERROR")
|
||||
solution.write_log("Failed to compile tests")
|
||||
except:
|
||||
solution.set_result('TEST ERROR')
|
||||
solution.set_result("TEST ERROR")
|
||||
raise
|
||||
with self.solution.log_fs as fs:
|
||||
fs.write(b'Unknown error\n')
|
||||
fs.write(b"Unknown error\n")
|
||||
solution.save()
|
||||
self.delete_everything()
|
||||
start_new(self.host)
|
||||
|
@@ -1,29 +1,28 @@
|
||||
class Method:
|
||||
def __init__(self, meth, name):
|
||||
self.meth = meth
|
||||
self.name = name
|
||||
|
||||
def __init__(self, meth, name):
|
||||
self.meth = meth
|
||||
self.name = name
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name
|
||||
|
||||
def execute(self):
|
||||
self.meth()
|
||||
def execute(self):
|
||||
self.meth()
|
||||
|
||||
|
||||
class Timer:
|
||||
methods = []
|
||||
methods = []
|
||||
|
||||
def push(self, meth):
|
||||
methods.append(math)
|
||||
def push(self, meth):
|
||||
methods.append(math)
|
||||
|
||||
def polling(self):
|
||||
for i in range(len(self.methods)):
|
||||
methods[i].execute()
|
||||
def polling(self):
|
||||
for i in range(len(self.methods)):
|
||||
methods[i].execute()
|
||||
|
||||
def remove(method_name):
|
||||
for method in self.methods:
|
||||
if method.name == method_name:
|
||||
self.methods.remove(method)
|
||||
return
|
||||
raise IndexError("No method in list")
|
||||
def remove(method_name):
|
||||
for method in self.methods:
|
||||
if method.name == method_name:
|
||||
self.methods.remove(method)
|
||||
return
|
||||
raise IndexError("No method in list")
|
||||
|
@@ -1,14 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from Main.models import *
|
||||
import Main.models
|
||||
|
||||
# Register your models here.
|
||||
|
||||
admin.site.register(Course)
|
||||
admin.site.register(Block)
|
||||
admin.site.register(Task)
|
||||
admin.site.register(Subscribe)
|
||||
admin.site.register(Restore)
|
||||
admin.site.register(UserInfo)
|
||||
admin.site.register(Solution)
|
||||
admin.site.register(System)
|
||||
admin.site.register(ExtraFile)
|
||||
for model in dir(Main.models):
|
||||
try:
|
||||
admin.site.register(eval("Main.models." + model))
|
||||
except:
|
||||
continue
|
||||
|
@@ -2,4 +2,5 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class MainConfig(AppConfig):
|
||||
name = 'Main'
|
||||
name = "Main"
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
|
@@ -1,8 +0,0 @@
|
||||
from subprocess import Popen
|
||||
from sys import stdout
|
||||
|
||||
|
||||
def shell(cmd, output=stdout):
|
||||
p = Popen(cmd, shell=True, stdout=output)
|
||||
p.wait()
|
||||
p.kill()
|
@@ -1,8 +1,5 @@
|
||||
from .main import check_admin
|
||||
from Sprint.settings import CONSTS
|
||||
|
||||
|
||||
def attributes(request):
|
||||
return {
|
||||
'current_page': 'settings' if '/settings' == request.path else 'admin' if '/admin/' in request.path else 'main',
|
||||
'is_admin': check_admin(request.user)
|
||||
}
|
||||
return CONSTS
|
||||
|
@@ -1,13 +0,0 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
|
||||
def login_required(boolean):
|
||||
def decorator(fun):
|
||||
def dec(request, *args, **kwargs):
|
||||
if request.user.is_authenticated != boolean:
|
||||
if boolean:
|
||||
return HttpResponseRedirect('/enter')
|
||||
return HttpResponseRedirect('/main')
|
||||
return fun(request, *args, **kwargs)
|
||||
return dec
|
||||
return decorator
|
@@ -2,7 +2,6 @@ from django import forms
|
||||
|
||||
|
||||
class PasswordField(forms.CharField):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.widget = forms.PasswordInput()
|
||||
@@ -10,11 +9,15 @@ class PasswordField(forms.CharField):
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
email = forms.EmailField(widget=forms.TextInput(attrs={"class": "input_simple"}))
|
||||
password = forms.CharField(widget=forms.PasswordInput(attrs={"class": "input_simple"}))
|
||||
password = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={"class": "input_simple"})
|
||||
)
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField(widget=forms.FileInput(attrs={'class': 'input_simple'}), required=False)
|
||||
file = forms.FileField(
|
||||
widget=forms.FileInput(attrs={"class": "input_simple"}), required=False
|
||||
)
|
||||
|
||||
|
||||
class TestsForm(forms.Form):
|
||||
@@ -22,11 +25,11 @@ class TestsForm(forms.Form):
|
||||
|
||||
|
||||
class ChangePasswordForm(forms.Form):
|
||||
old = PasswordField(label='Старый пароль')
|
||||
new = PasswordField(label='Новый пароль')
|
||||
again = PasswordField(label='Еще раз')
|
||||
old = PasswordField(label="Старый пароль")
|
||||
new = PasswordField(label="Новый пароль")
|
||||
again = PasswordField(label="Еще раз")
|
||||
|
||||
|
||||
class ResetPasswordForm(forms.Form):
|
||||
new = PasswordField(label='Новый пароль')
|
||||
again = PasswordField(label='Еще раз')
|
||||
new = PasswordField(label="Новый пароль")
|
||||
again = PasswordField(label="Еще раз")
|
||||
|
325
Main/main.py
325
Main/main.py
@@ -1,325 +0,0 @@
|
||||
import smtplib
|
||||
from contextlib import contextmanager
|
||||
from json import dumps
|
||||
from os import listdir, mkdir
|
||||
from os.path import isdir, basename, dirname, join, exists
|
||||
from random import choice
|
||||
from shutil import copyfile, rmtree
|
||||
from string import ascii_letters
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
import copydetect
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.transaction import atomic
|
||||
from django.utils import timezone
|
||||
from django.utils.datastructures import MultiValueDictKeyError
|
||||
|
||||
from Main.models import Course, Block, Solution, Restore, System, Subscribe, UserInfo
|
||||
from Sprint.settings import MEDIA_ROOT
|
||||
|
||||
|
||||
def get_in_html_tag(full, tag_name):
|
||||
try:
|
||||
return full.split('<div class="{}">'.format(tag_name))[1].split('</div>')[0]
|
||||
except IndexError:
|
||||
return ''
|
||||
|
||||
|
||||
def random_string():
|
||||
letters = ascii_letters
|
||||
return ''.join(choice(letters) for _ in range(20))
|
||||
|
||||
|
||||
def get_restore_hash():
|
||||
available = [r.code for r in Restore.objects.all()]
|
||||
while True:
|
||||
s = random_string()
|
||||
if s not in available:
|
||||
break
|
||||
return s
|
||||
|
||||
|
||||
def send(subject, to_addr, body_text):
|
||||
from_addr = System.objects.get(key='email_address').value
|
||||
body = "\r\n".join((
|
||||
"From: %s" % from_addr,
|
||||
"To: %s" % to_addr,
|
||||
"Subject: %s" % subject,
|
||||
"",
|
||||
body_text
|
||||
))
|
||||
|
||||
server = smtplib.SMTP('SMTP.Office365.com', 587)
|
||||
server.starttls()
|
||||
server.login(System.objects.get(key='email_address').value, System.objects.get(key='email_password').value)
|
||||
server.sendmail(from_addr, [to_addr], body)
|
||||
server.quit()
|
||||
|
||||
|
||||
def send_email(subject, to_addr, body_text):
|
||||
Thread(target=lambda: send(subject, to_addr, body_text)).start()
|
||||
|
||||
|
||||
def check_login(user):
|
||||
return user.is_authenticated
|
||||
|
||||
|
||||
def check_admin(user):
|
||||
if check_teacher(user):
|
||||
return True
|
||||
if not check_login(user):
|
||||
return False
|
||||
return len(Subscribe.objects.filter(user=user, is_assistant=True)) > 0
|
||||
|
||||
|
||||
def check_teacher(user):
|
||||
return user.is_staff and check_login(user)
|
||||
|
||||
|
||||
def check_god(user):
|
||||
return user.is_superuser and check_login(user)
|
||||
|
||||
|
||||
def courses_available(user):
|
||||
if user.is_superuser:
|
||||
return Course.objects.all()
|
||||
else:
|
||||
return [s.course for s in Subscribe.objects.filter(user=user)]
|
||||
|
||||
|
||||
def blocks_available(user):
|
||||
courses = courses_available(user)
|
||||
blocks = {}
|
||||
is_admin = check_admin(user)
|
||||
for course in courses:
|
||||
if is_admin:
|
||||
blocks[course] = Block.objects.filter(
|
||||
course=course
|
||||
)
|
||||
else:
|
||||
blocks[course] = Block.objects.filter(
|
||||
opened=True,
|
||||
time_start__lte=timezone.now(),
|
||||
course=course
|
||||
)
|
||||
return blocks
|
||||
|
||||
|
||||
def can_send_solution(user, task):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
try:
|
||||
s = Subscribe.objects.get(course=task.block.course, user=user)
|
||||
except ObjectDoesNotExist:
|
||||
return False
|
||||
if s.is_assistant:
|
||||
return True
|
||||
return task.block.time_start <= timezone.now() <= task.block.time_end and task.max_solutions_count > len(Solution.objects.filter(user=user, task=task)) and task.block.opened
|
||||
|
||||
|
||||
def check_permission_block(user, block):
|
||||
blocks = blocks_available(user)
|
||||
for course in blocks.keys():
|
||||
if block in blocks[course]:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_integer(x):
|
||||
try:
|
||||
int(x)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def check_admin_on_course(user, course):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
try:
|
||||
s = Subscribe.objects.get(user=user, course=course)
|
||||
except ObjectDoesNotExist:
|
||||
return False
|
||||
return s.is_assistant or user.is_staff
|
||||
|
||||
|
||||
def comparer(value1, value2):
|
||||
if value1 < value2:
|
||||
return 1
|
||||
elif value1 == value2:
|
||||
return 0
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
def result_comparer(result1, result2):
|
||||
verdicts = ['IN QUEUE', 'TESTING', 'TEST ERROR', 'SOLUTION ERROR', 'Compilation error', 'Time limit']
|
||||
if result1 in verdicts and result2 in verdicts:
|
||||
return comparer(verdicts.index(result1), verdicts.index(result2))
|
||||
if result1 in verdicts and result2 not in verdicts:
|
||||
return 1
|
||||
if result1 not in verdicts and result2 in verdicts:
|
||||
return -1
|
||||
return comparer(int(result1.split('/')[0]), int(result2.split('/')[0]))
|
||||
|
||||
|
||||
def solutions_filter(request):
|
||||
try:
|
||||
solutions = list(reversed(Solution.objects.filter(task__block_id=request['block_id'])))
|
||||
except MultiValueDictKeyError as e:
|
||||
return [Solution.objects.get(id=request['id'])]
|
||||
if 'solution_id' in request.keys():
|
||||
solutions = [solution for solution in solutions if any([solution.id == int(i) for i in request['solution_id'].strip().split()])]
|
||||
if 'task_name' in request.keys():
|
||||
solutions = [solution for solution in solutions if solution.task.name == request['task_name']]
|
||||
if 'user' in request.keys():
|
||||
solutions = [solution for solution in solutions if str(solution.userinfo) == request['user']]
|
||||
if 'group' in request.keys():
|
||||
solutions = [solution for solution in solutions if solution.userinfo.group == request['group']]
|
||||
if 'best_result' in request.keys():
|
||||
sols = {}
|
||||
for solution in solutions:
|
||||
if (solution.user.username, solution.task.id) in sols.keys():
|
||||
comp = result_comparer(sols[(solution.user.username, solution.task.id)][0].result, solution.result)
|
||||
if comp == 1:
|
||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
||||
elif comp == 0:
|
||||
sols[(solution.user.username, solution.task.id)].append(solution)
|
||||
else:
|
||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
||||
solutions = []
|
||||
for sol in sols.values():
|
||||
for val in sol:
|
||||
solutions.append(val)
|
||||
solutions = list(sorted(solutions, key=lambda s: s.id, reverse=True))
|
||||
if 'last_solution' in request.keys():
|
||||
visited = []
|
||||
new_solutions = []
|
||||
for solution in solutions:
|
||||
if (solution.user.username, solution.task.id) not in visited:
|
||||
visited.append((solution.user.username, solution.task.id))
|
||||
new_solutions.append(solution)
|
||||
solutions = new_solutions
|
||||
if 'only_students' in request.keys():
|
||||
solutions = [solution for solution in solutions if not check_admin_on_course(solution.user, solution.task.block.course)]
|
||||
if 'not_seen' in request.keys():
|
||||
solutions = [solution for solution in solutions if solution.mark == None]
|
||||
return sorted(solutions, key=lambda s: s.id, reverse=True)
|
||||
|
||||
|
||||
def re_test(solutions_request, request):
|
||||
from .Tester import Tester
|
||||
for sol in solutions_request:
|
||||
sol.details = ''
|
||||
with open(sol.log_file, 'wb') as fs:
|
||||
fs.write(b'')
|
||||
sol.save()
|
||||
Thread(target=lambda: Tester(sol, request.META['HTTP_HOST']).push()).start()
|
||||
sleep(.1)
|
||||
|
||||
|
||||
def block_solutions_info(block):
|
||||
all_solutions = Solution.objects.filter(task__block=block)
|
||||
all_users = [solution.userinfo for solution in all_solutions]
|
||||
return {
|
||||
'tasks': sorted(list(set([solution.task for solution in all_solutions])), key=lambda x: x.name),
|
||||
'users': sorted(list(set(all_users)), key=lambda x: str(x)),
|
||||
'groups': sorted(list(set([userinfo.group for userinfo in all_users])), key=lambda x: str(x))
|
||||
}
|
||||
|
||||
|
||||
def delete_folder(path):
|
||||
flag = True
|
||||
while flag:
|
||||
try:
|
||||
rmtree(dirname(path))
|
||||
flag = False
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def solution_path(path):
|
||||
files = [x for x in listdir(path) if x.endswith('.sln') and not x.startswith('.')]
|
||||
if files:
|
||||
return path
|
||||
return ''.join([solution_path(join(path, file)) for file in listdir(path) if isdir(join(path, file))])
|
||||
|
||||
|
||||
def register_user(u):
|
||||
password = random_string()
|
||||
user = User.objects.create_user(username=u['email'], email=u['email'], password=password)
|
||||
UserInfo.objects.create(
|
||||
surname=u['surname'],
|
||||
name=u['name'],
|
||||
middle_name=u['middle_name'],
|
||||
group=u['group'],
|
||||
user=user
|
||||
)
|
||||
send_email('You have been registered in Sprint!', u['email'],
|
||||
'Your password is: {}\nPlease change it after login in settings!\nhttps://sprint.cshse.ru/'.format(password))
|
||||
return user
|
||||
|
||||
|
||||
def check_cheating(solutions, block, cheating_percent):
|
||||
block.cheating_checking = True
|
||||
block.save()
|
||||
try:
|
||||
cheating_data = {}
|
||||
cheating_path = join(MEDIA_ROOT, 'cheating', str(block.id))
|
||||
if exists(cheating_path):
|
||||
rmtree(cheating_path)
|
||||
mkdir(cheating_path)
|
||||
for solution in solutions:
|
||||
for file in solution.user_files.keys():
|
||||
user_file = join(MEDIA_ROOT, 'solutions', str(solution.id), file)
|
||||
dest_file = join(cheating_path, '_'.join([str(solution.id), basename(file)]))
|
||||
copyfile(user_file, dest_file)
|
||||
files_len = len(solutions)
|
||||
files = listdir(cheating_path)
|
||||
for i in range(len(files) - 1):
|
||||
for j in range(i + 1, len(files)):
|
||||
file1 = files[i]
|
||||
file2 = files[j]
|
||||
s1 = file1.split('_')
|
||||
s2 = file2.split('_')
|
||||
sol1 = Solution.objects.get(id=int(s1[0]))
|
||||
sol2 = Solution.objects.get(id=int(s2[0]))
|
||||
filename1 = '_'.join(s1[1:])
|
||||
filename2 = '_'.join(s2[1:])
|
||||
if sol1.user == sol2.user or sol1.task != sol2.task or filename1 != filename2:
|
||||
continue
|
||||
fp1 = copydetect.CodeFingerprint(join(cheating_path, file1), 25, 1)
|
||||
fp2 = copydetect.CodeFingerprint(join(cheating_path, file2), 25, 1)
|
||||
token_overlap, similarities, slices = copydetect.compare_files(fp1, fp2)
|
||||
similarity = (similarities[0] + similarities[1]) / 2
|
||||
if similarity >= cheating_percent / 100:
|
||||
if sol1.user.id not in cheating_data.keys():
|
||||
cheating_data[sol1.user.id] = []
|
||||
if sol2.user.id not in cheating_data.keys():
|
||||
cheating_data[sol2.user.id] = []
|
||||
cheating_data[sol1.user.id].append({
|
||||
'source': True,
|
||||
'solution': sol1.id,
|
||||
'file': filename1,
|
||||
'similar': sol2.id,
|
||||
'similarity': round(similarity * 100, 2)
|
||||
})
|
||||
cheating_data[sol2.user.id].append({
|
||||
'source': False,
|
||||
'solution': sol2.id,
|
||||
'file': filename2,
|
||||
'similar': sol1.id,
|
||||
'similarity': round(similarity * 100, 2)
|
||||
})
|
||||
finally:
|
||||
if exists(cheating_path):
|
||||
rmtree(cheating_path)
|
||||
with open(block.cheating_results_path, 'w') as fs:
|
||||
fs.write(dumps(cheating_data))
|
||||
block = Block.objects.get(id=block.id)
|
||||
block.cheating_checking = False
|
||||
block.save()
|
||||
print('finished')
|
||||
|
@@ -15,84 +15,201 @@ class Migration(migrations.Migration):
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Block',
|
||||
name="Block",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.TextField()),
|
||||
('time_start', models.DateTimeField()),
|
||||
('time_end', models.DateTimeField()),
|
||||
('opened', models.IntegerField()),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("time_start", models.DateTimeField()),
|
||||
("time_end", models.DateTimeField()),
|
||||
("opened", models.IntegerField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Course',
|
||||
name="Course",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.TextField()),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserInfo',
|
||||
name="UserInfo",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('surname', models.TextField()),
|
||||
('name', models.TextField()),
|
||||
('middle_name', models.TextField()),
|
||||
('group_name', models.TextField()),
|
||||
('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("surname", models.TextField()),
|
||||
("name", models.TextField()),
|
||||
("middle_name", models.TextField()),
|
||||
("group_name", models.TextField()),
|
||||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Task',
|
||||
name="Task",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.TextField()),
|
||||
('legend', models.TextField()),
|
||||
('input', models.TextField()),
|
||||
('output', models.TextField()),
|
||||
('specifications', models.TextField()),
|
||||
('time_limit', models.IntegerField()),
|
||||
('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("legend", models.TextField()),
|
||||
("input", models.TextField()),
|
||||
("output", models.TextField()),
|
||||
("specifications", models.TextField()),
|
||||
("time_limit", models.IntegerField()),
|
||||
(
|
||||
"block",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.Block"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Subscribe',
|
||||
name="Subscribe",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_assistant', models.IntegerField()),
|
||||
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("is_assistant", models.IntegerField()),
|
||||
(
|
||||
"course",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.Course"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Solution',
|
||||
name="Solution",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('result', models.TextField()),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Task')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("result", models.TextField()),
|
||||
(
|
||||
"task",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.Task"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Restore',
|
||||
name="Restore",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.TextField()),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("code", models.TextField()),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Mark',
|
||||
name="Mark",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('mark', models.IntegerField()),
|
||||
('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("mark", models.IntegerField()),
|
||||
(
|
||||
"block",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.Block"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='block',
|
||||
name='course',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course'),
|
||||
model_name="block",
|
||||
name="course",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.Course"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0001_initial'),
|
||||
("Main", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='userinfo',
|
||||
old_name='group_name',
|
||||
new_name='group',
|
||||
model_name="userinfo",
|
||||
old_name="group_name",
|
||||
new_name="group",
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0002_auto_20200626_0946'),
|
||||
("Main", "0002_auto_20200626_0946"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='subscribe',
|
||||
name='is_assistant',
|
||||
model_name="subscribe",
|
||||
name="is_assistant",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
@@ -6,33 +6,33 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0003_auto_20200627_1959'),
|
||||
("Main", "0003_auto_20200627_1959"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='input',
|
||||
field=models.TextField(default=''),
|
||||
model_name="task",
|
||||
name="input",
|
||||
field=models.TextField(default=""),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='legend',
|
||||
field=models.TextField(default=''),
|
||||
model_name="task",
|
||||
name="legend",
|
||||
field=models.TextField(default=""),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='output',
|
||||
field=models.TextField(default=''),
|
||||
model_name="task",
|
||||
name="output",
|
||||
field=models.TextField(default=""),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='specifications',
|
||||
field=models.TextField(default=''),
|
||||
model_name="task",
|
||||
name="specifications",
|
||||
field=models.TextField(default=""),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='time_limit',
|
||||
model_name="task",
|
||||
name="time_limit",
|
||||
field=models.IntegerField(default=10000),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0004_auto_20200628_0917'),
|
||||
("Main", "0004_auto_20200628_0917"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='solution',
|
||||
name='time_sent',
|
||||
model_name="solution",
|
||||
name="time_sent",
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
||||
|
@@ -7,13 +7,15 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0005_solution_time_sent'),
|
||||
("Main", "0005_solution_time_sent"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='solution',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.UserInfo'),
|
||||
model_name="solution",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.UserInfo"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@@ -9,13 +9,15 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('Main', '0006_auto_20200628_1315'),
|
||||
("Main", "0006_auto_20200628_1315"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='solution',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
model_name="solution",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@@ -6,18 +6,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0007_auto_20200629_0833'),
|
||||
("Main", "0007_auto_20200629_0833"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userinfo',
|
||||
name='mark_notification',
|
||||
model_name="userinfo",
|
||||
name="mark_notification",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='userinfo',
|
||||
name='new_block_notification',
|
||||
model_name="userinfo",
|
||||
name="new_block_notification",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
@@ -6,18 +6,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0008_auto_20200702_2140'),
|
||||
("Main", "0008_auto_20200702_2140"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='solution',
|
||||
name='comment',
|
||||
field=models.TextField(default=''),
|
||||
model_name="solution",
|
||||
name="comment",
|
||||
field=models.TextField(default=""),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='solution',
|
||||
name='mark',
|
||||
model_name="solution",
|
||||
name="mark",
|
||||
field=models.IntegerField(null=True),
|
||||
),
|
||||
]
|
||||
|
@@ -6,16 +6,24 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0009_auto_20200704_1703'),
|
||||
("Main", "0009_auto_20200704_1703"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='System',
|
||||
name="System",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.TextField()),
|
||||
('value', models.TextField()),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("key", models.TextField()),
|
||||
("value", models.TextField()),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@@ -6,18 +6,26 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0010_system'),
|
||||
("Main", "0010_system"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ThreadSafe',
|
||||
name="ThreadSafe",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=80, unique=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("key", models.CharField(max_length=80, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Mark',
|
||||
name="Mark",
|
||||
),
|
||||
]
|
||||
|
@@ -7,21 +7,34 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0011_auto_20200814_2035'),
|
||||
("Main", "0011_auto_20200814_2035"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='weight',
|
||||
model_name="task",
|
||||
name="weight",
|
||||
field=models.FloatField(default=1.0),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExtraFile',
|
||||
name="ExtraFile",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('file', models.FileField(upload_to='')),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("file", models.FileField(upload_to="")),
|
||||
(
|
||||
"task",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.task"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@@ -6,11 +6,11 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0012_auto_20200901_1154'),
|
||||
("Main", "0012_auto_20200901_1154"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='ExtraFile',
|
||||
name="ExtraFile",
|
||||
),
|
||||
]
|
||||
|
@@ -7,17 +7,30 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0013_delete_extrafile'),
|
||||
("Main", "0013_delete_extrafile"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ExtraFile',
|
||||
name="ExtraFile",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('file', models.FileField(upload_to='')),
|
||||
('filename', models.TextField()),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("file", models.FileField(upload_to="")),
|
||||
("filename", models.TextField()),
|
||||
(
|
||||
"task",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.task"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@@ -6,18 +6,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0014_extrafile'),
|
||||
("Main", "0014_extrafile"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='max_mark',
|
||||
model_name="task",
|
||||
name="max_mark",
|
||||
field=models.IntegerField(default=10),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='extrafile',
|
||||
name='file',
|
||||
field=models.FileField(upload_to='data\\extra_files'),
|
||||
model_name="extrafile",
|
||||
name="file",
|
||||
field=models.FileField(upload_to="data\\extra_files"),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0015_auto_20200902_1555'),
|
||||
("Main", "0015_auto_20200902_1555"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='max_solutions_count',
|
||||
model_name="task",
|
||||
name="max_solutions_count",
|
||||
field=models.IntegerField(default=10),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0016_task_max_solutions_count'),
|
||||
("Main", "0016_task_max_solutions_count"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='solution',
|
||||
name='details',
|
||||
field=models.TextField(default=''),
|
||||
model_name="solution",
|
||||
name="details",
|
||||
field=models.TextField(default=""),
|
||||
),
|
||||
]
|
||||
|
@@ -6,12 +6,12 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0017_solution_details'),
|
||||
("Main", "0017_solution_details"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='extrafile',
|
||||
name='file',
|
||||
model_name="extrafile",
|
||||
name="file",
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0018_remove_extrafile_file'),
|
||||
("Main", "0018_remove_extrafile_file"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='show_details',
|
||||
model_name="task",
|
||||
name="show_details",
|
||||
field=models.IntegerField(default=1),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0019_task_show_details'),
|
||||
("Main", "0019_task_show_details"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='solution_type',
|
||||
field=models.TextField(default='Решение'),
|
||||
model_name="task",
|
||||
name="solution_type",
|
||||
field=models.TextField(default="Решение"),
|
||||
),
|
||||
]
|
||||
|
@@ -6,12 +6,12 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0020_task_solution_type'),
|
||||
("Main", "0020_task_solution_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='task',
|
||||
name='solution_type',
|
||||
model_name="task",
|
||||
name="solution_type",
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0021_remove_task_solution_type'),
|
||||
("Main", "0021_remove_task_solution_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='full_solution',
|
||||
model_name="task",
|
||||
name="full_solution",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0022_task_full_solution'),
|
||||
("Main", "0022_task_full_solution"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='extrafile',
|
||||
name='for_compilation',
|
||||
model_name="extrafile",
|
||||
name="for_compilation",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0023_extrafile_for_compilation'),
|
||||
("Main", "0023_extrafile_for_compilation"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='extrafile',
|
||||
name='sample',
|
||||
model_name="extrafile",
|
||||
name="sample",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
@@ -6,46 +6,46 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0024_extrafile_sample'),
|
||||
("Main", "0024_extrafile_sample"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='userinfo',
|
||||
name='mark_notification',
|
||||
model_name="userinfo",
|
||||
name="mark_notification",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userinfo',
|
||||
name='new_block_notification',
|
||||
model_name="userinfo",
|
||||
name="new_block_notification",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='block',
|
||||
name='opened',
|
||||
model_name="block",
|
||||
name="opened",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='extrafile',
|
||||
name='for_compilation',
|
||||
model_name="extrafile",
|
||||
name="for_compilation",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='extrafile',
|
||||
name='sample',
|
||||
model_name="extrafile",
|
||||
name="sample",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subscribe',
|
||||
name='is_assistant',
|
||||
model_name="subscribe",
|
||||
name="is_assistant",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='full_solution',
|
||||
model_name="task",
|
||||
name="full_solution",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='show_details',
|
||||
model_name="task",
|
||||
name="show_details",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0025_auto_20201106_1848'),
|
||||
("Main", "0025_auto_20201106_1848"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='block',
|
||||
name='show_rating',
|
||||
model_name="block",
|
||||
name="show_rating",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0026_block_show_rating'),
|
||||
("Main", "0026_block_show_rating"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='mark_formula',
|
||||
field=models.TextField(default='None'),
|
||||
model_name="task",
|
||||
name="mark_formula",
|
||||
field=models.TextField(default="None"),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0027_task_mark_formula'),
|
||||
("Main", "0027_task_mark_formula"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='show_result',
|
||||
model_name="task",
|
||||
name="show_result",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
|
@@ -6,18 +6,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0028_task_show_result'),
|
||||
("Main", "0028_task_show_result"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='block',
|
||||
name='priority',
|
||||
model_name="block",
|
||||
name="priority",
|
||||
field=models.IntegerField(default=5),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='priority',
|
||||
model_name="task",
|
||||
name="priority",
|
||||
field=models.IntegerField(default=5),
|
||||
),
|
||||
]
|
||||
|
@@ -9,19 +9,46 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('Main', '0029_auto_20210130_1950'),
|
||||
("Main", "0029_auto_20210130_1950"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
name="Message",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('for_all', models.BooleanField()),
|
||||
('text', models.TextField()),
|
||||
('reply_to', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Main.message')),
|
||||
('sender', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("for_all", models.BooleanField()),
|
||||
("text", models.TextField()),
|
||||
(
|
||||
"reply_to",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="Main.message",
|
||||
),
|
||||
),
|
||||
(
|
||||
"sender",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"task",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="Main.task"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0030_message'),
|
||||
("Main", "0030_message"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='block',
|
||||
name='cheating_checking',
|
||||
model_name="block",
|
||||
name="cheating_checking",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0031_block_cheating_checking'),
|
||||
("Main", "0031_block_cheating_checking"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='block',
|
||||
name='cheating_data',
|
||||
field=models.TextField(default='[]'),
|
||||
model_name="block",
|
||||
name="cheating_data",
|
||||
field=models.TextField(default="[]"),
|
||||
),
|
||||
]
|
||||
|
@@ -6,12 +6,12 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('Main', '0032_block_cheating_data'),
|
||||
("Main", "0032_block_cheating_data"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='block',
|
||||
name='cheating_data',
|
||||
model_name="block",
|
||||
name="cheating_data",
|
||||
),
|
||||
]
|
||||
|
463
Main/models.py
463
Main/models.py
@@ -1,463 +0,0 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_delete
|
||||
from os.path import sep, join, exists
|
||||
from os import remove
|
||||
|
||||
from Main.commands import shell
|
||||
from Sprint.settings import MEDIA_ROOT
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from json import loads
|
||||
|
||||
|
||||
class Course(models.Model):
|
||||
name = models.TextField()
|
||||
|
||||
@property
|
||||
def teachers(self):
|
||||
return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(user__is_staff=1, course=self)]
|
||||
|
||||
@property
|
||||
def subscribes(self):
|
||||
return sorted(Subscribe.objects.filter(course=self), key=lambda s: s.user.email)
|
||||
|
||||
@property
|
||||
def students(self):
|
||||
userinfo = lambda sub: sub.user.userinfo
|
||||
return sorted(Subscribe.objects.filter(course=self, is_assistant=False, user__is_staff=False), key=lambda s: userinfo(s).surname + userinfo(s).name + userinfo(s).middle_name)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Block(models.Model):
|
||||
name = models.TextField()
|
||||
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
||||
time_start = models.DateTimeField()
|
||||
time_end = models.DateTimeField()
|
||||
opened = models.BooleanField(default=False)
|
||||
show_rating = models.BooleanField(default=True)
|
||||
priority = models.IntegerField(default=5)
|
||||
cheating_checking = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
return Message.objects.filter(task__block=self)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def tasks(self):
|
||||
return Task.objects.filter(block=self)
|
||||
|
||||
@property
|
||||
def time_start_chrome(self):
|
||||
return self.time_start.strftime("%Y-%m-%dT%H:%M")
|
||||
|
||||
@property
|
||||
def time_end_chrome(self):
|
||||
return self.time_end.strftime("%Y-%m-%dT%H:%M")
|
||||
|
||||
@property
|
||||
def is_opened(self):
|
||||
return 'checked' if self.opened else ''
|
||||
|
||||
@property
|
||||
def solutions(self):
|
||||
return reversed(Solution.objects.filter(task__block=self))
|
||||
|
||||
@property
|
||||
def subscribed_users(self):
|
||||
return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(course=self.course)]
|
||||
|
||||
@property
|
||||
def cheating_results_path(self):
|
||||
return join(MEDIA_ROOT, 'cheating_results', str(self.id))
|
||||
|
||||
@property
|
||||
def cheating_checked(self):
|
||||
return self.cheating_results != {}
|
||||
|
||||
@property
|
||||
def cheating_results(self):
|
||||
return loads(open(self.cheating_results_path, 'r').read()) if exists(self.cheating_results_path) else {}
|
||||
|
||||
@property
|
||||
def cheating_status(self):
|
||||
if self.cheating_checking:
|
||||
return 'Идет проверка'
|
||||
if not exists(self.cheating_results_path):
|
||||
return 'Еще не проверено'
|
||||
return 'Проверка завершена'
|
||||
|
||||
|
||||
class Restore(models.Model):
|
||||
code = models.TextField()
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
||||
|
||||
class Subscribe(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
||||
is_assistant = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username + '|' + self.course.name
|
||||
|
||||
@property
|
||||
def userinfo(self):
|
||||
return UserInfo.objects.get(user=self.user)
|
||||
|
||||
@property
|
||||
def role(self):
|
||||
if self.user.is_superuser:
|
||||
return 'Администратор'
|
||||
if self.user.is_staff:
|
||||
return 'Преподаватель'
|
||||
return 'Ассистент' if self.is_assistant else 'Студент'
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
name = models.TextField()
|
||||
block = models.ForeignKey(Block, on_delete=models.CASCADE)
|
||||
legend = models.TextField(default='')
|
||||
input = models.TextField(default='')
|
||||
output = models.TextField(default='')
|
||||
specifications = models.TextField(default='')
|
||||
time_limit = models.IntegerField(default=10000)
|
||||
weight = models.FloatField(default=1.0)
|
||||
max_mark = models.IntegerField(default=10)
|
||||
max_solutions_count = models.IntegerField(default=10)
|
||||
show_result = models.BooleanField(default=True)
|
||||
show_details = models.BooleanField(default=False)
|
||||
full_solution = models.BooleanField(default=False)
|
||||
mark_formula = models.TextField(default='None')
|
||||
priority = models.IntegerField(default=5)
|
||||
|
||||
@property
|
||||
def students_solutions(self):
|
||||
students = [sub.user for sub in Subscribe.objects.filter(course=self.block.course)]
|
||||
solutions = Solution.objects.filter(task=self)
|
||||
return [sol for sol in solutions if sol.user in students]
|
||||
|
||||
@property
|
||||
def correct_count(self):
|
||||
solutions = self.students_solutions
|
||||
count = 0
|
||||
for sol in solutions:
|
||||
res = sol.result.split('/')
|
||||
if len(res) == 2 and res[0] == res[1]:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@property
|
||||
def solutions_count(self):
|
||||
return len(self.students_solutions)
|
||||
|
||||
@property
|
||||
def partially_passed(self):
|
||||
solutions = self.students_solutions
|
||||
count = 0
|
||||
for sol in solutions:
|
||||
res = sol.result.split('/')
|
||||
if len(res) == 2 and res[0] != res[1]:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@property
|
||||
def solutions_with_error(self):
|
||||
return self.solutions_count - self.correct_count - self.partially_passed
|
||||
|
||||
@property
|
||||
def samples(self):
|
||||
return [{
|
||||
'input': file,
|
||||
'output': file.answer
|
||||
} for file in ExtraFile.objects.filter(task=self, sample=True).order_by('filename')]
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def showable(self):
|
||||
return 'checked' if self.show_details else ''
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def tests_path(self):
|
||||
return join(MEDIA_ROOT, 'tests', str(self.id) + '.cs')
|
||||
|
||||
@property
|
||||
def tests_text(self):
|
||||
try:
|
||||
return open(self.tests_path(), 'r').read()
|
||||
except FileNotFoundError:
|
||||
with open(self.tests_path(), 'w') as fs:
|
||||
pass
|
||||
return ''
|
||||
|
||||
@property
|
||||
def tests_uploaded(self):
|
||||
from os.path import exists
|
||||
return exists(self.tests_path())
|
||||
|
||||
@property
|
||||
def files(self):
|
||||
return ExtraFile.objects.filter(task=self).order_by('filename')
|
||||
|
||||
@property
|
||||
def files_for_compilation(self):
|
||||
return ExtraFile.objects.filter(task=self, for_compilation=True)
|
||||
|
||||
|
||||
@property
|
||||
def is_full_solution(self):
|
||||
return 'checked' if self.full_solution else ''
|
||||
|
||||
|
||||
class UserInfo(models.Model):
|
||||
surname = models.TextField()
|
||||
name = models.TextField()
|
||||
middle_name = models.TextField()
|
||||
group = models.TextField()
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
|
||||
|
||||
def __eq__(self, obj):
|
||||
return str(self) == str(obj)
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
def __str__(self):
|
||||
return "{} {} {}".format(self.surname, self.name, self.middle_name)
|
||||
|
||||
|
||||
class Solution(models.Model):
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
result = models.TextField()
|
||||
details = models.TextField(default='')
|
||||
time_sent = models.DateTimeField(null=True)
|
||||
mark = models.IntegerField(null=True)
|
||||
comment = models.TextField(default='')
|
||||
|
||||
def set_result(self, result):
|
||||
self.result = result
|
||||
if len(result.split('/')) != 1:
|
||||
result = int(result.split('/')[0])
|
||||
try:
|
||||
self.mark = eval(self.task.mark_formula)
|
||||
except:
|
||||
self.mark = None
|
||||
self.save()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
def path(self):
|
||||
return join(MEDIA_ROOT, 'solutions', str(self.id))
|
||||
|
||||
def write_log(self, text):
|
||||
with self.log_fs as fs:
|
||||
fs.write(bytes(text + '\n', 'cp866'))
|
||||
|
||||
@property
|
||||
def log_file(self):
|
||||
return join(MEDIA_ROOT, 'logs', str(self.id) + '.log')
|
||||
|
||||
@property
|
||||
def log_text(self):
|
||||
try:
|
||||
return open(self.log_file, 'rb').read().decode('cp866')
|
||||
except FileNotFoundError:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def log_fs(self):
|
||||
return open(self.log_file, 'ab')
|
||||
|
||||
@property
|
||||
def userinfo(self):
|
||||
return UserInfo.objects.get(user=self.user)
|
||||
|
||||
@property
|
||||
def mark_property(self):
|
||||
return str(self.mark) if self.mark is not None else 'нет оценки'
|
||||
|
||||
@property
|
||||
def mark_select(self):
|
||||
line = ''
|
||||
if self.mark:
|
||||
line += '<option value="нет оценки">нет оценки</option>'
|
||||
else:
|
||||
line += '<option value="нет оценки" selected>нет оценки</option>'
|
||||
for mark in range(self.task.max_mark + 1):
|
||||
if mark == self.mark:
|
||||
line += '<option value="{}" selected>{}</option>'.format(mark, mark)
|
||||
else:
|
||||
line += '<option value="{}">{}</option>'.format(mark, mark)
|
||||
return line
|
||||
|
||||
@property
|
||||
def comment_property(self):
|
||||
return self.comment if self.comment else 'нет комментария'
|
||||
|
||||
@staticmethod
|
||||
def get_files(path):
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
files_dict = {}
|
||||
for file in listdir(path):
|
||||
if file == '__MACOSX' or file == 'test_folder' or file == 'bin' or file == 'obj' or file == '.vs':
|
||||
continue
|
||||
current_file = join(path, file)
|
||||
if isfile(current_file):
|
||||
if not current_file.endswith('.csproj') and not current_file.endswith('.sln'):
|
||||
try:
|
||||
files_dict[sep.join(current_file.split('solutions' + sep)[1].split(sep)[1:])] \
|
||||
= open(current_file, 'rb').read().decode('UTF-8')
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
else:
|
||||
files_dict = {**files_dict, **Solution.get_files(current_file)}
|
||||
return files_dict
|
||||
|
||||
@property
|
||||
def files(self):
|
||||
return Solution.get_files(self.path())
|
||||
|
||||
@property
|
||||
def user_files(self):
|
||||
f = {}
|
||||
comp_files = [ef.filename for ef in ExtraFile.objects.filter(task=self.task, for_compilation=True)]
|
||||
for fi in self.files.keys():
|
||||
if not fi in comp_files:
|
||||
f[fi] = self.files[fi]
|
||||
return f
|
||||
|
||||
@property
|
||||
def passed_all_tests(self):
|
||||
spl = self.result.split('/')
|
||||
return len(spl) == 2 and spl[0] == spl[1]
|
||||
|
||||
class System(models.Model):
|
||||
key = models.TextField()
|
||||
value = models.TextField()
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
||||
|
||||
class ExtraFile(models.Model):
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||
filename = models.TextField()
|
||||
for_compilation = models.BooleanField(default=False)
|
||||
sample = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
def answer(self):
|
||||
try:
|
||||
return ExtraFile.objects.get(task=self.task, filename=self.filename + '.a')
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
@property
|
||||
def num(self):
|
||||
try:
|
||||
return int(self.filename.split('.')[0])
|
||||
except ValueError:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def is_for_compilation(self):
|
||||
return 'checked' if self.for_compilation else ''
|
||||
|
||||
@property
|
||||
def is_sample(self):
|
||||
return 'checked' if self.sample else ''
|
||||
|
||||
@property
|
||||
def can_be_sample(self):
|
||||
try:
|
||||
int(self.filename)
|
||||
except:
|
||||
return False
|
||||
try:
|
||||
ans = ExtraFile.objects.get(task=self.task, filename=self.filename + '.a')
|
||||
except ObjectDoesNotExist:
|
||||
return False
|
||||
return self.readable and ans.readable
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return join(MEDIA_ROOT, 'extra_files', str(self.id))
|
||||
|
||||
|
||||
@property
|
||||
def readable(self):
|
||||
try:
|
||||
open(self.path, 'rb').read().decode('UTF-8')
|
||||
return True
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return open(self.path, 'rb').read().decode('UTF-8')
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.filename
|
||||
|
||||
|
||||
def write(self, data):
|
||||
with open(self.path, 'wb') as fs:
|
||||
fs.write(data)
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||
sender = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
|
||||
reply_to = models.ForeignKey('Message', on_delete=models.CASCADE, null=True)
|
||||
for_all = models.BooleanField()
|
||||
text = models.TextField()
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Task)
|
||||
def delete_task_hook(sender, instance, using, **kwargs):
|
||||
if exists(instance.tests_path()):
|
||||
from os import remove
|
||||
remove(instance.tests_path())
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Solution)
|
||||
def delete_solution_hook(sender, instance, using, **kwargs):
|
||||
if exists(instance.path()):
|
||||
from shutil import rmtree
|
||||
rmtree(instance.path())
|
||||
shell('docker rm --force solution_container_{}'.format(instance.id))
|
||||
shell('docker image rm solution_{}'.format(instance.id))
|
||||
|
||||
|
||||
@receiver(post_delete, sender=ExtraFile)
|
||||
def delete_file_hook(sender, instance, using, **kwargs):
|
||||
try:
|
||||
if exists(instance.path):
|
||||
remove(instance.path)
|
||||
except ValueError:
|
||||
pass
|
||||
if instance.filename.endswith('.a'):
|
||||
try:
|
||||
t = ExtraFile.objects.get(task=instance.task, filename=instance.filename[:-2])
|
||||
except ObjectDoesNotExist:
|
||||
return
|
||||
t.sample = False
|
||||
t.save()
|
10
Main/models/__init__.py
Normal file
10
Main/models/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from Main.models.userinfo import UserInfo
|
||||
from Main.models.group import Group
|
||||
from Main.models.task import Task
|
||||
from Main.models.file import File
|
||||
from Main.models.set import Set
|
||||
from Main.models.subscription import Subscription
|
||||
from Main.models.settask import SetTask
|
||||
from Main.models.solution import Solution
|
||||
from Main.models.language import Language
|
||||
from Main.models.extrafile import ExtraFile
|
27
Main/models/extrafile.py
Normal file
27
Main/models/extrafile.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from os import remove
|
||||
from os.path import join, exists
|
||||
|
||||
from django.db import models
|
||||
|
||||
from Sprint.settings import DATA_ROOT
|
||||
|
||||
|
||||
class ExtraFile(models.Model):
|
||||
task = models.ForeignKey('Task', on_delete=models.CASCADE)
|
||||
filename = models.TextField()
|
||||
is_test = 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))
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return open(self.path, 'r').read()
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if exists(self.path):
|
||||
remove(self.path)
|
||||
super().delete(using=using, keep_parents=keep_parents)
|
7
Main/models/file.py
Normal file
7
Main/models/file.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.db import models
|
||||
from Main.models.task import Task
|
||||
|
||||
|
||||
class File(models.Model):
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||
name = models.TextField()
|
7
Main/models/group.py
Normal file
7
Main/models/group.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.db import models
|
||||
from Main.models.set import Set
|
||||
|
||||
|
||||
class Group(models.Model):
|
||||
name = models.TextField()
|
||||
sets = models.ManyToManyField(Set)
|
13
Main/models/language.py
Normal file
13
Main/models/language.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Language(models.Model):
|
||||
name = models.TextField()
|
||||
work_name = models.TextField(default='')
|
||||
file_type = models.TextField(null=True)
|
||||
logo = models.ImageField(upload_to="logos", null=True)
|
||||
image = models.TextField(default='ubuntu')
|
||||
opened = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
8
Main/models/set.py
Normal file
8
Main/models/set.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Set(models.Model):
|
||||
name = models.TextField()
|
||||
public = models.BooleanField(default=False)
|
||||
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
10
Main/models/settask.py
Normal file
10
Main/models/settask.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.db import models
|
||||
|
||||
from Main.models.task import Task
|
||||
from Main.models.set import Set
|
||||
|
||||
|
||||
class SetTask(models.Model):
|
||||
set = models.ForeignKey(Set, on_delete=models.CASCADE)
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name="settasks")
|
||||
name = models.CharField(max_length=2)
|
40
Main/models/solution.py
Normal file
40
Main/models/solution.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from os import mkdir
|
||||
from os.path import join, exists
|
||||
from shutil import rmtree
|
||||
from subprocess import call
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from Main.models.task import Task
|
||||
from Main.models.language import Language
|
||||
from Sprint.settings import CONSTS, SOLUTIONS_ROOT
|
||||
|
||||
|
||||
class Solution(models.Model):
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
language = models.ForeignKey(Language, on_delete=models.SET_NULL, null=True)
|
||||
time_sent = models.DateTimeField(default=timezone.now)
|
||||
result = models.TextField(default=CONSTS["in_queue_status"])
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if exists(self.directory):
|
||||
rmtree(self.directory)
|
||||
super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
def create_dirs(self):
|
||||
mkdir(self.directory)
|
||||
mkdir(self.testing_directory)
|
||||
|
||||
@property
|
||||
def directory(self):
|
||||
return join(SOLUTIONS_ROOT, str(self.id))
|
||||
|
||||
@property
|
||||
def testing_directory(self):
|
||||
return join(self.directory, 'test_dir')
|
||||
|
||||
def exec_command(self, command, working_directory='app', timeout=None):
|
||||
return call(f'docker exec -i solution_{self.id} bash -c "cd {working_directory} && {command}"', shell=True, timeout=timeout)
|
12
Main/models/subscription.py
Normal file
12
Main/models/subscription.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from Main.models.group import Group
|
||||
|
||||
|
||||
class Subscription(models.Model):
|
||||
group = models.ForeignKey(
|
||||
Group, on_delete=models.CASCADE, related_name="subscriptions"
|
||||
)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
role = models.IntegerField()
|
26
Main/models/task.py
Normal file
26
Main/models/task.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from Main.models.extrafile import ExtraFile
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
name = models.TextField()
|
||||
public = models.BooleanField(default=False)
|
||||
legend = models.TextField(default="")
|
||||
input_format = models.TextField(default="")
|
||||
output_format = models.TextField(default="")
|
||||
specifications = models.TextField(default="")
|
||||
time_limit = models.IntegerField(default=10000)
|
||||
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def files(self):
|
||||
return ExtraFile.objects.filter(task=self, is_test=False)
|
||||
|
||||
@property
|
||||
def tests(self):
|
||||
return ExtraFile.objects.filter(task=self, is_test=True)
|
71
Main/models/userinfo.py
Normal file
71
Main/models/userinfo.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from Main.models.subscription import Subscription
|
||||
from Main.models.group import Group
|
||||
from Main.models.settask import SetTask
|
||||
from Main.models.task import Task
|
||||
from Sprint.settings import CONSTS
|
||||
|
||||
|
||||
class UserInfo(models.Model):
|
||||
surname = models.TextField()
|
||||
name = models.TextField()
|
||||
middle_name = models.TextField()
|
||||
last_request = models.DateTimeField(default=timezone.now)
|
||||
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)
|
||||
|
||||
def _append_task(self, task, tasks):
|
||||
if task.creator == self.user or task.public:
|
||||
tasks.append(task)
|
||||
return
|
||||
for st in SetTask.objects.filter(task=task):
|
||||
if st.set.public:
|
||||
tasks.append(task)
|
||||
return
|
||||
for group in Group.objects.filter(sets=st.set):
|
||||
for sub in Subscription.objects.filter(group=group):
|
||||
if sub.user == self.user:
|
||||
tasks.append(task)
|
||||
return
|
||||
|
||||
@property
|
||||
def available_tasks(self):
|
||||
tasks = []
|
||||
for task in Task.objects.all():
|
||||
self._append_task(task, tasks)
|
||||
return tasks
|
||||
|
||||
@property
|
||||
def place(self):
|
||||
return len(UserInfo.objects.filter(rating__gt=self.rating)) + 1
|
||||
|
||||
@property
|
||||
def activity_status(self):
|
||||
if timezone.now() - self.last_request <= timezone.timedelta(minutes=5):
|
||||
return CONSTS["online_status"]
|
||||
return timezone.datetime.strftime(self.last_request, "%d-%m-%Y %H:%M")
|
||||
|
||||
@property
|
||||
def can_create(self):
|
||||
# todo:
|
||||
return self.user.is_superuser
|
||||
|
||||
@property
|
||||
def has_profile_pic(self):
|
||||
try:
|
||||
return self.profile_picture.url is not None
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def profile_pic_url(self):
|
||||
if self.has_profile_pic:
|
||||
return self.profile_picture.url
|
||||
return "https://i2.wp.com/electrolabservice.com/wp-content/uploads/2021/01/blank-profile-picture-mystery-man-avatar-973460.jpg"
|
||||
|
||||
def __str__(self):
|
||||
return "{} {} {}".format(self.surname, self.name, self.middle_name)
|
9
Main/tasks.py
Normal file
9
Main/tasks.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from Main.models import Solution
|
||||
from Sprint.celery import app
|
||||
from SprintLib.testers import *
|
||||
|
||||
|
||||
@app.task
|
||||
def start_testing(solution_id):
|
||||
solution = Solution.objects.get(id=solution_id)
|
||||
eval(solution.language.work_name + 'Tester')(solution).execute()
|
@@ -1,112 +1,5 @@
|
||||
from django import template
|
||||
from Main.models import Solution, Task, UserInfo
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from Main.models import SetTask, Subscription
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter('mark_for_task')
|
||||
def mark_for_task(task, user):
|
||||
try:
|
||||
return round(list(Solution.objects.filter(task=task, user=user, mark__isnull=False))[-1].mark * 10 / task.max_mark)
|
||||
except IndexError:
|
||||
return 0
|
||||
|
||||
|
||||
@register.filter('mark_for_block')
|
||||
def mark_for_block(block, user):
|
||||
tasks = Task.objects.filter(block=block)
|
||||
mark = 0
|
||||
for task in tasks:
|
||||
mft = mark_for_task(task, user)
|
||||
mark += mft * task.weight
|
||||
return round(mark)
|
||||
|
||||
|
||||
@register.filter('marked')
|
||||
def marked(mark):
|
||||
return mark != -1
|
||||
|
||||
|
||||
@register.filter('mark_color')
|
||||
def mark_color(mark):
|
||||
mark = round(mark)
|
||||
if mark > 7:
|
||||
return '#00FF00'
|
||||
elif mark > 5:
|
||||
return '#FFFF00'
|
||||
elif mark > 3:
|
||||
return '#FAD7A0'
|
||||
elif mark > 0:
|
||||
return '#F1948A'
|
||||
else:
|
||||
return '#FFFFFF'
|
||||
|
||||
|
||||
@register.filter('in_dict')
|
||||
def in_dict(value, dict):
|
||||
return value in dict.keys()
|
||||
|
||||
|
||||
@register.filter('last_attempts')
|
||||
def last_attempts(user, task):
|
||||
return task.max_solutions_count - len(Solution.objects.filter(task=task, user=user))
|
||||
|
||||
|
||||
@register.filter('userinfo_by_user')
|
||||
def userinfo_by_user(user):
|
||||
return UserInfo.objects.get(user=user)
|
||||
|
||||
|
||||
@register.filter('mark_status')
|
||||
def mark_status(user, task):
|
||||
sols = Solution.objects.filter(user=user, task=task)
|
||||
if len(sols) == 0:
|
||||
return '-'
|
||||
return sols.last().result
|
||||
|
||||
|
||||
@register.filter('fully_marked')
|
||||
def fully_marked(user, task):
|
||||
return len(Solution.objects.filter(user=user, task=task, mark=None)) == 0
|
||||
|
||||
|
||||
@register.filter('is_code')
|
||||
def is_code(path):
|
||||
return path.endswith('.cs')
|
||||
|
||||
|
||||
@register.filter('num_range')
|
||||
def num_range(n):
|
||||
return range(1, n + 1)
|
||||
|
||||
|
||||
@register.filter('length')
|
||||
def length(collection):
|
||||
return len(collection)
|
||||
|
||||
|
||||
@register.filter('user_by_id')
|
||||
def user_by_id(user_id):
|
||||
return User.objects.get(id=user_id)
|
||||
|
||||
|
||||
@register.filter('dict_key')
|
||||
def dict_key(d, k):
|
||||
return d[k]
|
||||
|
||||
|
||||
@register.filter('solution_by_id')
|
||||
def solution_by_id(solution_id):
|
||||
return Solution.objects.get(id=solution_id)
|
||||
|
||||
|
||||
@register.filter('solution_file_text')
|
||||
def solution_file_text(solution, filename):
|
||||
files = solution.user_files
|
||||
for key in files.keys():
|
||||
value = files[key]
|
||||
if key.endswith(filename):
|
||||
return value
|
||||
raise Exception(f'No such file for solution {solution.id} and filename {filename}')
|
||||
|
833
Main/views.py
833
Main/views.py
@@ -1,833 +0,0 @@
|
||||
from json import load, dumps, loads
|
||||
from os import remove, mkdir, listdir, rename
|
||||
from os.path import sep, join, exists, isfile, dirname
|
||||
from shutil import rmtree, copytree, make_archive, copyfile
|
||||
from threading import Thread
|
||||
from zipfile import ZipFile, BadZipFile
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.contrib.auth import login, authenticate, logout
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from Main.templatetags.filters import *
|
||||
from Sprint.settings import MEDIA_ROOT
|
||||
from .Tester import Tester
|
||||
from .forms import *
|
||||
from .main import solutions_filter, check_admin_on_course, re_test, check_admin, check_teacher, random_string, \
|
||||
send_email, check_permission_block, is_integer, check_god, blocks_available, check_login, \
|
||||
get_restore_hash, block_solutions_info, solution_path, can_send_solution, get_in_html_tag, register_user, \
|
||||
check_cheating, result_comparer
|
||||
from .models import Block, Subscribe, Course, Restore, ExtraFile, Message
|
||||
from .decorators import login_required
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def download_rating(request):
|
||||
block = Block.objects.get(id=request.GET['block_id'])
|
||||
if not check_admin_on_course(request.user, block.course):
|
||||
return HttpResponseRedirect('/main')
|
||||
s = 'ФИО,'
|
||||
tasks = block.tasks
|
||||
users = [UserInfo.objects.get(user=user.user) for user in Subscribe.objects.filter(course=block.course, is_assistant=False, user__is_staff=False)]
|
||||
for task in tasks:
|
||||
try:
|
||||
s += task.name.split('.')[0] + ','
|
||||
except IndexError:
|
||||
s += task.name + ','
|
||||
s += 'Score,Summ\n'
|
||||
for user in users:
|
||||
s += str(user) + ','
|
||||
for task in tasks:
|
||||
s += str(mark_for_task(task, user.user)) + ','
|
||||
s += '0,' + str(mark_for_block(block, user.user)) + '\n'
|
||||
response = HttpResponse(bytes(s, encoding='utf-8'), content_type='application/force-download')
|
||||
response['Content-Disposition'] = 'inline; filename=rating.csv'
|
||||
return response
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def download(request):
|
||||
if not check_admin(request.user) or not check_admin_on_course(request.user, Block.objects.get(id=request.GET['block_id']).course):
|
||||
return HttpResponseRedirect('/main')
|
||||
sols = solutions_filter(request.GET)
|
||||
if len(sols) == 0:
|
||||
return HttpResponseRedirect('/admin/solutions?block_id=' + request.GET['block_id'])
|
||||
new_folder = join(MEDIA_ROOT, request.user.username)
|
||||
if exists(new_folder):
|
||||
try:
|
||||
rmtree(new_folder)
|
||||
except:
|
||||
remove(new_folder)
|
||||
mkdir(new_folder)
|
||||
cur_folder = join(new_folder, 'solutions')
|
||||
mkdir(cur_folder)
|
||||
for sol in sols:
|
||||
uinfo = UserInfo.objects.get(user=sol.user)
|
||||
folder = join(cur_folder, str(uinfo) + '-' + str(uinfo.user.id))
|
||||
if not exists(folder):
|
||||
mkdir(folder)
|
||||
files = sol.files
|
||||
files_for_compilation = [f.filename for f in sol.task.files_for_compilation]
|
||||
for f in files.copy().keys():
|
||||
if f.split(sep)[-1] in files_for_compilation:
|
||||
del files[f]
|
||||
if len(files.keys()) == 1:
|
||||
dest = join(folder, "-".join([sol.task.name.split('.')[0], str(sol.id), 'dotnet', sol.result.replace('/', 'from')])) + '.cs'
|
||||
source = join(sol.path(), list(files.keys())[0])
|
||||
with open(dest, 'wb') as fs:
|
||||
fs.write(open(source, 'rb').read())
|
||||
else:
|
||||
newname = join(folder, '-'.join([sol.task.name.split('.')[0], str(sol.id), 'dotnet', sol.result])).replace('/', 'from')
|
||||
mkdir(newname)
|
||||
for f in files.keys():
|
||||
with open(join(newname, f.split(sep)[-1]), 'wb') as fs:
|
||||
fs.write(open(join(sol.path(), f), 'rb').read())
|
||||
make_archive(newname, 'zip', newname)
|
||||
rmtree(newname)
|
||||
zip_folder = join(dirname(cur_folder), 'solutions')
|
||||
make_archive(zip_folder, 'zip', cur_folder)
|
||||
response = HttpResponse(open(zip_folder + '.zip', 'rb').read(), content_type='application/force-download')
|
||||
response['Content-Disposition'] = 'inline; filename=solutions.zip'
|
||||
rmtree(dirname(cur_folder))
|
||||
return response
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def queue(request):
|
||||
block = Block.objects.get(id=request.GET['block_id'])
|
||||
if not check_admin_on_course(request.user, block.course):
|
||||
return HttpResponseRedirect('/main')
|
||||
return render(request, 'queue.html', {'Block': block})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def docs(request):
|
||||
if not check_admin(request.user):
|
||||
return HttpResponseRedirect('/main')
|
||||
return render(request, "docs.html", context={'is_teacher': check_teacher(request.user)})
|
||||
|
||||
@login_required(True)
|
||||
def retest(request):
|
||||
solutions_request = solutions_filter(request.GET)
|
||||
if not check_admin_on_course(request.user, Block.objects.get(id=request.GET['block_id']).course):
|
||||
return HttpResponseRedirect('/main')
|
||||
req = '?block_id=' + str(request.GET['block_id'])
|
||||
for key in request.GET.keys():
|
||||
if key != 'block_id':
|
||||
req += '&{}={}'.format(key, request.GET[key])
|
||||
Thread(target=lambda: re_test(solutions_request, request)).start()
|
||||
if 'next' in request.GET.keys():
|
||||
return HttpResponseRedirect(request.GET['next'])
|
||||
return HttpResponseRedirect('/admin/solutions%s' % req)
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def solution(request):
|
||||
current_solution = Solution.objects.get(id=request.GET['id'])
|
||||
if current_solution.user != request.user:
|
||||
try:
|
||||
Subscribe.objects.get(user=request.user, is_assistant=True, course=current_solution.task.block.course)
|
||||
except:
|
||||
if not request.user.is_superuser:
|
||||
return HttpResponseRedirect('/main')
|
||||
can_edit = check_admin_on_course(request.user, current_solution.task.block.course)
|
||||
if not can_edit:
|
||||
# тут по хорошему надо использовать регулярки, но я что-то не разобрался
|
||||
while True:
|
||||
i = current_solution.details.find('<pre>')
|
||||
if i == -1:
|
||||
break
|
||||
j = current_solution.details.find('</pre>') + 6
|
||||
current_solution.details = current_solution.details.replace(current_solution.details[i:j], '')
|
||||
if current_solution.user != request.user:
|
||||
return HttpResponseRedirect('/main')
|
||||
solutions_request = solutions_filter(request.GET)
|
||||
if request.path == '/admin/solution':
|
||||
from_admin = True
|
||||
else:
|
||||
from_admin = False
|
||||
if request.method == 'POST' and can_edit:
|
||||
if request.POST['action'] == 'Зачесть':
|
||||
current_solution.mark = None if request.POST['mark'] == 'нет оценки' else int(request.POST['mark'])
|
||||
elif request.POST['action'] == 'Незачесть':
|
||||
current_solution.mark = 0
|
||||
else:
|
||||
current_solution.mark = current_solution.task.max_mark
|
||||
current_solution.comment = request.POST['comment']
|
||||
current_solution.save()
|
||||
return render(request, 'solution.html', context={'solution': current_solution,
|
||||
'from_admin': from_admin,
|
||||
'can_edit': can_edit,
|
||||
'path': request.path})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def solutions(request):
|
||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
||||
try:
|
||||
if not request.user.is_superuser:
|
||||
s = Subscribe.objects.get(user=request.user, course=current_block.course)
|
||||
if not s.is_assistant and not s.user.is_staff:
|
||||
return HttpResponseRedirect('/main')
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponseRedirect('/main')
|
||||
req = ''
|
||||
sols = solutions_filter(request.GET)
|
||||
for key in request.GET.keys():
|
||||
req += '&{}={}'.format(key, request.GET[key])
|
||||
if request.method == 'POST':
|
||||
Solution.objects.get(id=request.POST['DELETE_SOLUTION']).delete()
|
||||
return HttpResponseRedirect('/admin/solutions?block_id={}{}'.format(current_block.id, req))
|
||||
return render(request, 'solutions.html', context={'Block': current_block,
|
||||
'filter': ' '.join([str(sol.id) for sol in sols]),
|
||||
'solutions': sols,
|
||||
'req': req,
|
||||
'options': {key: request.GET[key] for key in request.GET.keys()},
|
||||
'solutions_info': block_solutions_info(current_block)})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def messages(request):
|
||||
return HttpResponseRedirect('/main')
|
||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
||||
return render(request, 'messages.html', context={'Block': current_block})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def users_settings(request):
|
||||
current_course = Course.objects.get(id=request.GET['course_id'])
|
||||
if not check_admin_on_course(request.user, current_course):
|
||||
if not request.user.is_superuser:
|
||||
return HttpResponseRedirect('/main')
|
||||
if request.method == 'POST':
|
||||
if 'input' in request.POST.keys():
|
||||
line = request.POST['input']
|
||||
if '@' in line:
|
||||
users = UserInfo.objects.filter(user__email=line)
|
||||
elif any(c.isdigit() for c in line):
|
||||
users = UserInfo.objects.filter(group=line)
|
||||
else:
|
||||
try:
|
||||
s, n, m = line.split(' ')
|
||||
except ValueError:
|
||||
s, n, m = '', '', ''
|
||||
users = list(UserInfo.objects.filter(surname=s, name=n, middle_name=m)) + list(UserInfo.objects.filter(group=line))
|
||||
for user in users:
|
||||
try:
|
||||
Subscribe.objects.get(user=user.user, course=current_course)
|
||||
except ObjectDoesNotExist:
|
||||
Subscribe.objects.create(user=user.user, course=current_course)
|
||||
elif 'user_delete' in request.POST.keys():
|
||||
username = request.POST['user_delete']
|
||||
course_id = request.GET['course_id']
|
||||
Subscribe.objects.get(user__email=username, course_id=course_id).delete()
|
||||
elif 'file' in request.FILES.keys():
|
||||
users = load(request.FILES['file'])
|
||||
for u in users:
|
||||
password = random_string()
|
||||
flag = False
|
||||
try:
|
||||
user = User.objects.get(email=u['email'])
|
||||
except ObjectDoesNotExist:
|
||||
flag = True
|
||||
if flag:
|
||||
user = register_user(u)
|
||||
try:
|
||||
Subscribe.objects.get(user=user, course=current_course)
|
||||
except ObjectDoesNotExist:
|
||||
Subscribe.objects.create(user=user, course=current_course, is_assistant=False)
|
||||
else:
|
||||
key = list(request.POST.keys())[1]
|
||||
role = request.POST[key]
|
||||
username = '_'.join(key.split('_')[1:])
|
||||
s = Subscribe.objects.get(user__email=username, course=current_course)
|
||||
s.is_assistant = role == 'Ассистент'
|
||||
s.save()
|
||||
return HttpResponseRedirect('/admin/users_settings?course_id=' + str(current_course.id))
|
||||
return render(request, 'users_settings.html', context={'course': current_course})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def task(request):
|
||||
current_task = Task.objects.get(id=request.GET['id'])
|
||||
user = request.user
|
||||
if not check_permission_block(user, current_task.block):
|
||||
return HttpResponseRedirect('/main')
|
||||
can_send = can_send_solution(user, current_task)
|
||||
if request.method == 'POST':
|
||||
if 'message' in request.POST.keys():
|
||||
Message.objects.create(
|
||||
task=current_task,
|
||||
sender=request.user,
|
||||
reply_to=None,
|
||||
for_all=False,
|
||||
text=request.POST['message']
|
||||
)
|
||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
||||
if 'file' in request.FILES.keys() and can_send:
|
||||
current_solution = Solution.objects.create(
|
||||
task=current_task,
|
||||
user=request.user,
|
||||
result='IN QUEUE',
|
||||
time_sent=timezone.now()
|
||||
)
|
||||
log_file_path = current_solution.log_file
|
||||
with open(log_file_path, 'wb') as fs:
|
||||
pass
|
||||
solution_dir = current_solution.path() + sep
|
||||
if exists(solution_dir):
|
||||
rmtree(solution_dir)
|
||||
mkdir(solution_dir)
|
||||
with open(solution_dir + 'solution.zip', 'wb') as fs:
|
||||
for chunk in request.FILES['file'].chunks():
|
||||
fs.write(chunk)
|
||||
flag = False
|
||||
solution_created = False
|
||||
try:
|
||||
with ZipFile(solution_dir + 'solution.zip') as obj:
|
||||
obj.extractall(solution_dir)
|
||||
except BadZipFile:
|
||||
rename(solution_dir + 'solution.zip', solution_dir + request.FILES['file'].name)
|
||||
sln_path = solution_path(solution_dir)
|
||||
if current_task.full_solution != bool(sln_path):
|
||||
current_solution.result = 'TEST ERROR'
|
||||
with open(log_file_path, 'ab') as fs:
|
||||
fs.write(b'Can\'t find sln file in solution' if current_task.full_solution else b'Sln file in path')
|
||||
current_solution.save()
|
||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
||||
if not bool(sln_path):
|
||||
copytree('SampleSolution', join(solution_dir, 'Solution'))
|
||||
for file in listdir(solution_dir):
|
||||
if file == 'solution.zip' or file == 'Solution':
|
||||
continue
|
||||
cur_file = join(solution_dir, file)
|
||||
if isfile(cur_file):
|
||||
copyfile(cur_file, join(solution_dir, 'Solution', 'SampleProject', file))
|
||||
remove(cur_file)
|
||||
else:
|
||||
rmtree(cur_file)
|
||||
if not current_task.full_solution:
|
||||
for file in current_task.files_for_compilation:
|
||||
copyfile(file.path, join(solution_dir, 'Solution', 'SampleProject', file.filename))
|
||||
#Tester(current_solution, request.META['HTTP_HOST']).push()
|
||||
Thread(target=lambda: Tester(current_solution, request.META['HTTP_HOST']).push()).start()
|
||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
||||
return render(request, 'task.html', context={'task': current_task,
|
||||
'solutions': reversed(Solution.objects.filter(task=current_task, user=user)),
|
||||
'can_send': can_send,
|
||||
'can_edit': check_admin_on_course(request.user, current_task.block.course)})
|
||||
|
||||
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def task_test(request):
|
||||
current_task = Task.objects.get(id=request.GET['id'])
|
||||
user = request.user
|
||||
if not check_permission_block(user, current_task.block):
|
||||
return HttpResponseRedirect('/main')
|
||||
can_send = can_send_solution(user, current_task)
|
||||
if request.method == 'POST':
|
||||
if 'file' in request.FILES.keys() and can_send:
|
||||
current_solution = Solution.objects.create(
|
||||
task=current_task,
|
||||
user=request.user,
|
||||
result='IN QUEUE',
|
||||
time_sent=timezone.now()
|
||||
)
|
||||
log_file_path = current_solution.log_file
|
||||
with open(log_file_path, 'wb') as fs:
|
||||
pass
|
||||
solution_dir = current_solution.path() + sep
|
||||
if exists(solution_dir):
|
||||
rmtree(solution_dir)
|
||||
mkdir(solution_dir)
|
||||
with open(solution_dir + 'solution.zip', 'wb') as fs:
|
||||
for chunk in request.FILES['file'].chunks():
|
||||
fs.write(chunk)
|
||||
flag = False
|
||||
solution_created = False
|
||||
try:
|
||||
with ZipFile(solution_dir + 'solution.zip') as obj:
|
||||
obj.extractall(solution_dir)
|
||||
except BadZipFile:
|
||||
rename(solution_dir + 'solution.zip', solution_dir + request.FILES['file'].name)
|
||||
sln_path = solution_path(solution_dir)
|
||||
if current_task.full_solution != bool(sln_path):
|
||||
current_solution.result = 'TEST ERROR'
|
||||
with open(log_file_path, 'ab') as fs:
|
||||
fs.write(b'Can\'t find sln file in solution' if current_task.full_solution else b'Sln file in path')
|
||||
current_solution.save()
|
||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
||||
if not bool(sln_path):
|
||||
copytree('SampleSolution', join(solution_dir, 'Solution'))
|
||||
for file in listdir(solution_dir):
|
||||
if file == 'solution.zip' or file == 'Solution':
|
||||
continue
|
||||
cur_file = join(solution_dir, file)
|
||||
if isfile(cur_file):
|
||||
copyfile(cur_file, join(solution_dir, 'Solution', 'SampleProject', file))
|
||||
remove(cur_file)
|
||||
else:
|
||||
rmtree(cur_file)
|
||||
if not current_task.full_solution:
|
||||
for file in current_task.files_for_compilation:
|
||||
copyfile(file.path, join(solution_dir, 'Solution', 'SampleProject', file.filename))
|
||||
#Tester(current_solution, request.META['HTTP_HOST']).push()
|
||||
Thread(target=lambda: Tester(current_solution, request.META['HTTP_HOST']).push()).start()
|
||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def block(request):
|
||||
current_block = Block.objects.get(id=request.GET['id'])
|
||||
if not check_permission_block(request.user, current_block):
|
||||
return HttpResponseRedirect('/main')
|
||||
return render(request, 'block.html', context={'Block': current_block,
|
||||
'is_admin': check_admin(request.user),
|
||||
'can_edit': check_admin_on_course(request.user, current_block.course),
|
||||
'user': request.user})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def task_settings(request):
|
||||
if not check_admin(request.user):
|
||||
return HttpResponseRedirect('/main')
|
||||
current_task = Task.objects.get(id=request.GET['id'])
|
||||
if request.method == 'POST':
|
||||
action = request.POST['ACTION']
|
||||
if action == 'DELETE':
|
||||
t = Task.objects.get(id=request.GET['id'])
|
||||
block_id = t.block.id
|
||||
t.delete()
|
||||
return HttpResponseRedirect('/admin/block?id=' + str(block_id))
|
||||
elif action.startswith('SAVE_EXTRA_FILE_'):
|
||||
i = action.split('_')[-1]
|
||||
ef = ExtraFile.objects.get(id=int(i))
|
||||
with open(ef.path, 'wb') as fs:
|
||||
file_text = request.POST['extra_file_text_' + i]
|
||||
fs.write(bytes(file_text, encoding='utf-8'))
|
||||
ef.for_compilation = '{}_for_compilation'.format(ef.id) in request.POST.keys()
|
||||
ef.save()
|
||||
elif action == 'SAVE':
|
||||
current_task.legend, current_task.input, current_task.output, current_task.specifications = \
|
||||
request.POST['legend'], request.POST['input'], request.POST['output'], request.POST['specifications']
|
||||
current_task.time_limit = int(request.POST['time_limit']) if is_integer(request.POST['time_limit']) else 10000
|
||||
current_task.show_details = 'show_details' in request.POST.keys()
|
||||
current_task.full_solution = 'full_solution' in request.POST.keys()
|
||||
current_task.mark_formula = request.POST['mark_formula']
|
||||
current_task.show_result = 'show_result' in request.POST.keys()
|
||||
current_task.priority = request.POST['priority']
|
||||
for ef in ExtraFile.objects.filter(task=current_task):
|
||||
ef.sample = 'sample_' + str(ef.id) in request.POST.keys()
|
||||
ef.save()
|
||||
try:
|
||||
current_task.weight = float(request.POST['weight'].replace(',', '.'))
|
||||
except ValueError:
|
||||
current_task.weight = 1.0
|
||||
try:
|
||||
current_task.max_mark = int(request.POST['max_mark'])
|
||||
if current_task.max_mark == 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
current_task.max_mark = 10
|
||||
try:
|
||||
current_task.max_solutions_count = int(request.POST['max_solutions_count'])
|
||||
except ValueError:
|
||||
current_task.max_solutions_count = 10
|
||||
|
||||
elif action == 'UPLOAD_EXTRA_FILE':
|
||||
if request.FILES['file'].name.endswith('.zip'):
|
||||
try:
|
||||
wdir = join(MEDIA_ROOT, 'extra_files', 'files' + str(current_task.id))
|
||||
if exists(wdir):
|
||||
rmtree(wdir)
|
||||
mkdir(wdir)
|
||||
with open(join(wdir, 'file.zip'), 'wb') as fs:
|
||||
for chunk in request.FILES['file'].chunks():
|
||||
fs.write(chunk)
|
||||
with ZipFile(join(wdir, 'file.zip')) as obj:
|
||||
obj.extractall(wdir)
|
||||
remove(join(wdir, 'file.zip'))
|
||||
for file in listdir(wdir):
|
||||
if isfile(join(wdir, file)):
|
||||
try:
|
||||
ef = ExtraFile.objects.get(filename=file, task=current_task)
|
||||
except ObjectDoesNotExist:
|
||||
ef = ExtraFile.objects.create(filename=file, task=current_task)
|
||||
ef.write(open(join(wdir, file), 'rb').read())
|
||||
rmtree(wdir)
|
||||
except BadZipFile:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
ef = ExtraFile.objects.get(filename=request.FILES['file'].name, task=current_task)
|
||||
except ObjectDoesNotExist:
|
||||
ef = ExtraFile.objects.create(filename=request.FILES['file'].name, task=current_task)
|
||||
with open(ef.path, 'wb') as fs:
|
||||
for chunk in request.FILES['file'].chunks():
|
||||
fs.write(chunk)
|
||||
elif action == 'CREATE_EXTRA_FILE':
|
||||
try:
|
||||
ExtraFile.objects.get(task=current_task, filename=request.POST['newfile_name'])
|
||||
except ObjectDoesNotExist:
|
||||
ef = ExtraFile.objects.create(task=current_task, filename=request.POST['newfile_name'])
|
||||
f = open(join(MEDIA_ROOT, 'extra_files', str(ef.id)), 'w')
|
||||
f.close()
|
||||
elif action.startswith('DELETE_FILE_'):
|
||||
ExtraFile.objects.get(id=int(action.split('_')[-1])).delete()
|
||||
elif action == 'SAVE_TESTS':
|
||||
tt = request.POST['tests_text']
|
||||
cs_file = current_task.tests_path()
|
||||
with open(cs_file, 'wb') as fs:
|
||||
fs.write(bytes(tt, encoding='utf-8'))
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
current_task.save()
|
||||
return HttpResponseRedirect('/admin/task?id=' + str(current_task.id))
|
||||
return render(request, 'task_settings.html', context={'task': current_task,
|
||||
'tests': TestsForm(),
|
||||
'is_superuser': check_teacher(request.user)})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def block_settings(request):
|
||||
if not check_admin(request.user):
|
||||
return HttpResponseRedirect('/main')
|
||||
current_block = Block.objects.get(id=request.GET['id'])
|
||||
if not check_permission_block(request.user, current_block):
|
||||
return HttpResponseRedirect('/main')
|
||||
if request.method == 'POST':
|
||||
if 'name' in request.POST.keys():
|
||||
current_block = Block.objects.get(id=request.POST['block_id'])
|
||||
if not check_teacher(request.user) or not check_permission_block(request.user, current_block):
|
||||
return HttpResponseRedirect('/main')
|
||||
task_name = request.POST['name']
|
||||
current_task = Task.objects.create(
|
||||
name=task_name,
|
||||
block=current_block
|
||||
)
|
||||
with open(current_task.tests_path(), 'w') as fs:
|
||||
pass
|
||||
return HttpResponseRedirect('/admin/task?id=' + str(current_task.id))
|
||||
if 'file' in request.FILES.keys():
|
||||
if exists(request.user.username):
|
||||
rmtree(request.user.username)
|
||||
mkdir(request.user.username)
|
||||
with open(join(request.user.username, 'task.zip'), 'wb') as fs:
|
||||
for chunk in request.FILES['file'].chunks():
|
||||
fs.write(chunk)
|
||||
try:
|
||||
with ZipFile(join(request.user.username, 'task.zip')) as obj:
|
||||
obj.extractall(request.user.username)
|
||||
except BadZipFile:
|
||||
rmtree(request.user.username)
|
||||
return HttpResponseRedirect('/admin/block?id={}'.format(current_block.id))
|
||||
task = Task.objects.create(name='Новый таск', block=current_block)
|
||||
root = request.user.username
|
||||
if exists(join(root, 'meta.json')):
|
||||
data = loads(open(join(root, 'meta.json'), 'r', encoding='utf-8').read())
|
||||
task.name = data['localizedNames']['ru']
|
||||
task.time_limit = data['invocationLimits']['idlenessLimitMillis']
|
||||
for f in sorted(listdir(join(root, 'tests'))):
|
||||
e = ExtraFile.objects.create(
|
||||
task=task,
|
||||
filename=f,
|
||||
for_compilation=False
|
||||
)
|
||||
try:
|
||||
e.sample=data['testSets'][str(int(f.split('.')[0]))]['example'] and not f.endswith('.a')
|
||||
except KeyError:
|
||||
e.sample = False
|
||||
e.save()
|
||||
copyfile(join(root, 'tests', f), join(MEDIA_ROOT, 'extra_files', str(e.id)))
|
||||
statements = open(join(root, 'statements', 'ru', 'html', 'statement.html'), 'r', encoding='utf-8').read()
|
||||
task.legend = get_in_html_tag(statements, 'legend')
|
||||
task.input = get_in_html_tag(statements, 'input-specification')
|
||||
task.output = get_in_html_tag(statements, 'output-specification')
|
||||
task.specifications = get_in_html_tag(statements, 'notes')
|
||||
task.save()
|
||||
with open(join(MEDIA_ROOT, 'tests', str(task.id) + '.cs'), 'w') as fs:
|
||||
pass
|
||||
rmtree(root)
|
||||
return HttpResponseRedirect('/admin/task?id={}'.format(task.id))
|
||||
if 'block_delete' in request.POST.keys():
|
||||
Block.objects.get(id=request.POST['block_delete']).delete()
|
||||
return HttpResponseRedirect('/admin/main')
|
||||
else:
|
||||
time_start = make_aware(datetime.strptime(request.POST['time_start'], "%Y-%m-%dT%H:%M") + timedelta(hours=3))
|
||||
time_end = make_aware(datetime.strptime(request.POST['time_end'], "%Y-%m-%dT%H:%M") + timedelta(hours=3))
|
||||
current_block.opened = 'opened' in request.POST.keys()
|
||||
current_block.time_start = time_start
|
||||
current_block.time_end = time_end
|
||||
current_block.show_rating = "rating" in request.POST.keys()
|
||||
current_block.priority = request.POST['priority']
|
||||
current_block.save()
|
||||
return HttpResponseRedirect('/admin/block?id={}'.format(current_block.id))
|
||||
return render(request, 'block_settings.html', context={'is_superuser': check_teacher(request.user),
|
||||
'Block': current_block})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def solutions_table(request):
|
||||
current_task = Task.objects.get(id=request.GET['id'])
|
||||
user = request.user
|
||||
if not check_permission_block(user, current_task.block):
|
||||
return HttpResponse("done")
|
||||
sols = Solution.objects.filter(task=current_task, user=user)
|
||||
can_edit = check_admin_on_course(request.user, current_task.block.course)
|
||||
# тут по хорошему надо использовать регулярки, но я что-то не разобрался
|
||||
if not can_edit:
|
||||
for sol in sols:
|
||||
while True:
|
||||
i = sol.details.find('<pre>')
|
||||
if i == -1:
|
||||
break
|
||||
j = sol.details.find('</pre>') + 6
|
||||
sol.details = sol.details.replace(sol.details[i:j], '')
|
||||
if any(sol.result == 'TESTING' or sol.result == 'IN QUEUE' for sol in sols) or 'render' in request.GET.keys():
|
||||
return render(request, 'solutions_table.html', context={
|
||||
'solutions': reversed(sols),
|
||||
'can_edit': can_edit,
|
||||
'task': current_task})
|
||||
return HttpResponse('done')
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def queue_table(request):
|
||||
block = Block.objects.get(id=request.GET['block_id'])
|
||||
if not check_admin_on_course(request.user, block):
|
||||
return HttpResponse('get away from here')
|
||||
sols = list(Solution.objects.filter(task__block=block, result='TESTING')) + list(Solution.objects.filter(task__block=block, result='IN QUEUE'))
|
||||
return render(request, 'queue_table.html', {'solutions': sorted(sols, key=lambda x: -x.task.block.priority * 10 - x.task.priority)})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def get_result_data(request):
|
||||
solution = Solution.objects.get(id=request.GET['id'])
|
||||
if not check_admin_on_course(request.user, solution.task.block.course):
|
||||
return HttpResponse(dumps({'success': False}))
|
||||
return HttpResponse(dumps({
|
||||
'success': True,
|
||||
'results_text': solution.details,
|
||||
'tests_text': solution.task.tests_text,
|
||||
'log_text': solution.log_text
|
||||
}))
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def get_comment_data(request):
|
||||
solution = Solution.objects.get(id=request.GET['id'])
|
||||
if not check_admin_on_course(request.user, solution.task.block.course):
|
||||
return HttpResponse(dumps({'success': False}))
|
||||
return HttpResponse(dumps({
|
||||
'success': True,
|
||||
'comment_text': solution.comment
|
||||
}))
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def rating(request):
|
||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
||||
if not check_admin_on_course(request.user, current_block.course) and not current_block.show_rating:
|
||||
return HttpResponseRedirect('/main')
|
||||
return render(request, 'rating.html', context={'Block': Block.objects.get(id=request.GET['block_id']), 'admin_course': check_admin_on_course(request.user, current_block.course)})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def cheating(request):
|
||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
||||
if not check_admin_on_course(request.user, current_block.course):
|
||||
return HttpResponseRedirect('/main')
|
||||
if request.method == 'POST':
|
||||
if not current_block.cheating_checking:
|
||||
req = ['_'.join(elem.split('_')[1:]) for elem in request.POST.keys() if elem.startswith('check_')]
|
||||
tasks = Task.objects.filter(id__in=[int(elem.split('_')[1]) for elem in req if elem.startswith('task')])
|
||||
users = User.objects.filter(id__in=[int(elem.split('_')[1]) for elem in req if elem.startswith('user')])
|
||||
solutions = Solution.objects.filter(user__in=users).filter(task__in=tasks)
|
||||
if 'all_tests' in request.POST.keys():
|
||||
solutions = [sol for sol in solutions if sol.passed_all_tests]
|
||||
if 'best_result' in request.POST.keys():
|
||||
sols = {}
|
||||
for solution in solutions:
|
||||
if (solution.user.username, solution.task.id) in sols.keys():
|
||||
comp = result_comparer(sols[(solution.user.username, solution.task.id)][0].result, solution.result)
|
||||
if comp == 1:
|
||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
||||
elif comp == 0:
|
||||
sols[(solution.user.username, solution.task.id)].append(solution)
|
||||
else:
|
||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
||||
solutions = [val for sol in sols.values() for val in sol]
|
||||
solutions = list(sorted(solutions, key=lambda s: s.id))
|
||||
if 'last_solution' in request.POST.keys():
|
||||
sols = {}
|
||||
for sol in solutions:
|
||||
pair = sol.user, sol.task
|
||||
if pair not in sols.keys():
|
||||
sols[pair] = []
|
||||
sols[pair].append(sol)
|
||||
solutions = [sols[key][len(sols[key]) - 1] for key in sols.keys()]
|
||||
Thread(target=check_cheating, args=(solutions, current_block, int(request.POST['cheating_percent']))).start()
|
||||
return HttpResponseRedirect('/admin/cheating?block_id=' + str(current_block.id))
|
||||
return render(request, 'cheating.html', {'Block': current_block})
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def admin(request):
|
||||
if not check_admin(request.user):
|
||||
return HttpResponseRedirect('/main')
|
||||
if request.method == 'POST':
|
||||
if 'invite' in request.POST.keys():
|
||||
register_user(request.POST)
|
||||
return HttpResponseRedirect('/admin/main')
|
||||
name = request.POST['name']
|
||||
course = Course.objects.get(id=request.POST['course_id'])
|
||||
current_block = Block.objects.create(name=name,
|
||||
course=course,
|
||||
opened=False,
|
||||
time_start=timezone.now(),
|
||||
time_end=timezone.now())
|
||||
if not check_teacher(request.user):
|
||||
return HttpResponseRedirect('/main')
|
||||
try:
|
||||
Subscribe.objects.get(user=request.user, course=course)
|
||||
except ObjectDoesNotExist:
|
||||
if not request.user.is_superuser:
|
||||
return HttpResponseRedirect('/main')
|
||||
return HttpResponseRedirect('/admin/block?id=' + str(current_block.id))
|
||||
return render(request, "admin.html", context={"blocks": blocks_available(request.user),
|
||||
'is_superuser': check_god(request.user),
|
||||
'is_teacher': check_teacher(request.user)})
|
||||
|
||||
|
||||
@login_required(False)
|
||||
def reset_password(request):
|
||||
code = request.GET['code']
|
||||
try:
|
||||
res = Restore.objects.get(code=code)
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponseRedirect('/enter')
|
||||
context = {'form': ResetPasswordForm()}
|
||||
if request.method == 'GET':
|
||||
return render(request, 'reset_password.html', context=context)
|
||||
else:
|
||||
if request.POST['new'] != request.POST['again']:
|
||||
context['error'] = 'Пароли не совпадают'
|
||||
return render(request, 'reset_password.html', context=context)
|
||||
else:
|
||||
res.user.set_password(request.POST['new'])
|
||||
res.user.save()
|
||||
res.delete()
|
||||
return HttpResponseRedirect('/enter')
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def settings(request):
|
||||
context = {'is_admin': check_admin(request.user), 'form': ChangePasswordForm()}
|
||||
if request.method == 'POST':
|
||||
old = request.POST['old']
|
||||
new = request.POST['new'].strip()
|
||||
again = request.POST['again'].strip()
|
||||
username = request.user.username
|
||||
user = request.user
|
||||
if user is None:
|
||||
context['error'] = 'Неверный пароль'
|
||||
if len(new) < 8 or not any([a.isdigit() for a in new]) or new.lower() == new:
|
||||
context['error'] = 'Пароль слишком слабый'
|
||||
elif new != again:
|
||||
context['error'] = 'Пароли не совпадают'
|
||||
elif new == '' or new.replace(' ', '') == '':
|
||||
context['error'] = 'Некорректный пароль'
|
||||
else:
|
||||
user.set_password(new)
|
||||
user.save()
|
||||
context['error'] = 'Пароль успешно изменен'
|
||||
login(request, user)
|
||||
return render(request, 'settings.html', context=context)
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def exit(request):
|
||||
logout(request)
|
||||
return HttpResponseRedirect('/enter')
|
||||
|
||||
|
||||
def redirect(request):
|
||||
return HttpResponseRedirect('/main')
|
||||
|
||||
|
||||
@login_required(True)
|
||||
def main(request):
|
||||
return render(request, 'main.html', context={'blocks': blocks_available(request.user)})
|
||||
|
||||
|
||||
@login_required(False)
|
||||
def restore(request):
|
||||
if request.method == 'GET':
|
||||
return render(request, 'restore.html')
|
||||
else:
|
||||
email = request.POST['email']
|
||||
try:
|
||||
user = User.objects.get(email=email)
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponseRedirect('/enter')
|
||||
h = get_restore_hash()
|
||||
try:
|
||||
r = Restore.objects.get(user__email=email)
|
||||
r.code = h
|
||||
r.save()
|
||||
except ObjectDoesNotExist:
|
||||
Restore.objects.create(user=user, code=h)
|
||||
send_email('Reset password',
|
||||
email,
|
||||
'Restore your password using this link:\nhttp://{}/reset_password?code={}'
|
||||
.format(request.META['HTTP_HOST'], h))
|
||||
return HttpResponseRedirect('/enter')
|
||||
|
||||
|
||||
@login_required(False)
|
||||
def enter(request):
|
||||
if check_login(request.user):
|
||||
return HttpResponseRedirect('/main')
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
user = User.objects.get(username=request.POST['email'])
|
||||
except ObjectDoesNotExist:
|
||||
try:
|
||||
user = User.objects.get(email=request.POST['email'])
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponseRedirect('/enter?error_message=Данного пользователя не существует')
|
||||
user = authenticate(username=user.username, password=request.POST['password'].strip())
|
||||
if user is not None:
|
||||
login(request, user)
|
||||
return HttpResponseRedirect('/enter?error_message=Неверный пароль')
|
||||
return render(request, "enter.html", context={"form": LoginForm(), 'error_message': request.GET.get('error_message', '')})
|
||||
|
||||
|
||||
@login_required(False)
|
||||
def register(request):
|
||||
if request.method == 'POST':
|
||||
if len(request.POST['password'].strip()) < 8:
|
||||
return HttpResponseRedirect('/register?error_message=Пароль слишком слабый')
|
||||
if request.POST['password'] != request.POST['repeat_password']:
|
||||
return HttpResponseRedirect('/register?error_message=Пароли не совпадают')
|
||||
if len(User.objects.filter(username=request.POST['username'])):
|
||||
return HttpResponseRedirect('/register?error_message=Данное имя пользователя уже занято')
|
||||
if len(User.objects.filter(email=request.POST['email'])):
|
||||
return HttpResponseRedirect('/register?error_message=Пользователь под таким email уже зарегистрирован')
|
||||
user = User.objects.create_user(request.POST['username'], request.POST['email'], password=request.POST['password'])
|
||||
userinfo = UserInfo.objects.create(
|
||||
surname = request.POST['surname'],
|
||||
name = request.POST['name'],
|
||||
middle_name = request.POST['middle_name']
|
||||
)
|
||||
user.userinfo = userinfo
|
||||
user.save()
|
||||
return HttpResponseRedirect('/enter')
|
||||
return render(request, 'register.html', {'error_message': request.GET.get('error_message', '')})
|
40
Main/views/AccountView.py
Normal file
40
Main/views/AccountView.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class AccountView(BaseView):
|
||||
view_file = "account.html"
|
||||
required_login = True
|
||||
|
||||
def get(self):
|
||||
if "username" in self.request.GET.keys():
|
||||
self.context["account"] = User.objects.get(
|
||||
username=self.request.GET["username"]
|
||||
)
|
||||
else:
|
||||
self.context["account"] = self.request.user
|
||||
self.context["owner"] = self.context["account"] == self.request.user
|
||||
self.context["error_message"] = self.request.GET.get("error_message", "")
|
||||
|
||||
def post_upload_photo(self):
|
||||
self.request.user.userinfo.profile_picture.delete()
|
||||
self.request.user.userinfo.profile_picture = self.request.FILES["file"]
|
||||
self.request.user.userinfo.save()
|
||||
return "/account"
|
||||
|
||||
def post_change_password(self):
|
||||
password = self.request.POST["password"].strip()
|
||||
new_password = self.request.POST["new_password"].strip()
|
||||
user = authenticate(username=self.request.user.username, password=password)
|
||||
if not user:
|
||||
return "/account?error_message=Неправильно указан пароль"
|
||||
if len(new_password) < 8:
|
||||
return "/account?error_message=Пароль слишком слабый"
|
||||
if new_password != self.request.POST["repeat"]:
|
||||
return "/account?error_message=Пароли не совпадают"
|
||||
self.request.user.set_password(new_password)
|
||||
self.request.user.save()
|
||||
login(self.request, self.request.user)
|
||||
return "/account?error_message=Пароль успешно установлен"
|
29
Main/views/EnterView.py
Normal file
29
Main/views/EnterView.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class EnterView(BaseView):
|
||||
view_file = "enter.html"
|
||||
required_login = False
|
||||
|
||||
def get(self):
|
||||
self.context["error_message"] = self.request.GET.get("error_message", "")
|
||||
|
||||
def post(self):
|
||||
try:
|
||||
user = User.objects.get(username=self.request.POST["email"])
|
||||
except ObjectDoesNotExist:
|
||||
try:
|
||||
user = User.objects.get(email=self.request.POST["email"])
|
||||
except ObjectDoesNotExist:
|
||||
return "/enter?error_message=Данного пользователя не существует"
|
||||
user = authenticate(
|
||||
username=user.username, password=self.request.POST["password"].strip()
|
||||
)
|
||||
if user is not None:
|
||||
login(self.request, user)
|
||||
return "/"
|
||||
return "/enter?error_message=Неверный пароль"
|
11
Main/views/ExitView.py
Normal file
11
Main/views/ExitView.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.contrib.auth import logout
|
||||
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class ExitView(BaseView):
|
||||
required_login = True
|
||||
|
||||
def get(self):
|
||||
logout(self.request)
|
||||
return "/"
|
6
Main/views/MainView.py
Normal file
6
Main/views/MainView.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class MainView(BaseView):
|
||||
view_file = "main.html"
|
||||
required_login = True
|
11
Main/views/RatingView.py
Normal file
11
Main/views/RatingView.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class RatingView(BaseView):
|
||||
view_file = "rating.html"
|
||||
required_login = True
|
||||
|
||||
def get(self):
|
||||
self.context["users"] = User.objects.all().order_by('-userinfo__rating')
|
40
Main/views/RegisterView.py
Normal file
40
Main/views/RegisterView.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from Main.models import UserInfo
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class RegisterView(BaseView):
|
||||
view_file = "register.html"
|
||||
required_login = False
|
||||
|
||||
def get(self):
|
||||
self.context["error_message"] = self.request.GET.get("error_message", "")
|
||||
|
||||
def post(self):
|
||||
data = {**self.request.POST}
|
||||
data["password"] = data["password"].strip()
|
||||
if len(data["password"]) < 8:
|
||||
return "/register?error_message=Пароль слишком слабый"
|
||||
if data["password"] != data["repeat_password"]:
|
||||
return "/register?error_message=Пароли не совпадают"
|
||||
if len(User.objects.filter(username=data["username"])):
|
||||
return "/register?error_message=Данное имя пользователя уже занято"
|
||||
|
||||
if len(User.objects.filter(email=data["email"])):
|
||||
return "/register?error_message=Пользователь под таким email уже зарегистрирован"
|
||||
user = User.objects.create_user(
|
||||
data["username"],
|
||||
data["email"],
|
||||
password=data["password"],
|
||||
)
|
||||
userinfo = UserInfo.objects.create(
|
||||
surname=data["surname"],
|
||||
name=data["name"],
|
||||
middle_name=data["middle_name"],
|
||||
user=user,
|
||||
)
|
||||
user.userinfo = userinfo
|
||||
user.save()
|
||||
# todo: реализовать подтверждение по почте
|
||||
return "/enter"
|
12
Main/views/SetsView.py
Normal file
12
Main/views/SetsView.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from Main.models import Set
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class SetsView(BaseView):
|
||||
view_file = "sets.html"
|
||||
required_login = True
|
||||
|
||||
def post(self):
|
||||
task_name = self.request.POST["name"]
|
||||
task = Set.objects.create(name=task_name, creator=self.request.user)
|
||||
return f"/admin/task?task_id={task.id}"
|
21
Main/views/SolutionsTableView.py
Normal file
21
Main/views/SolutionsTableView.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.http import HttpResponse
|
||||
|
||||
from Main.models import Solution
|
||||
from Sprint.settings import CONSTS
|
||||
from SprintLib.BaseView import BaseView
|
||||
|
||||
|
||||
class SolutionsTableView(BaseView):
|
||||
view_file = 'solutions_table.html'
|
||||
required_login = True
|
||||
|
||||
def get(self):
|
||||
self.context['solutions'] = Solution.objects.filter(
|
||||
user=self.request.user, task=self.entities.task
|
||||
).order_by("-time_sent")
|
||||
if 'render' in self.request.GET.keys():
|
||||
return
|
||||
for sol in self.context['solutions']:
|
||||
if sol.result == CONSTS['testing_status'] or sol.result == CONSTS['in_queue_status']:
|
||||
return
|
||||
return HttpResponse('done')
|
80
Main/views/TaskSettingsView.py
Normal file
80
Main/views/TaskSettingsView.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import HttpResponse
|
||||
|
||||
from Main.models import ExtraFile
|
||||
from SprintLib.BaseView import BaseView, AccessError
|
||||
|
||||
|
||||
class TaskSettingsView(BaseView):
|
||||
view_file = "task_settings.html"
|
||||
required_login = True
|
||||
|
||||
def pre_handle(self):
|
||||
if self.entities.task not in self.request.user.userinfo.available_tasks:
|
||||
raise AccessError()
|
||||
|
||||
def get(self):
|
||||
self.context['error_message'] = self.request.GET.get('error_message', '')
|
||||
|
||||
def post(self):
|
||||
for key, value in self.request.POST.items():
|
||||
setattr(self.entities.task, key, value.strip())
|
||||
self.entities.task.save()
|
||||
return f"/admin/task?task_id={self.entities.task.id}"
|
||||
|
||||
def _upload(self, is_test):
|
||||
filename = self.request.FILES['file'].name
|
||||
ef, created = None, None
|
||||
if is_test:
|
||||
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))
|
||||
if not created:
|
||||
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(
|
||||
task=self.entities.task,
|
||||
filename=filename,
|
||||
is_test=is_test
|
||||
)
|
||||
with open(ef.path, 'wb') as fs:
|
||||
for chunk in self.request.FILES['file'].chunks():
|
||||
fs.write(chunk)
|
||||
try:
|
||||
open(ef.path, 'r').read()
|
||||
ef.readable = True
|
||||
except UnicodeDecodeError:
|
||||
ef.readable = False
|
||||
ef.save()
|
||||
return '/admin/task?task_id=' + str(self.entities.task.id)
|
||||
|
||||
def post_file_upload(self):
|
||||
return self._upload(False)
|
||||
|
||||
def post_test_upload(self):
|
||||
return self._upload(True)
|
||||
|
||||
def post_delete_file(self):
|
||||
ef = ExtraFile.objects.get(id=self.request.POST['id'])
|
||||
ef.delete()
|
||||
return HttpResponse("ok")
|
||||
|
||||
def _create(self, is_test):
|
||||
name = self.request.POST['newfile_name']
|
||||
|
||||
ef, created = ExtraFile.objects.get_or_create(filename=name, task=self.entities.task)
|
||||
if not created:
|
||||
return f'/admin/task?task_id={self.entities.task.id}&error_message=Файл с таким именем уже существует'
|
||||
with open(ef.path, 'w') as fs:
|
||||
fs.write('')
|
||||
ef.is_test = is_test
|
||||
ef.readable = True
|
||||
ef.save()
|
||||
return f'/admin/task?task_id={self.entities.task.id}'
|
||||
|
||||
def post_create_file(self):
|
||||
return self._create(False)
|
||||
|
||||
def post_create_test(self):
|
||||
return self._create(True)
|
46
Main/views/TaskView.py
Normal file
46
Main/views/TaskView.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from zipfile import ZipFile
|
||||
|
||||
from Main.models import Solution
|
||||
from Main.tasks import start_testing
|
||||
from SprintLib.BaseView import BaseView, Language
|
||||
from SprintLib.testers import *
|
||||
|
||||
|
||||
class TaskView(BaseView):
|
||||
required_login = True
|
||||
view_file = "task.html"
|
||||
|
||||
def get(self):
|
||||
self.context['languages'] = Language.objects.filter(opened=True).order_by('name')
|
||||
|
||||
def pre_handle(self):
|
||||
if self.request.method == 'GET':
|
||||
return
|
||||
self.solution = Solution.objects.create(
|
||||
task=self.entities.task,
|
||||
user=self.request.user,
|
||||
language_id=self.request.POST["language"]
|
||||
)
|
||||
self.solution.create_dirs()
|
||||
|
||||
def post_0(self):
|
||||
# отправка решения через текст
|
||||
filename = 'solution.' + self.solution.language.file_type
|
||||
file_path = join(self.solution.directory, filename)
|
||||
with open(file_path, 'w') as fs:
|
||||
fs.write(self.request.POST['code'])
|
||||
start_testing.delay(self.solution.id)
|
||||
return "task?task_id=" + str(self.entities.task.id)
|
||||
|
||||
def post_1(self):
|
||||
# отправка решения через файл
|
||||
filename = self.request.FILES['file'].name
|
||||
file_path = join(self.solution.directory, filename)
|
||||
with open(file_path, 'wb') as fs:
|
||||
for chunk in self.request.FILES['file'].chunks():
|
||||
fs.write(chunk)
|
||||
if filename.endswith('.zip'):
|
||||
with ZipFile(file_path) as obj:
|
||||
obj.extractall(self.solution.directory)
|
||||
start_testing.delay(self.solution.id)
|
||||
return "task?task_id=" + str(self.entities.task.id)
|
13
Main/views/TasksView.py
Normal file
13
Main/views/TasksView.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from Main.models import Task
|
||||
from SprintLib.BaseView import BaseView
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
class TasksView(BaseView):
|
||||
view_file = "tasks.html"
|
||||
required_login = True
|
||||
|
||||
def post(self):
|
||||
task_name = self.request.POST["name"]
|
||||
task = Task.objects.create(name=task_name, creator=self.request.user)
|
||||
return f"/admin/task?task_id={task.id}"
|
11
Main/views/__init__.py
Normal file
11
Main/views/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from Main.views.EnterView import EnterView
|
||||
from Main.views.RegisterView import RegisterView
|
||||
from Main.views.MainView import MainView
|
||||
from Main.views.TasksView import TasksView
|
||||
from Main.views.AccountView import AccountView
|
||||
from Main.views.ExitView import ExitView
|
||||
from Main.views.TaskSettingsView import TaskSettingsView
|
||||
from Main.views.RatingView import RatingView
|
||||
from Main.views.SetsView import SetsView
|
||||
from Main.views.TaskView import TaskView
|
||||
from Main.views.SolutionsTableView import SolutionsTableView
|
@@ -0,0 +1,3 @@
|
||||
from .celery import app as celery
|
||||
|
||||
__all__ = ('celery',)
|
||||
|
@@ -11,6 +11,6 @@ import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Sprint.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
|
17
Sprint/celery.py
Normal file
17
Sprint/celery.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
|
||||
# Set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings')
|
||||
|
||||
app = Celery('Sprint')
|
||||
|
||||
# Using a string here means the worker doesn't have to serialize
|
||||
# the configuration object to child processes.
|
||||
# - namespace='CELERY' means all celery-related configuration keys
|
||||
# should have a `CELERY_` prefix.
|
||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||
|
||||
# Load task modules from all registered Django apps.
|
||||
app.autodiscover_tasks()
|
@@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '-w#*mn6*fa8a=(-c0@klx&$vl%hpiy&l(u*3%0a#2)wdt##(z2'
|
||||
SECRET_KEY = "-w#*mn6*fa8a=(-c0@klx&$vl%hpiy&l(u*3%0a#2)wdt##(z2"
|
||||
|
||||
DEPLOY = False
|
||||
|
||||
@@ -30,69 +30,65 @@ DEBUG = not DEPLOY
|
||||
SECURE_SSL_REDIRECT = DEPLOY
|
||||
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'*'
|
||||
]
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'grappelli',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'Main.apps.MainConfig'
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"Main.apps.MainConfig",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'Sprint.urls'
|
||||
ROOT_URLCONF = "Sprint.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')]
|
||||
,
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'Main.context_processors.attributes'
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"Main.context_processors.attributes",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'Sprint.wsgi.application'
|
||||
WSGI_APPLICATION = "Sprint.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': os.getenv('POSTGRES_DB'),
|
||||
'USER': os.getenv('POSTGRES_USER'),
|
||||
'PASSWORD': os.getenv('POSTGRES_PASSWORD'),
|
||||
'HOST': '0.0.0.0',
|
||||
'PORT': '5432',
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"NAME": "postgres",
|
||||
"USER": "postgres",
|
||||
"PASSWORD": "password",
|
||||
"HOST": "0.0.0.0",
|
||||
"PORT": "5432",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,16 +98,16 @@ DATABASES = {
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -121,14 +117,11 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
|
||||
_ = lambda s: s
|
||||
|
||||
LANGUAGES = (
|
||||
('en', _('English')),
|
||||
('ru', _('Russian'))
|
||||
)
|
||||
LANGUAGES = (("en", _("English")), ("ru", _("Russian")))
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = 'Europe/Moscow'
|
||||
TIME_ZONE = "Europe/Moscow"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
@@ -140,11 +133,13 @@ USE_TZ = True
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_URL = "/static/"
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'data')
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
DATA_ROOT = os.path.join(BASE_DIR, "data")
|
||||
SOLUTIONS_ROOT = os.path.join(DATA_ROOT, "solutions")
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "Main/static"),
|
||||
@@ -152,6 +147,17 @@ STATICFILES_DIRS = [
|
||||
|
||||
|
||||
# Authentication backends
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
)
|
||||
AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",)
|
||||
|
||||
|
||||
# Celery Configuration Options
|
||||
CELERY_TIMEZONE = "Europe/Moscow"
|
||||
CELERY_TASK_TRACK_STARTED = True
|
||||
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
|
||||
|
||||
CONSTS = {
|
||||
"online_status": "Online",
|
||||
"in_queue_status": "In queue",
|
||||
"testing_status": "Testing",
|
||||
"ok_status": "OK",
|
||||
}
|
||||
|
@@ -1,39 +1,20 @@
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path, include
|
||||
from Main import views
|
||||
from django.urls import path
|
||||
from Main.views import *
|
||||
from Sprint import settings
|
||||
|
||||
urlpatterns = [
|
||||
path('grappelli/', include('grappelli.urls')), # grappelli URLS
|
||||
path('main', views.main),
|
||||
path('settings', views.settings),
|
||||
path('enter', views.enter, name='enter'),
|
||||
path('register', views.register),
|
||||
path('restore', views.restore, name='restore'),
|
||||
path('reset_password', views.reset_password),
|
||||
path('exit', views.exit),
|
||||
path('block', views.block),
|
||||
path('task', views.task),
|
||||
path('solution', views.solution),
|
||||
path('rating', views.rating),
|
||||
path('messages', views.messages),
|
||||
path('admin/rating', views.rating),
|
||||
path('admin/download_rating', views.download_rating),
|
||||
path('admin/solution', views.solution),
|
||||
path('admin/retest', views.retest),
|
||||
path('admin/docs', views.docs),
|
||||
path('admin/block', views.block_settings),
|
||||
path('admin/task', views.task_settings),
|
||||
path('admin/main', views.admin),
|
||||
path('admin/solutions', views.solutions),
|
||||
path('admin/users_settings', views.users_settings),
|
||||
path('admin/download', views.download),
|
||||
path('admin/queue', views.queue),
|
||||
path('admin/cheating', views.cheating),
|
||||
path('queue_table', views.queue_table),
|
||||
path('task_test', views.task_test),
|
||||
path('solutions_table', views.solutions_table),
|
||||
path('get_result_data', views.get_result_data),
|
||||
path('get_comment_data', views.get_comment_data),
|
||||
path('admin/', admin.site.urls),
|
||||
re_path('^', views.redirect)
|
||||
]
|
||||
path("enter", EnterView.as_view()),
|
||||
path("register", RegisterView.as_view()),
|
||||
path("rating", RatingView.as_view()),
|
||||
path("tasks", TasksView.as_view()),
|
||||
path("account", AccountView.as_view()),
|
||||
path("exit", ExitView.as_view()),
|
||||
path("admin/task", TaskSettingsView.as_view()),
|
||||
path("sets", SetsView.as_view()),
|
||||
path("task", TaskView.as_view()),
|
||||
path("solutions_table", SolutionsTableView.as_view()),
|
||||
path("", MainView.as_view()),
|
||||
path("admin/", admin.site.urls),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
@@ -11,6 +11,6 @@ import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Sprint.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
18
SprintLib/BaseDaemon.py
Normal file
18
SprintLib/BaseDaemon.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import asyncio
|
||||
import sys
|
||||
from time import sleep
|
||||
|
||||
|
||||
class BaseDaemon:
|
||||
def command(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
async def execute(self):
|
||||
cmd = self.command()
|
||||
proc = await asyncio.create_subprocess_shell(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
|
||||
stdout, stderr = await proc.communicate()
|
||||
print(f"[{cmd!r} exited with {proc.returncode}]")
|
||||
if stdout:
|
||||
print(f"[stdout]\n{stdout.decode()}")
|
||||
if stderr:
|
||||
print(f"[stderr]\n{stderr.decode()}")
|
72
SprintLib/BaseView.py
Normal file
72
SprintLib/BaseView.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
|
||||
from SprintLib.EntityStorage import EntityStorage
|
||||
from Main.models import *
|
||||
|
||||
|
||||
class AccessError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseView:
|
||||
request: WSGIRequest = None
|
||||
context: dict = {}
|
||||
entities = EntityStorage()
|
||||
required_login: bool = None
|
||||
view_file: str = None
|
||||
|
||||
@classmethod
|
||||
def as_view(cls):
|
||||
def execute(request):
|
||||
if request.user.is_authenticated:
|
||||
user_info = request.user.userinfo
|
||||
user_info.last_request = timezone.now()
|
||||
user_info.save()
|
||||
c = cls()
|
||||
if c.required_login is not None:
|
||||
if c.required_login and not request.user.is_authenticated:
|
||||
return HttpResponseRedirect("/enter")
|
||||
if not c.required_login and request.user.is_authenticated:
|
||||
return HttpResponseRedirect("/")
|
||||
request_method = request.method.lower()
|
||||
c.request = request
|
||||
for key in request.GET.keys():
|
||||
if key.endswith("_id"):
|
||||
model_name = key.rstrip("_id")
|
||||
c.entities.add(
|
||||
model_name,
|
||||
eval(model_name.capitalize()).objects.get(
|
||||
id=int(request.GET[key])
|
||||
),
|
||||
)
|
||||
context = c.entities.entities
|
||||
if "action" in request.POST.keys():
|
||||
request_method += "_" + request.POST["action"]
|
||||
method = getattr(c, request_method, None)
|
||||
try:
|
||||
data = c.pre_handle()
|
||||
if method:
|
||||
if data is None:
|
||||
data = method()
|
||||
if type(data) == str:
|
||||
return HttpResponseRedirect(data)
|
||||
if data is not None:
|
||||
return data
|
||||
context = {**context, **c.context}
|
||||
return render(request, c.view_file, context)
|
||||
except AccessError:
|
||||
return HttpResponseRedirect("/")
|
||||
|
||||
return execute
|
||||
|
||||
def pre_handle(self):
|
||||
pass
|
||||
|
||||
def get(self):
|
||||
pass
|
||||
|
||||
def post(self):
|
||||
pass
|
6
SprintLib/EntityStorage.py
Normal file
6
SprintLib/EntityStorage.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class EntityStorage:
|
||||
entities = {}
|
||||
|
||||
def add(self, name, entity):
|
||||
self.entities[name] = entity
|
||||
setattr(self, name, entity)
|
0
SprintLib/__init__.py
Normal file
0
SprintLib/__init__.py
Normal file
80
SprintLib/testers/BaseTester.py
Normal file
80
SprintLib/testers/BaseTester.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from os import listdir
|
||||
from os.path import join
|
||||
from shutil import copyfile, rmtree
|
||||
from subprocess import call, TimeoutExpired
|
||||
|
||||
from Main.models import ExtraFile
|
||||
from Sprint.settings import CONSTS
|
||||
from SprintLib.utils import copy_content
|
||||
|
||||
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseTester:
|
||||
working_directory = "app"
|
||||
|
||||
def before_test(self):
|
||||
files = [file for file in listdir(self.solution.testing_directory) if file.endswith('.' + self.solution.language.file_type)]
|
||||
code = self.solution.exec_command(f'{self.build_command} {" ".join(files)}', working_directory=self.working_directory)
|
||||
if code != 0:
|
||||
raise TestException('CE')
|
||||
|
||||
def test(self, filename):
|
||||
code = self.solution.exec_command(
|
||||
f"< {filename} {self.command} > output.txt",
|
||||
timeout=self.solution.task.time_limit / 1000,
|
||||
)
|
||||
if code != 0:
|
||||
raise TestException("RE")
|
||||
result = open(
|
||||
join(self.solution.testing_directory, "output.txt"), "r"
|
||||
).read()
|
||||
if result.strip() != self.predicted.strip():
|
||||
raise TestException("WA")
|
||||
|
||||
def after_test(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return "./executable.exe"
|
||||
|
||||
@property
|
||||
def build_command(self):
|
||||
return ""
|
||||
|
||||
def __init__(self, solution):
|
||||
self.solution = solution
|
||||
|
||||
def execute(self):
|
||||
copy_content(self.solution.directory, self.solution.testing_directory, ('test_dir',))
|
||||
self.solution.result = CONSTS["testing_status"]
|
||||
self.solution.save()
|
||||
call(
|
||||
f"docker run --name solution_{self.solution.id} --volume={self.solution.testing_directory}:/{self.working_directory} -t -d {self.solution.language.image}",
|
||||
shell=True,
|
||||
)
|
||||
for file in ExtraFile.objects.filter(task=self.solution.task):
|
||||
copyfile(file.path, join(self.solution.testing_directory, file.filename))
|
||||
try:
|
||||
self.before_test()
|
||||
for test in self.solution.task.tests:
|
||||
if not test.filename.endswith(".a"):
|
||||
self.predicted = ExtraFile.objects.get(
|
||||
task=self.solution.task, filename=test.filename + ".a"
|
||||
).text
|
||||
self.test(test.filename)
|
||||
self.after_test()
|
||||
self.solution.result = CONSTS["ok_status"]
|
||||
except TestException as e:
|
||||
self.solution.result = str(e)
|
||||
except TimeoutExpired:
|
||||
self.solution.result = "TL"
|
||||
except Exception as e:
|
||||
self.solution.result = "TE"
|
||||
print(str(e))
|
||||
self.solution.save()
|
||||
call(f"docker rm --force solution_{self.solution.id}", shell=True)
|
||||
rmtree(self.solution.testing_directory)
|
11
SprintLib/testers/CSharpTester.py
Normal file
11
SprintLib/testers/CSharpTester.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from SprintLib.testers import BaseTester
|
||||
|
||||
|
||||
class CSharpTester(BaseTester):
|
||||
@property
|
||||
def build_command(self):
|
||||
return "csc /out:executable.exe"
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return "mono executable.exe"
|
9
SprintLib/testers/CppTester.py
Normal file
9
SprintLib/testers/CppTester.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from os import listdir
|
||||
|
||||
from SprintLib.testers import BaseTester, TestException
|
||||
|
||||
|
||||
class CppTester(BaseTester):
|
||||
@property
|
||||
def build_command(self):
|
||||
return "g++ -o executable.exe"
|
8
SprintLib/testers/GoTester.py
Normal file
8
SprintLib/testers/GoTester.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from SprintLib.testers import BaseTester
|
||||
|
||||
|
||||
class GoTester(BaseTester):
|
||||
working_directory = "../app"
|
||||
|
||||
def build_command(self):
|
||||
return "go build -o executable.exe"
|
23
SprintLib/testers/JavaTester.py
Normal file
23
SprintLib/testers/JavaTester.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from os import listdir
|
||||
|
||||
from SprintLib.testers import BaseTester, TestException
|
||||
|
||||
|
||||
class JavaTester(BaseTester):
|
||||
_executable = None
|
||||
|
||||
def before_test(self):
|
||||
files = [file for file in listdir(self.solution.testing_directory) if file.endswith('.java')]
|
||||
code = self.solution.exec_command(f"javac {' '.join(files)}")
|
||||
if code != 0:
|
||||
raise TestException('CE')
|
||||
for file in listdir(self.solution.testing_directory):
|
||||
if file.endswith('.class'):
|
||||
self._executable = file.rstrip('.class')
|
||||
break
|
||||
if self._executable is None:
|
||||
raise TestException("TE")
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return f"java -classpath . {self._executable}"
|
15
SprintLib/testers/KotlinTester.py
Normal file
15
SprintLib/testers/KotlinTester.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from os import listdir
|
||||
|
||||
from SprintLib.testers import BaseTester, TestException
|
||||
|
||||
|
||||
class KotlinTester(BaseTester):
|
||||
def before_test(self):
|
||||
files = [file for file in listdir(self.solution.testing_directory) if file.endswith('.kt')]
|
||||
code = self.solution.exec_command(f'kotlinc {" ".join(files)} -include-runtime -d solution.jar')
|
||||
if code != 0:
|
||||
raise TestException('CE')
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return "java -jar solution.jar"
|
19
SprintLib/testers/Python3Tester.py
Normal file
19
SprintLib/testers/Python3Tester.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from os import listdir
|
||||
|
||||
from SprintLib.testers.BaseTester import BaseTester, TestException
|
||||
|
||||
|
||||
class Python3Tester(BaseTester):
|
||||
file = None
|
||||
|
||||
def before_test(self):
|
||||
for file in listdir(self.solution.testing_directory):
|
||||
if file.endswith(".py"):
|
||||
self.file = file
|
||||
break
|
||||
if self.file is None:
|
||||
raise TestException("TE")
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return f"python {self.file}"
|
7
SprintLib/testers/__init__.py
Normal file
7
SprintLib/testers/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .BaseTester import *
|
||||
from .Python3Tester import *
|
||||
from .CppTester import *
|
||||
from .GoTester import *
|
||||
from .JavaTester import *
|
||||
from .CSharpTester import *
|
||||
from .KotlinTester import *
|
15
SprintLib/utils.py
Normal file
15
SprintLib/utils.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
from shutil import copyfile, copytree
|
||||
|
||||
|
||||
def copy_content(from_dir, to_dir, exc=()):
|
||||
for file in listdir(from_dir):
|
||||
if file in exc:
|
||||
continue
|
||||
full_path = join(from_dir, file)
|
||||
if isfile(full_path):
|
||||
func = copyfile
|
||||
else:
|
||||
func = copytree
|
||||
func(full_path, join(to_dir, file))
|
3
apply_models.sh
Normal file
3
apply_models.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
source venv/bin/activate
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
0
daemons/__init__.py
Normal file
0
daemons/__init__.py
Normal file
6
daemons/celery.py
Normal file
6
daemons/celery.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from SprintLib.BaseDaemon import BaseDaemon
|
||||
|
||||
|
||||
class Daemon(BaseDaemon):
|
||||
def command(self):
|
||||
return "celery -A Sprint worker -l INFO --concurrency=4"
|
6
daemons/redis.py
Normal file
6
daemons/redis.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from SprintLib.BaseDaemon import BaseDaemon
|
||||
|
||||
|
||||
class Daemon(BaseDaemon):
|
||||
def command(self):
|
||||
return "redis-server"
|
6
daemons/web.py
Normal file
6
daemons/web.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from SprintLib.BaseDaemon import BaseDaemon
|
||||
|
||||
|
||||
class Daemon(BaseDaemon):
|
||||
def command(self):
|
||||
return "python manage.py runserver"
|
@@ -2,26 +2,14 @@ version: "3"
|
||||
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
command: redis-server
|
||||
celery:
|
||||
restart: always
|
||||
build: .
|
||||
command: "celery -A Sprint worker -l INFO"
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
postgres:
|
||||
restart: always
|
||||
image: postgres
|
||||
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgres
|
||||
- ./dbscripts/postgres:/docker-entrypoint-initdb.d
|
||||
|
@@ -1,32 +1,30 @@
|
||||
amqp==1.4.9
|
||||
amqp==5.0.6
|
||||
anyjson==0.3.3
|
||||
asgiref==3.4.1
|
||||
billiard==3.3.0.23
|
||||
celery==3.1.26.post2
|
||||
click==7.1.2
|
||||
asgiref==3.3.4
|
||||
billiard==3.6.4.0
|
||||
cached-property==1.5.2
|
||||
celery==5.2.0b2
|
||||
click==8.0.1
|
||||
click-didyoumean==0.0.3
|
||||
click-plugins==1.1.1
|
||||
click-repl==0.2.0
|
||||
copydetect==0.2.1
|
||||
cycler==0.10.0
|
||||
Django==3.2.5
|
||||
django-celery==3.3.1
|
||||
django-grappelli==2.15.1
|
||||
Jinja2==3.0.1
|
||||
kiwisolver==1.3.1
|
||||
kombu==3.0.37
|
||||
MarkupSafe==2.0.1
|
||||
matplotlib==3.4.2
|
||||
numpy==1.21.0
|
||||
dj-database-url==0.5.0
|
||||
Django==3.2.4
|
||||
gunicorn==20.1.0
|
||||
importlib-metadata==4.5.0
|
||||
kombu==5.1.0
|
||||
numpy==1.20.3
|
||||
pandas==1.2.4
|
||||
Pillow==8.3.1
|
||||
prompt-toolkit==3.0.19
|
||||
prompt-toolkit==3.0.18
|
||||
psycopg2==2.9.1
|
||||
Pygments==2.9.0
|
||||
pyparsing==2.4.7
|
||||
psycopg2-binary==2.9.1
|
||||
python-dateutil==2.8.1
|
||||
pytz==2021.1
|
||||
redis==3.5.3
|
||||
six==1.16.0
|
||||
sqlparse==0.4.1
|
||||
tqdm==4.61.2
|
||||
typing-extensions==3.10.0.0
|
||||
vine==5.0.0
|
||||
wcwidth==0.2.5
|
||||
zipp==3.5.0
|
||||
|
1
setup.sh
1
setup.sh
@@ -3,4 +3,3 @@ pip3 install venv
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
docker-compose up -d
|
||||
|
36
start.py
Executable file
36
start.py
Executable file
@@ -0,0 +1,36 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from os import listdir
|
||||
|
||||
|
||||
async def execute(service):
|
||||
while True:
|
||||
try:
|
||||
mod = __import__(f'daemons.{service}')
|
||||
module = getattr(mod, service)
|
||||
daemon = getattr(module, 'Daemon')
|
||||
await daemon().execute()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
async def main():
|
||||
if sys.argv[1] == "--all":
|
||||
services = list(
|
||||
map(
|
||||
lambda x: x[:-3],
|
||||
[file for file in listdir("daemons") if file.endswith(".py") and file != '__init__.py'],
|
||||
)
|
||||
)
|
||||
else:
|
||||
services = sys.argv[1:]
|
||||
await asyncio.gather(*[execute(service) for service in services])
|
||||
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
90
templates/account.html
Normal file
90
templates/account.html
Normal file
@@ -0,0 +1,90 @@
|
||||
{% extends 'base_main.html' %}
|
||||
|
||||
{% block title %}Аккаунт{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h2 style="margin-bottom: 40px;">Информация об аккаунте</h2>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div style="height: 100%; width: 100%;">
|
||||
<img src="{{ account.userinfo.profile_pic_url }}" height="100%" width="100%" alt="Фото профиля">
|
||||
</div>
|
||||
{% if owner %}
|
||||
<label for="file-upload" class="btn btn-light" style="margin-top: -100px; margin-left: 10%; width: 80%;">
|
||||
<i class="fa fa-upload"></i> Загрузить фото
|
||||
</label>
|
||||
<input type="file" form="photoform" style="display: none;" accept="image/png, image/jpg" class="btn form-control-file" id="file-upload" value="Выбрать файл" name="file" onchange="document.getElementById('photoform').submit();">
|
||||
<form method="POST" enctype="multipart/form-data" id="photoform">
|
||||
<input type="hidden" name="action" value="upload_photo">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<h3>
|
||||
{{ account.userinfo.surname }} {{ account.userinfo.name }} {{ account.userinfo.middle_name }}
|
||||
<span style="margin-left: 15px;" class="badge badge-{% if account.userinfo.activity_status == online_status %}success{% else %}secondary{% endif %}">{{ account.userinfo.activity_status }}</span>
|
||||
</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<h2><i class="fa fa-user"></i></h2>
|
||||
</td>
|
||||
<td><div style="width: 20px;"></div></td>
|
||||
<td>
|
||||
<p style="padding-top: 8px; font-size: 24px;">{{ account.username }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h2><i class="fa fa-at"></i></h2>
|
||||
</td>
|
||||
<td><div style="width: 20px;"></div></td>
|
||||
<td>
|
||||
<p style="padding-top: 8px; font-size: 24px;">{{ account.email }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h2><i class="fa fa-star"></i></h2>
|
||||
</td>
|
||||
<td><div style="width: 20px;"></div></td>
|
||||
<td>
|
||||
<p style="padding-top: 8px; font-size: 24px;">{{ account.userinfo.rating }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h2><i class="fa fa-arrow-up"></i></h2>
|
||||
</td>
|
||||
<td><div style="width: 20px;"></div></td>
|
||||
<td>
|
||||
<p style="padding-top: 8px; font-size: 24px;">{{ account.userinfo.place }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h2><i class="fa fa-calendar"></i></h2>
|
||||
</td>
|
||||
<td><div style="width: 20px;"></div></td>
|
||||
<td>
|
||||
<p style="padding-top: 8px; font-size: 24px;">{{ account.date_joined.date }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<hr><hr>
|
||||
{% if owner %}
|
||||
<p style="color: red;">{{ error_message }}</p>
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="change_password">
|
||||
<h2 style="margin-bottom: 40px; margin-top: 40px;">Смена пароля</h2>
|
||||
<input type="password" placeholder="Текущий пароль" name="password" style="margin-bottom: 20px; width: 300px;"><br>
|
||||
<input type="password" placeholder="Новый пароль" name="new_password" style="margin-bottom: 20px; width: 300px;"><br>
|
||||
<input type="password" placeholder="Повторить пароль" name="repeat" style="margin-bottom: 20px; width: 300px;"><br>
|
||||
<button type="submit" class="btn btn-light">Сменить пароль</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user