registration implemented

This commit is contained in:
Egor Matveev
2021-08-29 21:43:34 +03:00
parent f72801d6f5
commit 1307c16ec1
125 changed files with 2158 additions and 4631 deletions

View File

@@ -1,4 +1,4 @@
FROM python:3.8 FROM python:3.7
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
ENV DJANGO_SETTINGS_MODULE Sprint.settings ENV DJANGO_SETTINGS_MODULE Sprint.settings

View File

@@ -9,11 +9,16 @@ from .main import solution_path
def start_new(host): 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: if in_queue:
sol = in_queue[0] sol = in_queue[0]
for s in in_queue: 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: if dif > 0:
sol = s sol = s
elif dif == 0 and s.id < sol.id: elif dif == 0 and s.id < sol.id:
@@ -22,7 +27,7 @@ def start_new(host):
def is_project(path): 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): def get_node_value(element):
@@ -30,15 +35,16 @@ def get_node_value(element):
def nunit_path(working_dir): 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: class Tester:
def __init__(self, solution, host): def __init__(self, solution, host):
self.solution = solution self.solution = solution
self.host = host self.host = host
self.working_dir = '' self.working_dir = ""
self.files = [] self.files = []
# функция компиляции # функция компиляции
@@ -47,24 +53,25 @@ class Tester:
# shell('msbuild ' + path + ' /p:Configuration=Debug') # shell('msbuild ' + path + ' /p:Configuration=Debug')
# решение для Windows # решение для 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: with self.solution.log_fs as fs:
shell(cmd, fs) shell(cmd, fs)
def build_and_copy(self, path, working_dir): def build_and_copy(self, path, working_dir):
if exists(join(path, 'bin', 'Debug')): if exists(join(path, "bin", "Debug")):
rmtree(join(path, 'bin', 'Debug')) rmtree(join(path, "bin", "Debug"))
self.build(path) self.build(path)
name = basename(path) name = basename(path)
if not exists(join(path, 'bin', 'Debug')) or not any( if not exists(join(path, "bin", "Debug")) or not any(
x.endswith('.exe') for x in listdir(join(path, 'bin', 'Debug'))): x.endswith(".exe") for x in listdir(join(path, "bin", "Debug"))
):
return False return False
self.files.append(basename(path)) self.files.append(basename(path))
for file in listdir(join(path, 'bin', 'Debug')): for file in listdir(join(path, "bin", "Debug")):
if exists(join(path, 'bin', 'Debug', file)): if exists(join(path, "bin", "Debug", file)):
new_file = join(working_dir, basename(file)) new_file = join(working_dir, basename(file))
try: try:
copyfile(join(path, 'bin', 'Debug', file), new_file) copyfile(join(path, "bin", "Debug", file), new_file)
except: except:
pass pass
else: else:
@@ -73,110 +80,132 @@ class Tester:
def push(self): def push(self):
solution = self.solution solution = self.solution
if solution.result == 'SOLUTION ERROR': if solution.result == "SOLUTION ERROR":
return return
solution.result = 'IN QUEUE' solution.result = "IN QUEUE"
solution.save() solution.save()
from Main.models import System 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() self.test()
def delete_everything(self): def delete_everything(self):
ssp = solution_path(self.solution.path()) ssp = solution_path(self.solution.path())
sln_path = join(ssp, '.idea') sln_path = join(ssp, ".idea")
if exists(sln_path): if exists(sln_path):
rmtree(sln_path) rmtree(sln_path)
sln_path = join(ssp, '.vs') sln_path = join(ssp, ".vs")
if exists(sln_path): if exists(sln_path):
rmtree(sln_path) rmtree(sln_path)
sln_path = ssp sln_path = ssp
for p in listdir(sln_path): for p in listdir(sln_path):
if isdir(join(sln_path, p)): if isdir(join(sln_path, p)):
if exists(join(sln_path, p, 'bin')): if exists(join(sln_path, p, "bin")):
rmtree(join(sln_path, p, 'bin')) rmtree(join(sln_path, p, "bin"))
if exists(join(sln_path, p, 'obj')): if exists(join(sln_path, p, "obj")):
rmtree(join(sln_path, p, 'obj')) rmtree(join(sln_path, p, "obj"))
if exists(self.working_dir): if exists(self.working_dir):
rmtree(self.working_dir) rmtree(self.working_dir)
if exists(join(self.solution.path(), 'solution.zip')): if exists(join(self.solution.path(), "solution.zip")):
remove(join(self.solution.path(), 'solution.zip')) remove(join(self.solution.path(), "solution.zip"))
if exists(join(self.solution.path(), '__MACOSX')): if exists(join(self.solution.path(), "__MACOSX")):
rmtree(join(self.solution.path(), '__MACOSX')) rmtree(join(self.solution.path(), "__MACOSX"))
if exists(join(sln_path, '.DS_Store')): if exists(join(sln_path, ".DS_Store")):
remove(join(sln_path, '.DS_Store')) remove(join(sln_path, ".DS_Store"))
if exists(join(sln_path, 'test_folder')): if exists(join(sln_path, "test_folder")):
rmtree(join(sln_path, 'test_folder')) rmtree(join(sln_path, "test_folder"))
def nunit_testing(self): def nunit_testing(self):
solution = self.solution solution = self.solution
with self.solution.log_fs as fs: with self.solution.log_fs as fs:
fs.write(b'Building image\n') fs.write(b"Building image\n")
shell('docker build -t solution_{} {}'.format(self.solution.id, self.working_dir)) shell(
"docker build -t solution_{} {}".format(self.solution.id, self.working_dir)
)
with self.solution.log_fs as fs: with self.solution.log_fs as fs:
fs.write(b'Image built successfully\n') fs.write(b"Image built successfully\n")
def execute(): def execute():
with self.solution.log_fs as fs: with self.solution.log_fs as fs:
shell('docker run --name solution_container_{} solution_{}'.format(self.solution.id, self.solution.id), shell(
output=fs) "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 = Thread(target=execute)
t.start() t.start()
t.join(self.solution.task.time_limit / 1000) 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: with self.solution.log_fs as fs:
shell('docker cp solution_container_{}:/app/TestResults.xml {}'.format(self.solution.id, self.working_dir), shell(
fs) "docker cp solution_container_{}:/app/TestResults.xml {}".format(
self.solution.id, self.working_dir
),
fs,
)
with self.solution.log_fs as 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: with self.solution.log_fs as fs:
shell('docker image rm solution_{}'.format(self.solution.id), fs) shell("docker image rm solution_{}".format(self.solution.id), fs)
if not exists(join(self.working_dir, 'TestResults.xml')): if not exists(join(self.working_dir, "TestResults.xml")):
self.solution.set_result('Time limit') self.solution.set_result("Time limit")
solution.write_log('Result file not found in container') solution.write_log("Result file not found in container")
return return
solution.write_log('Result file found in container') solution.write_log("Result file found in container")
try: try:
doc = parse(join(self.working_dir, 'TestResults.xml')) doc = parse(join(self.working_dir, "TestResults.xml"))
res = get_node_value(doc.getElementsByTagName('Passed')) + '/' + get_node_value( res = (
doc.getElementsByTagName('Total')) get_node_value(doc.getElementsByTagName("Passed"))
self.solution.details = '' + "/"
for el in doc.getElementsByTagName('Result'): + get_node_value(doc.getElementsByTagName("Total"))
self.solution.details += '<h5><b>' + get_node_value(el.getElementsByTagName('MethodName')) + '</b></h5>' )
r = get_node_value(el.getElementsByTagName('Successful')) self.solution.details = ""
if r == 'true': 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>' self.solution.details += '<div style="color: green;">Passed</div>'
else: else:
self.solution.details += '<div style="color: red;">Failed</div>' self.solution.details += '<div style="color: red;">Failed</div>'
mes = get_node_value(el.getElementsByTagName('Message')) mes = get_node_value(el.getElementsByTagName("Message"))
self.solution.details += '<pre>{}</pre>'.format(mes) self.solution.details += "<pre>{}</pre>".format(mes)
except: except:
solution.write_log('Unknown error') solution.write_log("Unknown error")
res = 'TEST ERROR' res = "TEST ERROR"
self.solution.set_result(res) self.solution.set_result(res)
def test(self): def test(self):
solution = self.solution solution = self.solution
solution.result = 'TESTING' solution.result = "TESTING"
solution.save() solution.save()
try: try:
if not exists(self.solution.task.tests_path()): if not exists(self.solution.task.tests_path()):
with self.solution.log_fs as fs: with self.solution.log_fs as fs:
fs.write(b'No test file found\n') fs.write(b"No test file found\n")
solution.set_result('TEST ERROR') solution.set_result("TEST ERROR")
solution.save() solution.save()
self.delete_everything() self.delete_everything()
start_new(self.host) start_new(self.host)
return return
sln_path = solution_path(join(MEDIA_ROOT, 'solutions', str(solution.id))) sln_path = solution_path(join(MEDIA_ROOT, "solutions", str(solution.id)))
if sln_path == '': if sln_path == "":
solution.set_result('TEST ERROR') solution.set_result("TEST ERROR")
solution.save() solution.save()
self.delete_everything() self.delete_everything()
start_new(self.host) start_new(self.host)
return return
working_dir = join(sln_path, 'test_folder') working_dir = join(sln_path, "test_folder")
if exists(working_dir): if exists(working_dir):
try: try:
rmtree(working_dir) rmtree(working_dir)
@@ -184,60 +213,63 @@ class Tester:
remove(working_dir) remove(working_dir)
mkdir(working_dir) mkdir(working_dir)
with self.solution.log_fs as fs: 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): 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 prj = project
project = join(sln_path, 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): if not self.build_and_copy(project, working_dir):
solution.set_result('Compilation error') solution.set_result("Compilation error")
solution.write_log('Failed to compile project {}'.format(prj)) solution.write_log("Failed to compile project {}".format(prj))
solution.save() solution.save()
self.delete_everything() self.delete_everything()
start_new(self.host) start_new(self.host)
return return
dll_path = solution.task.tests_path() dll_path = solution.task.tests_path()
solution.write_log('Copying test file to working directory') solution.write_log("Copying test file to working directory")
copyfile(dll_path, join(working_dir, str(solution.task.id) + '.cs')) copyfile(dll_path, join(working_dir, str(solution.task.id) + ".cs"))
solution.write_log('Test file copied') solution.write_log("Test file copied")
for file in listdir('SprintTest'): for file in listdir("SprintTest"):
try: try:
copyfile(join('SprintTest', file), join(working_dir, file)) copyfile(join("SprintTest", file), join(working_dir, file))
except: except:
pass pass
self.working_dir = working_dir self.working_dir = working_dir
build_tests_cmd = 'csc -out:{} -t:library /r:{} /r:{} /r:{} '.format(join(self.working_dir, 'tests.dll'), build_tests_cmd = "csc -out:{} -t:library /r:{} /r:{} /r:{} ".format(
join(self.working_dir, join(self.working_dir, "tests.dll"),
'SprintTest.dll'), join(self.working_dir, "SprintTest.dll"),
join(working_dir, join(working_dir, "System.Runtime.dll"),
'System.Runtime.dll'), join(working_dir, "System.Reflection.dll"),
join(working_dir, )
'System.Reflection.dll'))
for file in self.files: 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() build_tests_cmd += self.solution.task.tests_path()
if exists(join(self.working_dir, 'tests.dll')): if exists(join(self.working_dir, "tests.dll")):
remove(join(self.working_dir, 'tests.dll')) remove(join(self.working_dir, "tests.dll"))
solution.write_log('Building tests file started') solution.write_log("Building tests file started")
with self.solution.log_fs as fs: with self.solution.log_fs as fs:
shell(build_tests_cmd, fs) shell(build_tests_cmd, fs)
with self.solution.log_fs as fs: with self.solution.log_fs as fs:
fs.write(b'Building tests file finished\n') fs.write(b"Building tests file finished\n")
if exists(join(self.working_dir, 'tests.dll')): if exists(join(self.working_dir, "tests.dll")):
with self.solution.log_fs as fs: 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): for file in ExtraFile.objects.filter(task=self.solution.task):
copyfile(file.path, join(working_dir, file.filename)) copyfile(file.path, join(working_dir, file.filename))
self.nunit_testing() self.nunit_testing()
else: else:
solution.set_result('TEST ERROR') solution.set_result("TEST ERROR")
solution.write_log('Failed to compile tests') solution.write_log("Failed to compile tests")
except: except:
solution.set_result('TEST ERROR') solution.set_result("TEST ERROR")
raise raise
with self.solution.log_fs as fs: with self.solution.log_fs as fs:
fs.write(b'Unknown error\n') fs.write(b"Unknown error\n")
solution.save() solution.save()
self.delete_everything() self.delete_everything()
start_new(self.host) start_new(self.host)

View File

@@ -1,29 +1,28 @@
class Method: class Method:
def __init__(self, meth, name):
self.meth = meth
self.name = name
def __init__(self, meth, name): def __eq__(self, other):
self.meth = meth return self.name == other.name
self.name = name
def __eq__(self, other): def execute(self):
return self.name == other.name self.meth()
def execute(self):
self.meth()
class Timer: class Timer:
methods = [] methods = []
def push(self, meth): def push(self, meth):
methods.append(math) methods.append(math)
def polling(self): def polling(self):
for i in range(len(self.methods)): for i in range(len(self.methods)):
methods[i].execute() methods[i].execute()
def remove(method_name): def remove(method_name):
for method in self.methods: for method in self.methods:
if method.name == method_name: if method.name == method_name:
self.methods.remove(method) self.methods.remove(method)
return return
raise IndexError("No method in list") raise IndexError("No method in list")

View File

@@ -1,14 +1,11 @@
from django.contrib import admin from django.contrib import admin
from Main.models import * from Main.models import *
import Main.models
# Register your models here. # Register your models here.
admin.site.register(Course) for model in dir(Main.models):
admin.site.register(Block) try:
admin.site.register(Task) admin.site.register(eval("Main.models." + model))
admin.site.register(Subscribe) except:
admin.site.register(Restore) continue
admin.site.register(UserInfo)
admin.site.register(Solution)
admin.site.register(System)
admin.site.register(ExtraFile)

View File

@@ -2,4 +2,5 @@ from django.apps import AppConfig
class MainConfig(AppConfig): class MainConfig(AppConfig):
name = 'Main' name = "Main"
default_auto_field = "django.db.models.BigAutoField"

View File

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

View File

@@ -1,8 +1,5 @@
from .main import check_admin from Sprint.settings import CONSTS
def attributes(request): def attributes(request):
return { return CONSTS
'current_page': 'settings' if '/settings' == request.path else 'admin' if '/admin/' in request.path else 'main',
'is_admin': check_admin(request.user)
}

View File

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

View File

@@ -2,7 +2,6 @@ from django import forms
class PasswordField(forms.CharField): class PasswordField(forms.CharField):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.widget = forms.PasswordInput() self.widget = forms.PasswordInput()
@@ -10,11 +9,15 @@ class PasswordField(forms.CharField):
class LoginForm(forms.Form): class LoginForm(forms.Form):
email = forms.EmailField(widget=forms.TextInput(attrs={"class": "input_simple"})) 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): 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): class TestsForm(forms.Form):
@@ -22,11 +25,11 @@ class TestsForm(forms.Form):
class ChangePasswordForm(forms.Form): class ChangePasswordForm(forms.Form):
old = PasswordField(label='Старый пароль') old = PasswordField(label="Старый пароль")
new = PasswordField(label='Новый пароль') new = PasswordField(label="Новый пароль")
again = PasswordField(label='Еще раз') again = PasswordField(label="Еще раз")
class ResetPasswordForm(forms.Form): class ResetPasswordForm(forms.Form):
new = PasswordField(label='Новый пароль') new = PasswordField(label="Новый пароль")
again = PasswordField(label='Еще раз') again = PasswordField(label="Еще раз")

View File

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

View File

@@ -15,84 +15,201 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Block', name="Block",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.TextField()), "id",
('time_start', models.DateTimeField()), models.AutoField(
('time_end', models.DateTimeField()), auto_created=True,
('opened', models.IntegerField()), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.TextField()),
("time_start", models.DateTimeField()),
("time_end", models.DateTimeField()),
("opened", models.IntegerField()),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Course', name="Course",
fields=[ 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( migrations.CreateModel(
name='UserInfo', name="UserInfo",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('surname', models.TextField()), "id",
('name', models.TextField()), models.AutoField(
('middle_name', models.TextField()), auto_created=True,
('group_name', models.TextField()), primary_key=True,
('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 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( migrations.CreateModel(
name='Task', name="Task",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.TextField()), "id",
('legend', models.TextField()), models.AutoField(
('input', models.TextField()), auto_created=True,
('output', models.TextField()), primary_key=True,
('specifications', models.TextField()), serialize=False,
('time_limit', models.IntegerField()), verbose_name="ID",
('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')), ),
),
("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( migrations.CreateModel(
name='Subscribe', name="Subscribe",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('is_assistant', models.IntegerField()), "id",
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course')), models.AutoField(
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 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( migrations.CreateModel(
name='Solution', name="Solution",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('result', models.TextField()), "id",
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Task')), models.AutoField(
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 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( migrations.CreateModel(
name='Restore', name="Restore",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('code', models.TextField()), "id",
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 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( migrations.CreateModel(
name='Mark', name="Mark",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('mark', models.IntegerField()), "id",
('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')), models.AutoField(
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 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( migrations.AddField(
model_name='block', model_name="block",
name='course', name="course",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="Main.Course"
),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0001_initial'), ("Main", "0001_initial"),
] ]
operations = [ operations = [
migrations.RenameField( migrations.RenameField(
model_name='userinfo', model_name="userinfo",
old_name='group_name', old_name="group_name",
new_name='group', new_name="group",
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0002_auto_20200626_0946'), ("Main", "0002_auto_20200626_0946"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='subscribe', model_name="subscribe",
name='is_assistant', name="is_assistant",
field=models.IntegerField(default=0), field=models.IntegerField(default=0),
), ),
] ]

View File

@@ -6,33 +6,33 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0003_auto_20200627_1959'), ("Main", "0003_auto_20200627_1959"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='input', name="input",
field=models.TextField(default=''), field=models.TextField(default=""),
), ),
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='legend', name="legend",
field=models.TextField(default=''), field=models.TextField(default=""),
), ),
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='output', name="output",
field=models.TextField(default=''), field=models.TextField(default=""),
), ),
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='specifications', name="specifications",
field=models.TextField(default=''), field=models.TextField(default=""),
), ),
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='time_limit', name="time_limit",
field=models.IntegerField(default=10000), field=models.IntegerField(default=10000),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0004_auto_20200628_0917'), ("Main", "0004_auto_20200628_0917"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='solution', model_name="solution",
name='time_sent', name="time_sent",
field=models.DateTimeField(null=True), field=models.DateTimeField(null=True),
), ),
] ]

View File

@@ -7,13 +7,15 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0005_solution_time_sent'), ("Main", "0005_solution_time_sent"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='solution', model_name="solution",
name='user', name="user",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.UserInfo'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="Main.UserInfo"
),
), ),
] ]

View File

@@ -9,13 +9,15 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('Main', '0006_auto_20200628_1315'), ("Main", "0006_auto_20200628_1315"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='solution', model_name="solution",
name='user', name="user",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
), ),
] ]

View File

@@ -6,18 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0007_auto_20200629_0833'), ("Main", "0007_auto_20200629_0833"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='userinfo', model_name="userinfo",
name='mark_notification', name="mark_notification",
field=models.IntegerField(default=0), field=models.IntegerField(default=0),
), ),
migrations.AddField( migrations.AddField(
model_name='userinfo', model_name="userinfo",
name='new_block_notification', name="new_block_notification",
field=models.IntegerField(default=0), field=models.IntegerField(default=0),
), ),
] ]

View File

@@ -6,18 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0008_auto_20200702_2140'), ("Main", "0008_auto_20200702_2140"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='solution', model_name="solution",
name='comment', name="comment",
field=models.TextField(default=''), field=models.TextField(default=""),
), ),
migrations.AddField( migrations.AddField(
model_name='solution', model_name="solution",
name='mark', name="mark",
field=models.IntegerField(null=True), field=models.IntegerField(null=True),
), ),
] ]

View File

@@ -6,16 +6,24 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0009_auto_20200704_1703'), ("Main", "0009_auto_20200704_1703"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='System', name="System",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('key', models.TextField()), "id",
('value', models.TextField()), models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("key", models.TextField()),
("value", models.TextField()),
], ],
), ),
] ]

View File

@@ -6,18 +6,26 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0010_system'), ("Main", "0010_system"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='ThreadSafe', name="ThreadSafe",
fields=[ 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( migrations.DeleteModel(
name='Mark', name="Mark",
), ),
] ]

View File

@@ -7,21 +7,34 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0011_auto_20200814_2035'), ("Main", "0011_auto_20200814_2035"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='weight', name="weight",
field=models.FloatField(default=1.0), field=models.FloatField(default=1.0),
), ),
migrations.CreateModel( migrations.CreateModel(
name='ExtraFile', name="ExtraFile",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('file', models.FileField(upload_to='')), "id",
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')), 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"
),
),
], ],
), ),
] ]

View File

@@ -6,11 +6,11 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0012_auto_20200901_1154'), ("Main", "0012_auto_20200901_1154"),
] ]
operations = [ operations = [
migrations.DeleteModel( migrations.DeleteModel(
name='ExtraFile', name="ExtraFile",
), ),
] ]

View File

@@ -7,17 +7,30 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0013_delete_extrafile'), ("Main", "0013_delete_extrafile"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='ExtraFile', name="ExtraFile",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('file', models.FileField(upload_to='')), "id",
('filename', models.TextField()), models.AutoField(
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')), 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"
),
),
], ],
), ),
] ]

View File

@@ -6,18 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0014_extrafile'), ("Main", "0014_extrafile"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='max_mark', name="max_mark",
field=models.IntegerField(default=10), field=models.IntegerField(default=10),
), ),
migrations.AlterField( migrations.AlterField(
model_name='extrafile', model_name="extrafile",
name='file', name="file",
field=models.FileField(upload_to='data\\extra_files'), field=models.FileField(upload_to="data\\extra_files"),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0015_auto_20200902_1555'), ("Main", "0015_auto_20200902_1555"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='max_solutions_count', name="max_solutions_count",
field=models.IntegerField(default=10), field=models.IntegerField(default=10),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0016_task_max_solutions_count'), ("Main", "0016_task_max_solutions_count"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='solution', model_name="solution",
name='details', name="details",
field=models.TextField(default=''), field=models.TextField(default=""),
), ),
] ]

View File

@@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0017_solution_details'), ("Main", "0017_solution_details"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='extrafile', model_name="extrafile",
name='file', name="file",
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0018_remove_extrafile_file'), ("Main", "0018_remove_extrafile_file"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='show_details', name="show_details",
field=models.IntegerField(default=1), field=models.IntegerField(default=1),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0019_task_show_details'), ("Main", "0019_task_show_details"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='solution_type', name="solution_type",
field=models.TextField(default='Решение'), field=models.TextField(default="Решение"),
), ),
] ]

View File

@@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0020_task_solution_type'), ("Main", "0020_task_solution_type"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='task', model_name="task",
name='solution_type', name="solution_type",
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0021_remove_task_solution_type'), ("Main", "0021_remove_task_solution_type"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='full_solution', name="full_solution",
field=models.IntegerField(default=0), field=models.IntegerField(default=0),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0022_task_full_solution'), ("Main", "0022_task_full_solution"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='extrafile', model_name="extrafile",
name='for_compilation', name="for_compilation",
field=models.IntegerField(default=0), field=models.IntegerField(default=0),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0023_extrafile_for_compilation'), ("Main", "0023_extrafile_for_compilation"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='extrafile', model_name="extrafile",
name='sample', name="sample",
field=models.IntegerField(default=0), field=models.IntegerField(default=0),
), ),
] ]

View File

@@ -6,46 +6,46 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0024_extrafile_sample'), ("Main", "0024_extrafile_sample"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='userinfo', model_name="userinfo",
name='mark_notification', name="mark_notification",
), ),
migrations.RemoveField( migrations.RemoveField(
model_name='userinfo', model_name="userinfo",
name='new_block_notification', name="new_block_notification",
), ),
migrations.AlterField( migrations.AlterField(
model_name='block', model_name="block",
name='opened', name="opened",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='extrafile', model_name="extrafile",
name='for_compilation', name="for_compilation",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='extrafile', model_name="extrafile",
name='sample', name="sample",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='subscribe', model_name="subscribe",
name='is_assistant', name="is_assistant",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='full_solution', name="full_solution",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='show_details', name="show_details",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0025_auto_20201106_1848'), ("Main", "0025_auto_20201106_1848"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='block', model_name="block",
name='show_rating', name="show_rating",
field=models.BooleanField(default=True), field=models.BooleanField(default=True),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0026_block_show_rating'), ("Main", "0026_block_show_rating"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='mark_formula', name="mark_formula",
field=models.TextField(default='None'), field=models.TextField(default="None"),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0027_task_mark_formula'), ("Main", "0027_task_mark_formula"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='show_result', name="show_result",
field=models.BooleanField(default=True), field=models.BooleanField(default=True),
), ),
] ]

View File

@@ -6,18 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0028_task_show_result'), ("Main", "0028_task_show_result"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='block', model_name="block",
name='priority', name="priority",
field=models.IntegerField(default=5), field=models.IntegerField(default=5),
), ),
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='priority', name="priority",
field=models.IntegerField(default=5), field=models.IntegerField(default=5),
), ),
] ]

View File

@@ -9,19 +9,46 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('Main', '0029_auto_20210130_1950'), ("Main", "0029_auto_20210130_1950"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Message', name="Message",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('for_all', models.BooleanField()), "id",
('text', models.TextField()), models.AutoField(
('reply_to', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Main.message')), auto_created=True,
('sender', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), primary_key=True,
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')), 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"
),
),
], ],
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0030_message'), ("Main", "0030_message"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='block', model_name="block",
name='cheating_checking', name="cheating_checking",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
] ]

View File

@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0031_block_cheating_checking'), ("Main", "0031_block_cheating_checking"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='block', model_name="block",
name='cheating_data', name="cheating_data",
field=models.TextField(default='[]'), field=models.TextField(default="[]"),
), ),
] ]

View File

@@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('Main', '0032_block_cheating_data'), ("Main", "0032_block_cheating_data"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='block', model_name="block",
name='cheating_data', name="cheating_data",
), ),
] ]

View File

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

View 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
View 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
View 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
View 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()

View File

@@ -1,112 +1,5 @@
from django import template 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 = 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}')

View File

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

View 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
View 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}"

View 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')

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

View File

@@ -0,0 +1,3 @@
from .celery import app as celery
__all__ = ('celery',)

View File

@@ -11,6 +11,6 @@ import os
from django.core.asgi import get_asgi_application 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() application = get_asgi_application()

17
Sprint/celery.py Normal file
View 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()

View File

@@ -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/ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # 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 DEPLOY = False
@@ -30,69 +30,65 @@ DEBUG = not DEPLOY
SECURE_SSL_REDIRECT = DEPLOY SECURE_SSL_REDIRECT = DEPLOY
ALLOWED_HOSTS = [ ALLOWED_HOSTS = ["*"]
'*'
]
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'grappelli', "django.contrib.admin",
'django.contrib.admin', "django.contrib.auth",
'django.contrib.auth', "django.contrib.contenttypes",
'django.contrib.contenttypes', "django.contrib.sessions",
'django.contrib.sessions', "django.contrib.messages",
'django.contrib.messages', "django.contrib.staticfiles",
'django.contrib.staticfiles', "Main.apps.MainConfig",
'Main.apps.MainConfig'
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
'django.middleware.locale.LocaleMiddleware', "django.middleware.locale.LocaleMiddleware",
] ]
ROOT_URLCONF = 'Sprint.urls' ROOT_URLCONF = "Sprint.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [os.path.join(BASE_DIR, 'templates')] "DIRS": [os.path.join(BASE_DIR, "templates")],
, "APP_DIRS": True,
'APP_DIRS': True, "OPTIONS": {
'OPTIONS': { "context_processors": [
'context_processors': [ "django.template.context_processors.debug",
'django.template.context_processors.debug', "django.template.context_processors.request",
'django.template.context_processors.request', "django.contrib.auth.context_processors.auth",
'django.contrib.auth.context_processors.auth', "django.contrib.messages.context_processors.messages",
'django.contrib.messages.context_processors.messages', "Main.context_processors.attributes",
'Main.context_processors.attributes'
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'Sprint.wsgi.application' WSGI_APPLICATION = "Sprint.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.postgresql_psycopg2', "ENGINE": "django.db.backends.postgresql_psycopg2",
'NAME': os.getenv('POSTGRES_DB'), "NAME": "postgres",
'USER': os.getenv('POSTGRES_USER'), "USER": "postgres",
'PASSWORD': os.getenv('POSTGRES_PASSWORD'), "PASSWORD": "password",
'HOST': '0.0.0.0', "HOST": "0.0.0.0",
'PORT': '5432', "PORT": "5432",
} }
} }
@@ -102,16 +98,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [ 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 _ = lambda s: s
LANGUAGES = ( LANGUAGES = (("en", _("English")), ("ru", _("Russian")))
('en', _('English')),
('ru', _('Russian'))
)
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = "en-us"
TIME_ZONE = 'Europe/Moscow' TIME_ZONE = "Europe/Moscow"
USE_I18N = True USE_I18N = True
@@ -140,11 +133,13 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # 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") STATIC_ROOT = os.path.join(BASE_DIR, "static")
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_ROOT = os.path.join(BASE_DIR, 'data') DATA_ROOT = os.path.join(BASE_DIR, "data")
SOLUTIONS_ROOT = os.path.join(DATA_ROOT, "solutions")
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, "Main/static"), os.path.join(BASE_DIR, "Main/static"),
@@ -152,6 +147,17 @@ STATICFILES_DIRS = [
# Authentication backends # Authentication backends
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",)
'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",
}

View File

@@ -1,39 +1,20 @@
from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import path, re_path, include from django.urls import path
from Main import views from Main.views import *
from Sprint import settings
urlpatterns = [ urlpatterns = [
path('grappelli/', include('grappelli.urls')), # grappelli URLS path("enter", EnterView.as_view()),
path('main', views.main), path("register", RegisterView.as_view()),
path('settings', views.settings), path("rating", RatingView.as_view()),
path('enter', views.enter, name='enter'), path("tasks", TasksView.as_view()),
path('register', views.register), path("account", AccountView.as_view()),
path('restore', views.restore, name='restore'), path("exit", ExitView.as_view()),
path('reset_password', views.reset_password), path("admin/task", TaskSettingsView.as_view()),
path('exit', views.exit), path("sets", SetsView.as_view()),
path('block', views.block), path("task", TaskView.as_view()),
path('task', views.task), path("solutions_table", SolutionsTableView.as_view()),
path('solution', views.solution), path("", MainView.as_view()),
path('rating', views.rating), path("admin/", admin.site.urls),
path('messages', views.messages), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
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)
]

View File

@@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application 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() application = get_wsgi_application()

18
SprintLib/BaseDaemon.py Normal file
View 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
View 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

View 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
View File

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

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

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

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

View 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}"

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

View 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}"

View 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
View 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
View File

@@ -0,0 +1,3 @@
source venv/bin/activate
python manage.py makemigrations
python manage.py migrate

0
daemons/__init__.py Normal file
View File

6
daemons/celery.py Normal file
View 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
View 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
View File

@@ -0,0 +1,6 @@
from SprintLib.BaseDaemon import BaseDaemon
class Daemon(BaseDaemon):
def command(self):
return "python manage.py runserver"

View File

@@ -2,26 +2,14 @@ version: "3"
services: 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: postgres:
restart: always restart: always
image: postgres image: postgres
environment: environment:
POSTGRES_USER: ${POSTGRES_USER} POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: password
POSTGRES_DB: ${POSTGRES_DB} POSTGRES_DB: postgres
ports: ports:
- "5432:5432" - "5432:5432"
volumes:
- postgres_data:/var/lib/postgres
- ./dbscripts/postgres:/docker-entrypoint-initdb.d

0
manage.py Normal file → Executable file
View File

View File

@@ -1,32 +1,30 @@
amqp==1.4.9 amqp==5.0.6
anyjson==0.3.3 anyjson==0.3.3
asgiref==3.4.1 asgiref==3.3.4
billiard==3.3.0.23 billiard==3.6.4.0
celery==3.1.26.post2 cached-property==1.5.2
click==7.1.2 celery==5.2.0b2
click==8.0.1
click-didyoumean==0.0.3 click-didyoumean==0.0.3
click-plugins==1.1.1 click-plugins==1.1.1
click-repl==0.2.0 click-repl==0.2.0
copydetect==0.2.1 dj-database-url==0.5.0
cycler==0.10.0 Django==3.2.4
Django==3.2.5 gunicorn==20.1.0
django-celery==3.3.1 importlib-metadata==4.5.0
django-grappelli==2.15.1 kombu==5.1.0
Jinja2==3.0.1 numpy==1.20.3
kiwisolver==1.3.1 pandas==1.2.4
kombu==3.0.37
MarkupSafe==2.0.1
matplotlib==3.4.2
numpy==1.21.0
Pillow==8.3.1 Pillow==8.3.1
prompt-toolkit==3.0.19 prompt-toolkit==3.0.18
psycopg2==2.9.1 psycopg2==2.9.1
Pygments==2.9.0 psycopg2-binary==2.9.1
pyparsing==2.4.7
python-dateutil==2.8.1 python-dateutil==2.8.1
pytz==2021.1 pytz==2021.1
redis==3.5.3
six==1.16.0 six==1.16.0
sqlparse==0.4.1 sqlparse==0.4.1
tqdm==4.61.2 typing-extensions==3.10.0.0
vine==5.0.0 vine==5.0.0
wcwidth==0.2.5 wcwidth==0.2.5
zipp==3.5.0

View File

@@ -3,4 +3,3 @@ pip3 install venv
python3 -m venv venv python3 -m venv venv
source venv/bin/activate source venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
docker-compose up -d

36
start.py Executable file
View 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
View 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