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 += '
{}'.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('
') - 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 %} +
+ + |
+ + |
+ {{ account.username }} + |
+
+ + |
+ + |
+ {{ account.email }} + |
+
+ + |
+ + |
+ {{ account.userinfo.rating }} + |
+
+ + |
+ + |
+ {{ account.userinfo.place }} + |
+
+ + |
+ + |
+ {{ account.date_joined.date }} + |
+
{{ error_message }}
+ + {% 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 %} -