start adding exam dates
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-05-13 10:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('anatomy', '0016_alter_exam_authors_only'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection ends', null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='restrict_to_dates',
|
||||||
|
field=models.BooleanField(default=False, help_text='If the exam/collection should only be taken between the start and end date times'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='start_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection starts', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
+1
-1
@@ -413,7 +413,7 @@ def exam_scores_cid_user(request, pk, cid, passcode):
|
|||||||
if not exam.exam_mode:
|
if not exam.exam_mode:
|
||||||
raise Http404("Packet not in exam mode")
|
raise Http404("Packet not in exam mode")
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, passcode, request):
|
if not exam.check_cid_user(cid, passcode, request.user):
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
questions = exam.exam_questions.all()
|
questions = exam.exam_questions.all()
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-05-13 10:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('atlas', '0050_rename_questions_casedetail_question_schema'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection ends', null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='restrict_to_dates',
|
||||||
|
field=models.BooleanField(default=False, help_text='If the exam/collection should only be taken between the start and end date times'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='start_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection starts', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
+2
-2
@@ -1736,7 +1736,7 @@ def collection_take_overview(
|
|||||||
"""
|
"""
|
||||||
collection = get_object_or_404(CaseCollection, pk=pk)
|
collection = get_object_or_404(CaseCollection, pk=pk)
|
||||||
|
|
||||||
collection.check_user_can_take(cid, passcode, request)
|
collection.check_user_can_take(cid, passcode, request.user)
|
||||||
|
|
||||||
cid_user_exam = collection.get_or_create_cid_user_exam(
|
cid_user_exam = collection.get_or_create_cid_user_exam(
|
||||||
cid=cid, user_user=request.user
|
cid=cid, user_user=request.user
|
||||||
@@ -1819,7 +1819,7 @@ def collection_case_view_take(
|
|||||||
form = None
|
form = None
|
||||||
answer: None | CidReportAnswer | UserReportAnswer = None
|
answer: None | CidReportAnswer | UserReportAnswer = None
|
||||||
|
|
||||||
collection.check_user_can_take(cid, passcode, request)
|
collection.check_user_can_take(cid, passcode, request.user)
|
||||||
|
|
||||||
case, case_count = collection.get_case_by_index(case_number, case_count=True)
|
case, case_count = collection.get_case_by_index(case_number, case_count=True)
|
||||||
|
|
||||||
|
|||||||
+31
-3
@@ -8,6 +8,9 @@ from django.forms import (
|
|||||||
HiddenInput,
|
HiddenInput,
|
||||||
EmailField,
|
EmailField,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
|
DateField,
|
||||||
|
SplitDateTimeField,
|
||||||
|
SplitDateTimeWidget,
|
||||||
)
|
)
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
from atlas.models import CaseCollection
|
from atlas.models import CaseCollection
|
||||||
@@ -79,6 +82,24 @@ class ExamFormMixin:
|
|||||||
required=False,
|
required=False,
|
||||||
queryset=user_user_group_queryset,
|
queryset=user_user_group_queryset,
|
||||||
)
|
)
|
||||||
|
self.fields["start_date"] = SplitDateTimeField(
|
||||||
|
widget=SplitDateTimeWidget(
|
||||||
|
date_attrs={"type": "date", "class": "datepicker"},
|
||||||
|
time_attrs={"type": "time", "class": "timepicker"},
|
||||||
|
date_format="%Y-%m-%d",
|
||||||
|
time_format="%H:%M",
|
||||||
|
),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
self.fields["end_date"] = SplitDateTimeField(
|
||||||
|
widget=SplitDateTimeWidget(
|
||||||
|
date_attrs={"type": "date", "class": "datepicker"},
|
||||||
|
time_attrs={"type": "time", "class": "timepicker"},
|
||||||
|
date_format="%Y-%m-%d",
|
||||||
|
time_format="%H:%M",
|
||||||
|
),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = [
|
fields = [
|
||||||
@@ -88,6 +109,9 @@ class ExamFormMixin:
|
|||||||
"authors_only",
|
"authors_only",
|
||||||
"exam_mode",
|
"exam_mode",
|
||||||
"include_history",
|
"include_history",
|
||||||
|
"start_date",
|
||||||
|
"end_date",
|
||||||
|
"restrict_to_dates",
|
||||||
"active",
|
"active",
|
||||||
"publish_results",
|
"publish_results",
|
||||||
"archive",
|
"archive",
|
||||||
@@ -96,6 +120,11 @@ class ExamFormMixin:
|
|||||||
# "author",
|
# "author",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# widgets = {
|
||||||
|
# "start_date": TextInput(attrs={"type": "date"}),
|
||||||
|
# "end_date": TextInput(attrs={"type": "date"}),
|
||||||
|
# }
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
instance = ModelForm.save(self, False)
|
instance = ModelForm.save(self, False)
|
||||||
instance.save()
|
instance.save()
|
||||||
@@ -588,9 +617,7 @@ class ExamCollectionForm(ModelForm):
|
|||||||
self.fields[reverse_prop] = ModelMultipleChoiceField(
|
self.fields[reverse_prop] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=exam_obj.objects.filter(archive=False),
|
queryset=exam_obj.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(
|
widget=FilteredSelectMultiple(verbose_name=name, is_stacked=False),
|
||||||
verbose_name=name, is_stacked=False
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
@@ -619,6 +646,7 @@ class ExamCollectionForm(ModelForm):
|
|||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class ExamCollectionCloneForm(ModelForm):
|
class ExamCollectionCloneForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ExamCollection
|
model = ExamCollection
|
||||||
|
|||||||
+55
-16
@@ -2,9 +2,10 @@ from collections import defaultdict
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from typing import Self, Tuple
|
from typing import Optional, Self, Tuple
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.forms import ValidationError
|
||||||
from django.http import Http404, HttpRequest
|
from django.http import Http404, HttpRequest
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
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")
|
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(
|
active = models.BooleanField(
|
||||||
help_text="If an exam/collection should be available to take", default=False
|
help_text="If an exam/collection should be available to take", default=False
|
||||||
)
|
)
|
||||||
@@ -583,6 +588,22 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
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):
|
def get_app_name(self):
|
||||||
raise NotImplementedError("You must implement get_app_name in the derived class")
|
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):
|
def check_logged_in_user(self, request: HttpRequest):
|
||||||
"""Helper to check if the logged in user can access the exam"""
|
"""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(
|
def check_user_can_review(
|
||||||
self, cid, passcode, request: HttpRequest, active_only=False
|
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(
|
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
|
Helper to check if a user is allowed to access an exam/collection
|
||||||
@@ -624,33 +645,40 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
|||||||
Raises:
|
Raises:
|
||||||
Http404: If user does not have access
|
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
|
# If it is not active no one can take
|
||||||
print(0)
|
elif active_only and not self.active:
|
||||||
if active_only and not self.active:
|
|
||||||
raise Http404("Exam not found")
|
raise Http404("Exam not found")
|
||||||
|
|
||||||
print(1)
|
|
||||||
# If candidates only check they have access
|
# If candidates only check they have access
|
||||||
if self.candidates_only:
|
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")
|
raise Http404("Error accessing exam")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(2)
|
|
||||||
# Otherwise check that they are a valid user.
|
# Otherwise check that they are a valid user.
|
||||||
if not request.user.is_active:
|
if user is None:
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
def check_cid_user(
|
def check_cid_user(
|
||||||
self,
|
self,
|
||||||
cid: int | None = None,
|
cid: int | None = None,
|
||||||
passcode: str | None = None,
|
passcode: str | None = None,
|
||||||
request: HttpRequest | None = None,
|
user: User | None = None,
|
||||||
|
#request: HttpRequest | None = None,
|
||||||
user_id: int | None = None,
|
user_id: int | None = None,
|
||||||
allow_authors: bool = True,
|
allow_authors: bool = True,
|
||||||
):
|
):
|
||||||
print(f"Check cid user: {cid=}, {passcode=}, exam_id={self.pk}")
|
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
|
return True
|
||||||
|
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
@@ -662,7 +690,10 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if cid is None and user_id is None:
|
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
|
# Start by checking if the logged in user can access
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
@@ -739,7 +770,7 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_cid_user_exams(
|
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":
|
) -> "CidUserExam":
|
||||||
content_type = ContentType.objects.get_for_model(self)
|
content_type = ContentType.objects.get_for_model(self)
|
||||||
if cid_user is None and user_user is None:
|
if cid_user is None and user_user is None:
|
||||||
@@ -833,16 +864,22 @@ class ExamBase(ExamOrCollectionGenericBase):
|
|||||||
|
|
||||||
stats_graph = models.TextField(default=0)
|
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:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def save(self, *args, recreate_json=True, **kwargs):
|
def save(self, *args, recreate_json=True, **kwargs):
|
||||||
self.recreate_json = recreate_json
|
self.recreate_json = recreate_json
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
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):
|
def get_questions(self):
|
||||||
"""Returns a list of questions in the exam in the correct order"""
|
"""Returns a list of questions in the exam in the correct order"""
|
||||||
@@ -900,6 +937,8 @@ class ExamBase(ExamOrCollectionGenericBase):
|
|||||||
|
|
||||||
def get_time_limit(self):
|
def get_time_limit(self):
|
||||||
"""Returns a human readable time limit"""
|
"""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(":")
|
h, m, s = str(datetime.timedelta(seconds=self.time_limit)).split(":")
|
||||||
|
|
||||||
time_limit = ""
|
time_limit = ""
|
||||||
|
|||||||
+6
-6
@@ -1487,7 +1487,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
f"{exam.app_name=}, {exam.name=}, {cid=}, {passcode=}, {request.user=}"
|
f"{exam.app_name=}, {exam.name=}, {cid=}, {passcode=}, {request.user=}"
|
||||||
)
|
)
|
||||||
if exam.exam_mode and not exam.check_cid_user(
|
if exam.exam_mode and not exam.check_cid_user(
|
||||||
cid, passcode, request=request, user_id=request.user.pk
|
cid, passcode, user=request.user, user_id=request.user.pk
|
||||||
):
|
):
|
||||||
print(exam.name, "fail")
|
print(exam.name, "fail")
|
||||||
continue
|
continue
|
||||||
@@ -1736,7 +1736,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
user_id = None
|
user_id = None
|
||||||
else:
|
else:
|
||||||
user_id = request.user.pk
|
user_id = request.user.pk
|
||||||
if exam.exam_mode and not exam.check_cid_user(cid, passcode, request, user_id):
|
if exam.exam_mode and not exam.check_cid_user(cid, passcode, request.user, user_id):
|
||||||
raise Http404("No available exam")
|
raise Http404("No available exam")
|
||||||
|
|
||||||
# exam_json_cache = cache.get("{}_exam_json_{}".format(self.app_name, pk))
|
# exam_json_cache = cache.get("{}_exam_json_{}".format(self.app_name, pk))
|
||||||
@@ -1811,7 +1811,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
user_id = None
|
user_id = None
|
||||||
else:
|
else:
|
||||||
user_id = request.user.pk
|
user_id = request.user.pk
|
||||||
if not exam.check_cid_user(cid, passcode, request, user_id):
|
if not exam.check_cid_user(cid, passcode, request.user, user_id):
|
||||||
raise Http404("No available exam")
|
raise Http404("No available exam")
|
||||||
|
|
||||||
time = timezone.now()
|
time = timezone.now()
|
||||||
@@ -1850,7 +1850,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
user_id = None
|
user_id = None
|
||||||
else:
|
else:
|
||||||
user_id = request.user.pk
|
user_id = request.user.pk
|
||||||
if not exam.check_cid_user(cid, passcode, request, user_id):
|
if not exam.check_cid_user(cid, passcode, request.user, user_id):
|
||||||
raise Http404("No available exam")
|
raise Http404("No available exam")
|
||||||
|
|
||||||
if request.GET.get("_"):
|
if request.GET.get("_"):
|
||||||
@@ -1883,7 +1883,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
|
|
||||||
print("CID", cid)
|
print("CID", cid)
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, passcode, request, user_id):
|
if not exam.check_cid_user(cid, passcode, request.user, user_id):
|
||||||
raise Http404("No available exam")
|
raise Http404("No available exam")
|
||||||
|
|
||||||
return redirect("{}:question_json_unbased".format(self.app_name), pk=sk)
|
return redirect("{}:question_json_unbased".format(self.app_name), pk=sk)
|
||||||
@@ -1921,7 +1921,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
if not exam.exam_mode:
|
if not exam.exam_mode:
|
||||||
raise Http404("Packet not in exam mode")
|
raise Http404("Packet not in exam mode")
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, passcode, request):
|
if not exam.check_cid_user(cid, passcode, request.user):
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
if user is not None:
|
if user is not None:
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-05-13 10:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('longs', '0022_alter_exam_authors_only'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection ends', null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='restrict_to_dates',
|
||||||
|
field=models.BooleanField(default=False, help_text='If the exam/collection should only be taken between the start and end date times'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='start_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection starts', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-05-13 10:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('physics', '0009_alter_exam_authors_only'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection ends', null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='restrict_to_dates',
|
||||||
|
field=models.BooleanField(default=False, help_text='If the exam/collection should only be taken between the start and end date times'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='start_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection starts', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
+2
-2
@@ -139,7 +139,7 @@ def exam_start(request, pk):
|
|||||||
def exam_take_overview(request, pk, cid=None, passcode=None):
|
def exam_take_overview(request, pk, cid=None, passcode=None):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
exam.check_user_can_take(cid, passcode, request)
|
exam.check_user_can_take(cid, passcode, request.user)
|
||||||
|
|
||||||
questions = exam.exam_questions.all()
|
questions = exam.exam_questions.all()
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ def exam_take(request, pk: int, sk: int, cid: str| None = None, passcode: str| N
|
|||||||
if not exam.active:
|
if not exam.active:
|
||||||
return exam_inactive(request, context={"exam": exam})
|
return exam_inactive(request, context={"exam": exam})
|
||||||
|
|
||||||
exam.check_user_can_take(cid, passcode, request)
|
exam.check_user_can_take(cid, passcode, request.user)
|
||||||
|
|
||||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user)
|
cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-05-13 10:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('rapids', '0008_alter_exam_authors_only'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection ends', null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='restrict_to_dates',
|
||||||
|
field=models.BooleanField(default=False, help_text='If the exam/collection should only be taken between the start and end date times'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='start_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection starts', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from re import A
|
from re import A
|
||||||
|
import time
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.http import Http404
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -508,3 +513,53 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
generated_questions.pop(-1).delete()
|
generated_questions.pop(-1).delete()
|
||||||
users_tested = users_tested + 1
|
users_tested = users_tested + 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_exams_with_dates(db, client):
|
||||||
|
create_cid_user_and_groups(db)
|
||||||
|
exam = create_exam(db)
|
||||||
|
|
||||||
|
|
||||||
|
active_exams = client.get(reverse(f"{APP_NAME}:active_exams"))
|
||||||
|
assert active_exams.status_code == 200
|
||||||
|
assert len(json.loads(active_exams.content)["exams"]) == 0
|
||||||
|
|
||||||
|
cid_user_1000 = CidUser.objects.get(cid=1000)
|
||||||
|
user1 = User.objects.get(username="user1")
|
||||||
|
exam.check_user_can_take(None, None, user1)
|
||||||
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode)
|
||||||
|
|
||||||
|
exam.active = False
|
||||||
|
exam.save()
|
||||||
|
|
||||||
|
with pytest.raises(Http404):
|
||||||
|
exam.check_user_can_take(None, None, user1)
|
||||||
|
|
||||||
|
with pytest.raises(Http404):
|
||||||
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode)
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
exam.restrict_to_dates = True
|
||||||
|
exam.save()
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
exam.restrict_to_dates = True
|
||||||
|
exam.end_date = timezone.now()
|
||||||
|
exam.start_date = timezone.now() + timezone.timedelta(days=1)
|
||||||
|
exam.save()
|
||||||
|
|
||||||
|
exam.start_date = timezone.now()
|
||||||
|
exam.end_date = None # The previously set value seems to remain...
|
||||||
|
|
||||||
|
exam.restrict_to_dates = True
|
||||||
|
exam.save()
|
||||||
|
exam.check_user_can_take(None, None, user1)
|
||||||
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode)
|
||||||
|
|
||||||
|
exam.end_date = timezone.now()
|
||||||
|
exam.save()
|
||||||
|
|
||||||
|
with pytest.raises(Http404):
|
||||||
|
exam.check_user_can_take(None, None, user1)
|
||||||
|
|
||||||
|
with pytest.raises(Http404):
|
||||||
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-05-13 10:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sbas', '0009_alter_exam_authors_only'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection ends', null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='restrict_to_dates',
|
||||||
|
field=models.BooleanField(default=False, help_text='If the exam/collection should only be taken between the start and end date times'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='start_date',
|
||||||
|
field=models.DateTimeField(help_text='Date (and time) the exam/collection starts', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
+2
-2
@@ -119,7 +119,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None):
|
|||||||
if not exam.active:
|
if not exam.active:
|
||||||
return exam_inactive(request, context={"exam": exam})
|
return exam_inactive(request, context={"exam": exam})
|
||||||
|
|
||||||
exam.check_user_can_take(cid, passcode, request)
|
exam.check_user_can_take(cid, passcode, request.user)
|
||||||
|
|
||||||
questions = exam.exam_questions.all()
|
questions = exam.exam_questions.all()
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ def exam_take(request, pk: int, sk: int, cid: int = None, passcode: str = None):
|
|||||||
if not exam.active:
|
if not exam.active:
|
||||||
return exam_inactive(request, context={"exam": exam})
|
return exam_inactive(request, context={"exam": exam})
|
||||||
|
|
||||||
exam.check_user_can_take(cid, passcode, request)
|
exam.check_user_can_take(cid, passcode, request.user)
|
||||||
|
|
||||||
cid_user_exam = exam.get_or_create_cid_user_exam(cid = cid, user_user=request.user)
|
cid_user_exam = exam.get_or_create_cid_user_exam(cid = cid, user_user=request.user)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user