diff --git a/common/utils.py b/common/utils.py index 5953f44d7..422310780 100644 --- a/common/utils.py +++ b/common/utils.py @@ -2,7 +2,7 @@ import os import re import tarfile -from datetime import timedelta +from datetime import timedelta, datetime from functools import lru_cache from typing import NewType @@ -11,8 +11,10 @@ from django.http import HttpRequest from ipware import get_client_ip +from .exceptions.http_exceptions import HttpException404, HttpException403 from .inbus import inbus + IPAddressString = NewType("IPAddressString", str) @@ -109,3 +111,50 @@ def build_absolute_uri(request, location): if base_uri: return "".join([base_uri, location]) return request.build_absolute_uri(location) + + +def prohibit_during_test(function): + """ + Decorator that restricts access to a page if the student has any ongoing exams. + + The decorated function must accept the following parameters: + - request + - assignment_id + + During the ongoing test access is granted only for ongoing exams and tasks whose hard deadline ends before the exam starts. + """ + + def wrapper(*args, **kwargs): + from .models import AssignedTask + from .task import get_active_exams_at + + request = args[0] + + if is_teacher(request.user): + return function(*args, **kwargs) + + active_exams = get_active_exams_at(request.user, datetime.now(), timedelta(0)) + + if not active_exams: + return function(*args, **kwargs) + + assignment_id = kwargs.get("assignment_id") + + try: + assignment = AssignedTask.objects.get(pk=assignment_id) + except AssignedTask.DoesNotExist: + raise HttpException404(f"AssignedTask with id {assignment_id} not found") + + # if task is any of ongoing exams allow it + for exam in active_exams: + if exam.pk == assignment_id: + return function(*args, **kwargs) + + if assignment.has_hard_deadline() and assignment.deadline is not None: + # check if the deadline has expired before the start of all exams + if all(map(lambda e: assignment.deadline < e.assigned, active_exams)): + return function(*args, **kwargs) + + raise HttpException403("Access to this task is prohibited during exam") + + return wrapper diff --git a/web/views/student.py b/web/views/student.py index d60873356..2d2b3df9a 100644 --- a/web/views/student.py +++ b/web/views/student.py @@ -9,6 +9,7 @@ import tarfile import tempfile from collections import namedtuple +from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict, List, Optional from zipfile import ZIP_DEFLATED, ZipFile @@ -61,8 +62,9 @@ ) from common.plagcheck.moss import PlagiarismMatch, moss_result from common.submit import SubmitRateLimited, store_submit, SubmitPastHardDeadline +from common.task import get_active_exams_at from common.upload import MAX_UPLOAD_FILECOUNT, TooManyFilesError -from common.utils import is_teacher +from common.utils import is_teacher, prohibit_during_test from evaluator.results import EvaluationResult from evaluator.testsets import TestSet from kelvin.settings import BASE_DIR, MAX_INLINE_CONTENT_BYTES, MAX_INLINE_LINES @@ -318,6 +320,7 @@ def build(match: PlagiarismMatch) -> PlagiarismEntry: @login_required() +@prohibit_during_test def task_detail(request, assignment_id, submit_num=None, login=None): submits = Submit.objects.filter( assignment__pk=assignment_id, @@ -551,6 +554,7 @@ def submit_source(request, submit_id, path): @login_required +@prohibit_during_test def submit_diff(request, login, assignment_id, submit_a, submit_b): submit = get_object_or_404( Submit, assignment_id=assignment_id, student__username=login, submit_num=submit_a @@ -614,6 +618,7 @@ def get_patch(p1, p2): @login_required +@prohibit_during_test def submit_comments(request, assignment_id, login, submit_num): submit = get_object_or_404( Submit, assignment_id=assignment_id, student__username=login, submit_num=submit_num @@ -667,7 +672,19 @@ def dump_comment(comment): "notification_id": notification_id, } + currently_active_exams: List[AssignedTask] = get_active_exams_at( + request.user, datetime.now(), timedelta(0) + ) + if request.method == "POST": + if not is_teacher(request.user) and len(currently_active_exams) > 0: + return JsonResponse( + { + "error": "It is not allowed to create new comment's during test. Please wait until test is done.." + }, + status=400, + ) + data = json.loads(request.body) comment = Comment() comment.submit = submit @@ -774,6 +791,25 @@ def dump_comment(comment): submit_data: SubmitData = get_submit_data(submit) + priorities = { + "video": 0, + "img": 1, + "source": 2, + } + + if not is_teacher(request.user) and len(currently_active_exams) > 0: + return JsonResponse( + { + "sources": sorted( + result.values(), key=lambda f: (priorities[f["type"]], f["path"]) + ), + "summary_comments": [], + "submits": submits, + "current_submit": submit.submit_num, + "deadline": submit.assignment.deadline, + } + ) + # add comments from pipeline for pipe in submit_data.results: for source, comments in pipe.comments.items(): @@ -876,12 +912,6 @@ def can_view_suggestion(state: SuggestionState, user) -> bool: } ) - priorities = { - "video": 0, - "img": 1, - "source": 2, - } - return JsonResponse( { "sources": sorted(result.values(), key=lambda f: (priorities[f["type"]], f["path"])), @@ -1088,6 +1118,7 @@ def raw_result_content(request, submit_id, test_name, result_type, file): raise HttpException404() +@prohibit_during_test def submit_download(request, assignment_id: int, login: str, submit_num: int): submit = get_object_or_404( Submit, assignment_id=assignment_id, student__username=login, submit_num=submit_num @@ -1121,6 +1152,7 @@ def ui(request): @csrf_exempt +@prohibit_during_test def upload_results(request, assignment_id, submit_num, login): submit = get_object_or_404( Submit, assignment_id=assignment_id, submit_num=submit_num, student__username=login