start adding exam dates

This commit is contained in:
Ross
2024-05-18 08:39:45 +01:00
parent f37b3312e5
commit 4367b88057
14 changed files with 322 additions and 32 deletions
+55 -16
View File
@@ -2,9 +2,10 @@ from collections import defaultdict
import datetime
import json
import os
from typing import Self, Tuple
from typing import Optional, Self, Tuple
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import models
from django.forms import ValidationError
from django.http import Http404, HttpRequest
from django.utils import timezone
from django.core.validators import MaxValueValidator, MinValueValidator
@@ -553,6 +554,10 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
name = models.CharField(max_length=200, help_text="Name of the exam/collection")
start_date = models.DateTimeField(help_text="Date (and time) the exam/collection starts", null=True, blank=True)
end_date = models.DateTimeField(help_text="Date (and time) the exam/collection ends", null=True, blank=True)
restrict_to_dates = models.BooleanField(default=False, help_text="If the exam/collection should only be taken between the start and end date times")
active = models.BooleanField(
help_text="If an exam/collection should be available to take", default=False
)
@@ -583,6 +588,22 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)
def clean(self, *args, **kwargs):
if self.restrict_to_dates:
if self.start_date is None:
raise ValidationError("If restrict to dates is set, a start date must be set")
if self.end_date is not None and self.end_date <= self.start_date:
print(f"{self.start_date=}")
print(f"{self.end_date=}")
raise ValidationError("End date must be after start date")
return super().clean(*args, **kwargs)
def get_app_name(self):
raise NotImplementedError("You must implement get_app_name in the derived class")
@@ -603,15 +624,15 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
def check_logged_in_user(self, request: HttpRequest):
"""Helper to check if the logged in user can access the exam"""
return self.check_cid_user(request=request, user_id=request.user.id)
return self.check_cid_user(user=request.user, user_id=request.user.id)
def check_user_can_review(
self, cid, passcode, request: HttpRequest, active_only=False
):
return self.check_user_can_take(cid, passcode, request, active_only)
return self.check_user_can_take(cid, passcode, request.user, active_only)
def check_user_can_take(
self, cid, passcode, request: HttpRequest, active_only=True
self, cid, passcode, user = None, active_only=True
):
"""
Helper to check if a user is allowed to access an exam/collection
@@ -624,33 +645,40 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
Raises:
Http404: If user does not have access
"""
# If we are limiting by dates check that the current date is within the range
if self.restrict_to_dates:
if self.start_date >= timezone.now() or (self.end_date is not None and self.end_date <= timezone.now()):
raise Http404("Exam not found")
# If it is not active no one can take
print(0)
if active_only and not self.active:
elif active_only and not self.active:
raise Http404("Exam not found")
print(1)
# If candidates only check they have access
if self.candidates_only:
if not self.check_cid_user(cid, passcode, request):
if not self.check_cid_user(cid, passcode, user):
raise Http404("Error accessing exam")
return
print(2)
# Otherwise check that they are a valid user.
if not request.user.is_active:
if user is None:
raise Http404("Error accessing exam")
if not user.is_active:
raise Http404("Error accessing exam")
return
def check_cid_user(
self,
cid: int | None = None,
passcode: str | None = None,
request: HttpRequest | None = None,
user: User | None = None,
#request: HttpRequest | None = None,
user_id: int | None = None,
allow_authors: bool = True,
):
print(f"Check cid user: {cid=}, {passcode=}, exam_id={self.pk}")
if request is not None and request.user.is_superuser:
if user is not None and user.is_superuser:
return True
if user_id is not None:
@@ -662,7 +690,10 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
return False
if cid is None and user_id is None:
user_id = request.user.id
if user is not None:
user_id = user.id
else:
return False
# Start by checking if the logged in user can access
if user_id is not None:
@@ -739,7 +770,7 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
raise NotImplementedError
def get_cid_user_exams(
self, cid_user: "CidUser" = None, user_user: User = None
self, cid_user: Optional["CidUser"] = None, user_user: User = None
) -> "CidUserExam":
content_type = ContentType.objects.get_for_model(self)
if cid_user is None and user_user is None:
@@ -833,16 +864,22 @@ class ExamBase(ExamOrCollectionGenericBase):
stats_graph = models.TextField(default=0)
user_scores = models.JSONField(default=dict)
user_scores = models.JSONField(default=dict, blank=True)
exam_results_emailed = models.DateTimeField(default=None, null=True)
exam_results_emailed = models.DateTimeField(default=None, null=True, blank=True)
class Meta:
abstract = True
def save(self, *args, recreate_json=True, **kwargs):
self.recreate_json = recreate_json
super().save(*args, **kwargs)
#def clean(self, *args, **kwargs):
# if self.user_scores is None:
# self.user_scores = {}
# return super().clean(*args, **kwargs)
def get_questions(self):
"""Returns a list of questions in the exam in the correct order"""
@@ -900,6 +937,8 @@ class ExamBase(ExamOrCollectionGenericBase):
def get_time_limit(self):
"""Returns a human readable time limit"""
if self.time_limit is None:
return "No time limit set"
h, m, s = str(datetime.timedelta(seconds=self.time_limit)).split(":")
time_limit = ""