From 4a32e570761934458a6bc630f18d409e91931368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Ad=C3=A1mek?= Date: Sun, 26 Oct 2025 10:21:19 +0100 Subject: [PATCH 1/2] Create prohibit_during_test decorator --- common/utils.py | 51 +++++++++++++++++++++++++++++++++++++++++++- web/views/student.py | 7 +++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/common/utils.py b/common/utils.py index 5953f44d7..8133f7d1b 100644 --- a/common/utils.py +++ b/common/utils.py @@ -2,13 +2,15 @@ import os import re import tarfile -from datetime import timedelta +from datetime import timedelta, datetime from functools import lru_cache from typing import NewType import django.contrib.auth.models import requests +from django.core.exceptions import PermissionDenied from django.http import HttpRequest +from django.http.response import Http404 from ipware import get_client_ip from .inbus import inbus @@ -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 Http404(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 PermissionDenied("Access to this task is prohibited during exam") + + return wrapper diff --git a/web/views/student.py b/web/views/student.py index d60873356..e775d1d83 100644 --- a/web/views/student.py +++ b/web/views/student.py @@ -62,7 +62,7 @@ from common.plagcheck.moss import PlagiarismMatch, moss_result from common.submit import SubmitRateLimited, store_submit, SubmitPastHardDeadline 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 +318,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 +552,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 +616,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 @@ -1088,6 +1091,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 +1125,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 From 32bec73e8395bc5dd9184a6edb8473b9b80cc1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Ad=C3=A1mek?= Date: Thu, 27 Nov 2025 17:19:24 +0100 Subject: [PATCH 2/2] Resolve comment problem --- common/utils.py | 8 ++++---- web/views/student.py | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/common/utils.py b/common/utils.py index 8133f7d1b..422310780 100644 --- a/common/utils.py +++ b/common/utils.py @@ -8,13 +8,13 @@ import django.contrib.auth.models import requests -from django.core.exceptions import PermissionDenied from django.http import HttpRequest -from django.http.response import Http404 from ipware import get_client_ip +from .exceptions.http_exceptions import HttpException404, HttpException403 from .inbus import inbus + IPAddressString = NewType("IPAddressString", str) @@ -143,7 +143,7 @@ def wrapper(*args, **kwargs): try: assignment = AssignedTask.objects.get(pk=assignment_id) except AssignedTask.DoesNotExist: - raise Http404(f"AssignedTask with id {assignment_id} not found") + raise HttpException404(f"AssignedTask with id {assignment_id} not found") # if task is any of ongoing exams allow it for exam in active_exams: @@ -155,6 +155,6 @@ def wrapper(*args, **kwargs): if all(map(lambda e: assignment.deadline < e.assigned, active_exams)): return function(*args, **kwargs) - raise PermissionDenied("Access to this task is prohibited during exam") + 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 e775d1d83..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,6 +62,7 @@ ) 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, prohibit_during_test from evaluator.results import EvaluationResult @@ -670,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 @@ -777,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(): @@ -879,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"])),