registration implemented
This commit is contained in:
@@ -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
|
||||||
|
218
Main/Tester.py
218
Main/Tester.py
@@ -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)
|
||||||
|
@@ -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")
|
||||||
|
@@ -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)
|
|
||||||
|
@@ -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"
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
from subprocess import Popen
|
|
||||||
from sys import stdout
|
|
||||||
|
|
||||||
|
|
||||||
def shell(cmd, output=stdout):
|
|
||||||
p = Popen(cmd, shell=True, stdout=output)
|
|
||||||
p.wait()
|
|
||||||
p.kill()
|
|
@@ -1,8 +1,5 @@
|
|||||||
from .main import check_admin
|
from Sprint.settings import CONSTS
|
||||||
|
|
||||||
|
|
||||||
def attributes(request):
|
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)
|
|
||||||
}
|
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
from django.http import HttpResponseRedirect
|
|
||||||
|
|
||||||
|
|
||||||
def login_required(boolean):
|
|
||||||
def decorator(fun):
|
|
||||||
def dec(request, *args, **kwargs):
|
|
||||||
if request.user.is_authenticated != boolean:
|
|
||||||
if boolean:
|
|
||||||
return HttpResponseRedirect('/enter')
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
return fun(request, *args, **kwargs)
|
|
||||||
return dec
|
|
||||||
return decorator
|
|
@@ -2,7 +2,6 @@ from django import forms
|
|||||||
|
|
||||||
|
|
||||||
class PasswordField(forms.CharField):
|
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="Еще раз")
|
||||||
|
325
Main/main.py
325
Main/main.py
@@ -1,325 +0,0 @@
|
|||||||
import smtplib
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from json import dumps
|
|
||||||
from os import listdir, mkdir
|
|
||||||
from os.path import isdir, basename, dirname, join, exists
|
|
||||||
from random import choice
|
|
||||||
from shutil import copyfile, rmtree
|
|
||||||
from string import ascii_letters
|
|
||||||
from threading import Thread
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
import copydetect
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.db.transaction import atomic
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.datastructures import MultiValueDictKeyError
|
|
||||||
|
|
||||||
from Main.models import Course, Block, Solution, Restore, System, Subscribe, UserInfo
|
|
||||||
from Sprint.settings import MEDIA_ROOT
|
|
||||||
|
|
||||||
|
|
||||||
def get_in_html_tag(full, tag_name):
|
|
||||||
try:
|
|
||||||
return full.split('<div class="{}">'.format(tag_name))[1].split('</div>')[0]
|
|
||||||
except IndexError:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def random_string():
|
|
||||||
letters = ascii_letters
|
|
||||||
return ''.join(choice(letters) for _ in range(20))
|
|
||||||
|
|
||||||
|
|
||||||
def get_restore_hash():
|
|
||||||
available = [r.code for r in Restore.objects.all()]
|
|
||||||
while True:
|
|
||||||
s = random_string()
|
|
||||||
if s not in available:
|
|
||||||
break
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def send(subject, to_addr, body_text):
|
|
||||||
from_addr = System.objects.get(key='email_address').value
|
|
||||||
body = "\r\n".join((
|
|
||||||
"From: %s" % from_addr,
|
|
||||||
"To: %s" % to_addr,
|
|
||||||
"Subject: %s" % subject,
|
|
||||||
"",
|
|
||||||
body_text
|
|
||||||
))
|
|
||||||
|
|
||||||
server = smtplib.SMTP('SMTP.Office365.com', 587)
|
|
||||||
server.starttls()
|
|
||||||
server.login(System.objects.get(key='email_address').value, System.objects.get(key='email_password').value)
|
|
||||||
server.sendmail(from_addr, [to_addr], body)
|
|
||||||
server.quit()
|
|
||||||
|
|
||||||
|
|
||||||
def send_email(subject, to_addr, body_text):
|
|
||||||
Thread(target=lambda: send(subject, to_addr, body_text)).start()
|
|
||||||
|
|
||||||
|
|
||||||
def check_login(user):
|
|
||||||
return user.is_authenticated
|
|
||||||
|
|
||||||
|
|
||||||
def check_admin(user):
|
|
||||||
if check_teacher(user):
|
|
||||||
return True
|
|
||||||
if not check_login(user):
|
|
||||||
return False
|
|
||||||
return len(Subscribe.objects.filter(user=user, is_assistant=True)) > 0
|
|
||||||
|
|
||||||
|
|
||||||
def check_teacher(user):
|
|
||||||
return user.is_staff and check_login(user)
|
|
||||||
|
|
||||||
|
|
||||||
def check_god(user):
|
|
||||||
return user.is_superuser and check_login(user)
|
|
||||||
|
|
||||||
|
|
||||||
def courses_available(user):
|
|
||||||
if user.is_superuser:
|
|
||||||
return Course.objects.all()
|
|
||||||
else:
|
|
||||||
return [s.course for s in Subscribe.objects.filter(user=user)]
|
|
||||||
|
|
||||||
|
|
||||||
def blocks_available(user):
|
|
||||||
courses = courses_available(user)
|
|
||||||
blocks = {}
|
|
||||||
is_admin = check_admin(user)
|
|
||||||
for course in courses:
|
|
||||||
if is_admin:
|
|
||||||
blocks[course] = Block.objects.filter(
|
|
||||||
course=course
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
blocks[course] = Block.objects.filter(
|
|
||||||
opened=True,
|
|
||||||
time_start__lte=timezone.now(),
|
|
||||||
course=course
|
|
||||||
)
|
|
||||||
return blocks
|
|
||||||
|
|
||||||
|
|
||||||
def can_send_solution(user, task):
|
|
||||||
if user.is_superuser:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
s = Subscribe.objects.get(course=task.block.course, user=user)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return False
|
|
||||||
if s.is_assistant:
|
|
||||||
return True
|
|
||||||
return task.block.time_start <= timezone.now() <= task.block.time_end and task.max_solutions_count > len(Solution.objects.filter(user=user, task=task)) and task.block.opened
|
|
||||||
|
|
||||||
|
|
||||||
def check_permission_block(user, block):
|
|
||||||
blocks = blocks_available(user)
|
|
||||||
for course in blocks.keys():
|
|
||||||
if block in blocks[course]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def is_integer(x):
|
|
||||||
try:
|
|
||||||
int(x)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def check_admin_on_course(user, course):
|
|
||||||
if user.is_superuser:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
s = Subscribe.objects.get(user=user, course=course)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return False
|
|
||||||
return s.is_assistant or user.is_staff
|
|
||||||
|
|
||||||
|
|
||||||
def comparer(value1, value2):
|
|
||||||
if value1 < value2:
|
|
||||||
return 1
|
|
||||||
elif value1 == value2:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
|
|
||||||
def result_comparer(result1, result2):
|
|
||||||
verdicts = ['IN QUEUE', 'TESTING', 'TEST ERROR', 'SOLUTION ERROR', 'Compilation error', 'Time limit']
|
|
||||||
if result1 in verdicts and result2 in verdicts:
|
|
||||||
return comparer(verdicts.index(result1), verdicts.index(result2))
|
|
||||||
if result1 in verdicts and result2 not in verdicts:
|
|
||||||
return 1
|
|
||||||
if result1 not in verdicts and result2 in verdicts:
|
|
||||||
return -1
|
|
||||||
return comparer(int(result1.split('/')[0]), int(result2.split('/')[0]))
|
|
||||||
|
|
||||||
|
|
||||||
def solutions_filter(request):
|
|
||||||
try:
|
|
||||||
solutions = list(reversed(Solution.objects.filter(task__block_id=request['block_id'])))
|
|
||||||
except MultiValueDictKeyError as e:
|
|
||||||
return [Solution.objects.get(id=request['id'])]
|
|
||||||
if 'solution_id' in request.keys():
|
|
||||||
solutions = [solution for solution in solutions if any([solution.id == int(i) for i in request['solution_id'].strip().split()])]
|
|
||||||
if 'task_name' in request.keys():
|
|
||||||
solutions = [solution for solution in solutions if solution.task.name == request['task_name']]
|
|
||||||
if 'user' in request.keys():
|
|
||||||
solutions = [solution for solution in solutions if str(solution.userinfo) == request['user']]
|
|
||||||
if 'group' in request.keys():
|
|
||||||
solutions = [solution for solution in solutions if solution.userinfo.group == request['group']]
|
|
||||||
if 'best_result' in request.keys():
|
|
||||||
sols = {}
|
|
||||||
for solution in solutions:
|
|
||||||
if (solution.user.username, solution.task.id) in sols.keys():
|
|
||||||
comp = result_comparer(sols[(solution.user.username, solution.task.id)][0].result, solution.result)
|
|
||||||
if comp == 1:
|
|
||||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
|
||||||
elif comp == 0:
|
|
||||||
sols[(solution.user.username, solution.task.id)].append(solution)
|
|
||||||
else:
|
|
||||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
|
||||||
solutions = []
|
|
||||||
for sol in sols.values():
|
|
||||||
for val in sol:
|
|
||||||
solutions.append(val)
|
|
||||||
solutions = list(sorted(solutions, key=lambda s: s.id, reverse=True))
|
|
||||||
if 'last_solution' in request.keys():
|
|
||||||
visited = []
|
|
||||||
new_solutions = []
|
|
||||||
for solution in solutions:
|
|
||||||
if (solution.user.username, solution.task.id) not in visited:
|
|
||||||
visited.append((solution.user.username, solution.task.id))
|
|
||||||
new_solutions.append(solution)
|
|
||||||
solutions = new_solutions
|
|
||||||
if 'only_students' in request.keys():
|
|
||||||
solutions = [solution for solution in solutions if not check_admin_on_course(solution.user, solution.task.block.course)]
|
|
||||||
if 'not_seen' in request.keys():
|
|
||||||
solutions = [solution for solution in solutions if solution.mark == None]
|
|
||||||
return sorted(solutions, key=lambda s: s.id, reverse=True)
|
|
||||||
|
|
||||||
|
|
||||||
def re_test(solutions_request, request):
|
|
||||||
from .Tester import Tester
|
|
||||||
for sol in solutions_request:
|
|
||||||
sol.details = ''
|
|
||||||
with open(sol.log_file, 'wb') as fs:
|
|
||||||
fs.write(b'')
|
|
||||||
sol.save()
|
|
||||||
Thread(target=lambda: Tester(sol, request.META['HTTP_HOST']).push()).start()
|
|
||||||
sleep(.1)
|
|
||||||
|
|
||||||
|
|
||||||
def block_solutions_info(block):
|
|
||||||
all_solutions = Solution.objects.filter(task__block=block)
|
|
||||||
all_users = [solution.userinfo for solution in all_solutions]
|
|
||||||
return {
|
|
||||||
'tasks': sorted(list(set([solution.task for solution in all_solutions])), key=lambda x: x.name),
|
|
||||||
'users': sorted(list(set(all_users)), key=lambda x: str(x)),
|
|
||||||
'groups': sorted(list(set([userinfo.group for userinfo in all_users])), key=lambda x: str(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def delete_folder(path):
|
|
||||||
flag = True
|
|
||||||
while flag:
|
|
||||||
try:
|
|
||||||
rmtree(dirname(path))
|
|
||||||
flag = False
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def solution_path(path):
|
|
||||||
files = [x for x in listdir(path) if x.endswith('.sln') and not x.startswith('.')]
|
|
||||||
if files:
|
|
||||||
return path
|
|
||||||
return ''.join([solution_path(join(path, file)) for file in listdir(path) if isdir(join(path, file))])
|
|
||||||
|
|
||||||
|
|
||||||
def register_user(u):
|
|
||||||
password = random_string()
|
|
||||||
user = User.objects.create_user(username=u['email'], email=u['email'], password=password)
|
|
||||||
UserInfo.objects.create(
|
|
||||||
surname=u['surname'],
|
|
||||||
name=u['name'],
|
|
||||||
middle_name=u['middle_name'],
|
|
||||||
group=u['group'],
|
|
||||||
user=user
|
|
||||||
)
|
|
||||||
send_email('You have been registered in Sprint!', u['email'],
|
|
||||||
'Your password is: {}\nPlease change it after login in settings!\nhttps://sprint.cshse.ru/'.format(password))
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
def check_cheating(solutions, block, cheating_percent):
|
|
||||||
block.cheating_checking = True
|
|
||||||
block.save()
|
|
||||||
try:
|
|
||||||
cheating_data = {}
|
|
||||||
cheating_path = join(MEDIA_ROOT, 'cheating', str(block.id))
|
|
||||||
if exists(cheating_path):
|
|
||||||
rmtree(cheating_path)
|
|
||||||
mkdir(cheating_path)
|
|
||||||
for solution in solutions:
|
|
||||||
for file in solution.user_files.keys():
|
|
||||||
user_file = join(MEDIA_ROOT, 'solutions', str(solution.id), file)
|
|
||||||
dest_file = join(cheating_path, '_'.join([str(solution.id), basename(file)]))
|
|
||||||
copyfile(user_file, dest_file)
|
|
||||||
files_len = len(solutions)
|
|
||||||
files = listdir(cheating_path)
|
|
||||||
for i in range(len(files) - 1):
|
|
||||||
for j in range(i + 1, len(files)):
|
|
||||||
file1 = files[i]
|
|
||||||
file2 = files[j]
|
|
||||||
s1 = file1.split('_')
|
|
||||||
s2 = file2.split('_')
|
|
||||||
sol1 = Solution.objects.get(id=int(s1[0]))
|
|
||||||
sol2 = Solution.objects.get(id=int(s2[0]))
|
|
||||||
filename1 = '_'.join(s1[1:])
|
|
||||||
filename2 = '_'.join(s2[1:])
|
|
||||||
if sol1.user == sol2.user or sol1.task != sol2.task or filename1 != filename2:
|
|
||||||
continue
|
|
||||||
fp1 = copydetect.CodeFingerprint(join(cheating_path, file1), 25, 1)
|
|
||||||
fp2 = copydetect.CodeFingerprint(join(cheating_path, file2), 25, 1)
|
|
||||||
token_overlap, similarities, slices = copydetect.compare_files(fp1, fp2)
|
|
||||||
similarity = (similarities[0] + similarities[1]) / 2
|
|
||||||
if similarity >= cheating_percent / 100:
|
|
||||||
if sol1.user.id not in cheating_data.keys():
|
|
||||||
cheating_data[sol1.user.id] = []
|
|
||||||
if sol2.user.id not in cheating_data.keys():
|
|
||||||
cheating_data[sol2.user.id] = []
|
|
||||||
cheating_data[sol1.user.id].append({
|
|
||||||
'source': True,
|
|
||||||
'solution': sol1.id,
|
|
||||||
'file': filename1,
|
|
||||||
'similar': sol2.id,
|
|
||||||
'similarity': round(similarity * 100, 2)
|
|
||||||
})
|
|
||||||
cheating_data[sol2.user.id].append({
|
|
||||||
'source': False,
|
|
||||||
'solution': sol2.id,
|
|
||||||
'file': filename2,
|
|
||||||
'similar': sol1.id,
|
|
||||||
'similarity': round(similarity * 100, 2)
|
|
||||||
})
|
|
||||||
finally:
|
|
||||||
if exists(cheating_path):
|
|
||||||
rmtree(cheating_path)
|
|
||||||
with open(block.cheating_results_path, 'w') as fs:
|
|
||||||
fs.write(dumps(cheating_data))
|
|
||||||
block = Block.objects.get(id=block.id)
|
|
||||||
block.cheating_checking = False
|
|
||||||
block.save()
|
|
||||||
print('finished')
|
|
||||||
|
|
@@ -15,84 +15,201 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
operations = [
|
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"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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=""),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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="Решение"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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="[]"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -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",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
463
Main/models.py
463
Main/models.py
@@ -1,463 +0,0 @@
|
|||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db import models
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.db.models.signals import post_delete
|
|
||||||
from os.path import sep, join, exists
|
|
||||||
from os import remove
|
|
||||||
|
|
||||||
from Main.commands import shell
|
|
||||||
from Sprint.settings import MEDIA_ROOT
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from json import loads
|
|
||||||
|
|
||||||
|
|
||||||
class Course(models.Model):
|
|
||||||
name = models.TextField()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def teachers(self):
|
|
||||||
return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(user__is_staff=1, course=self)]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def subscribes(self):
|
|
||||||
return sorted(Subscribe.objects.filter(course=self), key=lambda s: s.user.email)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def students(self):
|
|
||||||
userinfo = lambda sub: sub.user.userinfo
|
|
||||||
return sorted(Subscribe.objects.filter(course=self, is_assistant=False, user__is_staff=False), key=lambda s: userinfo(s).surname + userinfo(s).name + userinfo(s).middle_name)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Block(models.Model):
|
|
||||||
name = models.TextField()
|
|
||||||
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
|
||||||
time_start = models.DateTimeField()
|
|
||||||
time_end = models.DateTimeField()
|
|
||||||
opened = models.BooleanField(default=False)
|
|
||||||
show_rating = models.BooleanField(default=True)
|
|
||||||
priority = models.IntegerField(default=5)
|
|
||||||
cheating_checking = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def messages(self):
|
|
||||||
return Message.objects.filter(task__block=self)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tasks(self):
|
|
||||||
return Task.objects.filter(block=self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def time_start_chrome(self):
|
|
||||||
return self.time_start.strftime("%Y-%m-%dT%H:%M")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def time_end_chrome(self):
|
|
||||||
return self.time_end.strftime("%Y-%m-%dT%H:%M")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_opened(self):
|
|
||||||
return 'checked' if self.opened else ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def solutions(self):
|
|
||||||
return reversed(Solution.objects.filter(task__block=self))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def subscribed_users(self):
|
|
||||||
return [UserInfo.objects.get(user=s.user) for s in Subscribe.objects.filter(course=self.course)]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cheating_results_path(self):
|
|
||||||
return join(MEDIA_ROOT, 'cheating_results', str(self.id))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cheating_checked(self):
|
|
||||||
return self.cheating_results != {}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cheating_results(self):
|
|
||||||
return loads(open(self.cheating_results_path, 'r').read()) if exists(self.cheating_results_path) else {}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cheating_status(self):
|
|
||||||
if self.cheating_checking:
|
|
||||||
return 'Идет проверка'
|
|
||||||
if not exists(self.cheating_results_path):
|
|
||||||
return 'Еще не проверено'
|
|
||||||
return 'Проверка завершена'
|
|
||||||
|
|
||||||
|
|
||||||
class Restore(models.Model):
|
|
||||||
code = models.TextField()
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.user.username
|
|
||||||
|
|
||||||
|
|
||||||
class Subscribe(models.Model):
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
|
||||||
is_assistant = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.user.username + '|' + self.course.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def userinfo(self):
|
|
||||||
return UserInfo.objects.get(user=self.user)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def role(self):
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return 'Администратор'
|
|
||||||
if self.user.is_staff:
|
|
||||||
return 'Преподаватель'
|
|
||||||
return 'Ассистент' if self.is_assistant else 'Студент'
|
|
||||||
|
|
||||||
|
|
||||||
class Task(models.Model):
|
|
||||||
name = models.TextField()
|
|
||||||
block = models.ForeignKey(Block, on_delete=models.CASCADE)
|
|
||||||
legend = models.TextField(default='')
|
|
||||||
input = models.TextField(default='')
|
|
||||||
output = models.TextField(default='')
|
|
||||||
specifications = models.TextField(default='')
|
|
||||||
time_limit = models.IntegerField(default=10000)
|
|
||||||
weight = models.FloatField(default=1.0)
|
|
||||||
max_mark = models.IntegerField(default=10)
|
|
||||||
max_solutions_count = models.IntegerField(default=10)
|
|
||||||
show_result = models.BooleanField(default=True)
|
|
||||||
show_details = models.BooleanField(default=False)
|
|
||||||
full_solution = models.BooleanField(default=False)
|
|
||||||
mark_formula = models.TextField(default='None')
|
|
||||||
priority = models.IntegerField(default=5)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def students_solutions(self):
|
|
||||||
students = [sub.user for sub in Subscribe.objects.filter(course=self.block.course)]
|
|
||||||
solutions = Solution.objects.filter(task=self)
|
|
||||||
return [sol for sol in solutions if sol.user in students]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def correct_count(self):
|
|
||||||
solutions = self.students_solutions
|
|
||||||
count = 0
|
|
||||||
for sol in solutions:
|
|
||||||
res = sol.result.split('/')
|
|
||||||
if len(res) == 2 and res[0] == res[1]:
|
|
||||||
count += 1
|
|
||||||
return count
|
|
||||||
|
|
||||||
@property
|
|
||||||
def solutions_count(self):
|
|
||||||
return len(self.students_solutions)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def partially_passed(self):
|
|
||||||
solutions = self.students_solutions
|
|
||||||
count = 0
|
|
||||||
for sol in solutions:
|
|
||||||
res = sol.result.split('/')
|
|
||||||
if len(res) == 2 and res[0] != res[1]:
|
|
||||||
count += 1
|
|
||||||
return count
|
|
||||||
|
|
||||||
@property
|
|
||||||
def solutions_with_error(self):
|
|
||||||
return self.solutions_count - self.correct_count - self.partially_passed
|
|
||||||
|
|
||||||
@property
|
|
||||||
def samples(self):
|
|
||||||
return [{
|
|
||||||
'input': file,
|
|
||||||
'output': file.answer
|
|
||||||
} for file in ExtraFile.objects.filter(task=self, sample=True).order_by('filename')]
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def showable(self):
|
|
||||||
return 'checked' if self.show_details else ''
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def tests_path(self):
|
|
||||||
return join(MEDIA_ROOT, 'tests', str(self.id) + '.cs')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tests_text(self):
|
|
||||||
try:
|
|
||||||
return open(self.tests_path(), 'r').read()
|
|
||||||
except FileNotFoundError:
|
|
||||||
with open(self.tests_path(), 'w') as fs:
|
|
||||||
pass
|
|
||||||
return ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tests_uploaded(self):
|
|
||||||
from os.path import exists
|
|
||||||
return exists(self.tests_path())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def files(self):
|
|
||||||
return ExtraFile.objects.filter(task=self).order_by('filename')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def files_for_compilation(self):
|
|
||||||
return ExtraFile.objects.filter(task=self, for_compilation=True)
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_full_solution(self):
|
|
||||||
return 'checked' if self.full_solution else ''
|
|
||||||
|
|
||||||
|
|
||||||
class UserInfo(models.Model):
|
|
||||||
surname = models.TextField()
|
|
||||||
name = models.TextField()
|
|
||||||
middle_name = models.TextField()
|
|
||||||
group = models.TextField()
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
|
|
||||||
|
|
||||||
def __eq__(self, obj):
|
|
||||||
return str(self) == str(obj)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{} {} {}".format(self.surname, self.name, self.middle_name)
|
|
||||||
|
|
||||||
|
|
||||||
class Solution(models.Model):
|
|
||||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
result = models.TextField()
|
|
||||||
details = models.TextField(default='')
|
|
||||||
time_sent = models.DateTimeField(null=True)
|
|
||||||
mark = models.IntegerField(null=True)
|
|
||||||
comment = models.TextField(default='')
|
|
||||||
|
|
||||||
def set_result(self, result):
|
|
||||||
self.result = result
|
|
||||||
if len(result.split('/')) != 1:
|
|
||||||
result = int(result.split('/')[0])
|
|
||||||
try:
|
|
||||||
self.mark = eval(self.task.mark_formula)
|
|
||||||
except:
|
|
||||||
self.mark = None
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.id)
|
|
||||||
|
|
||||||
def path(self):
|
|
||||||
return join(MEDIA_ROOT, 'solutions', str(self.id))
|
|
||||||
|
|
||||||
def write_log(self, text):
|
|
||||||
with self.log_fs as fs:
|
|
||||||
fs.write(bytes(text + '\n', 'cp866'))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def log_file(self):
|
|
||||||
return join(MEDIA_ROOT, 'logs', str(self.id) + '.log')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def log_text(self):
|
|
||||||
try:
|
|
||||||
return open(self.log_file, 'rb').read().decode('cp866')
|
|
||||||
except FileNotFoundError:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def log_fs(self):
|
|
||||||
return open(self.log_file, 'ab')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def userinfo(self):
|
|
||||||
return UserInfo.objects.get(user=self.user)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mark_property(self):
|
|
||||||
return str(self.mark) if self.mark is not None else 'нет оценки'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mark_select(self):
|
|
||||||
line = ''
|
|
||||||
if self.mark:
|
|
||||||
line += '<option value="нет оценки">нет оценки</option>'
|
|
||||||
else:
|
|
||||||
line += '<option value="нет оценки" selected>нет оценки</option>'
|
|
||||||
for mark in range(self.task.max_mark + 1):
|
|
||||||
if mark == self.mark:
|
|
||||||
line += '<option value="{}" selected>{}</option>'.format(mark, mark)
|
|
||||||
else:
|
|
||||||
line += '<option value="{}">{}</option>'.format(mark, mark)
|
|
||||||
return line
|
|
||||||
|
|
||||||
@property
|
|
||||||
def comment_property(self):
|
|
||||||
return self.comment if self.comment else 'нет комментария'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_files(path):
|
|
||||||
from os import listdir
|
|
||||||
from os.path import isfile, join
|
|
||||||
files_dict = {}
|
|
||||||
for file in listdir(path):
|
|
||||||
if file == '__MACOSX' or file == 'test_folder' or file == 'bin' or file == 'obj' or file == '.vs':
|
|
||||||
continue
|
|
||||||
current_file = join(path, file)
|
|
||||||
if isfile(current_file):
|
|
||||||
if not current_file.endswith('.csproj') and not current_file.endswith('.sln'):
|
|
||||||
try:
|
|
||||||
files_dict[sep.join(current_file.split('solutions' + sep)[1].split(sep)[1:])] \
|
|
||||||
= open(current_file, 'rb').read().decode('UTF-8')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
files_dict = {**files_dict, **Solution.get_files(current_file)}
|
|
||||||
return files_dict
|
|
||||||
|
|
||||||
@property
|
|
||||||
def files(self):
|
|
||||||
return Solution.get_files(self.path())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user_files(self):
|
|
||||||
f = {}
|
|
||||||
comp_files = [ef.filename for ef in ExtraFile.objects.filter(task=self.task, for_compilation=True)]
|
|
||||||
for fi in self.files.keys():
|
|
||||||
if not fi in comp_files:
|
|
||||||
f[fi] = self.files[fi]
|
|
||||||
return f
|
|
||||||
|
|
||||||
@property
|
|
||||||
def passed_all_tests(self):
|
|
||||||
spl = self.result.split('/')
|
|
||||||
return len(spl) == 2 and spl[0] == spl[1]
|
|
||||||
|
|
||||||
class System(models.Model):
|
|
||||||
key = models.TextField()
|
|
||||||
value = models.TextField()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.key
|
|
||||||
|
|
||||||
|
|
||||||
class ExtraFile(models.Model):
|
|
||||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
|
||||||
filename = models.TextField()
|
|
||||||
for_compilation = models.BooleanField(default=False)
|
|
||||||
sample = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def answer(self):
|
|
||||||
try:
|
|
||||||
return ExtraFile.objects.get(task=self.task, filename=self.filename + '.a')
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def num(self):
|
|
||||||
try:
|
|
||||||
return int(self.filename.split('.')[0])
|
|
||||||
except ValueError:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_for_compilation(self):
|
|
||||||
return 'checked' if self.for_compilation else ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_sample(self):
|
|
||||||
return 'checked' if self.sample else ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def can_be_sample(self):
|
|
||||||
try:
|
|
||||||
int(self.filename)
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
ans = ExtraFile.objects.get(task=self.task, filename=self.filename + '.a')
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return False
|
|
||||||
return self.readable and ans.readable
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path(self):
|
|
||||||
return join(MEDIA_ROOT, 'extra_files', str(self.id))
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def readable(self):
|
|
||||||
try:
|
|
||||||
open(self.path, 'rb').read().decode('UTF-8')
|
|
||||||
return True
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self):
|
|
||||||
return open(self.path, 'rb').read().decode('UTF-8')
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.filename
|
|
||||||
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
with open(self.path, 'wb') as fs:
|
|
||||||
fs.write(data)
|
|
||||||
|
|
||||||
|
|
||||||
class Message(models.Model):
|
|
||||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
|
||||||
sender = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
|
|
||||||
reply_to = models.ForeignKey('Message', on_delete=models.CASCADE, null=True)
|
|
||||||
for_all = models.BooleanField()
|
|
||||||
text = models.TextField()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Task)
|
|
||||||
def delete_task_hook(sender, instance, using, **kwargs):
|
|
||||||
if exists(instance.tests_path()):
|
|
||||||
from os import remove
|
|
||||||
remove(instance.tests_path())
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Solution)
|
|
||||||
def delete_solution_hook(sender, instance, using, **kwargs):
|
|
||||||
if exists(instance.path()):
|
|
||||||
from shutil import rmtree
|
|
||||||
rmtree(instance.path())
|
|
||||||
shell('docker rm --force solution_container_{}'.format(instance.id))
|
|
||||||
shell('docker image rm solution_{}'.format(instance.id))
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=ExtraFile)
|
|
||||||
def delete_file_hook(sender, instance, using, **kwargs):
|
|
||||||
try:
|
|
||||||
if exists(instance.path):
|
|
||||||
remove(instance.path)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if instance.filename.endswith('.a'):
|
|
||||||
try:
|
|
||||||
t = ExtraFile.objects.get(task=instance.task, filename=instance.filename[:-2])
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return
|
|
||||||
t.sample = False
|
|
||||||
t.save()
|
|
10
Main/models/__init__.py
Normal file
10
Main/models/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from Main.models.userinfo import UserInfo
|
||||||
|
from Main.models.group import Group
|
||||||
|
from Main.models.task import Task
|
||||||
|
from Main.models.file import File
|
||||||
|
from Main.models.set import Set
|
||||||
|
from Main.models.subscription import Subscription
|
||||||
|
from Main.models.settask import SetTask
|
||||||
|
from Main.models.solution import Solution
|
||||||
|
from Main.models.language import Language
|
||||||
|
from Main.models.extrafile import ExtraFile
|
27
Main/models/extrafile.py
Normal file
27
Main/models/extrafile.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from os import remove
|
||||||
|
from os.path import join, exists
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from Sprint.settings import DATA_ROOT
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraFile(models.Model):
|
||||||
|
task = models.ForeignKey('Task', on_delete=models.CASCADE)
|
||||||
|
filename = models.TextField()
|
||||||
|
is_test = models.BooleanField(null=True)
|
||||||
|
readable = models.BooleanField(null=True)
|
||||||
|
test_number = models.IntegerField(null=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return join(DATA_ROOT, 'extra_files', str(self.id))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
return open(self.path, 'r').read()
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
if exists(self.path):
|
||||||
|
remove(self.path)
|
||||||
|
super().delete(using=using, keep_parents=keep_parents)
|
7
Main/models/file.py
Normal file
7
Main/models/file.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from django.db import models
|
||||||
|
from Main.models.task import Task
|
||||||
|
|
||||||
|
|
||||||
|
class File(models.Model):
|
||||||
|
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||||
|
name = models.TextField()
|
7
Main/models/group.py
Normal file
7
Main/models/group.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from django.db import models
|
||||||
|
from Main.models.set import Set
|
||||||
|
|
||||||
|
|
||||||
|
class Group(models.Model):
|
||||||
|
name = models.TextField()
|
||||||
|
sets = models.ManyToManyField(Set)
|
13
Main/models/language.py
Normal file
13
Main/models/language.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Language(models.Model):
|
||||||
|
name = models.TextField()
|
||||||
|
work_name = models.TextField(default='')
|
||||||
|
file_type = models.TextField(null=True)
|
||||||
|
logo = models.ImageField(upload_to="logos", null=True)
|
||||||
|
image = models.TextField(default='ubuntu')
|
||||||
|
opened = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
8
Main/models/set.py
Normal file
8
Main/models/set.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Set(models.Model):
|
||||||
|
name = models.TextField()
|
||||||
|
public = models.BooleanField(default=False)
|
||||||
|
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
10
Main/models/settask.py
Normal file
10
Main/models/settask.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from Main.models.task import Task
|
||||||
|
from Main.models.set import Set
|
||||||
|
|
||||||
|
|
||||||
|
class SetTask(models.Model):
|
||||||
|
set = models.ForeignKey(Set, on_delete=models.CASCADE)
|
||||||
|
task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name="settasks")
|
||||||
|
name = models.CharField(max_length=2)
|
40
Main/models/solution.py
Normal file
40
Main/models/solution.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from os import mkdir
|
||||||
|
from os.path import join, exists
|
||||||
|
from shutil import rmtree
|
||||||
|
from subprocess import call
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from Main.models.task import Task
|
||||||
|
from Main.models.language import Language
|
||||||
|
from Sprint.settings import CONSTS, SOLUTIONS_ROOT
|
||||||
|
|
||||||
|
|
||||||
|
class Solution(models.Model):
|
||||||
|
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
language = models.ForeignKey(Language, on_delete=models.SET_NULL, null=True)
|
||||||
|
time_sent = models.DateTimeField(default=timezone.now)
|
||||||
|
result = models.TextField(default=CONSTS["in_queue_status"])
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
if exists(self.directory):
|
||||||
|
rmtree(self.directory)
|
||||||
|
super().delete(using=using, keep_parents=keep_parents)
|
||||||
|
|
||||||
|
def create_dirs(self):
|
||||||
|
mkdir(self.directory)
|
||||||
|
mkdir(self.testing_directory)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def directory(self):
|
||||||
|
return join(SOLUTIONS_ROOT, str(self.id))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def testing_directory(self):
|
||||||
|
return join(self.directory, 'test_dir')
|
||||||
|
|
||||||
|
def exec_command(self, command, working_directory='app', timeout=None):
|
||||||
|
return call(f'docker exec -i solution_{self.id} bash -c "cd {working_directory} && {command}"', shell=True, timeout=timeout)
|
12
Main/models/subscription.py
Normal file
12
Main/models/subscription.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from Main.models.group import Group
|
||||||
|
|
||||||
|
|
||||||
|
class Subscription(models.Model):
|
||||||
|
group = models.ForeignKey(
|
||||||
|
Group, on_delete=models.CASCADE, related_name="subscriptions"
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
role = models.IntegerField()
|
26
Main/models/task.py
Normal file
26
Main/models/task.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from Main.models.extrafile import ExtraFile
|
||||||
|
|
||||||
|
|
||||||
|
class Task(models.Model):
|
||||||
|
name = models.TextField()
|
||||||
|
public = models.BooleanField(default=False)
|
||||||
|
legend = models.TextField(default="")
|
||||||
|
input_format = models.TextField(default="")
|
||||||
|
output_format = models.TextField(default="")
|
||||||
|
specifications = models.TextField(default="")
|
||||||
|
time_limit = models.IntegerField(default=10000)
|
||||||
|
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
return ExtraFile.objects.filter(task=self, is_test=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tests(self):
|
||||||
|
return ExtraFile.objects.filter(task=self, is_test=True)
|
71
Main/models/userinfo.py
Normal file
71
Main/models/userinfo.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from Main.models.subscription import Subscription
|
||||||
|
from Main.models.group import Group
|
||||||
|
from Main.models.settask import SetTask
|
||||||
|
from Main.models.task import Task
|
||||||
|
from Sprint.settings import CONSTS
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfo(models.Model):
|
||||||
|
surname = models.TextField()
|
||||||
|
name = models.TextField()
|
||||||
|
middle_name = models.TextField()
|
||||||
|
last_request = models.DateTimeField(default=timezone.now)
|
||||||
|
profile_picture = models.ImageField(upload_to="profile_pictures", null=True)
|
||||||
|
rating = models.IntegerField(default=0)
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
|
||||||
|
|
||||||
|
def _append_task(self, task, tasks):
|
||||||
|
if task.creator == self.user or task.public:
|
||||||
|
tasks.append(task)
|
||||||
|
return
|
||||||
|
for st in SetTask.objects.filter(task=task):
|
||||||
|
if st.set.public:
|
||||||
|
tasks.append(task)
|
||||||
|
return
|
||||||
|
for group in Group.objects.filter(sets=st.set):
|
||||||
|
for sub in Subscription.objects.filter(group=group):
|
||||||
|
if sub.user == self.user:
|
||||||
|
tasks.append(task)
|
||||||
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_tasks(self):
|
||||||
|
tasks = []
|
||||||
|
for task in Task.objects.all():
|
||||||
|
self._append_task(task, tasks)
|
||||||
|
return tasks
|
||||||
|
|
||||||
|
@property
|
||||||
|
def place(self):
|
||||||
|
return len(UserInfo.objects.filter(rating__gt=self.rating)) + 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def activity_status(self):
|
||||||
|
if timezone.now() - self.last_request <= timezone.timedelta(minutes=5):
|
||||||
|
return CONSTS["online_status"]
|
||||||
|
return timezone.datetime.strftime(self.last_request, "%d-%m-%Y %H:%M")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_create(self):
|
||||||
|
# todo:
|
||||||
|
return self.user.is_superuser
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_profile_pic(self):
|
||||||
|
try:
|
||||||
|
return self.profile_picture.url is not None
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def profile_pic_url(self):
|
||||||
|
if self.has_profile_pic:
|
||||||
|
return self.profile_picture.url
|
||||||
|
return "https://i2.wp.com/electrolabservice.com/wp-content/uploads/2021/01/blank-profile-picture-mystery-man-avatar-973460.jpg"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} {} {}".format(self.surname, self.name, self.middle_name)
|
9
Main/tasks.py
Normal file
9
Main/tasks.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from Main.models import Solution
|
||||||
|
from Sprint.celery import app
|
||||||
|
from SprintLib.testers import *
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def start_testing(solution_id):
|
||||||
|
solution = Solution.objects.get(id=solution_id)
|
||||||
|
eval(solution.language.work_name + 'Tester')(solution).execute()
|
@@ -1,112 +1,5 @@
|
|||||||
from django import template
|
from 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}')
|
|
||||||
|
833
Main/views.py
833
Main/views.py
@@ -1,833 +0,0 @@
|
|||||||
from json import load, dumps, loads
|
|
||||||
from os import remove, mkdir, listdir, rename
|
|
||||||
from os.path import sep, join, exists, isfile, dirname
|
|
||||||
from shutil import rmtree, copytree, make_archive, copyfile
|
|
||||||
from threading import Thread
|
|
||||||
from zipfile import ZipFile, BadZipFile
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
from django.contrib.auth import login, authenticate, logout
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.timezone import make_aware
|
|
||||||
|
|
||||||
from Main.templatetags.filters import *
|
|
||||||
from Sprint.settings import MEDIA_ROOT
|
|
||||||
from .Tester import Tester
|
|
||||||
from .forms import *
|
|
||||||
from .main import solutions_filter, check_admin_on_course, re_test, check_admin, check_teacher, random_string, \
|
|
||||||
send_email, check_permission_block, is_integer, check_god, blocks_available, check_login, \
|
|
||||||
get_restore_hash, block_solutions_info, solution_path, can_send_solution, get_in_html_tag, register_user, \
|
|
||||||
check_cheating, result_comparer
|
|
||||||
from .models import Block, Subscribe, Course, Restore, ExtraFile, Message
|
|
||||||
from .decorators import login_required
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def download_rating(request):
|
|
||||||
block = Block.objects.get(id=request.GET['block_id'])
|
|
||||||
if not check_admin_on_course(request.user, block.course):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
s = 'ФИО,'
|
|
||||||
tasks = block.tasks
|
|
||||||
users = [UserInfo.objects.get(user=user.user) for user in Subscribe.objects.filter(course=block.course, is_assistant=False, user__is_staff=False)]
|
|
||||||
for task in tasks:
|
|
||||||
try:
|
|
||||||
s += task.name.split('.')[0] + ','
|
|
||||||
except IndexError:
|
|
||||||
s += task.name + ','
|
|
||||||
s += 'Score,Summ\n'
|
|
||||||
for user in users:
|
|
||||||
s += str(user) + ','
|
|
||||||
for task in tasks:
|
|
||||||
s += str(mark_for_task(task, user.user)) + ','
|
|
||||||
s += '0,' + str(mark_for_block(block, user.user)) + '\n'
|
|
||||||
response = HttpResponse(bytes(s, encoding='utf-8'), content_type='application/force-download')
|
|
||||||
response['Content-Disposition'] = 'inline; filename=rating.csv'
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def download(request):
|
|
||||||
if not check_admin(request.user) or not check_admin_on_course(request.user, Block.objects.get(id=request.GET['block_id']).course):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
sols = solutions_filter(request.GET)
|
|
||||||
if len(sols) == 0:
|
|
||||||
return HttpResponseRedirect('/admin/solutions?block_id=' + request.GET['block_id'])
|
|
||||||
new_folder = join(MEDIA_ROOT, request.user.username)
|
|
||||||
if exists(new_folder):
|
|
||||||
try:
|
|
||||||
rmtree(new_folder)
|
|
||||||
except:
|
|
||||||
remove(new_folder)
|
|
||||||
mkdir(new_folder)
|
|
||||||
cur_folder = join(new_folder, 'solutions')
|
|
||||||
mkdir(cur_folder)
|
|
||||||
for sol in sols:
|
|
||||||
uinfo = UserInfo.objects.get(user=sol.user)
|
|
||||||
folder = join(cur_folder, str(uinfo) + '-' + str(uinfo.user.id))
|
|
||||||
if not exists(folder):
|
|
||||||
mkdir(folder)
|
|
||||||
files = sol.files
|
|
||||||
files_for_compilation = [f.filename for f in sol.task.files_for_compilation]
|
|
||||||
for f in files.copy().keys():
|
|
||||||
if f.split(sep)[-1] in files_for_compilation:
|
|
||||||
del files[f]
|
|
||||||
if len(files.keys()) == 1:
|
|
||||||
dest = join(folder, "-".join([sol.task.name.split('.')[0], str(sol.id), 'dotnet', sol.result.replace('/', 'from')])) + '.cs'
|
|
||||||
source = join(sol.path(), list(files.keys())[0])
|
|
||||||
with open(dest, 'wb') as fs:
|
|
||||||
fs.write(open(source, 'rb').read())
|
|
||||||
else:
|
|
||||||
newname = join(folder, '-'.join([sol.task.name.split('.')[0], str(sol.id), 'dotnet', sol.result])).replace('/', 'from')
|
|
||||||
mkdir(newname)
|
|
||||||
for f in files.keys():
|
|
||||||
with open(join(newname, f.split(sep)[-1]), 'wb') as fs:
|
|
||||||
fs.write(open(join(sol.path(), f), 'rb').read())
|
|
||||||
make_archive(newname, 'zip', newname)
|
|
||||||
rmtree(newname)
|
|
||||||
zip_folder = join(dirname(cur_folder), 'solutions')
|
|
||||||
make_archive(zip_folder, 'zip', cur_folder)
|
|
||||||
response = HttpResponse(open(zip_folder + '.zip', 'rb').read(), content_type='application/force-download')
|
|
||||||
response['Content-Disposition'] = 'inline; filename=solutions.zip'
|
|
||||||
rmtree(dirname(cur_folder))
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def queue(request):
|
|
||||||
block = Block.objects.get(id=request.GET['block_id'])
|
|
||||||
if not check_admin_on_course(request.user, block.course):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
return render(request, 'queue.html', {'Block': block})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def docs(request):
|
|
||||||
if not check_admin(request.user):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
return render(request, "docs.html", context={'is_teacher': check_teacher(request.user)})
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def retest(request):
|
|
||||||
solutions_request = solutions_filter(request.GET)
|
|
||||||
if not check_admin_on_course(request.user, Block.objects.get(id=request.GET['block_id']).course):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
req = '?block_id=' + str(request.GET['block_id'])
|
|
||||||
for key in request.GET.keys():
|
|
||||||
if key != 'block_id':
|
|
||||||
req += '&{}={}'.format(key, request.GET[key])
|
|
||||||
Thread(target=lambda: re_test(solutions_request, request)).start()
|
|
||||||
if 'next' in request.GET.keys():
|
|
||||||
return HttpResponseRedirect(request.GET['next'])
|
|
||||||
return HttpResponseRedirect('/admin/solutions%s' % req)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def solution(request):
|
|
||||||
current_solution = Solution.objects.get(id=request.GET['id'])
|
|
||||||
if current_solution.user != request.user:
|
|
||||||
try:
|
|
||||||
Subscribe.objects.get(user=request.user, is_assistant=True, course=current_solution.task.block.course)
|
|
||||||
except:
|
|
||||||
if not request.user.is_superuser:
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
can_edit = check_admin_on_course(request.user, current_solution.task.block.course)
|
|
||||||
if not can_edit:
|
|
||||||
# тут по хорошему надо использовать регулярки, но я что-то не разобрался
|
|
||||||
while True:
|
|
||||||
i = current_solution.details.find('<pre>')
|
|
||||||
if i == -1:
|
|
||||||
break
|
|
||||||
j = current_solution.details.find('</pre>') + 6
|
|
||||||
current_solution.details = current_solution.details.replace(current_solution.details[i:j], '')
|
|
||||||
if current_solution.user != request.user:
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
solutions_request = solutions_filter(request.GET)
|
|
||||||
if request.path == '/admin/solution':
|
|
||||||
from_admin = True
|
|
||||||
else:
|
|
||||||
from_admin = False
|
|
||||||
if request.method == 'POST' and can_edit:
|
|
||||||
if request.POST['action'] == 'Зачесть':
|
|
||||||
current_solution.mark = None if request.POST['mark'] == 'нет оценки' else int(request.POST['mark'])
|
|
||||||
elif request.POST['action'] == 'Незачесть':
|
|
||||||
current_solution.mark = 0
|
|
||||||
else:
|
|
||||||
current_solution.mark = current_solution.task.max_mark
|
|
||||||
current_solution.comment = request.POST['comment']
|
|
||||||
current_solution.save()
|
|
||||||
return render(request, 'solution.html', context={'solution': current_solution,
|
|
||||||
'from_admin': from_admin,
|
|
||||||
'can_edit': can_edit,
|
|
||||||
'path': request.path})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def solutions(request):
|
|
||||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
|
||||||
try:
|
|
||||||
if not request.user.is_superuser:
|
|
||||||
s = Subscribe.objects.get(user=request.user, course=current_block.course)
|
|
||||||
if not s.is_assistant and not s.user.is_staff:
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
req = ''
|
|
||||||
sols = solutions_filter(request.GET)
|
|
||||||
for key in request.GET.keys():
|
|
||||||
req += '&{}={}'.format(key, request.GET[key])
|
|
||||||
if request.method == 'POST':
|
|
||||||
Solution.objects.get(id=request.POST['DELETE_SOLUTION']).delete()
|
|
||||||
return HttpResponseRedirect('/admin/solutions?block_id={}{}'.format(current_block.id, req))
|
|
||||||
return render(request, 'solutions.html', context={'Block': current_block,
|
|
||||||
'filter': ' '.join([str(sol.id) for sol in sols]),
|
|
||||||
'solutions': sols,
|
|
||||||
'req': req,
|
|
||||||
'options': {key: request.GET[key] for key in request.GET.keys()},
|
|
||||||
'solutions_info': block_solutions_info(current_block)})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def messages(request):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
|
||||||
return render(request, 'messages.html', context={'Block': current_block})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def users_settings(request):
|
|
||||||
current_course = Course.objects.get(id=request.GET['course_id'])
|
|
||||||
if not check_admin_on_course(request.user, current_course):
|
|
||||||
if not request.user.is_superuser:
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
if request.method == 'POST':
|
|
||||||
if 'input' in request.POST.keys():
|
|
||||||
line = request.POST['input']
|
|
||||||
if '@' in line:
|
|
||||||
users = UserInfo.objects.filter(user__email=line)
|
|
||||||
elif any(c.isdigit() for c in line):
|
|
||||||
users = UserInfo.objects.filter(group=line)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
s, n, m = line.split(' ')
|
|
||||||
except ValueError:
|
|
||||||
s, n, m = '', '', ''
|
|
||||||
users = list(UserInfo.objects.filter(surname=s, name=n, middle_name=m)) + list(UserInfo.objects.filter(group=line))
|
|
||||||
for user in users:
|
|
||||||
try:
|
|
||||||
Subscribe.objects.get(user=user.user, course=current_course)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
Subscribe.objects.create(user=user.user, course=current_course)
|
|
||||||
elif 'user_delete' in request.POST.keys():
|
|
||||||
username = request.POST['user_delete']
|
|
||||||
course_id = request.GET['course_id']
|
|
||||||
Subscribe.objects.get(user__email=username, course_id=course_id).delete()
|
|
||||||
elif 'file' in request.FILES.keys():
|
|
||||||
users = load(request.FILES['file'])
|
|
||||||
for u in users:
|
|
||||||
password = random_string()
|
|
||||||
flag = False
|
|
||||||
try:
|
|
||||||
user = User.objects.get(email=u['email'])
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
flag = True
|
|
||||||
if flag:
|
|
||||||
user = register_user(u)
|
|
||||||
try:
|
|
||||||
Subscribe.objects.get(user=user, course=current_course)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
Subscribe.objects.create(user=user, course=current_course, is_assistant=False)
|
|
||||||
else:
|
|
||||||
key = list(request.POST.keys())[1]
|
|
||||||
role = request.POST[key]
|
|
||||||
username = '_'.join(key.split('_')[1:])
|
|
||||||
s = Subscribe.objects.get(user__email=username, course=current_course)
|
|
||||||
s.is_assistant = role == 'Ассистент'
|
|
||||||
s.save()
|
|
||||||
return HttpResponseRedirect('/admin/users_settings?course_id=' + str(current_course.id))
|
|
||||||
return render(request, 'users_settings.html', context={'course': current_course})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def task(request):
|
|
||||||
current_task = Task.objects.get(id=request.GET['id'])
|
|
||||||
user = request.user
|
|
||||||
if not check_permission_block(user, current_task.block):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
can_send = can_send_solution(user, current_task)
|
|
||||||
if request.method == 'POST':
|
|
||||||
if 'message' in request.POST.keys():
|
|
||||||
Message.objects.create(
|
|
||||||
task=current_task,
|
|
||||||
sender=request.user,
|
|
||||||
reply_to=None,
|
|
||||||
for_all=False,
|
|
||||||
text=request.POST['message']
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
|
||||||
if 'file' in request.FILES.keys() and can_send:
|
|
||||||
current_solution = Solution.objects.create(
|
|
||||||
task=current_task,
|
|
||||||
user=request.user,
|
|
||||||
result='IN QUEUE',
|
|
||||||
time_sent=timezone.now()
|
|
||||||
)
|
|
||||||
log_file_path = current_solution.log_file
|
|
||||||
with open(log_file_path, 'wb') as fs:
|
|
||||||
pass
|
|
||||||
solution_dir = current_solution.path() + sep
|
|
||||||
if exists(solution_dir):
|
|
||||||
rmtree(solution_dir)
|
|
||||||
mkdir(solution_dir)
|
|
||||||
with open(solution_dir + 'solution.zip', 'wb') as fs:
|
|
||||||
for chunk in request.FILES['file'].chunks():
|
|
||||||
fs.write(chunk)
|
|
||||||
flag = False
|
|
||||||
solution_created = False
|
|
||||||
try:
|
|
||||||
with ZipFile(solution_dir + 'solution.zip') as obj:
|
|
||||||
obj.extractall(solution_dir)
|
|
||||||
except BadZipFile:
|
|
||||||
rename(solution_dir + 'solution.zip', solution_dir + request.FILES['file'].name)
|
|
||||||
sln_path = solution_path(solution_dir)
|
|
||||||
if current_task.full_solution != bool(sln_path):
|
|
||||||
current_solution.result = 'TEST ERROR'
|
|
||||||
with open(log_file_path, 'ab') as fs:
|
|
||||||
fs.write(b'Can\'t find sln file in solution' if current_task.full_solution else b'Sln file in path')
|
|
||||||
current_solution.save()
|
|
||||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
|
||||||
if not bool(sln_path):
|
|
||||||
copytree('SampleSolution', join(solution_dir, 'Solution'))
|
|
||||||
for file in listdir(solution_dir):
|
|
||||||
if file == 'solution.zip' or file == 'Solution':
|
|
||||||
continue
|
|
||||||
cur_file = join(solution_dir, file)
|
|
||||||
if isfile(cur_file):
|
|
||||||
copyfile(cur_file, join(solution_dir, 'Solution', 'SampleProject', file))
|
|
||||||
remove(cur_file)
|
|
||||||
else:
|
|
||||||
rmtree(cur_file)
|
|
||||||
if not current_task.full_solution:
|
|
||||||
for file in current_task.files_for_compilation:
|
|
||||||
copyfile(file.path, join(solution_dir, 'Solution', 'SampleProject', file.filename))
|
|
||||||
#Tester(current_solution, request.META['HTTP_HOST']).push()
|
|
||||||
Thread(target=lambda: Tester(current_solution, request.META['HTTP_HOST']).push()).start()
|
|
||||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
|
||||||
return render(request, 'task.html', context={'task': current_task,
|
|
||||||
'solutions': reversed(Solution.objects.filter(task=current_task, user=user)),
|
|
||||||
'can_send': can_send,
|
|
||||||
'can_edit': check_admin_on_course(request.user, current_task.block.course)})
|
|
||||||
|
|
||||||
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def task_test(request):
|
|
||||||
current_task = Task.objects.get(id=request.GET['id'])
|
|
||||||
user = request.user
|
|
||||||
if not check_permission_block(user, current_task.block):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
can_send = can_send_solution(user, current_task)
|
|
||||||
if request.method == 'POST':
|
|
||||||
if 'file' in request.FILES.keys() and can_send:
|
|
||||||
current_solution = Solution.objects.create(
|
|
||||||
task=current_task,
|
|
||||||
user=request.user,
|
|
||||||
result='IN QUEUE',
|
|
||||||
time_sent=timezone.now()
|
|
||||||
)
|
|
||||||
log_file_path = current_solution.log_file
|
|
||||||
with open(log_file_path, 'wb') as fs:
|
|
||||||
pass
|
|
||||||
solution_dir = current_solution.path() + sep
|
|
||||||
if exists(solution_dir):
|
|
||||||
rmtree(solution_dir)
|
|
||||||
mkdir(solution_dir)
|
|
||||||
with open(solution_dir + 'solution.zip', 'wb') as fs:
|
|
||||||
for chunk in request.FILES['file'].chunks():
|
|
||||||
fs.write(chunk)
|
|
||||||
flag = False
|
|
||||||
solution_created = False
|
|
||||||
try:
|
|
||||||
with ZipFile(solution_dir + 'solution.zip') as obj:
|
|
||||||
obj.extractall(solution_dir)
|
|
||||||
except BadZipFile:
|
|
||||||
rename(solution_dir + 'solution.zip', solution_dir + request.FILES['file'].name)
|
|
||||||
sln_path = solution_path(solution_dir)
|
|
||||||
if current_task.full_solution != bool(sln_path):
|
|
||||||
current_solution.result = 'TEST ERROR'
|
|
||||||
with open(log_file_path, 'ab') as fs:
|
|
||||||
fs.write(b'Can\'t find sln file in solution' if current_task.full_solution else b'Sln file in path')
|
|
||||||
current_solution.save()
|
|
||||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
|
||||||
if not bool(sln_path):
|
|
||||||
copytree('SampleSolution', join(solution_dir, 'Solution'))
|
|
||||||
for file in listdir(solution_dir):
|
|
||||||
if file == 'solution.zip' or file == 'Solution':
|
|
||||||
continue
|
|
||||||
cur_file = join(solution_dir, file)
|
|
||||||
if isfile(cur_file):
|
|
||||||
copyfile(cur_file, join(solution_dir, 'Solution', 'SampleProject', file))
|
|
||||||
remove(cur_file)
|
|
||||||
else:
|
|
||||||
rmtree(cur_file)
|
|
||||||
if not current_task.full_solution:
|
|
||||||
for file in current_task.files_for_compilation:
|
|
||||||
copyfile(file.path, join(solution_dir, 'Solution', 'SampleProject', file.filename))
|
|
||||||
#Tester(current_solution, request.META['HTTP_HOST']).push()
|
|
||||||
Thread(target=lambda: Tester(current_solution, request.META['HTTP_HOST']).push()).start()
|
|
||||||
return HttpResponseRedirect('/task?id=' + str(current_task.id))
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def block(request):
|
|
||||||
current_block = Block.objects.get(id=request.GET['id'])
|
|
||||||
if not check_permission_block(request.user, current_block):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
return render(request, 'block.html', context={'Block': current_block,
|
|
||||||
'is_admin': check_admin(request.user),
|
|
||||||
'can_edit': check_admin_on_course(request.user, current_block.course),
|
|
||||||
'user': request.user})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def task_settings(request):
|
|
||||||
if not check_admin(request.user):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
current_task = Task.objects.get(id=request.GET['id'])
|
|
||||||
if request.method == 'POST':
|
|
||||||
action = request.POST['ACTION']
|
|
||||||
if action == 'DELETE':
|
|
||||||
t = Task.objects.get(id=request.GET['id'])
|
|
||||||
block_id = t.block.id
|
|
||||||
t.delete()
|
|
||||||
return HttpResponseRedirect('/admin/block?id=' + str(block_id))
|
|
||||||
elif action.startswith('SAVE_EXTRA_FILE_'):
|
|
||||||
i = action.split('_')[-1]
|
|
||||||
ef = ExtraFile.objects.get(id=int(i))
|
|
||||||
with open(ef.path, 'wb') as fs:
|
|
||||||
file_text = request.POST['extra_file_text_' + i]
|
|
||||||
fs.write(bytes(file_text, encoding='utf-8'))
|
|
||||||
ef.for_compilation = '{}_for_compilation'.format(ef.id) in request.POST.keys()
|
|
||||||
ef.save()
|
|
||||||
elif action == 'SAVE':
|
|
||||||
current_task.legend, current_task.input, current_task.output, current_task.specifications = \
|
|
||||||
request.POST['legend'], request.POST['input'], request.POST['output'], request.POST['specifications']
|
|
||||||
current_task.time_limit = int(request.POST['time_limit']) if is_integer(request.POST['time_limit']) else 10000
|
|
||||||
current_task.show_details = 'show_details' in request.POST.keys()
|
|
||||||
current_task.full_solution = 'full_solution' in request.POST.keys()
|
|
||||||
current_task.mark_formula = request.POST['mark_formula']
|
|
||||||
current_task.show_result = 'show_result' in request.POST.keys()
|
|
||||||
current_task.priority = request.POST['priority']
|
|
||||||
for ef in ExtraFile.objects.filter(task=current_task):
|
|
||||||
ef.sample = 'sample_' + str(ef.id) in request.POST.keys()
|
|
||||||
ef.save()
|
|
||||||
try:
|
|
||||||
current_task.weight = float(request.POST['weight'].replace(',', '.'))
|
|
||||||
except ValueError:
|
|
||||||
current_task.weight = 1.0
|
|
||||||
try:
|
|
||||||
current_task.max_mark = int(request.POST['max_mark'])
|
|
||||||
if current_task.max_mark == 0:
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
current_task.max_mark = 10
|
|
||||||
try:
|
|
||||||
current_task.max_solutions_count = int(request.POST['max_solutions_count'])
|
|
||||||
except ValueError:
|
|
||||||
current_task.max_solutions_count = 10
|
|
||||||
|
|
||||||
elif action == 'UPLOAD_EXTRA_FILE':
|
|
||||||
if request.FILES['file'].name.endswith('.zip'):
|
|
||||||
try:
|
|
||||||
wdir = join(MEDIA_ROOT, 'extra_files', 'files' + str(current_task.id))
|
|
||||||
if exists(wdir):
|
|
||||||
rmtree(wdir)
|
|
||||||
mkdir(wdir)
|
|
||||||
with open(join(wdir, 'file.zip'), 'wb') as fs:
|
|
||||||
for chunk in request.FILES['file'].chunks():
|
|
||||||
fs.write(chunk)
|
|
||||||
with ZipFile(join(wdir, 'file.zip')) as obj:
|
|
||||||
obj.extractall(wdir)
|
|
||||||
remove(join(wdir, 'file.zip'))
|
|
||||||
for file in listdir(wdir):
|
|
||||||
if isfile(join(wdir, file)):
|
|
||||||
try:
|
|
||||||
ef = ExtraFile.objects.get(filename=file, task=current_task)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
ef = ExtraFile.objects.create(filename=file, task=current_task)
|
|
||||||
ef.write(open(join(wdir, file), 'rb').read())
|
|
||||||
rmtree(wdir)
|
|
||||||
except BadZipFile:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
ef = ExtraFile.objects.get(filename=request.FILES['file'].name, task=current_task)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
ef = ExtraFile.objects.create(filename=request.FILES['file'].name, task=current_task)
|
|
||||||
with open(ef.path, 'wb') as fs:
|
|
||||||
for chunk in request.FILES['file'].chunks():
|
|
||||||
fs.write(chunk)
|
|
||||||
elif action == 'CREATE_EXTRA_FILE':
|
|
||||||
try:
|
|
||||||
ExtraFile.objects.get(task=current_task, filename=request.POST['newfile_name'])
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
ef = ExtraFile.objects.create(task=current_task, filename=request.POST['newfile_name'])
|
|
||||||
f = open(join(MEDIA_ROOT, 'extra_files', str(ef.id)), 'w')
|
|
||||||
f.close()
|
|
||||||
elif action.startswith('DELETE_FILE_'):
|
|
||||||
ExtraFile.objects.get(id=int(action.split('_')[-1])).delete()
|
|
||||||
elif action == 'SAVE_TESTS':
|
|
||||||
tt = request.POST['tests_text']
|
|
||||||
cs_file = current_task.tests_path()
|
|
||||||
with open(cs_file, 'wb') as fs:
|
|
||||||
fs.write(bytes(tt, encoding='utf-8'))
|
|
||||||
else:
|
|
||||||
raise NotImplementedError()
|
|
||||||
current_task.save()
|
|
||||||
return HttpResponseRedirect('/admin/task?id=' + str(current_task.id))
|
|
||||||
return render(request, 'task_settings.html', context={'task': current_task,
|
|
||||||
'tests': TestsForm(),
|
|
||||||
'is_superuser': check_teacher(request.user)})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def block_settings(request):
|
|
||||||
if not check_admin(request.user):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
current_block = Block.objects.get(id=request.GET['id'])
|
|
||||||
if not check_permission_block(request.user, current_block):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
if request.method == 'POST':
|
|
||||||
if 'name' in request.POST.keys():
|
|
||||||
current_block = Block.objects.get(id=request.POST['block_id'])
|
|
||||||
if not check_teacher(request.user) or not check_permission_block(request.user, current_block):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
task_name = request.POST['name']
|
|
||||||
current_task = Task.objects.create(
|
|
||||||
name=task_name,
|
|
||||||
block=current_block
|
|
||||||
)
|
|
||||||
with open(current_task.tests_path(), 'w') as fs:
|
|
||||||
pass
|
|
||||||
return HttpResponseRedirect('/admin/task?id=' + str(current_task.id))
|
|
||||||
if 'file' in request.FILES.keys():
|
|
||||||
if exists(request.user.username):
|
|
||||||
rmtree(request.user.username)
|
|
||||||
mkdir(request.user.username)
|
|
||||||
with open(join(request.user.username, 'task.zip'), 'wb') as fs:
|
|
||||||
for chunk in request.FILES['file'].chunks():
|
|
||||||
fs.write(chunk)
|
|
||||||
try:
|
|
||||||
with ZipFile(join(request.user.username, 'task.zip')) as obj:
|
|
||||||
obj.extractall(request.user.username)
|
|
||||||
except BadZipFile:
|
|
||||||
rmtree(request.user.username)
|
|
||||||
return HttpResponseRedirect('/admin/block?id={}'.format(current_block.id))
|
|
||||||
task = Task.objects.create(name='Новый таск', block=current_block)
|
|
||||||
root = request.user.username
|
|
||||||
if exists(join(root, 'meta.json')):
|
|
||||||
data = loads(open(join(root, 'meta.json'), 'r', encoding='utf-8').read())
|
|
||||||
task.name = data['localizedNames']['ru']
|
|
||||||
task.time_limit = data['invocationLimits']['idlenessLimitMillis']
|
|
||||||
for f in sorted(listdir(join(root, 'tests'))):
|
|
||||||
e = ExtraFile.objects.create(
|
|
||||||
task=task,
|
|
||||||
filename=f,
|
|
||||||
for_compilation=False
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
e.sample=data['testSets'][str(int(f.split('.')[0]))]['example'] and not f.endswith('.a')
|
|
||||||
except KeyError:
|
|
||||||
e.sample = False
|
|
||||||
e.save()
|
|
||||||
copyfile(join(root, 'tests', f), join(MEDIA_ROOT, 'extra_files', str(e.id)))
|
|
||||||
statements = open(join(root, 'statements', 'ru', 'html', 'statement.html'), 'r', encoding='utf-8').read()
|
|
||||||
task.legend = get_in_html_tag(statements, 'legend')
|
|
||||||
task.input = get_in_html_tag(statements, 'input-specification')
|
|
||||||
task.output = get_in_html_tag(statements, 'output-specification')
|
|
||||||
task.specifications = get_in_html_tag(statements, 'notes')
|
|
||||||
task.save()
|
|
||||||
with open(join(MEDIA_ROOT, 'tests', str(task.id) + '.cs'), 'w') as fs:
|
|
||||||
pass
|
|
||||||
rmtree(root)
|
|
||||||
return HttpResponseRedirect('/admin/task?id={}'.format(task.id))
|
|
||||||
if 'block_delete' in request.POST.keys():
|
|
||||||
Block.objects.get(id=request.POST['block_delete']).delete()
|
|
||||||
return HttpResponseRedirect('/admin/main')
|
|
||||||
else:
|
|
||||||
time_start = make_aware(datetime.strptime(request.POST['time_start'], "%Y-%m-%dT%H:%M") + timedelta(hours=3))
|
|
||||||
time_end = make_aware(datetime.strptime(request.POST['time_end'], "%Y-%m-%dT%H:%M") + timedelta(hours=3))
|
|
||||||
current_block.opened = 'opened' in request.POST.keys()
|
|
||||||
current_block.time_start = time_start
|
|
||||||
current_block.time_end = time_end
|
|
||||||
current_block.show_rating = "rating" in request.POST.keys()
|
|
||||||
current_block.priority = request.POST['priority']
|
|
||||||
current_block.save()
|
|
||||||
return HttpResponseRedirect('/admin/block?id={}'.format(current_block.id))
|
|
||||||
return render(request, 'block_settings.html', context={'is_superuser': check_teacher(request.user),
|
|
||||||
'Block': current_block})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def solutions_table(request):
|
|
||||||
current_task = Task.objects.get(id=request.GET['id'])
|
|
||||||
user = request.user
|
|
||||||
if not check_permission_block(user, current_task.block):
|
|
||||||
return HttpResponse("done")
|
|
||||||
sols = Solution.objects.filter(task=current_task, user=user)
|
|
||||||
can_edit = check_admin_on_course(request.user, current_task.block.course)
|
|
||||||
# тут по хорошему надо использовать регулярки, но я что-то не разобрался
|
|
||||||
if not can_edit:
|
|
||||||
for sol in sols:
|
|
||||||
while True:
|
|
||||||
i = sol.details.find('<pre>')
|
|
||||||
if i == -1:
|
|
||||||
break
|
|
||||||
j = sol.details.find('</pre>') + 6
|
|
||||||
sol.details = sol.details.replace(sol.details[i:j], '')
|
|
||||||
if any(sol.result == 'TESTING' or sol.result == 'IN QUEUE' for sol in sols) or 'render' in request.GET.keys():
|
|
||||||
return render(request, 'solutions_table.html', context={
|
|
||||||
'solutions': reversed(sols),
|
|
||||||
'can_edit': can_edit,
|
|
||||||
'task': current_task})
|
|
||||||
return HttpResponse('done')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def queue_table(request):
|
|
||||||
block = Block.objects.get(id=request.GET['block_id'])
|
|
||||||
if not check_admin_on_course(request.user, block):
|
|
||||||
return HttpResponse('get away from here')
|
|
||||||
sols = list(Solution.objects.filter(task__block=block, result='TESTING')) + list(Solution.objects.filter(task__block=block, result='IN QUEUE'))
|
|
||||||
return render(request, 'queue_table.html', {'solutions': sorted(sols, key=lambda x: -x.task.block.priority * 10 - x.task.priority)})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def get_result_data(request):
|
|
||||||
solution = Solution.objects.get(id=request.GET['id'])
|
|
||||||
if not check_admin_on_course(request.user, solution.task.block.course):
|
|
||||||
return HttpResponse(dumps({'success': False}))
|
|
||||||
return HttpResponse(dumps({
|
|
||||||
'success': True,
|
|
||||||
'results_text': solution.details,
|
|
||||||
'tests_text': solution.task.tests_text,
|
|
||||||
'log_text': solution.log_text
|
|
||||||
}))
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def get_comment_data(request):
|
|
||||||
solution = Solution.objects.get(id=request.GET['id'])
|
|
||||||
if not check_admin_on_course(request.user, solution.task.block.course):
|
|
||||||
return HttpResponse(dumps({'success': False}))
|
|
||||||
return HttpResponse(dumps({
|
|
||||||
'success': True,
|
|
||||||
'comment_text': solution.comment
|
|
||||||
}))
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def rating(request):
|
|
||||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
|
||||||
if not check_admin_on_course(request.user, current_block.course) and not current_block.show_rating:
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
return render(request, 'rating.html', context={'Block': Block.objects.get(id=request.GET['block_id']), 'admin_course': check_admin_on_course(request.user, current_block.course)})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def cheating(request):
|
|
||||||
current_block = Block.objects.get(id=request.GET['block_id'])
|
|
||||||
if not check_admin_on_course(request.user, current_block.course):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
if request.method == 'POST':
|
|
||||||
if not current_block.cheating_checking:
|
|
||||||
req = ['_'.join(elem.split('_')[1:]) for elem in request.POST.keys() if elem.startswith('check_')]
|
|
||||||
tasks = Task.objects.filter(id__in=[int(elem.split('_')[1]) for elem in req if elem.startswith('task')])
|
|
||||||
users = User.objects.filter(id__in=[int(elem.split('_')[1]) for elem in req if elem.startswith('user')])
|
|
||||||
solutions = Solution.objects.filter(user__in=users).filter(task__in=tasks)
|
|
||||||
if 'all_tests' in request.POST.keys():
|
|
||||||
solutions = [sol for sol in solutions if sol.passed_all_tests]
|
|
||||||
if 'best_result' in request.POST.keys():
|
|
||||||
sols = {}
|
|
||||||
for solution in solutions:
|
|
||||||
if (solution.user.username, solution.task.id) in sols.keys():
|
|
||||||
comp = result_comparer(sols[(solution.user.username, solution.task.id)][0].result, solution.result)
|
|
||||||
if comp == 1:
|
|
||||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
|
||||||
elif comp == 0:
|
|
||||||
sols[(solution.user.username, solution.task.id)].append(solution)
|
|
||||||
else:
|
|
||||||
sols[(solution.user.username, solution.task.id)] = [solution]
|
|
||||||
solutions = [val for sol in sols.values() for val in sol]
|
|
||||||
solutions = list(sorted(solutions, key=lambda s: s.id))
|
|
||||||
if 'last_solution' in request.POST.keys():
|
|
||||||
sols = {}
|
|
||||||
for sol in solutions:
|
|
||||||
pair = sol.user, sol.task
|
|
||||||
if pair not in sols.keys():
|
|
||||||
sols[pair] = []
|
|
||||||
sols[pair].append(sol)
|
|
||||||
solutions = [sols[key][len(sols[key]) - 1] for key in sols.keys()]
|
|
||||||
Thread(target=check_cheating, args=(solutions, current_block, int(request.POST['cheating_percent']))).start()
|
|
||||||
return HttpResponseRedirect('/admin/cheating?block_id=' + str(current_block.id))
|
|
||||||
return render(request, 'cheating.html', {'Block': current_block})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def admin(request):
|
|
||||||
if not check_admin(request.user):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
if request.method == 'POST':
|
|
||||||
if 'invite' in request.POST.keys():
|
|
||||||
register_user(request.POST)
|
|
||||||
return HttpResponseRedirect('/admin/main')
|
|
||||||
name = request.POST['name']
|
|
||||||
course = Course.objects.get(id=request.POST['course_id'])
|
|
||||||
current_block = Block.objects.create(name=name,
|
|
||||||
course=course,
|
|
||||||
opened=False,
|
|
||||||
time_start=timezone.now(),
|
|
||||||
time_end=timezone.now())
|
|
||||||
if not check_teacher(request.user):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
try:
|
|
||||||
Subscribe.objects.get(user=request.user, course=course)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
if not request.user.is_superuser:
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
return HttpResponseRedirect('/admin/block?id=' + str(current_block.id))
|
|
||||||
return render(request, "admin.html", context={"blocks": blocks_available(request.user),
|
|
||||||
'is_superuser': check_god(request.user),
|
|
||||||
'is_teacher': check_teacher(request.user)})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(False)
|
|
||||||
def reset_password(request):
|
|
||||||
code = request.GET['code']
|
|
||||||
try:
|
|
||||||
res = Restore.objects.get(code=code)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return HttpResponseRedirect('/enter')
|
|
||||||
context = {'form': ResetPasswordForm()}
|
|
||||||
if request.method == 'GET':
|
|
||||||
return render(request, 'reset_password.html', context=context)
|
|
||||||
else:
|
|
||||||
if request.POST['new'] != request.POST['again']:
|
|
||||||
context['error'] = 'Пароли не совпадают'
|
|
||||||
return render(request, 'reset_password.html', context=context)
|
|
||||||
else:
|
|
||||||
res.user.set_password(request.POST['new'])
|
|
||||||
res.user.save()
|
|
||||||
res.delete()
|
|
||||||
return HttpResponseRedirect('/enter')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def settings(request):
|
|
||||||
context = {'is_admin': check_admin(request.user), 'form': ChangePasswordForm()}
|
|
||||||
if request.method == 'POST':
|
|
||||||
old = request.POST['old']
|
|
||||||
new = request.POST['new'].strip()
|
|
||||||
again = request.POST['again'].strip()
|
|
||||||
username = request.user.username
|
|
||||||
user = request.user
|
|
||||||
if user is None:
|
|
||||||
context['error'] = 'Неверный пароль'
|
|
||||||
if len(new) < 8 or not any([a.isdigit() for a in new]) or new.lower() == new:
|
|
||||||
context['error'] = 'Пароль слишком слабый'
|
|
||||||
elif new != again:
|
|
||||||
context['error'] = 'Пароли не совпадают'
|
|
||||||
elif new == '' or new.replace(' ', '') == '':
|
|
||||||
context['error'] = 'Некорректный пароль'
|
|
||||||
else:
|
|
||||||
user.set_password(new)
|
|
||||||
user.save()
|
|
||||||
context['error'] = 'Пароль успешно изменен'
|
|
||||||
login(request, user)
|
|
||||||
return render(request, 'settings.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def exit(request):
|
|
||||||
logout(request)
|
|
||||||
return HttpResponseRedirect('/enter')
|
|
||||||
|
|
||||||
|
|
||||||
def redirect(request):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(True)
|
|
||||||
def main(request):
|
|
||||||
return render(request, 'main.html', context={'blocks': blocks_available(request.user)})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(False)
|
|
||||||
def restore(request):
|
|
||||||
if request.method == 'GET':
|
|
||||||
return render(request, 'restore.html')
|
|
||||||
else:
|
|
||||||
email = request.POST['email']
|
|
||||||
try:
|
|
||||||
user = User.objects.get(email=email)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return HttpResponseRedirect('/enter')
|
|
||||||
h = get_restore_hash()
|
|
||||||
try:
|
|
||||||
r = Restore.objects.get(user__email=email)
|
|
||||||
r.code = h
|
|
||||||
r.save()
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
Restore.objects.create(user=user, code=h)
|
|
||||||
send_email('Reset password',
|
|
||||||
email,
|
|
||||||
'Restore your password using this link:\nhttp://{}/reset_password?code={}'
|
|
||||||
.format(request.META['HTTP_HOST'], h))
|
|
||||||
return HttpResponseRedirect('/enter')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(False)
|
|
||||||
def enter(request):
|
|
||||||
if check_login(request.user):
|
|
||||||
return HttpResponseRedirect('/main')
|
|
||||||
if request.method == 'POST':
|
|
||||||
try:
|
|
||||||
user = User.objects.get(username=request.POST['email'])
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
try:
|
|
||||||
user = User.objects.get(email=request.POST['email'])
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return HttpResponseRedirect('/enter?error_message=Данного пользователя не существует')
|
|
||||||
user = authenticate(username=user.username, password=request.POST['password'].strip())
|
|
||||||
if user is not None:
|
|
||||||
login(request, user)
|
|
||||||
return HttpResponseRedirect('/enter?error_message=Неверный пароль')
|
|
||||||
return render(request, "enter.html", context={"form": LoginForm(), 'error_message': request.GET.get('error_message', '')})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(False)
|
|
||||||
def register(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
if len(request.POST['password'].strip()) < 8:
|
|
||||||
return HttpResponseRedirect('/register?error_message=Пароль слишком слабый')
|
|
||||||
if request.POST['password'] != request.POST['repeat_password']:
|
|
||||||
return HttpResponseRedirect('/register?error_message=Пароли не совпадают')
|
|
||||||
if len(User.objects.filter(username=request.POST['username'])):
|
|
||||||
return HttpResponseRedirect('/register?error_message=Данное имя пользователя уже занято')
|
|
||||||
if len(User.objects.filter(email=request.POST['email'])):
|
|
||||||
return HttpResponseRedirect('/register?error_message=Пользователь под таким email уже зарегистрирован')
|
|
||||||
user = User.objects.create_user(request.POST['username'], request.POST['email'], password=request.POST['password'])
|
|
||||||
userinfo = UserInfo.objects.create(
|
|
||||||
surname = request.POST['surname'],
|
|
||||||
name = request.POST['name'],
|
|
||||||
middle_name = request.POST['middle_name']
|
|
||||||
)
|
|
||||||
user.userinfo = userinfo
|
|
||||||
user.save()
|
|
||||||
return HttpResponseRedirect('/enter')
|
|
||||||
return render(request, 'register.html', {'error_message': request.GET.get('error_message', '')})
|
|
40
Main/views/AccountView.py
Normal file
40
Main/views/AccountView.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class AccountView(BaseView):
|
||||||
|
view_file = "account.html"
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
if "username" in self.request.GET.keys():
|
||||||
|
self.context["account"] = User.objects.get(
|
||||||
|
username=self.request.GET["username"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.context["account"] = self.request.user
|
||||||
|
self.context["owner"] = self.context["account"] == self.request.user
|
||||||
|
self.context["error_message"] = self.request.GET.get("error_message", "")
|
||||||
|
|
||||||
|
def post_upload_photo(self):
|
||||||
|
self.request.user.userinfo.profile_picture.delete()
|
||||||
|
self.request.user.userinfo.profile_picture = self.request.FILES["file"]
|
||||||
|
self.request.user.userinfo.save()
|
||||||
|
return "/account"
|
||||||
|
|
||||||
|
def post_change_password(self):
|
||||||
|
password = self.request.POST["password"].strip()
|
||||||
|
new_password = self.request.POST["new_password"].strip()
|
||||||
|
user = authenticate(username=self.request.user.username, password=password)
|
||||||
|
if not user:
|
||||||
|
return "/account?error_message=Неправильно указан пароль"
|
||||||
|
if len(new_password) < 8:
|
||||||
|
return "/account?error_message=Пароль слишком слабый"
|
||||||
|
if new_password != self.request.POST["repeat"]:
|
||||||
|
return "/account?error_message=Пароли не совпадают"
|
||||||
|
self.request.user.set_password(new_password)
|
||||||
|
self.request.user.save()
|
||||||
|
login(self.request, self.request.user)
|
||||||
|
return "/account?error_message=Пароль успешно установлен"
|
29
Main/views/EnterView.py
Normal file
29
Main/views/EnterView.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class EnterView(BaseView):
|
||||||
|
view_file = "enter.html"
|
||||||
|
required_login = False
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.context["error_message"] = self.request.GET.get("error_message", "")
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=self.request.POST["email"])
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
try:
|
||||||
|
user = User.objects.get(email=self.request.POST["email"])
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return "/enter?error_message=Данного пользователя не существует"
|
||||||
|
user = authenticate(
|
||||||
|
username=user.username, password=self.request.POST["password"].strip()
|
||||||
|
)
|
||||||
|
if user is not None:
|
||||||
|
login(self.request, user)
|
||||||
|
return "/"
|
||||||
|
return "/enter?error_message=Неверный пароль"
|
11
Main/views/ExitView.py
Normal file
11
Main/views/ExitView.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.contrib.auth import logout
|
||||||
|
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class ExitView(BaseView):
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
logout(self.request)
|
||||||
|
return "/"
|
6
Main/views/MainView.py
Normal file
6
Main/views/MainView.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class MainView(BaseView):
|
||||||
|
view_file = "main.html"
|
||||||
|
required_login = True
|
11
Main/views/RatingView.py
Normal file
11
Main/views/RatingView.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class RatingView(BaseView):
|
||||||
|
view_file = "rating.html"
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.context["users"] = User.objects.all().order_by('-userinfo__rating')
|
40
Main/views/RegisterView.py
Normal file
40
Main/views/RegisterView.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from Main.models import UserInfo
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterView(BaseView):
|
||||||
|
view_file = "register.html"
|
||||||
|
required_login = False
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.context["error_message"] = self.request.GET.get("error_message", "")
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
data = {**self.request.POST}
|
||||||
|
data["password"] = data["password"].strip()
|
||||||
|
if len(data["password"]) < 8:
|
||||||
|
return "/register?error_message=Пароль слишком слабый"
|
||||||
|
if data["password"] != data["repeat_password"]:
|
||||||
|
return "/register?error_message=Пароли не совпадают"
|
||||||
|
if len(User.objects.filter(username=data["username"])):
|
||||||
|
return "/register?error_message=Данное имя пользователя уже занято"
|
||||||
|
|
||||||
|
if len(User.objects.filter(email=data["email"])):
|
||||||
|
return "/register?error_message=Пользователь под таким email уже зарегистрирован"
|
||||||
|
user = User.objects.create_user(
|
||||||
|
data["username"],
|
||||||
|
data["email"],
|
||||||
|
password=data["password"],
|
||||||
|
)
|
||||||
|
userinfo = UserInfo.objects.create(
|
||||||
|
surname=data["surname"],
|
||||||
|
name=data["name"],
|
||||||
|
middle_name=data["middle_name"],
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
user.userinfo = userinfo
|
||||||
|
user.save()
|
||||||
|
# todo: реализовать подтверждение по почте
|
||||||
|
return "/enter"
|
12
Main/views/SetsView.py
Normal file
12
Main/views/SetsView.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from Main.models import Set
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class SetsView(BaseView):
|
||||||
|
view_file = "sets.html"
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
task_name = self.request.POST["name"]
|
||||||
|
task = Set.objects.create(name=task_name, creator=self.request.user)
|
||||||
|
return f"/admin/task?task_id={task.id}"
|
21
Main/views/SolutionsTableView.py
Normal file
21
Main/views/SolutionsTableView.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
from Main.models import Solution
|
||||||
|
from Sprint.settings import CONSTS
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class SolutionsTableView(BaseView):
|
||||||
|
view_file = 'solutions_table.html'
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.context['solutions'] = Solution.objects.filter(
|
||||||
|
user=self.request.user, task=self.entities.task
|
||||||
|
).order_by("-time_sent")
|
||||||
|
if 'render' in self.request.GET.keys():
|
||||||
|
return
|
||||||
|
for sol in self.context['solutions']:
|
||||||
|
if sol.result == CONSTS['testing_status'] or sol.result == CONSTS['in_queue_status']:
|
||||||
|
return
|
||||||
|
return HttpResponse('done')
|
80
Main/views/TaskSettingsView.py
Normal file
80
Main/views/TaskSettingsView.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
from Main.models import ExtraFile
|
||||||
|
from SprintLib.BaseView import BaseView, AccessError
|
||||||
|
|
||||||
|
|
||||||
|
class TaskSettingsView(BaseView):
|
||||||
|
view_file = "task_settings.html"
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def pre_handle(self):
|
||||||
|
if self.entities.task not in self.request.user.userinfo.available_tasks:
|
||||||
|
raise AccessError()
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.context['error_message'] = self.request.GET.get('error_message', '')
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
for key, value in self.request.POST.items():
|
||||||
|
setattr(self.entities.task, key, value.strip())
|
||||||
|
self.entities.task.save()
|
||||||
|
return f"/admin/task?task_id={self.entities.task.id}"
|
||||||
|
|
||||||
|
def _upload(self, is_test):
|
||||||
|
filename = self.request.FILES['file'].name
|
||||||
|
ef, created = None, None
|
||||||
|
if is_test:
|
||||||
|
name = filename.strip('.a')
|
||||||
|
if not name.isnumeric():
|
||||||
|
return f'/admin/task?task_id={self.entities.task.id}&error_message=Формат файла не соответствует тесту'
|
||||||
|
ef, created = ExtraFile.objects.get_or_create(task=self.entities.task, is_test=True, test_number=int(name))
|
||||||
|
if not created:
|
||||||
|
return f'/admin/task?task_id={self.entities.task.id}'
|
||||||
|
if ef is None or created is None:
|
||||||
|
ef, created = ExtraFile.objects.get_or_create(
|
||||||
|
task=self.entities.task,
|
||||||
|
filename=filename,
|
||||||
|
is_test=is_test
|
||||||
|
)
|
||||||
|
with open(ef.path, 'wb') as fs:
|
||||||
|
for chunk in self.request.FILES['file'].chunks():
|
||||||
|
fs.write(chunk)
|
||||||
|
try:
|
||||||
|
open(ef.path, 'r').read()
|
||||||
|
ef.readable = True
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
ef.readable = False
|
||||||
|
ef.save()
|
||||||
|
return '/admin/task?task_id=' + str(self.entities.task.id)
|
||||||
|
|
||||||
|
def post_file_upload(self):
|
||||||
|
return self._upload(False)
|
||||||
|
|
||||||
|
def post_test_upload(self):
|
||||||
|
return self._upload(True)
|
||||||
|
|
||||||
|
def post_delete_file(self):
|
||||||
|
ef = ExtraFile.objects.get(id=self.request.POST['id'])
|
||||||
|
ef.delete()
|
||||||
|
return HttpResponse("ok")
|
||||||
|
|
||||||
|
def _create(self, is_test):
|
||||||
|
name = self.request.POST['newfile_name']
|
||||||
|
|
||||||
|
ef, created = ExtraFile.objects.get_or_create(filename=name, task=self.entities.task)
|
||||||
|
if not created:
|
||||||
|
return f'/admin/task?task_id={self.entities.task.id}&error_message=Файл с таким именем уже существует'
|
||||||
|
with open(ef.path, 'w') as fs:
|
||||||
|
fs.write('')
|
||||||
|
ef.is_test = is_test
|
||||||
|
ef.readable = True
|
||||||
|
ef.save()
|
||||||
|
return f'/admin/task?task_id={self.entities.task.id}'
|
||||||
|
|
||||||
|
def post_create_file(self):
|
||||||
|
return self._create(False)
|
||||||
|
|
||||||
|
def post_create_test(self):
|
||||||
|
return self._create(True)
|
46
Main/views/TaskView.py
Normal file
46
Main/views/TaskView.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from Main.models import Solution
|
||||||
|
from Main.tasks import start_testing
|
||||||
|
from SprintLib.BaseView import BaseView, Language
|
||||||
|
from SprintLib.testers import *
|
||||||
|
|
||||||
|
|
||||||
|
class TaskView(BaseView):
|
||||||
|
required_login = True
|
||||||
|
view_file = "task.html"
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.context['languages'] = Language.objects.filter(opened=True).order_by('name')
|
||||||
|
|
||||||
|
def pre_handle(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
return
|
||||||
|
self.solution = Solution.objects.create(
|
||||||
|
task=self.entities.task,
|
||||||
|
user=self.request.user,
|
||||||
|
language_id=self.request.POST["language"]
|
||||||
|
)
|
||||||
|
self.solution.create_dirs()
|
||||||
|
|
||||||
|
def post_0(self):
|
||||||
|
# отправка решения через текст
|
||||||
|
filename = 'solution.' + self.solution.language.file_type
|
||||||
|
file_path = join(self.solution.directory, filename)
|
||||||
|
with open(file_path, 'w') as fs:
|
||||||
|
fs.write(self.request.POST['code'])
|
||||||
|
start_testing.delay(self.solution.id)
|
||||||
|
return "task?task_id=" + str(self.entities.task.id)
|
||||||
|
|
||||||
|
def post_1(self):
|
||||||
|
# отправка решения через файл
|
||||||
|
filename = self.request.FILES['file'].name
|
||||||
|
file_path = join(self.solution.directory, filename)
|
||||||
|
with open(file_path, 'wb') as fs:
|
||||||
|
for chunk in self.request.FILES['file'].chunks():
|
||||||
|
fs.write(chunk)
|
||||||
|
if filename.endswith('.zip'):
|
||||||
|
with ZipFile(file_path) as obj:
|
||||||
|
obj.extractall(self.solution.directory)
|
||||||
|
start_testing.delay(self.solution.id)
|
||||||
|
return "task?task_id=" + str(self.entities.task.id)
|
13
Main/views/TasksView.py
Normal file
13
Main/views/TasksView.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from Main.models import Task
|
||||||
|
from SprintLib.BaseView import BaseView
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
|
class TasksView(BaseView):
|
||||||
|
view_file = "tasks.html"
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
task_name = self.request.POST["name"]
|
||||||
|
task = Task.objects.create(name=task_name, creator=self.request.user)
|
||||||
|
return f"/admin/task?task_id={task.id}"
|
11
Main/views/__init__.py
Normal file
11
Main/views/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from Main.views.EnterView import EnterView
|
||||||
|
from Main.views.RegisterView import RegisterView
|
||||||
|
from Main.views.MainView import MainView
|
||||||
|
from Main.views.TasksView import TasksView
|
||||||
|
from Main.views.AccountView import AccountView
|
||||||
|
from Main.views.ExitView import ExitView
|
||||||
|
from Main.views.TaskSettingsView import TaskSettingsView
|
||||||
|
from Main.views.RatingView import RatingView
|
||||||
|
from Main.views.SetsView import SetsView
|
||||||
|
from Main.views.TaskView import TaskView
|
||||||
|
from Main.views.SolutionsTableView import SolutionsTableView
|
@@ -0,0 +1,3 @@
|
|||||||
|
from .celery import app as celery
|
||||||
|
|
||||||
|
__all__ = ('celery',)
|
||||||
|
@@ -11,6 +11,6 @@ import os
|
|||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
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
17
Sprint/celery.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from celery import Celery
|
||||||
|
|
||||||
|
# Set the default Django settings module for the 'celery' program.
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings')
|
||||||
|
|
||||||
|
app = Celery('Sprint')
|
||||||
|
|
||||||
|
# Using a string here means the worker doesn't have to serialize
|
||||||
|
# the configuration object to child processes.
|
||||||
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
|
# should have a `CELERY_` prefix.
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
|
||||||
|
# Load task modules from all registered Django apps.
|
||||||
|
app.autodiscover_tasks()
|
@@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||||||
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
|
# 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",
|
||||||
|
}
|
||||||
|
@@ -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)
|
|
||||||
]
|
|
||||||
|
@@ -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
18
SprintLib/BaseDaemon.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDaemon:
|
||||||
|
def command(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def execute(self):
|
||||||
|
cmd = self.command()
|
||||||
|
proc = await asyncio.create_subprocess_shell(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
|
||||||
|
stdout, stderr = await proc.communicate()
|
||||||
|
print(f"[{cmd!r} exited with {proc.returncode}]")
|
||||||
|
if stdout:
|
||||||
|
print(f"[stdout]\n{stdout.decode()}")
|
||||||
|
if stderr:
|
||||||
|
print(f"[stderr]\n{stderr.decode()}")
|
72
SprintLib/BaseView.py
Normal file
72
SprintLib/BaseView.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from SprintLib.EntityStorage import EntityStorage
|
||||||
|
from Main.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class AccessError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseView:
|
||||||
|
request: WSGIRequest = None
|
||||||
|
context: dict = {}
|
||||||
|
entities = EntityStorage()
|
||||||
|
required_login: bool = None
|
||||||
|
view_file: str = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def as_view(cls):
|
||||||
|
def execute(request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
user_info = request.user.userinfo
|
||||||
|
user_info.last_request = timezone.now()
|
||||||
|
user_info.save()
|
||||||
|
c = cls()
|
||||||
|
if c.required_login is not None:
|
||||||
|
if c.required_login and not request.user.is_authenticated:
|
||||||
|
return HttpResponseRedirect("/enter")
|
||||||
|
if not c.required_login and request.user.is_authenticated:
|
||||||
|
return HttpResponseRedirect("/")
|
||||||
|
request_method = request.method.lower()
|
||||||
|
c.request = request
|
||||||
|
for key in request.GET.keys():
|
||||||
|
if key.endswith("_id"):
|
||||||
|
model_name = key.rstrip("_id")
|
||||||
|
c.entities.add(
|
||||||
|
model_name,
|
||||||
|
eval(model_name.capitalize()).objects.get(
|
||||||
|
id=int(request.GET[key])
|
||||||
|
),
|
||||||
|
)
|
||||||
|
context = c.entities.entities
|
||||||
|
if "action" in request.POST.keys():
|
||||||
|
request_method += "_" + request.POST["action"]
|
||||||
|
method = getattr(c, request_method, None)
|
||||||
|
try:
|
||||||
|
data = c.pre_handle()
|
||||||
|
if method:
|
||||||
|
if data is None:
|
||||||
|
data = method()
|
||||||
|
if type(data) == str:
|
||||||
|
return HttpResponseRedirect(data)
|
||||||
|
if data is not None:
|
||||||
|
return data
|
||||||
|
context = {**context, **c.context}
|
||||||
|
return render(request, c.view_file, context)
|
||||||
|
except AccessError:
|
||||||
|
return HttpResponseRedirect("/")
|
||||||
|
|
||||||
|
return execute
|
||||||
|
|
||||||
|
def pre_handle(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
pass
|
6
SprintLib/EntityStorage.py
Normal file
6
SprintLib/EntityStorage.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class EntityStorage:
|
||||||
|
entities = {}
|
||||||
|
|
||||||
|
def add(self, name, entity):
|
||||||
|
self.entities[name] = entity
|
||||||
|
setattr(self, name, entity)
|
0
SprintLib/__init__.py
Normal file
0
SprintLib/__init__.py
Normal file
80
SprintLib/testers/BaseTester.py
Normal file
80
SprintLib/testers/BaseTester.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from os import listdir
|
||||||
|
from os.path import join
|
||||||
|
from shutil import copyfile, rmtree
|
||||||
|
from subprocess import call, TimeoutExpired
|
||||||
|
|
||||||
|
from Main.models import ExtraFile
|
||||||
|
from Sprint.settings import CONSTS
|
||||||
|
from SprintLib.utils import copy_content
|
||||||
|
|
||||||
|
|
||||||
|
class TestException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTester:
|
||||||
|
working_directory = "app"
|
||||||
|
|
||||||
|
def before_test(self):
|
||||||
|
files = [file for file in listdir(self.solution.testing_directory) if file.endswith('.' + self.solution.language.file_type)]
|
||||||
|
code = self.solution.exec_command(f'{self.build_command} {" ".join(files)}', working_directory=self.working_directory)
|
||||||
|
if code != 0:
|
||||||
|
raise TestException('CE')
|
||||||
|
|
||||||
|
def test(self, filename):
|
||||||
|
code = self.solution.exec_command(
|
||||||
|
f"< {filename} {self.command} > output.txt",
|
||||||
|
timeout=self.solution.task.time_limit / 1000,
|
||||||
|
)
|
||||||
|
if code != 0:
|
||||||
|
raise TestException("RE")
|
||||||
|
result = open(
|
||||||
|
join(self.solution.testing_directory, "output.txt"), "r"
|
||||||
|
).read()
|
||||||
|
if result.strip() != self.predicted.strip():
|
||||||
|
raise TestException("WA")
|
||||||
|
|
||||||
|
def after_test(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def command(self):
|
||||||
|
return "./executable.exe"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def build_command(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def __init__(self, solution):
|
||||||
|
self.solution = solution
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
copy_content(self.solution.directory, self.solution.testing_directory, ('test_dir',))
|
||||||
|
self.solution.result = CONSTS["testing_status"]
|
||||||
|
self.solution.save()
|
||||||
|
call(
|
||||||
|
f"docker run --name solution_{self.solution.id} --volume={self.solution.testing_directory}:/{self.working_directory} -t -d {self.solution.language.image}",
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
for file in ExtraFile.objects.filter(task=self.solution.task):
|
||||||
|
copyfile(file.path, join(self.solution.testing_directory, file.filename))
|
||||||
|
try:
|
||||||
|
self.before_test()
|
||||||
|
for test in self.solution.task.tests:
|
||||||
|
if not test.filename.endswith(".a"):
|
||||||
|
self.predicted = ExtraFile.objects.get(
|
||||||
|
task=self.solution.task, filename=test.filename + ".a"
|
||||||
|
).text
|
||||||
|
self.test(test.filename)
|
||||||
|
self.after_test()
|
||||||
|
self.solution.result = CONSTS["ok_status"]
|
||||||
|
except TestException as e:
|
||||||
|
self.solution.result = str(e)
|
||||||
|
except TimeoutExpired:
|
||||||
|
self.solution.result = "TL"
|
||||||
|
except Exception as e:
|
||||||
|
self.solution.result = "TE"
|
||||||
|
print(str(e))
|
||||||
|
self.solution.save()
|
||||||
|
call(f"docker rm --force solution_{self.solution.id}", shell=True)
|
||||||
|
rmtree(self.solution.testing_directory)
|
11
SprintLib/testers/CSharpTester.py
Normal file
11
SprintLib/testers/CSharpTester.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from SprintLib.testers import BaseTester
|
||||||
|
|
||||||
|
|
||||||
|
class CSharpTester(BaseTester):
|
||||||
|
@property
|
||||||
|
def build_command(self):
|
||||||
|
return "csc /out:executable.exe"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def command(self):
|
||||||
|
return "mono executable.exe"
|
9
SprintLib/testers/CppTester.py
Normal file
9
SprintLib/testers/CppTester.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from os import listdir
|
||||||
|
|
||||||
|
from SprintLib.testers import BaseTester, TestException
|
||||||
|
|
||||||
|
|
||||||
|
class CppTester(BaseTester):
|
||||||
|
@property
|
||||||
|
def build_command(self):
|
||||||
|
return "g++ -o executable.exe"
|
8
SprintLib/testers/GoTester.py
Normal file
8
SprintLib/testers/GoTester.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from SprintLib.testers import BaseTester
|
||||||
|
|
||||||
|
|
||||||
|
class GoTester(BaseTester):
|
||||||
|
working_directory = "../app"
|
||||||
|
|
||||||
|
def build_command(self):
|
||||||
|
return "go build -o executable.exe"
|
23
SprintLib/testers/JavaTester.py
Normal file
23
SprintLib/testers/JavaTester.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from os import listdir
|
||||||
|
|
||||||
|
from SprintLib.testers import BaseTester, TestException
|
||||||
|
|
||||||
|
|
||||||
|
class JavaTester(BaseTester):
|
||||||
|
_executable = None
|
||||||
|
|
||||||
|
def before_test(self):
|
||||||
|
files = [file for file in listdir(self.solution.testing_directory) if file.endswith('.java')]
|
||||||
|
code = self.solution.exec_command(f"javac {' '.join(files)}")
|
||||||
|
if code != 0:
|
||||||
|
raise TestException('CE')
|
||||||
|
for file in listdir(self.solution.testing_directory):
|
||||||
|
if file.endswith('.class'):
|
||||||
|
self._executable = file.rstrip('.class')
|
||||||
|
break
|
||||||
|
if self._executable is None:
|
||||||
|
raise TestException("TE")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def command(self):
|
||||||
|
return f"java -classpath . {self._executable}"
|
15
SprintLib/testers/KotlinTester.py
Normal file
15
SprintLib/testers/KotlinTester.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from os import listdir
|
||||||
|
|
||||||
|
from SprintLib.testers import BaseTester, TestException
|
||||||
|
|
||||||
|
|
||||||
|
class KotlinTester(BaseTester):
|
||||||
|
def before_test(self):
|
||||||
|
files = [file for file in listdir(self.solution.testing_directory) if file.endswith('.kt')]
|
||||||
|
code = self.solution.exec_command(f'kotlinc {" ".join(files)} -include-runtime -d solution.jar')
|
||||||
|
if code != 0:
|
||||||
|
raise TestException('CE')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def command(self):
|
||||||
|
return "java -jar solution.jar"
|
19
SprintLib/testers/Python3Tester.py
Normal file
19
SprintLib/testers/Python3Tester.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from os import listdir
|
||||||
|
|
||||||
|
from SprintLib.testers.BaseTester import BaseTester, TestException
|
||||||
|
|
||||||
|
|
||||||
|
class Python3Tester(BaseTester):
|
||||||
|
file = None
|
||||||
|
|
||||||
|
def before_test(self):
|
||||||
|
for file in listdir(self.solution.testing_directory):
|
||||||
|
if file.endswith(".py"):
|
||||||
|
self.file = file
|
||||||
|
break
|
||||||
|
if self.file is None:
|
||||||
|
raise TestException("TE")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def command(self):
|
||||||
|
return f"python {self.file}"
|
7
SprintLib/testers/__init__.py
Normal file
7
SprintLib/testers/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from .BaseTester import *
|
||||||
|
from .Python3Tester import *
|
||||||
|
from .CppTester import *
|
||||||
|
from .GoTester import *
|
||||||
|
from .JavaTester import *
|
||||||
|
from .CSharpTester import *
|
||||||
|
from .KotlinTester import *
|
15
SprintLib/utils.py
Normal file
15
SprintLib/utils.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from os import listdir
|
||||||
|
from os.path import isfile, join
|
||||||
|
from shutil import copyfile, copytree
|
||||||
|
|
||||||
|
|
||||||
|
def copy_content(from_dir, to_dir, exc=()):
|
||||||
|
for file in listdir(from_dir):
|
||||||
|
if file in exc:
|
||||||
|
continue
|
||||||
|
full_path = join(from_dir, file)
|
||||||
|
if isfile(full_path):
|
||||||
|
func = copyfile
|
||||||
|
else:
|
||||||
|
func = copytree
|
||||||
|
func(full_path, join(to_dir, file))
|
3
apply_models.sh
Normal file
3
apply_models.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
source venv/bin/activate
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
0
daemons/__init__.py
Normal file
0
daemons/__init__.py
Normal file
6
daemons/celery.py
Normal file
6
daemons/celery.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from SprintLib.BaseDaemon import BaseDaemon
|
||||||
|
|
||||||
|
|
||||||
|
class Daemon(BaseDaemon):
|
||||||
|
def command(self):
|
||||||
|
return "celery -A Sprint worker -l INFO --concurrency=4"
|
6
daemons/redis.py
Normal file
6
daemons/redis.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from SprintLib.BaseDaemon import BaseDaemon
|
||||||
|
|
||||||
|
|
||||||
|
class Daemon(BaseDaemon):
|
||||||
|
def command(self):
|
||||||
|
return "redis-server"
|
6
daemons/web.py
Normal file
6
daemons/web.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from SprintLib.BaseDaemon import BaseDaemon
|
||||||
|
|
||||||
|
|
||||||
|
class Daemon(BaseDaemon):
|
||||||
|
def command(self):
|
||||||
|
return "python manage.py runserver"
|
@@ -2,26 +2,14 @@ version: "3"
|
|||||||
|
|
||||||
|
|
||||||
services:
|
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
|
|
||||||
|
@@ -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
|
||||||
|
1
setup.sh
1
setup.sh
@@ -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
36
start.py
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
from os import listdir
|
||||||
|
|
||||||
|
|
||||||
|
async def execute(service):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
mod = __import__(f'daemons.{service}')
|
||||||
|
module = getattr(mod, service)
|
||||||
|
daemon = getattr(module, 'Daemon')
|
||||||
|
await daemon().execute()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
finally:
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
if sys.argv[1] == "--all":
|
||||||
|
services = list(
|
||||||
|
map(
|
||||||
|
lambda x: x[:-3],
|
||||||
|
[file for file in listdir("daemons") if file.endswith(".py") and file != '__init__.py'],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
services = sys.argv[1:]
|
||||||
|
await asyncio.gather(*[execute(service) for service in services])
|
||||||
|
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
90
templates/account.html
Normal file
90
templates/account.html
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
{% extends 'base_main.html' %}
|
||||||
|
|
||||||
|
{% block title %}Аккаунт{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h2 style="margin-bottom: 40px;">Информация об аккаунте</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
|
<div style="height: 100%; width: 100%;">
|
||||||
|
<img src="{{ account.userinfo.profile_pic_url }}" height="100%" width="100%" alt="Фото профиля">
|
||||||
|
</div>
|
||||||
|
{% if owner %}
|
||||||
|
<label for="file-upload" class="btn btn-light" style="margin-top: -100px; margin-left: 10%; width: 80%;">
|
||||||
|
<i class="fa fa-upload"></i> Загрузить фото
|
||||||
|
</label>
|
||||||
|
<input type="file" form="photoform" style="display: none;" accept="image/png, image/jpg" class="btn form-control-file" id="file-upload" value="Выбрать файл" name="file" onchange="document.getElementById('photoform').submit();">
|
||||||
|
<form method="POST" enctype="multipart/form-data" id="photoform">
|
||||||
|
<input type="hidden" name="action" value="upload_photo">
|
||||||
|
{% csrf_token %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<h3>
|
||||||
|
{{ account.userinfo.surname }} {{ account.userinfo.name }} {{ account.userinfo.middle_name }}
|
||||||
|
<span style="margin-left: 15px;" class="badge badge-{% if account.userinfo.activity_status == online_status %}success{% else %}secondary{% endif %}">{{ account.userinfo.activity_status }}</span>
|
||||||
|
</h3>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<h2><i class="fa fa-user"></i></h2>
|
||||||
|
</td>
|
||||||
|
<td><div style="width: 20px;"></div></td>
|
||||||
|
<td>
|
||||||
|
<p style="padding-top: 8px; font-size: 24px;">{{ account.username }}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<h2><i class="fa fa-at"></i></h2>
|
||||||
|
</td>
|
||||||
|
<td><div style="width: 20px;"></div></td>
|
||||||
|
<td>
|
||||||
|
<p style="padding-top: 8px; font-size: 24px;">{{ account.email }}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<h2><i class="fa fa-star"></i></h2>
|
||||||
|
</td>
|
||||||
|
<td><div style="width: 20px;"></div></td>
|
||||||
|
<td>
|
||||||
|
<p style="padding-top: 8px; font-size: 24px;">{{ account.userinfo.rating }}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<h2><i class="fa fa-arrow-up"></i></h2>
|
||||||
|
</td>
|
||||||
|
<td><div style="width: 20px;"></div></td>
|
||||||
|
<td>
|
||||||
|
<p style="padding-top: 8px; font-size: 24px;">{{ account.userinfo.place }}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<h2><i class="fa fa-calendar"></i></h2>
|
||||||
|
</td>
|
||||||
|
<td><div style="width: 20px;"></div></td>
|
||||||
|
<td>
|
||||||
|
<p style="padding-top: 8px; font-size: 24px;">{{ account.date_joined.date }}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr><hr>
|
||||||
|
{% if owner %}
|
||||||
|
<p style="color: red;">{{ error_message }}</p>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="change_password">
|
||||||
|
<h2 style="margin-bottom: 40px; margin-top: 40px;">Смена пароля</h2>
|
||||||
|
<input type="password" placeholder="Текущий пароль" name="password" style="margin-bottom: 20px; width: 300px;"><br>
|
||||||
|
<input type="password" placeholder="Новый пароль" name="new_password" style="margin-bottom: 20px; width: 300px;"><br>
|
||||||
|
<input type="password" placeholder="Повторить пароль" name="repeat" style="margin-bottom: 20px; width: 300px;"><br>
|
||||||
|
<button type="submit" class="btn btn-light">Сменить пароль</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user