allow supervisors to view shared exams
This commit is contained in:
+16
-5
@@ -44,9 +44,7 @@ urlpatterns = [
|
||||
),
|
||||
path(
|
||||
"supervisor-autocomplete",
|
||||
views.SupervisorAutocomplete.as_view(
|
||||
model=Supervisor
|
||||
),
|
||||
views.SupervisorAutocomplete.as_view(model=Supervisor),
|
||||
name="supervisor-autocomplete",
|
||||
),
|
||||
path(
|
||||
@@ -166,7 +164,11 @@ urlpatterns = [
|
||||
path("cids/<int:cid>/details", views.cid_details, name="cid_details"),
|
||||
path("cids/create/email", views.create_cid_email, name="create_cid_email"),
|
||||
path("supervisor", views.SupervisorList.as_view(), name="supervisor"),
|
||||
path("supervisor/request_account", views.supervisor_request_account, name="supervisor_request_account"),
|
||||
path(
|
||||
"supervisor/request_account",
|
||||
views.supervisor_request_account,
|
||||
name="supervisor_request_account",
|
||||
),
|
||||
path(
|
||||
"supervisor/<int:pk>/overview",
|
||||
views.supervisor_overview,
|
||||
@@ -225,6 +227,11 @@ urlpatterns = [
|
||||
views.ExamCollectionDelete.as_view(),
|
||||
name="examcollection_delete",
|
||||
),
|
||||
path(
|
||||
"share_with_supervisor_toggle/<int:pk>/",
|
||||
views.toggle_share_with_supervisor,
|
||||
name="toggle_share_with_supervisor",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -380,7 +387,11 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
|
||||
),
|
||||
path("exam/", generic_exam_view.exam_list, name="exam_list"),
|
||||
path("exam/all", generic_exam_view.exam_list_all, name="exam_list_all"),
|
||||
path("exam/<int:exam_id>/order_questions", generic_exam_view.order_questions, name="order_questions"),
|
||||
path(
|
||||
"exam/<int:exam_id>/order_questions",
|
||||
generic_exam_view.order_questions,
|
||||
name="order_questions",
|
||||
),
|
||||
path("exam/<int:exam_id>/cids", generic_exam_view.exam_cids, name="exam_cids"),
|
||||
# path("exam/<int:exam_id>/groups", generic_exam_view.exam_groups_edit, name="exam_groups_edit"),
|
||||
path(
|
||||
|
||||
+78
-25
@@ -79,6 +79,7 @@ from .forms import (
|
||||
|
||||
from .models import (
|
||||
CidUser,
|
||||
CidUserExam,
|
||||
CidUserGroup,
|
||||
ExamBase,
|
||||
ExamCollection,
|
||||
@@ -171,27 +172,34 @@ class CidManagerRequiredMixin(UserPassesTestMixin):
|
||||
|
||||
def get_user_exams(user, supervisor_view=False):
|
||||
EXAM_ANSWER_MAP = {
|
||||
"physics": (PhysicsUserAnswer, PhysicsExam),
|
||||
"anatomy": (AnatomyUserAnswer, AnatomyExam),
|
||||
"rapids": (RapidsUserAnswer, RapidsExam),
|
||||
"longs": (LongsUserAnswer, LongsExam),
|
||||
"sbas": (SbasUserAnswer, SbasExam),
|
||||
"physics": (PhysicsUserAnswer, PhysicsExam, "physics_exams"),
|
||||
"anatomy": (AnatomyUserAnswer, AnatomyExam, "anatomy_exams"),
|
||||
"rapids": (RapidsUserAnswer, RapidsExam, "rapids_exams"),
|
||||
"longs": (LongsUserAnswer, LongsExam, "longs_exams"),
|
||||
"sbas": (SbasUserAnswer, SbasExam, "sbas_exams"),
|
||||
}
|
||||
kwargs = {
|
||||
"exam_mode": True,
|
||||
"archive": False
|
||||
}
|
||||
kwargs = {"exam_mode": True, "archive": False}
|
||||
|
||||
exams = []
|
||||
|
||||
if supervisor_view:
|
||||
|
||||
kwargs["results_supervisor_visible"] = True
|
||||
del kwargs["archive"]
|
||||
|
||||
exams = []
|
||||
for exam_type in EXAM_ANSWER_MAP:
|
||||
UserAnswer, Exam = EXAM_ANSWER_MAP[exam_type]
|
||||
UserAnswer, Exam, cid_rel = EXAM_ANSWER_MAP[exam_type]
|
||||
exam_answers = UserAnswer.objects.filter(user=user)
|
||||
exam_ids = exam_answers.values_list("exam").distinct()
|
||||
exams_to_add = Exam.objects.filter(id__in=exam_ids, **kwargs)
|
||||
if supervisor_view:
|
||||
extra_exam_ids = CidUserExam.objects.filter(
|
||||
user_user=user,
|
||||
share_with_supervisor=True,
|
||||
content_type=ContentType.objects.get_for_model(Exam),
|
||||
).values_list(cid_rel)
|
||||
|
||||
exams_to_add = exams_to_add | Exam.objects.filter(id__in=extra_exam_ids)
|
||||
if exams_to_add:
|
||||
exams.append((exam_type, exams_to_add))
|
||||
|
||||
@@ -528,7 +536,11 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
if (exam.open_access and exam.active) or user in exam.get_author_objects():
|
||||
return True
|
||||
|
||||
if user.is_authenticated and exam.active and exam.cid_user_exam.filter(user_user=user):
|
||||
if (
|
||||
user.is_authenticated
|
||||
and exam.active
|
||||
and exam.cid_user_exam.filter(user_user=user)
|
||||
):
|
||||
return True
|
||||
|
||||
if marker and user in exam.markers.all():
|
||||
@@ -2091,14 +2103,18 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
if not request.user.supervisor == user.userprofile.supervisor:
|
||||
raise PermissionDenied
|
||||
|
||||
return self.exam_scores_cid_user(request, pk, user=user, supervisor=user.userprofile.supervisor.user)
|
||||
return self.exam_scores_cid_user(
|
||||
request, pk, user=user, supervisor=user.userprofile.supervisor.user
|
||||
)
|
||||
|
||||
@method_decorator(user_passes_test(lambda u: u.is_superuser))
|
||||
def exam_scores_cid_user_admin(self, request, pk, cid):
|
||||
user = CidUser.objects.get(cid=cid)
|
||||
return self.exam_scores_cid_user(request, pk, cid, user.passcode)
|
||||
|
||||
def exam_scores_cid_user(self, request, pk, cid=None, passcode="", user=None, supervisor=None):
|
||||
def exam_scores_cid_user(
|
||||
self, request, pk, cid=None, passcode="", user=None, supervisor=None
|
||||
):
|
||||
exam: ExamBase = get_object_or_404(self.Exam, pk=pk)
|
||||
|
||||
if not exam.exam_mode:
|
||||
@@ -2110,11 +2126,11 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
|
||||
if supervisor is None:
|
||||
if not exam.check_cid_user(cid, passcode, request.user):
|
||||
#TODO clear this up
|
||||
# TODO clear this up
|
||||
# Should users be able to access results for exams they do not have access to (if they have results)?
|
||||
if not self.check_user_access(request.user, pk):
|
||||
# raise PermissionDenied
|
||||
#if request.user.supervisor == user.userprofile.supervisor:
|
||||
# if request.user.supervisor == user.userprofile.supervisor:
|
||||
# pass
|
||||
|
||||
# check for supervisor access
|
||||
@@ -2124,8 +2140,8 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
raise Exception("user must be defined")
|
||||
view_all_results = True
|
||||
|
||||
if user is not None:
|
||||
request.user = user
|
||||
if user is None:
|
||||
user = request.user
|
||||
|
||||
questions = (
|
||||
exam.get_questions()
|
||||
@@ -2142,7 +2158,6 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
answers_marks = []
|
||||
answers = []
|
||||
|
||||
|
||||
for q in questions:
|
||||
# Get user answer
|
||||
if cid is not None:
|
||||
@@ -2238,6 +2253,8 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
max_score = len(questions)
|
||||
|
||||
cid_user_exam = exam.cid_user_exam.filter(user_user=user).first()
|
||||
print(user)
|
||||
print(f"{cid_user_exam=}")
|
||||
|
||||
template_context = {
|
||||
"exam": exam,
|
||||
@@ -3829,8 +3846,7 @@ def supervisor_overview(request, pk):
|
||||
if not request.user.supervisor == supervisor:
|
||||
raise PermissionDenied()
|
||||
except Supervisor.RelatedObjectDoesNotExist:
|
||||
raise PermissionDenied()
|
||||
|
||||
raise PermissionDenied()
|
||||
|
||||
trainees = User.objects.filter(userprofile__supervisor=supervisor)
|
||||
|
||||
@@ -3849,7 +3865,7 @@ def supervisor_trainee(request, pk, trainee_id):
|
||||
if not request.user.supervisor == supervisor:
|
||||
raise PermissionDenied()
|
||||
except Supervisor.RelatedObjectDoesNotExist:
|
||||
raise PermissionDenied()
|
||||
raise PermissionDenied()
|
||||
|
||||
# We do this to check that the supervisor can (should) see the trainee results
|
||||
trainee = User.objects.get(userprofile__supervisor=supervisor, pk=trainee_id)
|
||||
@@ -3866,6 +3882,7 @@ def supervisor_trainee(request, pk, trainee_id):
|
||||
{"supervisor": supervisor, "trainee": trainee, "all_exams": exams},
|
||||
)
|
||||
|
||||
|
||||
def supervisor_request_account(request):
|
||||
if request.method == "POST":
|
||||
email = request.POST.get("email")
|
||||
@@ -3880,7 +3897,12 @@ def supervisor_request_account(request):
|
||||
return HttpResponse(f"Invalid email address ({email})")
|
||||
|
||||
if supervisor.user:
|
||||
return HttpResponse(format_html("User already exists. Reset your password <a href='{}'>here</a>", reverse("password_reset")))
|
||||
return HttpResponse(
|
||||
format_html(
|
||||
"User already exists. Reset your password <a href='{}'>here</a>",
|
||||
reverse("password_reset"),
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
user_account = User.objects.get(email=email)
|
||||
@@ -3891,13 +3913,44 @@ def supervisor_request_account(request):
|
||||
first, last = supervisor.name.split(" ", 1)
|
||||
except ValueError:
|
||||
first, last = supervisor.name, ""
|
||||
user_account = User.objects.create_user(email=email, username=email, password=secrets.token_hex(nbytes=16), first_name=first, last_name=last)
|
||||
user_account = User.objects.create_user(
|
||||
email=email,
|
||||
username=email,
|
||||
password=secrets.token_hex(nbytes=16),
|
||||
first_name=first,
|
||||
last_name=last,
|
||||
)
|
||||
|
||||
supervisor.user = user_account
|
||||
supervisor.save()
|
||||
|
||||
return HttpResponse(format_html("Account created, you now need to reset your password <a href='{}'>here</a>", reverse("password_reset")))
|
||||
return HttpResponse(
|
||||
format_html(
|
||||
"Account created, you now need to reset your password <a href='{}'>here</a>",
|
||||
reverse("password_reset"),
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
return render(request, "generic/supervisor_request_account.html", {})
|
||||
|
||||
|
||||
def toggle_share_with_supervisor(request, pk):
|
||||
if request.htmx:
|
||||
cid_user_exam = get_object_or_404(CidUserExam, pk=pk)
|
||||
|
||||
print(request.user)
|
||||
print(cid_user_exam)
|
||||
print(cid_user_exam.user_user)
|
||||
if request.user != cid_user_exam.user_user:
|
||||
return HttpResponse("Incorrect permissions")
|
||||
|
||||
cid_user_exam.share_with_supervisor = not cid_user_exam.share_with_supervisor
|
||||
cid_user_exam.save()
|
||||
|
||||
return HttpResponse(
|
||||
f"Shared with supervisor: {cid_user_exam.share_with_supervisor}"
|
||||
)
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
|
||||
{% block content %}
|
||||
<h1>Examinations</h1>
|
||||
|
||||
<details class="help-text">
|
||||
<summary><i class="bi bi-info-circle"></i> Help</summary>
|
||||
<p>This page shows the currently active examations (that you have access to manage).</p>
|
||||
|
||||
<p>Exams that are active will be available for candidates to take.</p>
|
||||
|
||||
<p>Once candidates have completed the exam (or subimitted some answers) they will immediately be able to view their answers on the platoform. They will not be able to see the correct answers (or their scores) until the exams results have been published.</p>
|
||||
|
||||
<p>A users submitted answers will remain available to the user until the exam is archived. Once an exam is archived the user will no longer be able to view their submitted answers.</p>
|
||||
|
||||
<p>Supervisors will have access to all exam results (including archived exams) for which they have access (when "results supervisor visible" is set to true or the user has shared their results).</p>
|
||||
</details>
|
||||
<div class="{{app_name}}">
|
||||
Active exams:<br/>
|
||||
<ul class="exam-list exam-list-active">
|
||||
|
||||
@@ -31,9 +31,16 @@
|
||||
{% endif %}
|
||||
<h2>Exam: {{ exam }}</h2>
|
||||
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
|
||||
{% if exam.results_supervisor_visible %}
|
||||
Exam results are visible to supervisor.<br/>
|
||||
{% else %}
|
||||
Share results with supervisor: {{cid_user_exam.share_with_supervisor}}<br/>
|
||||
|
||||
{% if cid_user_exam %}
|
||||
{% if exam.results_supervisor_visible %}
|
||||
Exam results are visible to supervisor.<br/>
|
||||
{% else %}
|
||||
Share results with supervisor: {{cid_user_exam.share_with_supervisor}} <button
|
||||
class="btn btn-sm"
|
||||
hx-get="{% url 'generic:toggle_share_with_supervisor' cid_user_exam.pk %}"
|
||||
>Toggle</button>
|
||||
<br/>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
Answers:
|
||||
Reference in New Issue
Block a user