From 1307c16ec1280bbd15443b9e4ca9e55a68d75d21 Mon Sep 17 00:00:00 2001 From: Egor Matveev Date: Sun, 29 Aug 2021 21:43:34 +0300 Subject: [PATCH] registration implemented --- Dockerfile | 2 +- Main/Tester.py | 218 +++-- Main/Timer.py | 39 +- Main/admin.py | 15 +- Main/apps.py | 3 +- Main/commands.py | 8 - Main/context_processors.py | 7 +- Main/decorators.py | 13 - Main/forms.py | 19 +- Main/main.py | 325 ------- Main/migrations/0001_initial.py | 211 ++++- Main/migrations/0002_auto_20200626_0946.py | 8 +- Main/migrations/0003_auto_20200627_1959.py | 6 +- Main/migrations/0004_auto_20200628_0917.py | 30 +- Main/migrations/0005_solution_time_sent.py | 6 +- Main/migrations/0006_auto_20200628_1315.py | 10 +- Main/migrations/0007_auto_20200629_0833.py | 10 +- Main/migrations/0008_auto_20200702_2140.py | 10 +- Main/migrations/0009_auto_20200704_1703.py | 12 +- Main/migrations/0010_system.py | 18 +- Main/migrations/0011_auto_20200814_2035.py | 18 +- Main/migrations/0012_auto_20200901_1154.py | 27 +- Main/migrations/0013_delete_extrafile.py | 4 +- Main/migrations/0014_extrafile.py | 25 +- Main/migrations/0015_auto_20200902_1555.py | 12 +- .../0016_task_max_solutions_count.py | 6 +- Main/migrations/0017_solution_details.py | 8 +- Main/migrations/0018_remove_extrafile_file.py | 6 +- Main/migrations/0019_task_show_details.py | 6 +- Main/migrations/0020_task_solution_type.py | 8 +- .../0021_remove_task_solution_type.py | 6 +- Main/migrations/0022_task_full_solution.py | 6 +- .../0023_extrafile_for_compilation.py | 6 +- Main/migrations/0024_extrafile_sample.py | 6 +- Main/migrations/0025_auto_20201106_1848.py | 34 +- Main/migrations/0026_block_show_rating.py | 6 +- Main/migrations/0027_task_mark_formula.py | 8 +- Main/migrations/0028_task_show_result.py | 6 +- Main/migrations/0029_auto_20210130_1950.py | 10 +- Main/migrations/0030_message.py | 43 +- .../0031_block_cheating_checking.py | 6 +- Main/migrations/0032_block_cheating_data.py | 8 +- .../0033_remove_block_cheating_data.py | 6 +- Main/models.py | 463 ---------- Main/models/__init__.py | 10 + Main/models/extrafile.py | 27 + Main/models/file.py | 7 + Main/models/group.py | 7 + Main/models/language.py | 13 + Main/models/set.py | 8 + Main/models/settask.py | 10 + Main/models/solution.py | 40 + Main/models/subscription.py | 12 + Main/models/task.py | 26 + Main/models/userinfo.py | 71 ++ Main/tasks.py | 9 + Main/templatetags/filters.py | 109 +-- Main/views.py | 833 ------------------ Main/views/AccountView.py | 40 + Main/views/EnterView.py | 29 + Main/views/ExitView.py | 11 + Main/views/MainView.py | 6 + Main/views/RatingView.py | 11 + Main/views/RegisterView.py | 40 + Main/views/SetsView.py | 12 + Main/views/SolutionsTableView.py | 21 + Main/views/TaskSettingsView.py | 80 ++ Main/views/TaskView.py | 46 + Main/views/TasksView.py | 13 + Main/views/__init__.py | 11 + Sprint/__init__.py | 3 + Sprint/asgi.py | 2 +- Sprint/celery.py | 17 + Sprint/settings.py | 118 +-- Sprint/urls.py | 53 +- Sprint/wsgi.py | 2 +- SprintLib/BaseDaemon.py | 18 + SprintLib/BaseView.py | 72 ++ SprintLib/EntityStorage.py | 6 + SprintLib/__init__.py | 0 SprintLib/testers/BaseTester.py | 80 ++ SprintLib/testers/CSharpTester.py | 11 + SprintLib/testers/CppTester.py | 9 + SprintLib/testers/GoTester.py | 8 + SprintLib/testers/JavaTester.py | 23 + SprintLib/testers/KotlinTester.py | 15 + SprintLib/testers/Python3Tester.py | 19 + SprintLib/testers/__init__.py | 7 + SprintLib/utils.py | 15 + apply_models.sh | 3 + daemons/__init__.py | 0 daemons/celery.py | 6 + daemons/redis.py | 6 + daemons/web.py | 6 + docker-compose.yaml | 22 +- manage.py | 0 requirements.txt | 38 +- setup.sh | 1 - start.py | 36 + templates/account.html | 90 ++ templates/admin.html | 90 -- templates/base.html | 78 +- templates/base_main.html | 35 + templates/block.html | 39 - templates/block_settings.html | 126 --- templates/cheating.html | 133 --- templates/docs.html | 235 ----- templates/enter.html | 95 +- templates/main.html | 29 +- templates/messages.html | 31 - templates/queue.html | 33 - templates/queue_table.html | 19 - templates/rating.html | 79 +- templates/reset_password.html | 29 - templates/restore.html | 36 - templates/sets.html | 47 + templates/settings.html | 18 - templates/solution.html | 271 ------ templates/solutions.html | 315 ------- templates/solutions_table.html | 60 +- templates/superuser.html | 85 -- templates/task.html | 343 ++------ templates/task_settings.html | 457 +++------- templates/tasks.html | 49 ++ templates/users_settings.html | 165 ---- 125 files changed, 2158 insertions(+), 4631 deletions(-) delete mode 100644 Main/commands.py delete mode 100644 Main/decorators.py delete mode 100644 Main/main.py delete mode 100644 Main/models.py create mode 100644 Main/models/__init__.py create mode 100644 Main/models/extrafile.py create mode 100644 Main/models/file.py create mode 100644 Main/models/group.py create mode 100644 Main/models/language.py create mode 100644 Main/models/set.py create mode 100644 Main/models/settask.py create mode 100644 Main/models/solution.py create mode 100644 Main/models/subscription.py create mode 100644 Main/models/task.py create mode 100644 Main/models/userinfo.py create mode 100644 Main/tasks.py delete mode 100644 Main/views.py create mode 100644 Main/views/AccountView.py create mode 100644 Main/views/EnterView.py create mode 100644 Main/views/ExitView.py create mode 100644 Main/views/MainView.py create mode 100644 Main/views/RatingView.py create mode 100644 Main/views/RegisterView.py create mode 100644 Main/views/SetsView.py create mode 100644 Main/views/SolutionsTableView.py create mode 100644 Main/views/TaskSettingsView.py create mode 100644 Main/views/TaskView.py create mode 100644 Main/views/TasksView.py create mode 100644 Main/views/__init__.py create mode 100644 Sprint/celery.py create mode 100644 SprintLib/BaseDaemon.py create mode 100644 SprintLib/BaseView.py create mode 100644 SprintLib/EntityStorage.py create mode 100644 SprintLib/__init__.py create mode 100644 SprintLib/testers/BaseTester.py create mode 100644 SprintLib/testers/CSharpTester.py create mode 100644 SprintLib/testers/CppTester.py create mode 100644 SprintLib/testers/GoTester.py create mode 100644 SprintLib/testers/JavaTester.py create mode 100644 SprintLib/testers/KotlinTester.py create mode 100644 SprintLib/testers/Python3Tester.py create mode 100644 SprintLib/testers/__init__.py create mode 100644 SprintLib/utils.py create mode 100644 apply_models.sh create mode 100644 daemons/__init__.py create mode 100644 daemons/celery.py create mode 100644 daemons/redis.py create mode 100644 daemons/web.py mode change 100644 => 100755 manage.py create mode 100755 start.py create mode 100644 templates/account.html delete mode 100644 templates/admin.html create mode 100644 templates/base_main.html delete mode 100644 templates/block.html delete mode 100644 templates/block_settings.html delete mode 100644 templates/cheating.html delete mode 100644 templates/docs.html delete mode 100644 templates/messages.html delete mode 100644 templates/queue.html delete mode 100644 templates/queue_table.html delete mode 100644 templates/reset_password.html delete mode 100644 templates/restore.html create mode 100644 templates/sets.html delete mode 100644 templates/settings.html delete mode 100644 templates/solution.html delete mode 100644 templates/solutions.html delete mode 100644 templates/superuser.html create mode 100644 templates/tasks.html delete mode 100644 templates/users_settings.html diff --git a/Dockerfile b/Dockerfile index f2ec306..0c901ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8 +FROM python:3.7 ENV PYTHONUNBUFFERED 1 ENV DJANGO_SETTINGS_MODULE Sprint.settings diff --git a/Main/Tester.py b/Main/Tester.py index 73febae..f638e12 100644 --- a/Main/Tester.py +++ b/Main/Tester.py @@ -9,11 +9,16 @@ from .main import solution_path def start_new(host): - in_queue = list(Solution.objects.filter(result='IN QUEUE')) + in_queue = list(Solution.objects.filter(result="IN QUEUE")) if in_queue: sol = in_queue[0] for s in in_queue: - dif = s.task.block.priority * 10 + s.task.priority - sol.task.block.priority * 10 - sol.task.priority + dif = ( + s.task.block.priority * 10 + + s.task.priority + - sol.task.block.priority * 10 + - sol.task.priority + ) if dif > 0: sol = s elif dif == 0 and s.id < sol.id: @@ -22,7 +27,7 @@ def start_new(host): def is_project(path): - return any([x.endswith('.csproj') for x in listdir(path)]) + return any([x.endswith(".csproj") for x in listdir(path)]) def get_node_value(element): @@ -30,15 +35,16 @@ def get_node_value(element): def nunit_path(working_dir): - return '..{}'.format(sep) * len(working_dir.split(sep)) + 'nunit_console{}nunit3-console.exe'.format(sep) + return "..{}".format(sep) * len( + working_dir.split(sep) + ) + "nunit_console{}nunit3-console.exe".format(sep) class Tester: - def __init__(self, solution, host): self.solution = solution self.host = host - self.working_dir = '' + self.working_dir = "" self.files = [] # функция компиляции @@ -47,24 +53,25 @@ class Tester: # shell('msbuild ' + path + ' /p:Configuration=Debug') # решение для Windows - cmd = 'dotnet build {} -o {}\\bin\\Debug'.format(path, path) + cmd = "dotnet build {} -o {}\\bin\\Debug".format(path, path) with self.solution.log_fs as fs: shell(cmd, fs) def build_and_copy(self, path, working_dir): - if exists(join(path, 'bin', 'Debug')): - rmtree(join(path, 'bin', 'Debug')) + if exists(join(path, "bin", "Debug")): + rmtree(join(path, "bin", "Debug")) self.build(path) name = basename(path) - if not exists(join(path, 'bin', 'Debug')) or not any( - x.endswith('.exe') for x in listdir(join(path, 'bin', 'Debug'))): + if not exists(join(path, "bin", "Debug")) or not any( + x.endswith(".exe") for x in listdir(join(path, "bin", "Debug")) + ): return False self.files.append(basename(path)) - for file in listdir(join(path, 'bin', 'Debug')): - if exists(join(path, 'bin', 'Debug', file)): + for file in listdir(join(path, "bin", "Debug")): + if exists(join(path, "bin", "Debug", file)): new_file = join(working_dir, basename(file)) try: - copyfile(join(path, 'bin', 'Debug', file), new_file) + copyfile(join(path, "bin", "Debug", file), new_file) except: pass else: @@ -73,110 +80,132 @@ class Tester: def push(self): solution = self.solution - if solution.result == 'SOLUTION ERROR': + if solution.result == "SOLUTION ERROR": return - solution.result = 'IN QUEUE' + solution.result = "IN QUEUE" solution.save() from Main.models import System - if len(Solution.objects.filter(result='TESTING')) < int(System.objects.get(key='queue_size').value): + + if len(Solution.objects.filter(result="TESTING")) < int( + System.objects.get(key="queue_size").value + ): self.test() def delete_everything(self): ssp = solution_path(self.solution.path()) - sln_path = join(ssp, '.idea') + sln_path = join(ssp, ".idea") if exists(sln_path): rmtree(sln_path) - sln_path = join(ssp, '.vs') + sln_path = join(ssp, ".vs") if exists(sln_path): rmtree(sln_path) sln_path = ssp for p in listdir(sln_path): if isdir(join(sln_path, p)): - if exists(join(sln_path, p, 'bin')): - rmtree(join(sln_path, p, 'bin')) - if exists(join(sln_path, p, 'obj')): - rmtree(join(sln_path, p, 'obj')) + if exists(join(sln_path, p, "bin")): + rmtree(join(sln_path, p, "bin")) + if exists(join(sln_path, p, "obj")): + rmtree(join(sln_path, p, "obj")) if exists(self.working_dir): rmtree(self.working_dir) - if exists(join(self.solution.path(), 'solution.zip')): - remove(join(self.solution.path(), 'solution.zip')) - if exists(join(self.solution.path(), '__MACOSX')): - rmtree(join(self.solution.path(), '__MACOSX')) - if exists(join(sln_path, '.DS_Store')): - remove(join(sln_path, '.DS_Store')) - if exists(join(sln_path, 'test_folder')): - rmtree(join(sln_path, 'test_folder')) + if exists(join(self.solution.path(), "solution.zip")): + remove(join(self.solution.path(), "solution.zip")) + if exists(join(self.solution.path(), "__MACOSX")): + rmtree(join(self.solution.path(), "__MACOSX")) + if exists(join(sln_path, ".DS_Store")): + remove(join(sln_path, ".DS_Store")) + if exists(join(sln_path, "test_folder")): + rmtree(join(sln_path, "test_folder")) def nunit_testing(self): solution = self.solution with self.solution.log_fs as fs: - fs.write(b'Building image\n') - shell('docker build -t solution_{} {}'.format(self.solution.id, self.working_dir)) + fs.write(b"Building image\n") + shell( + "docker build -t solution_{} {}".format(self.solution.id, self.working_dir) + ) with self.solution.log_fs as fs: - fs.write(b'Image built successfully\n') + fs.write(b"Image built successfully\n") def execute(): with self.solution.log_fs as fs: - shell('docker run --name solution_container_{} solution_{}'.format(self.solution.id, self.solution.id), - output=fs) + shell( + "docker run --name solution_container_{} solution_{}".format( + self.solution.id, self.solution.id + ), + output=fs, + ) - solution.write_log('Running container') + solution.write_log("Running container") t = Thread(target=execute) t.start() t.join(self.solution.task.time_limit / 1000) - solution.write_log('Running finished') + solution.write_log("Running finished") with self.solution.log_fs as fs: - shell('docker cp solution_container_{}:/app/TestResults.xml {}'.format(self.solution.id, self.working_dir), - fs) + shell( + "docker cp solution_container_{}:/app/TestResults.xml {}".format( + self.solution.id, self.working_dir + ), + fs, + ) with self.solution.log_fs as fs: - shell('docker rm --force solution_container_{}'.format(self.solution.id), fs) + shell( + "docker rm --force solution_container_{}".format(self.solution.id), fs + ) with self.solution.log_fs as fs: - shell('docker image rm solution_{}'.format(self.solution.id), fs) - if not exists(join(self.working_dir, 'TestResults.xml')): - self.solution.set_result('Time limit') - solution.write_log('Result file not found in container') + shell("docker image rm solution_{}".format(self.solution.id), fs) + if not exists(join(self.working_dir, "TestResults.xml")): + self.solution.set_result("Time limit") + solution.write_log("Result file not found in container") return - solution.write_log('Result file found in container') + solution.write_log("Result file found in container") try: - doc = parse(join(self.working_dir, 'TestResults.xml')) - res = get_node_value(doc.getElementsByTagName('Passed')) + '/' + get_node_value( - doc.getElementsByTagName('Total')) - self.solution.details = '' - for el in doc.getElementsByTagName('Result'): - self.solution.details += '
' + get_node_value(el.getElementsByTagName('MethodName')) + '
' - r = get_node_value(el.getElementsByTagName('Successful')) - if r == 'true': + doc = parse(join(self.working_dir, "TestResults.xml")) + res = ( + get_node_value(doc.getElementsByTagName("Passed")) + + "/" + + get_node_value(doc.getElementsByTagName("Total")) + ) + self.solution.details = "" + for el in doc.getElementsByTagName("Result"): + self.solution.details += ( + "
" + + get_node_value(el.getElementsByTagName("MethodName")) + + "
" + ) + r = get_node_value(el.getElementsByTagName("Successful")) + if r == "true": self.solution.details += '
Passed
' else: self.solution.details += '
Failed
' - mes = get_node_value(el.getElementsByTagName('Message')) - self.solution.details += '
{}
'.format(mes) + mes = get_node_value(el.getElementsByTagName("Message")) + self.solution.details += "
{}
".format(mes) except: - solution.write_log('Unknown error') - res = 'TEST ERROR' + solution.write_log("Unknown error") + res = "TEST ERROR" self.solution.set_result(res) def test(self): solution = self.solution - solution.result = 'TESTING' + solution.result = "TESTING" solution.save() try: if not exists(self.solution.task.tests_path()): with self.solution.log_fs as fs: - fs.write(b'No test file found\n') - solution.set_result('TEST ERROR') + fs.write(b"No test file found\n") + solution.set_result("TEST ERROR") solution.save() self.delete_everything() start_new(self.host) return - sln_path = solution_path(join(MEDIA_ROOT, 'solutions', str(solution.id))) - if sln_path == '': - solution.set_result('TEST ERROR') + sln_path = solution_path(join(MEDIA_ROOT, "solutions", str(solution.id))) + if sln_path == "": + solution.set_result("TEST ERROR") solution.save() self.delete_everything() start_new(self.host) return - working_dir = join(sln_path, 'test_folder') + working_dir = join(sln_path, "test_folder") if exists(working_dir): try: rmtree(working_dir) @@ -184,60 +213,63 @@ class Tester: remove(working_dir) mkdir(working_dir) with self.solution.log_fs as fs: - fs.write(b'Testing directory created\n') + fs.write(b"Testing directory created\n") for project in listdir(sln_path): - solution.write_log('Checking if {} is project'.format(project)) + solution.write_log("Checking if {} is project".format(project)) prj = project project = join(sln_path, project) - if isdir(project) and is_project(project) and basename(project) != 'TestsProject': + if ( + isdir(project) + and is_project(project) + and basename(project) != "TestsProject" + ): if not self.build_and_copy(project, working_dir): - solution.set_result('Compilation error') - solution.write_log('Failed to compile project {}'.format(prj)) + solution.set_result("Compilation error") + solution.write_log("Failed to compile project {}".format(prj)) solution.save() self.delete_everything() start_new(self.host) return dll_path = solution.task.tests_path() - solution.write_log('Copying test file to working directory') - copyfile(dll_path, join(working_dir, str(solution.task.id) + '.cs')) - solution.write_log('Test file copied') - for file in listdir('SprintTest'): + solution.write_log("Copying test file to working directory") + copyfile(dll_path, join(working_dir, str(solution.task.id) + ".cs")) + solution.write_log("Test file copied") + for file in listdir("SprintTest"): try: - copyfile(join('SprintTest', file), join(working_dir, file)) + copyfile(join("SprintTest", file), join(working_dir, file)) except: pass self.working_dir = working_dir - build_tests_cmd = 'csc -out:{} -t:library /r:{} /r:{} /r:{} '.format(join(self.working_dir, 'tests.dll'), - join(self.working_dir, - 'SprintTest.dll'), - join(working_dir, - 'System.Runtime.dll'), - join(working_dir, - 'System.Reflection.dll')) + build_tests_cmd = "csc -out:{} -t:library /r:{} /r:{} /r:{} ".format( + join(self.working_dir, "tests.dll"), + join(self.working_dir, "SprintTest.dll"), + join(working_dir, "System.Runtime.dll"), + join(working_dir, "System.Reflection.dll"), + ) for file in self.files: - build_tests_cmd += '/r:{}.dll '.format(join(self.working_dir, file)) + build_tests_cmd += "/r:{}.dll ".format(join(self.working_dir, file)) build_tests_cmd += self.solution.task.tests_path() - if exists(join(self.working_dir, 'tests.dll')): - remove(join(self.working_dir, 'tests.dll')) - solution.write_log('Building tests file started') + if exists(join(self.working_dir, "tests.dll")): + remove(join(self.working_dir, "tests.dll")) + solution.write_log("Building tests file started") with self.solution.log_fs as fs: shell(build_tests_cmd, fs) with self.solution.log_fs as fs: - fs.write(b'Building tests file finished\n') - if exists(join(self.working_dir, 'tests.dll')): + fs.write(b"Building tests file finished\n") + if exists(join(self.working_dir, "tests.dll")): with self.solution.log_fs as fs: - fs.write(b'Got .dll tests file\n') + fs.write(b"Got .dll tests file\n") for file in ExtraFile.objects.filter(task=self.solution.task): copyfile(file.path, join(working_dir, file.filename)) self.nunit_testing() else: - solution.set_result('TEST ERROR') - solution.write_log('Failed to compile tests') + solution.set_result("TEST ERROR") + solution.write_log("Failed to compile tests") except: - solution.set_result('TEST ERROR') + solution.set_result("TEST ERROR") raise with self.solution.log_fs as fs: - fs.write(b'Unknown error\n') + fs.write(b"Unknown error\n") solution.save() self.delete_everything() start_new(self.host) diff --git a/Main/Timer.py b/Main/Timer.py index 8b15ec3..0d66b04 100644 --- a/Main/Timer.py +++ b/Main/Timer.py @@ -1,29 +1,28 @@ class Method: + def __init__(self, meth, name): + self.meth = meth + self.name = name - def __init__(self, meth, name): - self.meth = meth - self.name = name + def __eq__(self, other): + return self.name == other.name - def __eq__(self, other): - return self.name == other.name - - def execute(self): - self.meth() + def execute(self): + self.meth() class Timer: - methods = [] + methods = [] - def push(self, meth): - methods.append(math) + def push(self, meth): + methods.append(math) - def polling(self): - for i in range(len(self.methods)): - methods[i].execute() + def polling(self): + for i in range(len(self.methods)): + methods[i].execute() - def remove(method_name): - for method in self.methods: - if method.name == method_name: - self.methods.remove(method) - return - raise IndexError("No method in list") + def remove(method_name): + for method in self.methods: + if method.name == method_name: + self.methods.remove(method) + return + raise IndexError("No method in list") diff --git a/Main/admin.py b/Main/admin.py index 6181281..c962a5c 100644 --- a/Main/admin.py +++ b/Main/admin.py @@ -1,14 +1,11 @@ from django.contrib import admin from Main.models import * +import Main.models # Register your models here. -admin.site.register(Course) -admin.site.register(Block) -admin.site.register(Task) -admin.site.register(Subscribe) -admin.site.register(Restore) -admin.site.register(UserInfo) -admin.site.register(Solution) -admin.site.register(System) -admin.site.register(ExtraFile) \ No newline at end of file +for model in dir(Main.models): + try: + admin.site.register(eval("Main.models." + model)) + except: + continue diff --git a/Main/apps.py b/Main/apps.py index 2d54e36..7a6973d 100644 --- a/Main/apps.py +++ b/Main/apps.py @@ -2,4 +2,5 @@ from django.apps import AppConfig class MainConfig(AppConfig): - name = 'Main' + name = "Main" + default_auto_field = "django.db.models.BigAutoField" diff --git a/Main/commands.py b/Main/commands.py deleted file mode 100644 index b181c53..0000000 --- a/Main/commands.py +++ /dev/null @@ -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() diff --git a/Main/context_processors.py b/Main/context_processors.py index 0f71e69..399c05f 100644 --- a/Main/context_processors.py +++ b/Main/context_processors.py @@ -1,8 +1,5 @@ -from .main import check_admin +from Sprint.settings import CONSTS def attributes(request): - return { - 'current_page': 'settings' if '/settings' == request.path else 'admin' if '/admin/' in request.path else 'main', - 'is_admin': check_admin(request.user) - } \ No newline at end of file + return CONSTS diff --git a/Main/decorators.py b/Main/decorators.py deleted file mode 100644 index fa678a9..0000000 --- a/Main/decorators.py +++ /dev/null @@ -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 diff --git a/Main/forms.py b/Main/forms.py index 4a7be6e..ef394b2 100644 --- a/Main/forms.py +++ b/Main/forms.py @@ -2,7 +2,6 @@ from django import forms class PasswordField(forms.CharField): - def __init__(self, **kwargs): super().__init__(**kwargs) self.widget = forms.PasswordInput() @@ -10,11 +9,15 @@ class PasswordField(forms.CharField): class LoginForm(forms.Form): email = forms.EmailField(widget=forms.TextInput(attrs={"class": "input_simple"})) - password = forms.CharField(widget=forms.PasswordInput(attrs={"class": "input_simple"})) + password = forms.CharField( + widget=forms.PasswordInput(attrs={"class": "input_simple"}) + ) class FileForm(forms.Form): - file = forms.FileField(widget=forms.FileInput(attrs={'class': 'input_simple'}), required=False) + file = forms.FileField( + widget=forms.FileInput(attrs={"class": "input_simple"}), required=False + ) class TestsForm(forms.Form): @@ -22,11 +25,11 @@ class TestsForm(forms.Form): class ChangePasswordForm(forms.Form): - old = PasswordField(label='Старый пароль') - new = PasswordField(label='Новый пароль') - again = PasswordField(label='Еще раз') + old = PasswordField(label="Старый пароль") + new = PasswordField(label="Новый пароль") + again = PasswordField(label="Еще раз") class ResetPasswordForm(forms.Form): - new = PasswordField(label='Новый пароль') - again = PasswordField(label='Еще раз') + new = PasswordField(label="Новый пароль") + again = PasswordField(label="Еще раз") diff --git a/Main/main.py b/Main/main.py deleted file mode 100644 index f8bcd37..0000000 --- a/Main/main.py +++ /dev/null @@ -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('
'.format(tag_name))[1].split('
')[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') - diff --git a/Main/migrations/0001_initial.py b/Main/migrations/0001_initial.py index e530768..1426f5e 100644 --- a/Main/migrations/0001_initial.py +++ b/Main/migrations/0001_initial.py @@ -15,84 +15,201 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Block', + name="Block", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField()), - ('time_start', models.DateTimeField()), - ('time_end', models.DateTimeField()), - ('opened', models.IntegerField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.TextField()), + ("time_start", models.DateTimeField()), + ("time_end", models.DateTimeField()), + ("opened", models.IntegerField()), ], ), migrations.CreateModel( - name='Course', + name="Course", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.TextField()), ], ), migrations.CreateModel( - name='UserInfo', + name="UserInfo", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('surname', models.TextField()), - ('name', models.TextField()), - ('middle_name', models.TextField()), - ('group_name', models.TextField()), - ('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("surname", models.TextField()), + ("name", models.TextField()), + ("middle_name", models.TextField()), + ("group_name", models.TextField()), + ( + "user", + models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='Task', + name="Task", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.TextField()), - ('legend', models.TextField()), - ('input', models.TextField()), - ('output', models.TextField()), - ('specifications', models.TextField()), - ('time_limit', models.IntegerField()), - ('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.TextField()), + ("legend", models.TextField()), + ("input", models.TextField()), + ("output", models.TextField()), + ("specifications", models.TextField()), + ("time_limit", models.IntegerField()), + ( + "block", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.Block" + ), + ), ], ), migrations.CreateModel( - name='Subscribe', + name="Subscribe", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_assistant', models.IntegerField()), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_assistant", models.IntegerField()), + ( + "course", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.Course" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='Solution', + name="Solution", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('result', models.TextField()), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Task')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("result", models.TextField()), + ( + "task", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.Task" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='Restore', + name="Restore", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('code', models.TextField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("code", models.TextField()), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='Mark', + name="Mark", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('mark', models.IntegerField()), - ('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Block')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("mark", models.IntegerField()), + ( + "block", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.Block" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AddField( - model_name='block', - name='course', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.Course'), + model_name="block", + name="course", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.Course" + ), ), ] diff --git a/Main/migrations/0002_auto_20200626_0946.py b/Main/migrations/0002_auto_20200626_0946.py index a25c745..ef28d9e 100644 --- a/Main/migrations/0002_auto_20200626_0946.py +++ b/Main/migrations/0002_auto_20200626_0946.py @@ -6,13 +6,13 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('Main', '0001_initial'), + ("Main", "0001_initial"), ] operations = [ migrations.RenameField( - model_name='userinfo', - old_name='group_name', - new_name='group', + model_name="userinfo", + old_name="group_name", + new_name="group", ), ] diff --git a/Main/migrations/0003_auto_20200627_1959.py b/Main/migrations/0003_auto_20200627_1959.py index 228e39c..bc2bb3f 100644 --- a/Main/migrations/0003_auto_20200627_1959.py +++ b/Main/migrations/0003_auto_20200627_1959.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0002_auto_20200626_0946'), + ("Main", "0002_auto_20200626_0946"), ] operations = [ migrations.AlterField( - model_name='subscribe', - name='is_assistant', + model_name="subscribe", + name="is_assistant", field=models.IntegerField(default=0), ), ] diff --git a/Main/migrations/0004_auto_20200628_0917.py b/Main/migrations/0004_auto_20200628_0917.py index 7696150..eb5cf58 100644 --- a/Main/migrations/0004_auto_20200628_0917.py +++ b/Main/migrations/0004_auto_20200628_0917.py @@ -6,33 +6,33 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0003_auto_20200627_1959'), + ("Main", "0003_auto_20200627_1959"), ] operations = [ migrations.AlterField( - model_name='task', - name='input', - field=models.TextField(default=''), + model_name="task", + name="input", + field=models.TextField(default=""), ), migrations.AlterField( - model_name='task', - name='legend', - field=models.TextField(default=''), + model_name="task", + name="legend", + field=models.TextField(default=""), ), migrations.AlterField( - model_name='task', - name='output', - field=models.TextField(default=''), + model_name="task", + name="output", + field=models.TextField(default=""), ), migrations.AlterField( - model_name='task', - name='specifications', - field=models.TextField(default=''), + model_name="task", + name="specifications", + field=models.TextField(default=""), ), migrations.AlterField( - model_name='task', - name='time_limit', + model_name="task", + name="time_limit", field=models.IntegerField(default=10000), ), ] diff --git a/Main/migrations/0005_solution_time_sent.py b/Main/migrations/0005_solution_time_sent.py index 39dfb60..8b651f0 100644 --- a/Main/migrations/0005_solution_time_sent.py +++ b/Main/migrations/0005_solution_time_sent.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0004_auto_20200628_0917'), + ("Main", "0004_auto_20200628_0917"), ] operations = [ migrations.AddField( - model_name='solution', - name='time_sent', + model_name="solution", + name="time_sent", field=models.DateTimeField(null=True), ), ] diff --git a/Main/migrations/0006_auto_20200628_1315.py b/Main/migrations/0006_auto_20200628_1315.py index d761af7..ee2160a 100644 --- a/Main/migrations/0006_auto_20200628_1315.py +++ b/Main/migrations/0006_auto_20200628_1315.py @@ -7,13 +7,15 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('Main', '0005_solution_time_sent'), + ("Main", "0005_solution_time_sent"), ] operations = [ migrations.AlterField( - model_name='solution', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.UserInfo'), + model_name="solution", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.UserInfo" + ), ), ] diff --git a/Main/migrations/0007_auto_20200629_0833.py b/Main/migrations/0007_auto_20200629_0833.py index 77a213f..ab22e73 100644 --- a/Main/migrations/0007_auto_20200629_0833.py +++ b/Main/migrations/0007_auto_20200629_0833.py @@ -9,13 +9,15 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('Main', '0006_auto_20200628_1315'), + ("Main", "0006_auto_20200628_1315"), ] operations = [ migrations.AlterField( - model_name='solution', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="solution", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/Main/migrations/0008_auto_20200702_2140.py b/Main/migrations/0008_auto_20200702_2140.py index 9207044..dafcde1 100644 --- a/Main/migrations/0008_auto_20200702_2140.py +++ b/Main/migrations/0008_auto_20200702_2140.py @@ -6,18 +6,18 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0007_auto_20200629_0833'), + ("Main", "0007_auto_20200629_0833"), ] operations = [ migrations.AddField( - model_name='userinfo', - name='mark_notification', + model_name="userinfo", + name="mark_notification", field=models.IntegerField(default=0), ), migrations.AddField( - model_name='userinfo', - name='new_block_notification', + model_name="userinfo", + name="new_block_notification", field=models.IntegerField(default=0), ), ] diff --git a/Main/migrations/0009_auto_20200704_1703.py b/Main/migrations/0009_auto_20200704_1703.py index 7c8eeb6..ea8fef1 100644 --- a/Main/migrations/0009_auto_20200704_1703.py +++ b/Main/migrations/0009_auto_20200704_1703.py @@ -6,18 +6,18 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0008_auto_20200702_2140'), + ("Main", "0008_auto_20200702_2140"), ] operations = [ migrations.AddField( - model_name='solution', - name='comment', - field=models.TextField(default=''), + model_name="solution", + name="comment", + field=models.TextField(default=""), ), migrations.AddField( - model_name='solution', - name='mark', + model_name="solution", + name="mark", field=models.IntegerField(null=True), ), ] diff --git a/Main/migrations/0010_system.py b/Main/migrations/0010_system.py index b388d98..a87213f 100644 --- a/Main/migrations/0010_system.py +++ b/Main/migrations/0010_system.py @@ -6,16 +6,24 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0009_auto_20200704_1703'), + ("Main", "0009_auto_20200704_1703"), ] operations = [ migrations.CreateModel( - name='System', + name="System", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.TextField()), - ('value', models.TextField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("key", models.TextField()), + ("value", models.TextField()), ], ), ] diff --git a/Main/migrations/0011_auto_20200814_2035.py b/Main/migrations/0011_auto_20200814_2035.py index b2b0209..a936c83 100644 --- a/Main/migrations/0011_auto_20200814_2035.py +++ b/Main/migrations/0011_auto_20200814_2035.py @@ -6,18 +6,26 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0010_system'), + ("Main", "0010_system"), ] operations = [ migrations.CreateModel( - name='ThreadSafe', + name="ThreadSafe", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.CharField(max_length=80, unique=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("key", models.CharField(max_length=80, unique=True)), ], ), migrations.DeleteModel( - name='Mark', + name="Mark", ), ] diff --git a/Main/migrations/0012_auto_20200901_1154.py b/Main/migrations/0012_auto_20200901_1154.py index 7da64e8..e267a3d 100644 --- a/Main/migrations/0012_auto_20200901_1154.py +++ b/Main/migrations/0012_auto_20200901_1154.py @@ -7,21 +7,34 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('Main', '0011_auto_20200814_2035'), + ("Main", "0011_auto_20200814_2035"), ] operations = [ migrations.AddField( - model_name='task', - name='weight', + model_name="task", + name="weight", field=models.FloatField(default=1.0), ), migrations.CreateModel( - name='ExtraFile', + name="ExtraFile", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('file', models.FileField(upload_to='')), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("file", models.FileField(upload_to="")), + ( + "task", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.task" + ), + ), ], ), ] diff --git a/Main/migrations/0013_delete_extrafile.py b/Main/migrations/0013_delete_extrafile.py index c303052..3239636 100644 --- a/Main/migrations/0013_delete_extrafile.py +++ b/Main/migrations/0013_delete_extrafile.py @@ -6,11 +6,11 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('Main', '0012_auto_20200901_1154'), + ("Main", "0012_auto_20200901_1154"), ] operations = [ migrations.DeleteModel( - name='ExtraFile', + name="ExtraFile", ), ] diff --git a/Main/migrations/0014_extrafile.py b/Main/migrations/0014_extrafile.py index cdea665..9f05ea8 100644 --- a/Main/migrations/0014_extrafile.py +++ b/Main/migrations/0014_extrafile.py @@ -7,17 +7,30 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('Main', '0013_delete_extrafile'), + ("Main", "0013_delete_extrafile"), ] operations = [ migrations.CreateModel( - name='ExtraFile', + name="ExtraFile", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('file', models.FileField(upload_to='')), - ('filename', models.TextField()), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("file", models.FileField(upload_to="")), + ("filename", models.TextField()), + ( + "task", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.task" + ), + ), ], ), ] diff --git a/Main/migrations/0015_auto_20200902_1555.py b/Main/migrations/0015_auto_20200902_1555.py index d6da9ba..ff5cf56 100644 --- a/Main/migrations/0015_auto_20200902_1555.py +++ b/Main/migrations/0015_auto_20200902_1555.py @@ -6,18 +6,18 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0014_extrafile'), + ("Main", "0014_extrafile"), ] operations = [ migrations.AddField( - model_name='task', - name='max_mark', + model_name="task", + name="max_mark", field=models.IntegerField(default=10), ), migrations.AlterField( - model_name='extrafile', - name='file', - field=models.FileField(upload_to='data\\extra_files'), + model_name="extrafile", + name="file", + field=models.FileField(upload_to="data\\extra_files"), ), ] diff --git a/Main/migrations/0016_task_max_solutions_count.py b/Main/migrations/0016_task_max_solutions_count.py index 2f772e4..00c3623 100644 --- a/Main/migrations/0016_task_max_solutions_count.py +++ b/Main/migrations/0016_task_max_solutions_count.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0015_auto_20200902_1555'), + ("Main", "0015_auto_20200902_1555"), ] operations = [ migrations.AddField( - model_name='task', - name='max_solutions_count', + model_name="task", + name="max_solutions_count", field=models.IntegerField(default=10), ), ] diff --git a/Main/migrations/0017_solution_details.py b/Main/migrations/0017_solution_details.py index 2b5d6f2..fd78408 100644 --- a/Main/migrations/0017_solution_details.py +++ b/Main/migrations/0017_solution_details.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0016_task_max_solutions_count'), + ("Main", "0016_task_max_solutions_count"), ] operations = [ migrations.AddField( - model_name='solution', - name='details', - field=models.TextField(default=''), + model_name="solution", + name="details", + field=models.TextField(default=""), ), ] diff --git a/Main/migrations/0018_remove_extrafile_file.py b/Main/migrations/0018_remove_extrafile_file.py index 74afce9..f23573f 100644 --- a/Main/migrations/0018_remove_extrafile_file.py +++ b/Main/migrations/0018_remove_extrafile_file.py @@ -6,12 +6,12 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('Main', '0017_solution_details'), + ("Main", "0017_solution_details"), ] operations = [ migrations.RemoveField( - model_name='extrafile', - name='file', + model_name="extrafile", + name="file", ), ] diff --git a/Main/migrations/0019_task_show_details.py b/Main/migrations/0019_task_show_details.py index 4093064..48236ae 100644 --- a/Main/migrations/0019_task_show_details.py +++ b/Main/migrations/0019_task_show_details.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0018_remove_extrafile_file'), + ("Main", "0018_remove_extrafile_file"), ] operations = [ migrations.AddField( - model_name='task', - name='show_details', + model_name="task", + name="show_details", field=models.IntegerField(default=1), ), ] diff --git a/Main/migrations/0020_task_solution_type.py b/Main/migrations/0020_task_solution_type.py index 8f0fdb9..7546c2a 100644 --- a/Main/migrations/0020_task_solution_type.py +++ b/Main/migrations/0020_task_solution_type.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0019_task_show_details'), + ("Main", "0019_task_show_details"), ] operations = [ migrations.AddField( - model_name='task', - name='solution_type', - field=models.TextField(default='Решение'), + model_name="task", + name="solution_type", + field=models.TextField(default="Решение"), ), ] diff --git a/Main/migrations/0021_remove_task_solution_type.py b/Main/migrations/0021_remove_task_solution_type.py index 0736cf7..cea7f13 100644 --- a/Main/migrations/0021_remove_task_solution_type.py +++ b/Main/migrations/0021_remove_task_solution_type.py @@ -6,12 +6,12 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('Main', '0020_task_solution_type'), + ("Main", "0020_task_solution_type"), ] operations = [ migrations.RemoveField( - model_name='task', - name='solution_type', + model_name="task", + name="solution_type", ), ] diff --git a/Main/migrations/0022_task_full_solution.py b/Main/migrations/0022_task_full_solution.py index 24eec3a..46b067e 100644 --- a/Main/migrations/0022_task_full_solution.py +++ b/Main/migrations/0022_task_full_solution.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0021_remove_task_solution_type'), + ("Main", "0021_remove_task_solution_type"), ] operations = [ migrations.AddField( - model_name='task', - name='full_solution', + model_name="task", + name="full_solution", field=models.IntegerField(default=0), ), ] diff --git a/Main/migrations/0023_extrafile_for_compilation.py b/Main/migrations/0023_extrafile_for_compilation.py index 9a360a4..f5987b8 100644 --- a/Main/migrations/0023_extrafile_for_compilation.py +++ b/Main/migrations/0023_extrafile_for_compilation.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0022_task_full_solution'), + ("Main", "0022_task_full_solution"), ] operations = [ migrations.AddField( - model_name='extrafile', - name='for_compilation', + model_name="extrafile", + name="for_compilation", field=models.IntegerField(default=0), ), ] diff --git a/Main/migrations/0024_extrafile_sample.py b/Main/migrations/0024_extrafile_sample.py index 31a9d3e..524288c 100644 --- a/Main/migrations/0024_extrafile_sample.py +++ b/Main/migrations/0024_extrafile_sample.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0023_extrafile_for_compilation'), + ("Main", "0023_extrafile_for_compilation"), ] operations = [ migrations.AddField( - model_name='extrafile', - name='sample', + model_name="extrafile", + name="sample", field=models.IntegerField(default=0), ), ] diff --git a/Main/migrations/0025_auto_20201106_1848.py b/Main/migrations/0025_auto_20201106_1848.py index 64f7d6e..df9790e 100644 --- a/Main/migrations/0025_auto_20201106_1848.py +++ b/Main/migrations/0025_auto_20201106_1848.py @@ -6,46 +6,46 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0024_extrafile_sample'), + ("Main", "0024_extrafile_sample"), ] operations = [ migrations.RemoveField( - model_name='userinfo', - name='mark_notification', + model_name="userinfo", + name="mark_notification", ), migrations.RemoveField( - model_name='userinfo', - name='new_block_notification', + model_name="userinfo", + name="new_block_notification", ), migrations.AlterField( - model_name='block', - name='opened', + model_name="block", + name="opened", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='extrafile', - name='for_compilation', + model_name="extrafile", + name="for_compilation", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='extrafile', - name='sample', + model_name="extrafile", + name="sample", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='subscribe', - name='is_assistant', + model_name="subscribe", + name="is_assistant", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='task', - name='full_solution', + model_name="task", + name="full_solution", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='task', - name='show_details', + model_name="task", + name="show_details", field=models.BooleanField(default=False), ), ] diff --git a/Main/migrations/0026_block_show_rating.py b/Main/migrations/0026_block_show_rating.py index 45f4c80..c0e6b67 100644 --- a/Main/migrations/0026_block_show_rating.py +++ b/Main/migrations/0026_block_show_rating.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0025_auto_20201106_1848'), + ("Main", "0025_auto_20201106_1848"), ] operations = [ migrations.AddField( - model_name='block', - name='show_rating', + model_name="block", + name="show_rating", field=models.BooleanField(default=True), ), ] diff --git a/Main/migrations/0027_task_mark_formula.py b/Main/migrations/0027_task_mark_formula.py index 6fb2096..faf0380 100644 --- a/Main/migrations/0027_task_mark_formula.py +++ b/Main/migrations/0027_task_mark_formula.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0026_block_show_rating'), + ("Main", "0026_block_show_rating"), ] operations = [ migrations.AddField( - model_name='task', - name='mark_formula', - field=models.TextField(default='None'), + model_name="task", + name="mark_formula", + field=models.TextField(default="None"), ), ] diff --git a/Main/migrations/0028_task_show_result.py b/Main/migrations/0028_task_show_result.py index 23fec4d..e4952b4 100644 --- a/Main/migrations/0028_task_show_result.py +++ b/Main/migrations/0028_task_show_result.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0027_task_mark_formula'), + ("Main", "0027_task_mark_formula"), ] operations = [ migrations.AddField( - model_name='task', - name='show_result', + model_name="task", + name="show_result", field=models.BooleanField(default=True), ), ] diff --git a/Main/migrations/0029_auto_20210130_1950.py b/Main/migrations/0029_auto_20210130_1950.py index 7b0458b..07101e9 100644 --- a/Main/migrations/0029_auto_20210130_1950.py +++ b/Main/migrations/0029_auto_20210130_1950.py @@ -6,18 +6,18 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0028_task_show_result'), + ("Main", "0028_task_show_result"), ] operations = [ migrations.AddField( - model_name='block', - name='priority', + model_name="block", + name="priority", field=models.IntegerField(default=5), ), migrations.AddField( - model_name='task', - name='priority', + model_name="task", + name="priority", field=models.IntegerField(default=5), ), ] diff --git a/Main/migrations/0030_message.py b/Main/migrations/0030_message.py index 815e0dd..43d14f2 100644 --- a/Main/migrations/0030_message.py +++ b/Main/migrations/0030_message.py @@ -9,19 +9,46 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('Main', '0029_auto_20210130_1950'), + ("Main", "0029_auto_20210130_1950"), ] operations = [ migrations.CreateModel( - name='Message', + name="Message", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('for_all', models.BooleanField()), - ('text', models.TextField()), - ('reply_to', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Main.message')), - ('sender', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Main.task')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("for_all", models.BooleanField()), + ("text", models.TextField()), + ( + "reply_to", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="Main.message", + ), + ), + ( + "sender", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "task", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="Main.task" + ), + ), ], ), ] diff --git a/Main/migrations/0031_block_cheating_checking.py b/Main/migrations/0031_block_cheating_checking.py index 57f91e6..4e2792f 100644 --- a/Main/migrations/0031_block_cheating_checking.py +++ b/Main/migrations/0031_block_cheating_checking.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0030_message'), + ("Main", "0030_message"), ] operations = [ migrations.AddField( - model_name='block', - name='cheating_checking', + model_name="block", + name="cheating_checking", field=models.BooleanField(default=False), ), ] diff --git a/Main/migrations/0032_block_cheating_data.py b/Main/migrations/0032_block_cheating_data.py index 3bbbe8d..b49f154 100644 --- a/Main/migrations/0032_block_cheating_data.py +++ b/Main/migrations/0032_block_cheating_data.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('Main', '0031_block_cheating_checking'), + ("Main", "0031_block_cheating_checking"), ] operations = [ migrations.AddField( - model_name='block', - name='cheating_data', - field=models.TextField(default='[]'), + model_name="block", + name="cheating_data", + field=models.TextField(default="[]"), ), ] diff --git a/Main/migrations/0033_remove_block_cheating_data.py b/Main/migrations/0033_remove_block_cheating_data.py index d27a788..d93e04a 100644 --- a/Main/migrations/0033_remove_block_cheating_data.py +++ b/Main/migrations/0033_remove_block_cheating_data.py @@ -6,12 +6,12 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('Main', '0032_block_cheating_data'), + ("Main", "0032_block_cheating_data"), ] operations = [ migrations.RemoveField( - model_name='block', - name='cheating_data', + model_name="block", + name="cheating_data", ), ] diff --git a/Main/models.py b/Main/models.py deleted file mode 100644 index 3e2e7f0..0000000 --- a/Main/models.py +++ /dev/null @@ -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 += '' - else: - line += '' - for mark in range(self.task.max_mark + 1): - if mark == self.mark: - line += ''.format(mark, mark) - else: - line += ''.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() diff --git a/Main/models/__init__.py b/Main/models/__init__.py new file mode 100644 index 0000000..cf117fa --- /dev/null +++ b/Main/models/__init__.py @@ -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 diff --git a/Main/models/extrafile.py b/Main/models/extrafile.py new file mode 100644 index 0000000..210aa14 --- /dev/null +++ b/Main/models/extrafile.py @@ -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) diff --git a/Main/models/file.py b/Main/models/file.py new file mode 100644 index 0000000..3486ef4 --- /dev/null +++ b/Main/models/file.py @@ -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() diff --git a/Main/models/group.py b/Main/models/group.py new file mode 100644 index 0000000..c662f3b --- /dev/null +++ b/Main/models/group.py @@ -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) diff --git a/Main/models/language.py b/Main/models/language.py new file mode 100644 index 0000000..e0fbb30 --- /dev/null +++ b/Main/models/language.py @@ -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 diff --git a/Main/models/set.py b/Main/models/set.py new file mode 100644 index 0000000..32f9444 --- /dev/null +++ b/Main/models/set.py @@ -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) diff --git a/Main/models/settask.py b/Main/models/settask.py new file mode 100644 index 0000000..31bbf95 --- /dev/null +++ b/Main/models/settask.py @@ -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) diff --git a/Main/models/solution.py b/Main/models/solution.py new file mode 100644 index 0000000..60d91c1 --- /dev/null +++ b/Main/models/solution.py @@ -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) diff --git a/Main/models/subscription.py b/Main/models/subscription.py new file mode 100644 index 0000000..851eab6 --- /dev/null +++ b/Main/models/subscription.py @@ -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() diff --git a/Main/models/task.py b/Main/models/task.py new file mode 100644 index 0000000..cca8c87 --- /dev/null +++ b/Main/models/task.py @@ -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) diff --git a/Main/models/userinfo.py b/Main/models/userinfo.py new file mode 100644 index 0000000..c448c05 --- /dev/null +++ b/Main/models/userinfo.py @@ -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) diff --git a/Main/tasks.py b/Main/tasks.py new file mode 100644 index 0000000..c61c472 --- /dev/null +++ b/Main/tasks.py @@ -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() diff --git a/Main/templatetags/filters.py b/Main/templatetags/filters.py index 8b55ade..a5462d1 100644 --- a/Main/templatetags/filters.py +++ b/Main/templatetags/filters.py @@ -1,112 +1,5 @@ from django import template -from Main.models import Solution, Task, UserInfo -from django.contrib.auth.models import User +from Main.models import SetTask, Subscription register = template.Library() - - -@register.filter('mark_for_task') -def mark_for_task(task, user): - try: - return round(list(Solution.objects.filter(task=task, user=user, mark__isnull=False))[-1].mark * 10 / task.max_mark) - except IndexError: - return 0 - - -@register.filter('mark_for_block') -def mark_for_block(block, user): - tasks = Task.objects.filter(block=block) - mark = 0 - for task in tasks: - mft = mark_for_task(task, user) - mark += mft * task.weight - return round(mark) - - -@register.filter('marked') -def marked(mark): - return mark != -1 - - -@register.filter('mark_color') -def mark_color(mark): - mark = round(mark) - if mark > 7: - return '#00FF00' - elif mark > 5: - return '#FFFF00' - elif mark > 3: - return '#FAD7A0' - elif mark > 0: - return '#F1948A' - else: - return '#FFFFFF' - - -@register.filter('in_dict') -def in_dict(value, dict): - return value in dict.keys() - - -@register.filter('last_attempts') -def last_attempts(user, task): - return task.max_solutions_count - len(Solution.objects.filter(task=task, user=user)) - - -@register.filter('userinfo_by_user') -def userinfo_by_user(user): - return UserInfo.objects.get(user=user) - - -@register.filter('mark_status') -def mark_status(user, task): - sols = Solution.objects.filter(user=user, task=task) - if len(sols) == 0: - return '-' - return sols.last().result - - -@register.filter('fully_marked') -def fully_marked(user, task): - return len(Solution.objects.filter(user=user, task=task, mark=None)) == 0 - - -@register.filter('is_code') -def is_code(path): - return path.endswith('.cs') - - -@register.filter('num_range') -def num_range(n): - return range(1, n + 1) - - -@register.filter('length') -def length(collection): - return len(collection) - - -@register.filter('user_by_id') -def user_by_id(user_id): - return User.objects.get(id=user_id) - - -@register.filter('dict_key') -def dict_key(d, k): - return d[k] - - -@register.filter('solution_by_id') -def solution_by_id(solution_id): - return Solution.objects.get(id=solution_id) - - -@register.filter('solution_file_text') -def solution_file_text(solution, filename): - files = solution.user_files - for key in files.keys(): - value = files[key] - if key.endswith(filename): - return value - raise Exception(f'No such file for solution {solution.id} and filename {filename}') diff --git a/Main/views.py b/Main/views.py deleted file mode 100644 index 631c6cd..0000000 --- a/Main/views.py +++ /dev/null @@ -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('
')
-            if i == -1:
-                break
-            j = current_solution.details.find('
') + 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('
')
-                if i == -1:
-                    break
-                j = sol.details.find('
') + 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', '')}) diff --git a/Main/views/AccountView.py b/Main/views/AccountView.py new file mode 100644 index 0000000..308a0d9 --- /dev/null +++ b/Main/views/AccountView.py @@ -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=Пароль успешно установлен" diff --git a/Main/views/EnterView.py b/Main/views/EnterView.py new file mode 100644 index 0000000..b3c940a --- /dev/null +++ b/Main/views/EnterView.py @@ -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=Неверный пароль" diff --git a/Main/views/ExitView.py b/Main/views/ExitView.py new file mode 100644 index 0000000..881d75f --- /dev/null +++ b/Main/views/ExitView.py @@ -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 "/" diff --git a/Main/views/MainView.py b/Main/views/MainView.py new file mode 100644 index 0000000..47ee857 --- /dev/null +++ b/Main/views/MainView.py @@ -0,0 +1,6 @@ +from SprintLib.BaseView import BaseView + + +class MainView(BaseView): + view_file = "main.html" + required_login = True diff --git a/Main/views/RatingView.py b/Main/views/RatingView.py new file mode 100644 index 0000000..9d70109 --- /dev/null +++ b/Main/views/RatingView.py @@ -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') diff --git a/Main/views/RegisterView.py b/Main/views/RegisterView.py new file mode 100644 index 0000000..dacfbe6 --- /dev/null +++ b/Main/views/RegisterView.py @@ -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" diff --git a/Main/views/SetsView.py b/Main/views/SetsView.py new file mode 100644 index 0000000..a5ddf5d --- /dev/null +++ b/Main/views/SetsView.py @@ -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}" diff --git a/Main/views/SolutionsTableView.py b/Main/views/SolutionsTableView.py new file mode 100644 index 0000000..f929ef4 --- /dev/null +++ b/Main/views/SolutionsTableView.py @@ -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') diff --git a/Main/views/TaskSettingsView.py b/Main/views/TaskSettingsView.py new file mode 100644 index 0000000..55c7cb0 --- /dev/null +++ b/Main/views/TaskSettingsView.py @@ -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) diff --git a/Main/views/TaskView.py b/Main/views/TaskView.py new file mode 100644 index 0000000..0ea6e1c --- /dev/null +++ b/Main/views/TaskView.py @@ -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) diff --git a/Main/views/TasksView.py b/Main/views/TasksView.py new file mode 100644 index 0000000..6c1ecf9 --- /dev/null +++ b/Main/views/TasksView.py @@ -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}" diff --git a/Main/views/__init__.py b/Main/views/__init__.py new file mode 100644 index 0000000..9d75970 --- /dev/null +++ b/Main/views/__init__.py @@ -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 diff --git a/Sprint/__init__.py b/Sprint/__init__.py index e69de29..711580c 100644 --- a/Sprint/__init__.py +++ b/Sprint/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery + +__all__ = ('celery',) diff --git a/Sprint/asgi.py b/Sprint/asgi.py index 4078f7f..7e04f6b 100644 --- a/Sprint/asgi.py +++ b/Sprint/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Sprint.settings") application = get_asgi_application() diff --git a/Sprint/celery.py b/Sprint/celery.py new file mode 100644 index 0000000..da680ac --- /dev/null +++ b/Sprint/celery.py @@ -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() diff --git a/Sprint/settings.py b/Sprint/settings.py index 912fb17..a2d2129 100644 --- a/Sprint/settings.py +++ b/Sprint/settings.py @@ -20,7 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '-w#*mn6*fa8a=(-c0@klx&$vl%hpiy&l(u*3%0a#2)wdt##(z2' +SECRET_KEY = "-w#*mn6*fa8a=(-c0@klx&$vl%hpiy&l(u*3%0a#2)wdt##(z2" DEPLOY = False @@ -30,69 +30,65 @@ DEBUG = not DEPLOY SECURE_SSL_REDIRECT = DEPLOY -ALLOWED_HOSTS = [ - '*' -] +ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ - 'grappelli', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'Main.apps.MainConfig' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "Main.apps.MainConfig", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.locale.LocaleMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django.middleware.locale.LocaleMiddleware", ] -ROOT_URLCONF = 'Sprint.urls' +ROOT_URLCONF = "Sprint.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')] - , - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'Main.context_processors.attributes' + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "Main.context_processors.attributes", ], }, }, ] -WSGI_APPLICATION = 'Sprint.wsgi.application' +WSGI_APPLICATION = "Sprint.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': os.getenv('POSTGRES_DB'), - 'USER': os.getenv('POSTGRES_USER'), - 'PASSWORD': os.getenv('POSTGRES_PASSWORD'), - 'HOST': '0.0.0.0', - 'PORT': '5432', + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "postgres", + "USER": "postgres", + "PASSWORD": "password", + "HOST": "0.0.0.0", + "PORT": "5432", } } @@ -102,16 +98,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -121,14 +117,11 @@ AUTH_PASSWORD_VALIDATORS = [ _ = lambda s: s -LANGUAGES = ( - ('en', _('English')), - ('ru', _('Russian')) -) +LANGUAGES = (("en", _("English")), ("ru", _("Russian"))) -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'Europe/Moscow' +TIME_ZONE = "Europe/Moscow" USE_I18N = True @@ -140,11 +133,13 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" +MEDIA_URL = "/media/" STATIC_ROOT = os.path.join(BASE_DIR, "static") - -MEDIA_ROOT = os.path.join(BASE_DIR, 'data') +MEDIA_ROOT = os.path.join(BASE_DIR, "media") +DATA_ROOT = os.path.join(BASE_DIR, "data") +SOLUTIONS_ROOT = os.path.join(DATA_ROOT, "solutions") STATICFILES_DIRS = [ os.path.join(BASE_DIR, "Main/static"), @@ -152,6 +147,17 @@ STATICFILES_DIRS = [ # Authentication backends -AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', - ) +AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",) + + +# Celery Configuration Options +CELERY_TIMEZONE = "Europe/Moscow" +CELERY_TASK_TRACK_STARTED = True +CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0' + +CONSTS = { + "online_status": "Online", + "in_queue_status": "In queue", + "testing_status": "Testing", + "ok_status": "OK", +} diff --git a/Sprint/urls.py b/Sprint/urls.py index de480b8..7fa9686 100644 --- a/Sprint/urls.py +++ b/Sprint/urls.py @@ -1,39 +1,20 @@ +from django.conf.urls.static import static from django.contrib import admin -from django.urls import path, re_path, include -from Main import views +from django.urls import path +from Main.views import * +from Sprint import settings urlpatterns = [ - path('grappelli/', include('grappelli.urls')), # grappelli URLS - path('main', views.main), - path('settings', views.settings), - path('enter', views.enter, name='enter'), - path('register', views.register), - path('restore', views.restore, name='restore'), - path('reset_password', views.reset_password), - path('exit', views.exit), - path('block', views.block), - path('task', views.task), - path('solution', views.solution), - path('rating', views.rating), - path('messages', views.messages), - path('admin/rating', views.rating), - path('admin/download_rating', views.download_rating), - path('admin/solution', views.solution), - path('admin/retest', views.retest), - path('admin/docs', views.docs), - path('admin/block', views.block_settings), - path('admin/task', views.task_settings), - path('admin/main', views.admin), - path('admin/solutions', views.solutions), - path('admin/users_settings', views.users_settings), - path('admin/download', views.download), - path('admin/queue', views.queue), - path('admin/cheating', views.cheating), - path('queue_table', views.queue_table), - path('task_test', views.task_test), - path('solutions_table', views.solutions_table), - path('get_result_data', views.get_result_data), - path('get_comment_data', views.get_comment_data), - path('admin/', admin.site.urls), - re_path('^', views.redirect) -] + path("enter", EnterView.as_view()), + path("register", RegisterView.as_view()), + path("rating", RatingView.as_view()), + path("tasks", TasksView.as_view()), + path("account", AccountView.as_view()), + path("exit", ExitView.as_view()), + path("admin/task", TaskSettingsView.as_view()), + path("sets", SetsView.as_view()), + path("task", TaskView.as_view()), + path("solutions_table", SolutionsTableView.as_view()), + path("", MainView.as_view()), + path("admin/", admin.site.urls), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/Sprint/wsgi.py b/Sprint/wsgi.py index 37c78bf..f7ee610 100644 --- a/Sprint/wsgi.py +++ b/Sprint/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Sprint.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Sprint.settings") application = get_wsgi_application() diff --git a/SprintLib/BaseDaemon.py b/SprintLib/BaseDaemon.py new file mode 100644 index 0000000..d509ff5 --- /dev/null +++ b/SprintLib/BaseDaemon.py @@ -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()}") diff --git a/SprintLib/BaseView.py b/SprintLib/BaseView.py new file mode 100644 index 0000000..4b1ea64 --- /dev/null +++ b/SprintLib/BaseView.py @@ -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 diff --git a/SprintLib/EntityStorage.py b/SprintLib/EntityStorage.py new file mode 100644 index 0000000..e832113 --- /dev/null +++ b/SprintLib/EntityStorage.py @@ -0,0 +1,6 @@ +class EntityStorage: + entities = {} + + def add(self, name, entity): + self.entities[name] = entity + setattr(self, name, entity) diff --git a/SprintLib/__init__.py b/SprintLib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/SprintLib/testers/BaseTester.py b/SprintLib/testers/BaseTester.py new file mode 100644 index 0000000..ae98322 --- /dev/null +++ b/SprintLib/testers/BaseTester.py @@ -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) diff --git a/SprintLib/testers/CSharpTester.py b/SprintLib/testers/CSharpTester.py new file mode 100644 index 0000000..41742e1 --- /dev/null +++ b/SprintLib/testers/CSharpTester.py @@ -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" diff --git a/SprintLib/testers/CppTester.py b/SprintLib/testers/CppTester.py new file mode 100644 index 0000000..7033fc5 --- /dev/null +++ b/SprintLib/testers/CppTester.py @@ -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" diff --git a/SprintLib/testers/GoTester.py b/SprintLib/testers/GoTester.py new file mode 100644 index 0000000..0ed1681 --- /dev/null +++ b/SprintLib/testers/GoTester.py @@ -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" diff --git a/SprintLib/testers/JavaTester.py b/SprintLib/testers/JavaTester.py new file mode 100644 index 0000000..830e198 --- /dev/null +++ b/SprintLib/testers/JavaTester.py @@ -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}" diff --git a/SprintLib/testers/KotlinTester.py b/SprintLib/testers/KotlinTester.py new file mode 100644 index 0000000..07b2e7d --- /dev/null +++ b/SprintLib/testers/KotlinTester.py @@ -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" diff --git a/SprintLib/testers/Python3Tester.py b/SprintLib/testers/Python3Tester.py new file mode 100644 index 0000000..691181e --- /dev/null +++ b/SprintLib/testers/Python3Tester.py @@ -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}" diff --git a/SprintLib/testers/__init__.py b/SprintLib/testers/__init__.py new file mode 100644 index 0000000..cf0c9a2 --- /dev/null +++ b/SprintLib/testers/__init__.py @@ -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 * diff --git a/SprintLib/utils.py b/SprintLib/utils.py new file mode 100644 index 0000000..66174b0 --- /dev/null +++ b/SprintLib/utils.py @@ -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)) diff --git a/apply_models.sh b/apply_models.sh new file mode 100644 index 0000000..e78480e --- /dev/null +++ b/apply_models.sh @@ -0,0 +1,3 @@ +source venv/bin/activate +python manage.py makemigrations +python manage.py migrate \ No newline at end of file diff --git a/daemons/__init__.py b/daemons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/daemons/celery.py b/daemons/celery.py new file mode 100644 index 0000000..6cdc7f5 --- /dev/null +++ b/daemons/celery.py @@ -0,0 +1,6 @@ +from SprintLib.BaseDaemon import BaseDaemon + + +class Daemon(BaseDaemon): + def command(self): + return "celery -A Sprint worker -l INFO --concurrency=4" diff --git a/daemons/redis.py b/daemons/redis.py new file mode 100644 index 0000000..59045b8 --- /dev/null +++ b/daemons/redis.py @@ -0,0 +1,6 @@ +from SprintLib.BaseDaemon import BaseDaemon + + +class Daemon(BaseDaemon): + def command(self): + return "redis-server" diff --git a/daemons/web.py b/daemons/web.py new file mode 100644 index 0000000..398f212 --- /dev/null +++ b/daemons/web.py @@ -0,0 +1,6 @@ +from SprintLib.BaseDaemon import BaseDaemon + + +class Daemon(BaseDaemon): + def command(self): + return "python manage.py runserver" diff --git a/docker-compose.yaml b/docker-compose.yaml index d5b6e40..5e5e194 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,26 +2,14 @@ version: "3" services: - redis: - image: redis:alpine - command: redis-server - celery: - restart: always - build: . - command: "celery -A Sprint worker -l INFO" - volumes: - - .:/app - depends_on: - - redis + postgres: restart: always image: postgres + environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: postgres ports: - "5432:5432" - volumes: - - postgres_data:/var/lib/postgres - - ./dbscripts/postgres:/docker-entrypoint-initdb.d diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt index 85ec828..e0b5e70 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,32 +1,30 @@ -amqp==1.4.9 +amqp==5.0.6 anyjson==0.3.3 -asgiref==3.4.1 -billiard==3.3.0.23 -celery==3.1.26.post2 -click==7.1.2 +asgiref==3.3.4 +billiard==3.6.4.0 +cached-property==1.5.2 +celery==5.2.0b2 +click==8.0.1 click-didyoumean==0.0.3 click-plugins==1.1.1 click-repl==0.2.0 -copydetect==0.2.1 -cycler==0.10.0 -Django==3.2.5 -django-celery==3.3.1 -django-grappelli==2.15.1 -Jinja2==3.0.1 -kiwisolver==1.3.1 -kombu==3.0.37 -MarkupSafe==2.0.1 -matplotlib==3.4.2 -numpy==1.21.0 +dj-database-url==0.5.0 +Django==3.2.4 +gunicorn==20.1.0 +importlib-metadata==4.5.0 +kombu==5.1.0 +numpy==1.20.3 +pandas==1.2.4 Pillow==8.3.1 -prompt-toolkit==3.0.19 +prompt-toolkit==3.0.18 psycopg2==2.9.1 -Pygments==2.9.0 -pyparsing==2.4.7 +psycopg2-binary==2.9.1 python-dateutil==2.8.1 pytz==2021.1 +redis==3.5.3 six==1.16.0 sqlparse==0.4.1 -tqdm==4.61.2 +typing-extensions==3.10.0.0 vine==5.0.0 wcwidth==0.2.5 +zipp==3.5.0 diff --git a/setup.sh b/setup.sh index 1340429..7ecffd4 100644 --- a/setup.sh +++ b/setup.sh @@ -3,4 +3,3 @@ pip3 install venv python3 -m venv venv source venv/bin/activate pip install -r requirements.txt -docker-compose up -d diff --git a/start.py b/start.py new file mode 100755 index 0000000..9f940bc --- /dev/null +++ b/start.py @@ -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()) diff --git a/templates/account.html b/templates/account.html new file mode 100644 index 0000000..de74bbb --- /dev/null +++ b/templates/account.html @@ -0,0 +1,90 @@ +{% extends 'base_main.html' %} + +{% block title %}Аккаунт{% endblock %} + +{% block main %} +

Информация об аккаунте

+
+
+
+ Фото профиля +
+ {% if owner %} + + +
+ + {% csrf_token %} +
+ {% endif %} +
+
+

+ {{ account.userinfo.surname }} {{ account.userinfo.name }} {{ account.userinfo.middle_name }} + {{ account.userinfo.activity_status }} +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+
+

{{ account.username }}

+
+

+
+

{{ account.email }}

+
+

+
+

{{ account.userinfo.rating }}

+
+

+
+

{{ account.userinfo.place }}

+
+

+
+

{{ account.date_joined.date }}

+
+
+
+

+ {% if owner %} +

{{ error_message }}

+
+ {% csrf_token %} + +

Смена пароля

+
+
+
+ +
+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/admin.html b/templates/admin.html deleted file mode 100644 index 2c6af90..0000000 --- a/templates/admin.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Админка{% endblock %} - -{% block styles %} - .form { - margin-bottom: 15px; - } -{% endblock %} - -{% block scripts %} - function change() { - let surname = document.getElementById('surname').value; - let name = document.getElementById('name').value; - let middle_name = document.getElementById('middle_name').value; - let group = document.getElementById('group').value; - let email = document.getElementById('email').value; - let invite = document.getElementById('inviteButton'); - console.log(surname + ' ' + name + ' ' + middle_name + ' ' + group + ' ' + email); - let dis = surname == '' || name == '' || middle_name == '' || group == '' || email == '' || email.indexOf('@') == -1 || email.indexOf('.') == -1 || email.indexOf('.') < email.indexOf('@'); - console.log(dis); - invite.disabled = dis; - } -{% endblock %} - -{% block content %} -

Доступные курсы

- {% for key, value in blocks.items %} -
{{ key.name }}
- {% for block in value %} - {{ block.name }}
- {% endfor %} - {% if is_teacher %} - - - - - {% endif %} - {% endfor %} - {% if is_superuser %} -

Пригласить пользователя

-
- {% csrf_token %} -
-
-
-
-
- -
-
-
- -
-
- {% endif %} -
- -
-{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 535b5f1..c896bcd 100644 --- a/templates/base.html +++ b/templates/base.html @@ -26,37 +26,69 @@
- - {% block content %}{% endblock %} + {% block body %} + {% endblock %}
\ No newline at end of file diff --git a/templates/base_main.html b/templates/base_main.html new file mode 100644 index 0000000..d217a2e --- /dev/null +++ b/templates/base_main.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% block body %} +
+ + + + +
+ + +
+
+ {% block main %} + {% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/block.html b/templates/block.html deleted file mode 100644 index a4faf45..0000000 --- a/templates/block.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends 'base.html' %} - -{% load filters %} - -{% block title %}{{ Block.name }}{% endblock %} - -{% block content %} -

{{ Block.name }} {% if can_edit %} {% endif %}

-
Таски
- - {% for task in Block.tasks %} - - - - - {% endfor %} -
- {{ task.name }} - - {% with mark=task|mark_for_task:user %} - {% if mark|marked %} -
- {{ mark }} -
- {% endif %} - {% endwith %} -
- {% if Block.show_rating %} - Рейтинг - {% endif %} - Сообщения -
- {% if Block.opened %} - Открыто для просмотра - {% else %} - Закрыто для просмотра - {% endif %} -
Доступно с {{ Block.time_start }} до {{ Block.time_end }} -{% endblock %} \ No newline at end of file diff --git a/templates/block_settings.html b/templates/block_settings.html deleted file mode 100644 index 68bfc6b..0000000 --- a/templates/block_settings.html +++ /dev/null @@ -1,126 +0,0 @@ -{% extends 'base.html' %} - -{% load filters %} - -{% block title %}{{ Block.name }}|настройки{% endblock %} - -{% block styles %} - input[type="file"] { - display: none; - } -{% endblock %} - -{% block scripts %} - function uploaded() { - document.getElementById('is_uploaded').style.display = 'block'; - document.getElementById('is_uploaded').nodeValue = document.getElementById('file-upload').nodeValue; - } -{% endblock %} - -{% block content %} -

{{ Block.name }}

-
Таски
- {% for task in Block.tasks %} - {{ task.name }}
- {% endfor %} - {% if is_superuser %} - - - -
-
Импортировать задачу из Я.Контест
-
- {% csrf_token %} - - - -
-
- {% endif %} -
- {% if is_superuser %} -

Ограничения по времени

-
- {% csrf_token %} - - - - - - - - - - - - - - - -
- - - -
- Открыто для просмотра -
- Показывать рейтинг участникам -
- Приоритет - -
- -
-
- {% endif %} -
- Рейтинг - - Очередь тестирования - Проверка на списывание - {% if is_superuser %} -
- {% csrf_token %} - - -
- {% endif %} -
-
-{% endblock %} \ No newline at end of file diff --git a/templates/cheating.html b/templates/cheating.html deleted file mode 100644 index a524620..0000000 --- a/templates/cheating.html +++ /dev/null @@ -1,133 +0,0 @@ -{% extends 'base.html' %} - -{% load filters %} - -{% block title %}{{ Block.name }} | списывание{% endblock %} - -{% block links %} - - - -{% endblock %} - -{% block scripts %} - function check_list(entity_type) { - var cbox = document.getElementById('check_' + entity_type + '_all'); - var boxes = document.getElementsByClassName('check_' + entity_type); - for (var i = 0; i < boxes.length; i++) { - boxes[i].checked = cbox.checked; - } - } - - function prepare_for_submit() { - var conf = confirm('Точно запускаем проверку? Новую проверку можно будет запустить только после завершения данной'); - if (conf) { - document.getElementById('main_form').submit(); - } - } -{% endblock %} - -{% block content %} -

Списывание в {{ Block.name }}

-
-
- {% csrf_token %} -
Проверить таски
- Все
- {% for task in Block.tasks %} - {{ task.name }}
- {% endfor %} -
-
Проверить пользователей
- Все
- {% for sub in Block.course.students %} - {{ sub.user.userinfo }}
- {% endfor %} -
-
Дополнительно
- - - - - - - - - - - - - - - - - -
- - - Проверять только решения с лучшим результатом -
- - - Проверять только последнее решение -
- - - Проверять только прошедшие все тесты -
- - - Какой процент схожести считать списыванием -
-
- -
-
-

{{ Block.cheating_status }}

- {% if Block.cheating_checked %} -

Результаты проверки

- {% for data in Block.cheating_results %} - {% with user=data|user_by_id cheating_data=Block.cheating_results|dict_key:data %} -

{{ user.userinfo }} [{{ cheating_data|length }}]

- - - {% endwith %} - {% endfor %} - {% endif %} -{% endblock %} \ No newline at end of file diff --git a/templates/docs.html b/templates/docs.html deleted file mode 100644 index cadb565..0000000 --- a/templates/docs.html +++ /dev/null @@ -1,235 +0,0 @@ -{% extends 'base.html' %} - -{% load static %} - -{% block title %}Методичка{% endblock %} - -{% block styles %} - .screenshot { - width: 100%; - } - .screenshot_div { - margin-top: 20px; - width: 80%; - margin-bottom: 20px; - } - .code { - padding: 15px; - margin-top: 15px; - margin-bottom: 15px; - } -{% endblock %} - -{% block content %} -

Методичка

-
-

О системе

-

Структура сервиса

- В сервисе существует два режима работы: - - И одна компанента: - -
-

Режим студента

- Режим студента показывает курсы, на которые подписан пользователь, а также блоки, содержащиеся в них.
- Напротив каждого блока стоит (или отсутствует) оценка за данный блок. -
- -
- При нажатии на блок пользователь попадает в настройки выбранного блока.
- На странице отображаются таски и (опционально) оценки за них.
- Под списком тасков отображается информация о возможности отправки решений в блок, а также временной интервал активности блока. -
- -
- Страница таска представляет из себя подробное описание задания, предлагаемого к решению.
- Описание таска содержит в себе следующие атрибуты: - - Далее находится форма для отправки решения (.zip архив) и список отправленных решений.
- Решение содержит в себе следующие атрибуты: - - Sprint поддерживает следующие варианты результатов тестирования: - -
- -
- При нажатии на результат тестирование можно получить информацию о том, какие именно тесты были пройдены, а какие - нет. -
- -
- При нажатии на id решения пользователь попадает на страницу решения.
- Страница с решением содержит ту же информацию, что и на странице таска.
- Помимо этого, на данной странице можно выставить оценку за решение и оставить комментарий. Для сохранения результата необходимо нажать на кнопку "Зачесть".
- Для выставления оценки 0 или максимальной оценки за таск, необходимо нажать на кнопку "Незачесть" или "Выставить максимальный балл" соответственно.
- Комментарий будет сохранен при нажатии на любую из кнопок.
- Внизу страницы отображаются файлы решения, содержащие только символы Unicode, иначе говоря, файлы с кодом. -
- -
-
-

Режим администратора

- Некоторые функции доступны только преподавателям
- Главная страница режима администратора показывает доступные для администрирования курсы, блоки в них и ссылку на данную методичку.
- Добавление нового блока в курс происходит по нажатии кнопки "Новый блок". -
- -
- При нажатии на кнопку "Участники курса" открывается страница с настройками участников курса.
- Для подписки существующих студентов или преподавателей на курс, необходимо ввести в форму "Добавить участников" ФИО студента или email, на который зарегестрирован аккаунт.
- Для подписки всех студентов определенной группы, нужно ввести название группы.
- Для отписки студента от курса, нужно нажать на кнопку "отписка" рядом с нужным студентом.
- Для того, чтобы сделать подписчика курса ассистентом или снятия с него таких прав, необходимо выбрать почту этого студента в форме "Назначить или разжаловать" и нажать на кнопку применить. Статус студента будет отображен в таблице ниже. -
- -
- В Sprint отсутствует регистрация в обычном понимании данного термина, поэтому регистрация студентов происходит при первом добавлении его к некоторому курсу.
- Прежде всего необходимо создать JSON файл по определенным правилам, описанным на странице участников курса. -
- -
- При нажатии на блок, отображается страница с настройками блока.
- Страница настроек блока содержит следующие аттрибуты: - - Ограничения по времени были внедрены для того, чтобы отложить запуск блока. Блок будет доступен студентам только если данный момент времени входит в интервал между временем начала и временем конца (границы включаются), и блок открыт для отправки решений.
- После установки нужных значений необходимо нажать на кнопку "Сохранить".
- Для создания нового таска после списка тасков есть кнопка "Новый таск". -
- -
- При нажатии на таск из списка, откроется страница с настройками таска.
- Поля "Легенда", "Входные данные", "Выходные данные" и "Спецификации" заполняются с помощью html разметки, которая потом будет отображена в режиме студента.
-
- -
- В настройках таска содержатся еще несколько аттрибутов: - - В форму загрузки тестов загружается .cs файл с Unit тестами библиотеки Nunit. По нажатии на кнопку "Посмотреть" можно посмотреть тесты и отредактировать их. Для сохранения файла необходимо нажать на кнопку "Сохранить" внизу страницы.
- В форму "Дополнительные файлы" можно загрузить файлы, которые будут скорированы в директорию с исполняемыми файлами при тестировании. -
- -
- При нажатии на кнопку "Посмотреть решения" на странице настроек блока открывается страница решений, загруженных в текущий блок.
- Функциональность таблицы решений внизу страницы схожа с подобной в режиме студента.
- Фильтр на данной странице содержит следующие поля: - - При нажатии на кнопку "Перетест" отфильтрованные решения будут перетестированы. -
- -
-
-

Darwin

- Одним из ограниений Unit тестирования является невозможность тестирования приватных методов и приватных классов.
- Для решения данной проблемы в Sprint содержится компонента Darwin. Загрузить библиотеку можно по данной ссылке.
- Darwin позволяет создавать объекты приватных классов, задавать значения приватным полям и свойствам, а также вызывать приватные методы.
- Основой библиотеки является класс DObject.
- В конструктор передается имя того класса, объект которого необходимо создать, с указанием пространства имен. -
-
-using Darwin;
-using Nunit.Framework;
-
-[TestFixture]
-public class Tests
-{
-    [Test]
-    public void Test1()
-    {
-        DObject d = new DObject("Calculator.Calc");
-    }
-}
-
-
- Для доступа к полям и свойствам класса нужно использовать индексатор.
-
-
-using Darwin;
-using Nunit.Framework;
-
-[TestFixture]
-public class Tests
-{
-    [Test]
-    public void Test1()
-    {
-        DObject d = new DObject("Calculator.Calc");
-        d["a"] = 5;
-        Assert.AreEqual(d["a"], 5);
-    }
-}
-
-
- Для вызова методов класса используется метод InvokeMethod.
- В качетсве параметров передается имя метода и массив параметров.
- Тип возвращаемого значения метода InvokeMethod - object, поэтому необходимо проводить каст к нужному типу после вызова. -
-
-using Darwin;
-using Nunit.Framework;
-
-[TestFixture]
-public class Tests
-{
-    [Test]
-    public void Test1()
-    {
-        DObject d = new DObject("Calculator.Calc");
-        d["a"] = 5;
-        Assert.AreEqual(d["a"], 5);
-        int val = (int)d.InvokeMethod("GetNumber", new object[] { d["a"] });
-        Assert.AreEqual(val, d["a"]);
-    }
-}
-
-
-
-

Use cases

-

Как собрать таск

-
-

Как написать Unit тест правильно

-
-

Как написать тест на входные и выходные данные

-
-

Что необходимо указать в спецификациях

-
-

Полезные html тэги

-{% endblock %} \ No newline at end of file diff --git a/templates/enter.html b/templates/enter.html index f4e2e47..e4b770c 100644 --- a/templates/enter.html +++ b/templates/enter.html @@ -1,67 +1,30 @@ - - {% load static %} - - - - - Sprint - - - - - - - - -
-
-
-

- Sprint -

-
-
-

{{ error_message }}

-
- {% csrf_token %} -
-
- -
-
-
- -
-
- -
-
-
- - \ No newline at end of file +{% block title %}Вход{% endblock %} + +{% block body %} +
+
+
+

+ Sprint +

+
+
+

{{ error_message }}

+
+ {% csrf_token %} +
+
+ +
+
+
+ +
+
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/main.html b/templates/main.html index 7053e11..a7915dc 100644 --- a/templates/main.html +++ b/templates/main.html @@ -1,30 +1,7 @@ -{% extends 'base.html' %} - -{% load filters %} +{% extends 'base_main.html' %} {% block title %}Главная{% endblock %} -{% block content %} -

Доступные курсы

- {% for key, value in blocks.items %} -
{{ key.name }}
- {% for block in value %} - - - - - -
- {{ block.name }} - - {% with mark=block|mark_for_block:user %} - {% if mark|marked %} -
- {{ mark }} -
- {% endif %} - {% endwith %} -
- {% endfor %} - {% endfor %} +{% block main %} +

Мои группы

{% endblock %} \ No newline at end of file diff --git a/templates/messages.html b/templates/messages.html deleted file mode 100644 index a233217..0000000 --- a/templates/messages.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends 'base.html' %} - -{% load filters %} - -{% block content %} -

Сообщения

- {% for message in Block.messages %} -
-
-
- {% with userinfo=message.sender|userinfo_by_user %} -
- - {{ userinfo }} | {{ userinfo.group }} | - {{ message.task.name }} - {% if is_admin %} | Решения участника{% endif %} - -
-
- {{ message.text }} -
-
- - -
- {% endwith %} -
-
-
- {% endfor %} -{% endblock %} \ No newline at end of file diff --git a/templates/queue.html b/templates/queue.html deleted file mode 100644 index 9cc3e5c..0000000 --- a/templates/queue.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends 'base.html' %} - -{% load filters %} - -{% block title %}{{ Block.name }} | очередь{% endblock %} - -{% block scripts %} - function doPoll() { - jQuery.get('/queue_table?block_id={{ Block.id }}', function(data) { - document.getElementById('body').innerHTML = data; - setTimeout(function() {doPoll()}, 2000); - }) - } -{% endblock %} - -{% block onload %}doPoll(){% endblock %} - -{% block content %} -

{{ Block.name }}

-

Очередь тестирования

- - - - - - - - - - - -
idТаскДата и время отправкиСтудентСтатус
-{% endblock %} \ No newline at end of file diff --git a/templates/queue_table.html b/templates/queue_table.html deleted file mode 100644 index 812dc05..0000000 --- a/templates/queue_table.html +++ /dev/null @@ -1,19 +0,0 @@ -{% for solution in solutions %} - - - {{ solution.id }} - - - {{ solution.task.name }} - - - {{ solution.time_sent }} - - - {{ solution.userinfo.surname }} {{ solution.userinfo.name }} {{ solution.userinfo.middle_name }} - - - {{ solution.result }} - - -{% endfor %} \ No newline at end of file diff --git a/templates/rating.html b/templates/rating.html index 7ea0e84..5dc768d 100644 --- a/templates/rating.html +++ b/templates/rating.html @@ -1,49 +1,34 @@ -{% extends 'base.html' %} +{% extends 'base_main.html' %} -{% load filters %} - -{% block title %}Рейтинг{% endblock %} - -{% block content %} -

Рейтинг (beta)

-

{{ Block.name }}

- - - - - {% for task in Block.tasks %} - - {% endfor %} - - - - - {% for sub in Block.course.students %} - - - {% for task in Block.tasks %} - {% with status=sub.user|mark_status:task %} - - {% endwith %} - {% endfor %} - - - {% endfor %} - -
Студент{% if admin_course %}{{ task.name }}{% else %}{{ task.name }}{% endif %}Оценка
{% if admin_course %}{{ sub.user|userinfo_by_user }}{% else %}{{ sub.user|userinfo_by_user }}{% endif %} - {% if current_page == 'admin' and status != '-' %} - {{ status }} - {% else %} - {{ status }} - {% endif %} - - {% with mark=Block|mark_for_block:sub.user %} - {% if mark|marked %} -
- {{ mark }} -
- {% endif %} - {% endwith %} -
- +{% block main %} +

Рейтинг

+ + + + + + + + + + + {% for u in users %} + + + + + + + {% endfor %} + +
#ПользовательДата регистрацииРейтинг
+ {{ u.userinfo.place }} + + + {{ u.username }} + + {{ u.date_joined.date }} + + {{ u.userinfo.rating }} +
{% endblock %} \ No newline at end of file diff --git a/templates/reset_password.html b/templates/reset_password.html deleted file mode 100644 index fa3a6be..0000000 --- a/templates/reset_password.html +++ /dev/null @@ -1,29 +0,0 @@ - - {% load static %} - - - - - Sprint - - - - - - -
-

- Sprint -

- {{ error }} -
- {% csrf_token %} - - {{ form }} -
- -
-
- - \ No newline at end of file diff --git a/templates/restore.html b/templates/restore.html deleted file mode 100644 index 5c56e6c..0000000 --- a/templates/restore.html +++ /dev/null @@ -1,36 +0,0 @@ - - {% load static %} - - - - - Восстановление пароля - - - - - - -
-

- Sprint -

-
- {% csrf_token %} - - - - - -
- Почта - - -
- -
- Если данный электронный адрес есть в базе, то на него будет отправлена ссылка для восстановления пароля. -
- - \ No newline at end of file diff --git a/templates/sets.html b/templates/sets.html new file mode 100644 index 0000000..e0e115b --- /dev/null +++ b/templates/sets.html @@ -0,0 +1,47 @@ +{% extends 'base_main.html' %} + +{% block main %} + + + + {% if user.userinfo.can_create %} + + {% endif %} + +
+

Сеты

+
+ + +
+ {% for set in user.userinfo.available_sets %} + {{ set.name }} {% if set.creator == user %} {% endif %}
+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/templates/settings.html b/templates/settings.html deleted file mode 100644 index 4026cf5..0000000 --- a/templates/settings.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Настройки{% endblock %} - -{% block content %} -

- Сменить пароль -

- {{ error }} -
- {% csrf_token %} - - {{ form }} -
- -
-
-{% endblock %} diff --git a/templates/solution.html b/templates/solution.html deleted file mode 100644 index d273c71..0000000 --- a/templates/solution.html +++ /dev/null @@ -1,271 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}{{ solution.task.name }}|решение{% endblock %} - -{% block links %} - - - -{% endblock %} - -{% load filters %} - -{% block scripts %} -{% if can_edit %} - function findGetParameter(parameterName) { - var result = null, - tmp = []; - location.search - .substr(1) - .split("&") - .forEach(function (item) { - tmp = item.split("="); - if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]); - }); - return result; - } - function next() { - const solutions_request = findGetParameter('solutions'); - const solutions = solutions_request.split(' '); - const current = findGetParameter('id'); - const current_index = solutions.findIndex((element) => element == current); - if (current_index != solutions.length - 1) { - var next_element = document.getElementById('next'); - next_element.setAttribute('href', '/admin/solution?id=' + solutions[current_index + 1] + '&solutions=' + solutions_request); - next_element.innerHTML = '->'; - } - } - function previous() { - const solutions_request = findGetParameter('solutions'); - const solutions = solutions_request.split(' '); - const current = findGetParameter('id'); - const current_index = solutions.findIndex((element) => element == current); - if (current_index != 0) { - var next_element = document.getElementById('previous'); - next_element.setAttribute('href', '/admin/solution?id=' + solutions[current_index - 1] + '&solutions=' + solutions_request); - next_element.innerHTML = '<-'; - } - } - function fillContent() { - next(); - previous(); - } - {% if can_edit %} - function showHideTests() { - var text = document.getElementById('tests_text'); - var button = document.getElementById('tests_button'); - text.hidden = !text.hidden; - if (text.hidden) { - button.textContent = 'Показать тесты'; - } else { - button.textContent = 'Скрыть тесты'; - } - } - function showHideLog() { - var text = document.getElementById('log_text'); - var button = document.getElementById('log_button'); - text.hidden = !text.hidden; - if (text.hidden) { - button.textContent = 'Показать лог'; - } else { - button.textContent = 'Скрыть лог'; - } - } - function retest() { - let del = confirm("Подтвердите перетест"); - if (del) { - const sols = findGetParameter('solutions'); - const link = '/admin/retest?block_id={{ solution.task.block.id }}&solution_id={{ solution.id }}&next={% autoescape off %}{{ path }}?id={{ solution.id }}{% if current_page == 'admin' %}%26solutions={% endif %}{% endautoescape %}'{% if current_page == 'admin' %} + sols.replaceAll(' ', '%20'){% endif %}; - window.location.href = link; - } - } - {% endif %} -{% endif %} -{% endblock %} - -{% block onload %}{% if can_edit %}fillContent(){% endif %}{% endblock %} - -{% block content %} -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Блок - - {{ solution.task.block.name }} - {% if current_page == 'admin' %} - | К посылкам | К рейтингу - {% endif %} -
- Таск - - {{ solution.task.name }} -
- Студент - - {{ solution.userinfo.surname }} {{ solution.userinfo.name }} {{ solution.userinfo.middle_name }} -
- id решения - -
- {% if can_edit %} - - {% endif %} - {{ solution.id }} - {% if can_edit %} - - {% endif %} - -
- Результат - - {% if can_edit or solution.task.show_details %} - - - - - - {% else %} - {% if solution.task.show_result %} - {{ solution.result }} - {% else %} - Accepted - {% endif %} - {% endif %} -
- Оценка - - {% if can_edit %} - {% csrf_token %} - - {% else %} - {% if solution.task.show_result %} - {{ solution.mark_property }} - {% else %} - {% if solution.mark == null %} - Checking - {% else %} - Checked - {% endif %} - {% endif %} - {% endif %} -
- Комментарий - - {% if can_edit %} - - {% else %} -
-{{ solution.comment_property }}
-
- {% endif %} -
- {% if can_edit %} - - - - - {% endif %} - -
-
-
-
-

Files

- {% for filename, text in solution.files.items %} -
{{ filename }}
- {% if filename|is_code %} -
-
-{{ text }}
-
-
- {% else %} -
-
-{{ text }}
-
-
- {% endif %} -
- {% endfor %} -{% endblock %} \ No newline at end of file diff --git a/templates/solutions.html b/templates/solutions.html deleted file mode 100644 index 863f602..0000000 --- a/templates/solutions.html +++ /dev/null @@ -1,315 +0,0 @@ -{% extends 'base.html' %} - -{% load filters %} - -{% block title %}{{ Block.name }} | решения{% endblock %} - -{% block scripts %} - function filter() { - const idi = document.getElementById('idi').value; - const task = document.getElementById('task_name').value; - const user = document.getElementById('user').value; - const group = document.getElementById('group').value; - const last_solution = document.getElementById('last_solution').checked; - const best_result = document.getElementById('best_result').checked; - const only_students = document.getElementById('only_students').checked; - const not_seen = document.getElementById('not_seen').checked; - var req = ''; - if (idi) { - req += '&solution_id=' + idi; - } - if (not_seen) { - req += '¬_seen=' + not_seen; - } - if (task) { - req += '&task_name=' + task; - } - if (user) { - req += '&user=' + user; - } - if (group) { - req += '&group=' + group; - } - if (last_solution) { - req += '&last_solution=' + last_solution; - } - if (best_result) { - req += '&best_result=' + best_result; - } - if (only_students) { - req += '&only_students=' + only_students; - } - window.location.href = '/admin/solutions?block_id={{ Block.id }}' + req; - } - function retest() { - let del = confirm("Подтвердите перетест"); - if (del) - window.location.href = '/admin/retest?block_id={{ Block.id }}{% autoescape off %}{{ req }}{% endautoescape %}'; - } - function download() { - let del = confirm("Подтвердите скачивание"); - if (del) { - window.location.href = '/admin/download?block_id={{ Block.id }}{% autoescape off %}{{ req }}{% endautoescape %}'; - } - } - function showHideTests() { - var text = document.getElementById('tests_text'); - var button = document.getElementById('tests_button'); - text.hidden = !text.hidden; - if (text.hidden) { - button.textContent = 'Показать тесты'; - } else { - button.textContent = 'Скрыть тесты'; - } - } - function showHideLog() { - var text = document.getElementById('log_text'); - var button = document.getElementById('log_button'); - text.hidden = !text.hidden; - if (text.hidden) { - button.textContent = 'Показать лог'; - } else { - button.textContent = 'Скрыть лог'; - } - } - function fillModalResults(id) { - jQuery.get('/get_result_data?id=' + id, function(data) { - const response = JSON.parse(data); - if (response['success'] == true) { - document.getElementById('resultModalLongTitle').innerHTML = 'Подробная информация о тестировании ' + id; - document.getElementById('results_text').innerHTML = response['results_text']; - document.getElementById('tests_text').innerHTML = '

Тесты


' + response['tests_text']; - document.getElementById('log_text').innerHTML = '

Лог


' + response['log_text']; - } - }); - } - function fillModalComments(id) { - jQuery.get('/get_comment_data?id=' + id, function(data) { - const response = JSON.parse(data); - if (response['success'] == true) { - document.getElementById('commentModalLongTitle').innerHTML = 'Комментарий к решению ' + id; - document.getElementById('comment_text').innerHTML = response['comment_text']; - } - }); - } -{% endblock %} - -{% block styles %} - .input_field { - width: 500px; - } -{% endblock %} - -{% block content %} - - - - -

{{ Block.name }}

-
-
-

Фильтр

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Id
Таск - -
Пользователь - -
Группа - -
Последнее решение{{ option.last_solution }}
Лучший результат
Только студенты
Еще не проверено
- -
-
-
- -
-
-

Статистика по таскам

- - - - - - - - - - {% for task in Block.tasks %} - - - - - - - - {% endfor %} - - -
ТаскВерноЧастичноС ошибкойВсе
{{ task.name }}{{ task.correct_count }}{{ task.partially_passed }}{{ task.solutions_with_error }}{{ task.solutions_count }}
-
- -
-
-
Решения
-
- {% csrf_token %} -
- - - - - - - - - - - - - - - - {% for solution in solutions %} - - - - - - - - - - - - {% endfor %} - -
idТаскПользовательВремя отправкиГруппаРезультатОценкаКомментарийДействия
- {{ solution.id }} - - {{ solution.task.name }} - - {{ solution.userinfo.surname }} {{ solution.userinfo.name }} {{ solution.userinfo.middle_name }} - - {{ solution.time_sent }} - - {{ solution.userinfo.group }} - - - - - {{ solution.mark_property }} - - {% if solution.comment %} - - - {% else %} -

Нет комментария

- {% endif %} -
- - -
-{% endblock %} \ No newline at end of file diff --git a/templates/solutions_table.html b/templates/solutions_table.html index 57b453c..5cf2550 100644 --- a/templates/solutions_table.html +++ b/templates/solutions_table.html @@ -1,45 +1,19 @@ {% for solution in solutions %} - - - {{ solution.id }} - - - {{ solution.time_sent }} - - - {% if can_edit or task.show_details %} - - - - {% if can_edit %} - - - {% endif %} - {% else %}{% if task.show_result %} - {{ solution.result }} - {% else %} - Accepted - {% endif %} - {% endif %} - - - - {% if task.show_result or can_edit %} - {{ solution.mark_property }} - {% else %} - {% if solution.mark == null %} - Checking - {% else %} - Checked - {% endif %} - {% endif %} - - -
-{{ solution.comment_property }}
-
- - + + + {{ solution.id }} + + + {{ solution.time_sent }} + + + + {{ solution.language }} + + +

+ {% if solution.result == testing_status %} {% endif %}{{ solution.result }} +

+ + {% endfor %} \ No newline at end of file diff --git a/templates/superuser.html b/templates/superuser.html deleted file mode 100644 index d14e97e..0000000 --- a/templates/superuser.html +++ /dev/null @@ -1,85 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Режим Бога{% endblock %} -{% block content %} -

Настройки системы

-
- {% csrf_token %} -
-
- - - - - - - - - {% for param in params %} - - - - - {% endfor %} - -
ПараметрЗначение
{{ param.key }}
-
-
- -
-
-

Курсы

- - - - - - - - - {% csrf_token %} - - - - - - - - {% for course in courses %} - - - - - - - {% endfor %} -
- id - - Название - - Преподаватели -
- - - - - -
- {{ course.id }} - - {{ course.name }} - - {% for teacher in course.teachers %} - {{ teacher.surname }} {{ teacher.name }} {{ teacher.middle_name }} - {% endfor %} - -
-
- -
-{% endblock %} \ No newline at end of file diff --git a/templates/task.html b/templates/task.html index 6c3e793..43a9006 100644 --- a/templates/task.html +++ b/templates/task.html @@ -1,290 +1,105 @@ -{% extends 'base.html' %} - -{% load filters %} - -{% block title %}{{ task.name }}{% endblock %} - -{% block styles %} - input[type="file"] { - display: none; - } -{% endblock %} - -{% block onload %}doPoll(){% endblock %} - -{% block links %} - -{% endblock %} +{% extends 'base_main.html' %} {% block scripts %} - function partyhard() { - var elem = document.getElementById('paint'); - elem.hidden = false; - } - function uploaded() { - document.getElementById('is_uploaded').style.display = 'block'; - document.getElementById('is_uploaded').nodeValue = document.getElementById('file-upload').nodeValue; + function change(num) { + inp = document.getElementById('soltype'); + if (inp.value == num) return; + document.getElementById('button' + inp.value).classList.remove('btn-dark'); + document.getElementById('button' + inp.value).classList.add('btn-light'); + document.getElementById('button' + inp.value).focused = false; + document.getElementById('input' + inp.value).hidden = true; + inp.value = 1 - inp.value; + document.getElementById('button' + inp.value).classList.remove('btn-light'); + document.getElementById('button' + inp.value).classList.add('btn-dark'); + document.getElementById('button' + inp.value).focused = false; + document.getElementById('input' + inp.value).hidden = false; + document.getElementById('chosen').hidden = true; + document.getElementById('file-upload').value = null; + document.getElementById('input0').value = ""; } function doPoll() { - jQuery.get('/solutions_table?id={{ task.id }}', function(data) { + jQuery.get('/solutions_table?task_id={{ task.id }}', function(data) { if (data == 'done') { return } else { document.getElementById('solutions').innerHTML = data; - if (current_solution != null) { - {% if can_edit %} - document.getElementById('log').innerHTML = document.getElementById('log_' + current_solution).innerHTML; - {% endif %} - document.getElementById('details').innerHTML = document.getElementById('details_' + current_solution).innerHTML; - } setTimeout(function() {doPoll()}, 2000); } }) - jQuery.get('/solutions_table?id={{ task.id }}&render=true', function(data) { + jQuery.get('/solutions_table?task_id={{ task.id }}&render=true', function(data) { document.getElementById('solutions').innerHTML = data; - if (current_solution != null) { - {% if can_edit %} - document.getElementById('log').innerHTML = document.getElementById('log_' + current_solution).innerHTML; - {% endif %} - document.getElementById('details').innerHTML = document.getElementById('details_' + current_solution).innerHTML; - } }) } - {% if can_edit %} - function showHideTests(id) { - var text = document.getElementById('tests_text_' + id); - var button = document.getElementById('tests_button_' + id); - text.hidden = !text.hidden; - if (text.hidden) { - button.textContent = 'Показать тесты'; - } else { - button.textContent = 'Скрыть тесты'; - } - } - function showHideLog(id) { - var text = document.getElementById('log_text_' + id); - var button = document.getElementById('log_button_' + id); - text.hidden = !text.hidden; - if (text.hidden) { - button.textContent = 'Показать лог'; - } else { - button.textContent = 'Скрыть лог'; - } - } - {% endif %} - {% if task.show_details or can_edit %} - function showData(id) { - current_solution = id; - const dataTypes = ['details'{% if can_edit %}, 'tests', 'log'{% endif %}]; - for (const dt of dataTypes) { - document.getElementById(dt).innerHTML = document.getElementById(dt + '_' + id).innerHTML; - } - document.getElementById('resultModalLongTitle').innerHTML = 'Подробная информация о тестировании ' + id; - } - {% endif %} {% endblock %} -{% block content %} - -
- Обратно к блоку -
-

- {{ task.name }} - {% if can_edit %} - - {% endif %} - -

- -
-
-
-

Легенда

- {% autoescape off %} - {{ task.legend }} - {% endautoescape %} -
-

Формат входных данных

- {% autoescape off %} +{% block onload %}doPoll(){% endblock %} - {{ task.input }} - {% endautoescape %} - -
-

Формат выходных данных

- {% autoescape off %} - - {{ task.output }} - {% endautoescape %} - -
-

Спецификации

- {% autoescape off %} - - {{ task.specifications }} - {% endautoescape %} -
-
-
-
Таски
- - {% for t in task.block.tasks %} - - {% with mark=t|mark_for_task:user %} - {% if mark|marked %} - - {% endif %} - {% endwith %} - - - {% endfor %} -
-
- {{ mark }} -
-
- {% if t.id == task.id %}{{ t.name }}{% else %}{{ t.name }}{% endif %}
-
-
-
-
-

Самплы

- {% for sample in task.samples %} -
Пример {{ sample.input.num }}

- - - - - - -
- Входные данные - - Выходные данные -
-
-
- - - - - -
-
-{{ sample.input.text }}
-
-
-
-{{ sample.output.text }}
-
-
-
- {% endfor %} -
- {% if can_send or can_edit %} -

Отправить решение

-
- {% csrf_token %} - - - -
-
- {% endif %} - {% if not can_edit and can_send %} - Осталось попыток: {{ user|last_attempts:task }} - {% endif %} - -{% if can_edit or task.show_details %} - - +{% if task.output_format %} +

Формат выходных данных

+{% autoescape off %} +{{ task.output_format }} +{% endautoescape %} +
{% endif %} -

Решения

- - - - - - - - - - - - - -
idДата и время отправкиРезультатОценкаКомментарий
- +{% if task.specifications %} +

Примечания

+{% autoescape off %} +{{ task.specifications }} +{% endautoescape %} +
+{% endif %} +

Отправить решение

+ + + + + + +
+
+ {% csrf_token %} +
+ + + + +
+
+
+

Решения

+ + + + + + + + + +
idВремя отправкиЯзыкРезультат
{% endblock %} \ No newline at end of file diff --git a/templates/task_settings.html b/templates/task_settings.html index 2b38586..b8e1000 100644 --- a/templates/task_settings.html +++ b/templates/task_settings.html @@ -1,327 +1,150 @@ -{% extends 'base.html' %} +{% extends 'base_main.html' %} -{% load filters %} - -{% block title %}{{ task.name }}|настройки{% endblock %} +{% block title %}{{ task.name }}{% endblock %} {% block scripts %} - function uploaded() { - document.getElementById('is_uploaded').style.display = 'block'; - document.getElementById('is_uploaded').nodeValue = document.getElementById('file-upload').nodeValue; - } - function uploaded1() { - document.getElementById('send').style.display = 'block'; - document.getElementById('is_uploaded1').nodeValue = document.getElementById('file-upload1').nodeValue; - } - function valueChanged() { - const other = document.getElementById('show_details'); - other.disabled = !document.getElementById('show_result').checked; - console.log('changed'); - if (other.disabled) { - other.checked = false; + function deleteFile(file_id) { + $.ajax({ + type: "POST", + url: "/admin/task?task_id={{ task.id }}", + data: {"id": file_id, "csrfmiddlewaretoken": document.getElementsByName("csrfmiddlewaretoken")[0].value, "action": "delete_file"}, + success: function(data) { + if (data == "ok") { + var elem = document.getElementById("file_" + file_id); + elem.parentNode.removeChild(elem); + } } - } - function set_checkboxes() { - if (!document.getElementById('show_result').checked) { - document.getElementById('show_details').disabled = true; - } - } + }); + } + function setActionCreate(action) { + document.getElementById('action_create').value = action; + } {% endblock %} -{% block onload %}set_checkboxes(){% endblock %} - -{% block styles %} - input[type="file"] { - display: none; - } - input[name="new_file"] { - display: none; - } - .my_td { - vertical-align: middle; - height: 20px; - } - .params { - margin-top: 20px; - } - .in { - width: 500%; - } -{% endblock %} -{% block content %} -
{{ task.block.name }}
-

{{ task.name }}

-
+{% block main %} +

Настройки задачи

+ + {% csrf_token %} + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Название задачи + +

+
+ Легенда + + +
+ Формат входных данных + + +
+ Формат выходных данных + + +
+ Примечания + + +
+ Ограничение по времени (мс) + + +
+ +
+

+

Загрузка тестов и файлов

+

{{ error_message }}

+ + + + + + + + + +
+

Тесты

+
+

Файлы

+
+ {% for test in task.tests %} +
+
+
+ {% endfor %} + + +
+ + {% csrf_token %} +
+
+ {% for file in task.files %} +
+
+
+ {% endfor %} + + +
+ + {% csrf_token %} +
+
+
{% csrf_token %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Легенда - - -
- Формат входных данных - - -
- Формат выходных данных - - -
- Спецификации - - -
- Ограничения по времени - - -
- Вес задачи - - -
- Максимальная оценка - - -
- Максимум решений - - -
- Формула оценки - - -
- Сдается полное решение - - -
- Показывать результат тестирования - - -
- Показывать детали тестирования - - -
- Приоритет - - -
- Тесты - - - - - -
- Дополнительные файлы и самплы - - - {% for file in task.files %} - - - - - - - {% endfor %} -
- {% if file.can_be_sample %} - - {% endif %} - - - - - - {% if file.readable %} - - - - - {% else %} - - {% endif %} - -
- - - - - - - -
- {% csrf_token %} - -
-
-
- + + + + + + + - {% if is_superuser %} -
- {% csrf_token %} - - -
- {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/tasks.html b/templates/tasks.html new file mode 100644 index 0000000..c90a054 --- /dev/null +++ b/templates/tasks.html @@ -0,0 +1,49 @@ +{% extends 'base_main.html' %} + +{% block title %}Задачи{% endblock %} + +{% block main %} + + + + {% if user.userinfo.can_create %} + + {% endif %} + +
+

Задачи

+
+ + +
+ {% for task in user.userinfo.available_tasks %} + {{ task.name }} {% if task.creator == user %} {% endif %}
+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/templates/users_settings.html b/templates/users_settings.html deleted file mode 100644 index 095f892..0000000 --- a/templates/users_settings.html +++ /dev/null @@ -1,165 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}{{ course.name }}|настройки{% endblock %} - -{% block scripts %} - function uploaded() { - document.getElementById('is_uploaded').style.display = 'block'; - document.getElementById('is_uploaded').nodeValue = document.getElementById('file-upload').nodeValue; - } -{% endblock %} - -{% block styles %} - input[type="file"] { - display: none; - } -{% endblock %} - -{% block content %} -

{{ course.name }}

-
Добавить участников
-
- {% csrf_token %} - - - - - -
- - - -
-
-
- {% csrf_token %} - - - -
-
- - - - - -
-

Участники

- - - - - - - - - - - - - - {% for user in course.subscribes %} - - - - - - - - - - {% endfor %} - -
ФамилияИмяОтчествоГруппаПочтаРоль
- {{ user.userinfo.surname }} - - {{ user.userinfo.name }} - - {{ user.userinfo.middle_name }} - - {{ user.userinfo.group }} - - {{ user.userinfo.user.email }} - - {% with role=user.role %} - {% if role == 'Студент' or role == 'Ассистент' %} -
- {% csrf_token %} - -
- {% else %} - {{ user.role }} - {% endif %} - {% endwith %} -
-
- {% csrf_token %} - - -
-
-{% endblock %} \ No newline at end of file