numerous supervisor upgrades
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 11:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('anatomy', '0023_exam_exam_open_access'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='results_supervisor_visible',
|
||||
field=models.BooleanField(default=False, help_text='If true the results will be visible to supervisors without candidate approval.'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 11:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('atlas', '0062_alter_casecollection_markers'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='casecollection',
|
||||
name='results_supervisor_visible',
|
||||
field=models.BooleanField(default=False, help_text='If true the results will be visible to supervisors without candidate approval.'),
|
||||
),
|
||||
]
|
||||
@@ -201,6 +201,7 @@ class ExamFormMixin:
|
||||
"active",
|
||||
"publish_results",
|
||||
"archive",
|
||||
"results_supervisor_visible"
|
||||
# "cid_user_groups",
|
||||
# "user_user_groups",
|
||||
# "author",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 13:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0018_alter_supervisor_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='examuserstatus',
|
||||
name='share_with_supervisor',
|
||||
field=models.BooleanField(default=False, help_text='If true the exam status and results will be available to a users supervisor'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 13:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0019_examuserstatus_share_with_supervisor'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='examuserstatus',
|
||||
name='share_with_supervisor',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ciduserexam',
|
||||
name='share_with_supervisor',
|
||||
field=models.BooleanField(default=False, help_text='If true the exam status and results will be available to a users supervisor'),
|
||||
),
|
||||
]
|
||||
@@ -602,6 +602,11 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
||||
default=False,
|
||||
)
|
||||
|
||||
results_supervisor_visible = models.BooleanField(
|
||||
help_text="If true the results will be visible to supervisors without candidate approval.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -1463,6 +1468,8 @@ class CidUserExam(models.Model):
|
||||
# TODO switch to json field?
|
||||
results_emailed_status = models.CharField(max_length=255, blank=True)
|
||||
|
||||
share_with_supervisor = models.BooleanField(default=False, help_text="If true the exam status and results will be available to a users supervisor")
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.cid_user is None:
|
||||
try:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<a href="{% url 'generic:manage_cid_exams' %}">Cids (exams)</a> /
|
||||
<a href="{% url 'generic:cid_group_view' %}">Cid Groups</a> /
|
||||
<a href="{% url 'generic:user_group_view' %}">User Groups</a> /
|
||||
<a href="{% url 'trainees' %}">Trainees</a> /
|
||||
<a href="{% url 'accounts_list' %}">Manage Users</a> /
|
||||
<a href="{% url 'generic:supervisor' %}">Manage Supervisors</a> /
|
||||
<a href="{% url 'generic:examcollection_list' %}">Collections</a>
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Supervisor
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% block navigation %}
|
||||
Supervisor:
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href='{% url "generic:supervisor_overview" supervisor.pk %}'>Overview</a> /
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -113,6 +113,7 @@ Open access: {{ exam.open_access }}<br />
|
||||
Author(s): {% for author in exam.author.all %}
|
||||
{{ author }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}<br/>
|
||||
<span title="If true the results from this exam will be visible to supervisors automatically. If false the trainee will have to manually approve access.">Supervisor visible: {{exam.results_supervisor_visible}}</span><br/>
|
||||
|
||||
<details>
|
||||
<summary>Extra</summary>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{% extends 'generic/base_supervisor.html' %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>Supervisor: {{supervisor.name}}</h2>
|
||||
<details class="help-text">
|
||||
<summary><i class="bi bi-info-circle"></i> Help</summary>
|
||||
<p>As a supervisor you have the ability to view results on the platform for your associated trainees.</p>
|
||||
<p>Some exams / collection results will be automatically available for you to review, others may require the trainee to allow access</p>
|
||||
<p>If your trainees are incorrect please contact ross.kruger@nhs.net</p>
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<h4>Trainees</h4>
|
||||
You have the following trainee(s):
|
||||
|
||||
<ul id="trainee-list">
|
||||
{% for trainee in trainees %}
|
||||
<li>
|
||||
<i class="bi bi-file-person"></i>
|
||||
<a href='{% url "generic:supervisor_trainee" supervisor.pk trainee.pk %}'>{{trainee.first_name}} {{trainee.last_name}}, {{trainee.email}}</a>
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block css %}
|
||||
<style>
|
||||
#trainee-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock css %}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
This form allows you to create an account if you are a supervisor. To do so please enter your email address.
|
||||
|
||||
<form>
|
||||
<input type="text" name="email" id="email" value="" required>
|
||||
<button
|
||||
hx-post="{% url 'generic:supervisor_request_account' %}"
|
||||
hx-target="#result"
|
||||
>Request Account</button>
|
||||
</form>
|
||||
<div id="result"></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{% extends 'generic/base_supervisor.html' %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>Trainee: {{trainee.first_name}} {{trainee.last_name}}</h2>
|
||||
email: {{trainee.email}}
|
||||
|
||||
|
||||
<h3>Results</h3>
|
||||
<div id="exam-results">
|
||||
{% if all_exams %}
|
||||
The following exam results for the trainee are available.
|
||||
{% for exam_type, exams in all_exams %}
|
||||
{% if exams %}
|
||||
<h4>{{exam_type|title}}</h4>
|
||||
<ul class='{{exam_type|lower}}'>
|
||||
{% for exam in exams %}
|
||||
<li class="exam" data-exam-id="{{exam.pk}}">
|
||||
|
||||
<a href= "{% url exam_type|add:':exam_scores_user_supervisor' pk=exam.pk user_id=trainee.pk %}" title="Click to view results">
|
||||
|
||||
{{exam}}</a> {% if exam.active %}[Active]{% endif %} {% if exam.publish_results %}[Results Published]{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
No results are available for this trainee.
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -166,6 +166,17 @@ 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/<int:pk>/overview",
|
||||
views.supervisor_overview,
|
||||
name="supervisor_overview",
|
||||
),
|
||||
path(
|
||||
"supervisor/<int:pk>/trainee/<int:trainee_id>/",
|
||||
views.supervisor_trainee,
|
||||
name="supervisor_trainee",
|
||||
),
|
||||
path(
|
||||
"supervisor/<int:pk>",
|
||||
views.SupervisorDetail.as_view(),
|
||||
@@ -342,6 +353,11 @@ def generic_exam_urls(generic_exam_view: GenericExamViews):
|
||||
generic_exam_view.exam_scores_user_admin,
|
||||
name="exam_scores_user_admin",
|
||||
),
|
||||
path(
|
||||
"exam/<int:pk>/scores/<int:user_id>/user/supervisor",
|
||||
generic_exam_view.exam_scores_user_supervisor,
|
||||
name="exam_scores_user_supervisor",
|
||||
),
|
||||
path(
|
||||
"exam/<int:pk>/toggle_active",
|
||||
generic_exam_view.exam_toggle_active,
|
||||
|
||||
+136
-6
@@ -98,12 +98,14 @@ from rapids.models import (
|
||||
ExamQuestionDetail as RapidsExamQuestionDetail,
|
||||
)
|
||||
from rapids.models import Exam as RapidsExam
|
||||
from rapids.models import UserAnswer as RapidsUserAnswer
|
||||
from longs.models import (
|
||||
Long as LongQuestion,
|
||||
LongSeries,
|
||||
ExamQuestionDetail as LongsExamQuestionDetail,
|
||||
)
|
||||
from longs.models import Exam as LongsExam
|
||||
from longs.models import UserAnswer as LongsUserAnswer
|
||||
from anatomy.models import (
|
||||
AnatomyQuestion as AnatomyQuestion,
|
||||
ExamQuestionDetail as AnatomyExamQuestionDetail,
|
||||
@@ -113,12 +115,15 @@ from sbas.models import (
|
||||
Question as SbasQuestion,
|
||||
ExamQuestionDetail as SbasExamQuestionDetail,
|
||||
)
|
||||
from anatomy.models import UserAnswer as AnatomyUserAnswer
|
||||
from sbas.models import Exam as SbasExam
|
||||
from sbas.models import UserAnswer as SbasUserAnswer
|
||||
from physics.models import (
|
||||
Question as PhysicsQuestion,
|
||||
ExamQuestionDetail as PhysicsExamQuestionDetail,
|
||||
)
|
||||
from physics.models import Exam as PhysicsExam
|
||||
from physics.models import UserAnswer as PhysicsUserAnswer
|
||||
|
||||
from django.db.models import Case, When
|
||||
from django.conf import settings
|
||||
@@ -164,6 +169,34 @@ class CidManagerRequiredMixin(UserPassesTestMixin):
|
||||
# raise PermissionDenied() # or Http404
|
||||
|
||||
|
||||
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),
|
||||
}
|
||||
kwargs = {
|
||||
"exam_mode": True,
|
||||
"archive": False
|
||||
}
|
||||
|
||||
if supervisor_view:
|
||||
kwargs["results_supervisor_visible"] = True
|
||||
|
||||
exams = []
|
||||
for exam_type in EXAM_ANSWER_MAP:
|
||||
UserAnswer, Exam = 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 exams_to_add:
|
||||
exams.append((exam_type, exams_to_add))
|
||||
|
||||
return exams
|
||||
|
||||
|
||||
def get_exam_model_from_app_name(app_name: str) -> ExamBase:
|
||||
EXAM_MAP = {
|
||||
"physics": PhysicsExam,
|
||||
@@ -494,6 +527,9 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
if (exam.open_access and exam.active) or user in exam.get_author_objects():
|
||||
return True
|
||||
|
||||
if exam.cid_user_exam.filter(user_user=user):
|
||||
return True
|
||||
|
||||
if marker and user in exam.markers.all():
|
||||
return True
|
||||
|
||||
@@ -1521,7 +1557,9 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
exams = self.Exam.objects.all()
|
||||
filter = self.ExtraExamFilter(request.GET, queryset=exams)
|
||||
else:
|
||||
exams = self.Exam.objects.filter(author__id=request.user.id) | self.Exam.objects.filter(open_access=True)
|
||||
exams = self.Exam.objects.filter(
|
||||
author__id=request.user.id
|
||||
) | self.Exam.objects.filter(open_access=True)
|
||||
filter = self.BasicExamFilter(request.GET, queryset=exams)
|
||||
|
||||
return render(
|
||||
@@ -2044,21 +2082,38 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
user = User.objects.get(id=user_id)
|
||||
return self.exam_scores_cid_user(request, pk, user=user)
|
||||
|
||||
def exam_scores_user_supervisor(self, request, pk, user_id):
|
||||
user = User.objects.get(id=user_id)
|
||||
if not request.user.supervisor == user.userprofile.supervisor:
|
||||
raise PermissionDenied
|
||||
|
||||
return self.exam_scores_cid_user(request, pk, user=user, supervisor=request.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):
|
||||
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:
|
||||
raise Http404("Packet not in exam mode")
|
||||
|
||||
if not exam.check_cid_user(cid, passcode, request.user):
|
||||
if not self.check_user_access(request.user, pk):
|
||||
# raise PermissionDenied
|
||||
raise Http404("Error accessing exam")
|
||||
if supervisor is None:
|
||||
if not exam.check_cid_user(cid, passcode, request.user):
|
||||
#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:
|
||||
# pass
|
||||
|
||||
# check for supervisor access
|
||||
raise Http404("Error accessing exam")
|
||||
else:
|
||||
if user is None:
|
||||
raise Exception("user must be defined")
|
||||
|
||||
if user is not None:
|
||||
request.user = user
|
||||
@@ -2176,6 +2231,8 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
case _:
|
||||
max_score = len(questions)
|
||||
|
||||
cid_user_exam = exam.cid_user_exam.filter(user_user=user).first()
|
||||
|
||||
template_context = {
|
||||
"exam": exam,
|
||||
"cid": cid,
|
||||
@@ -2187,6 +2244,8 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
"max_score": max_score,
|
||||
"answers_and_marks": answers_and_marks,
|
||||
"view_all_results": view_all_results,
|
||||
"supervisor": supervisor,
|
||||
"cid_user_exam": cid_user_exam,
|
||||
}
|
||||
|
||||
if self.normalise_score is not None:
|
||||
@@ -3754,3 +3813,74 @@ class UpdateQuestionMixin(RedirectMixin, RevisionMixin, UpdateView):
|
||||
if self.request.user in obj.get_author_objects():
|
||||
return context
|
||||
raise PermissionDenied() # or Http404
|
||||
|
||||
|
||||
def supervisor_overview(request, pk):
|
||||
supervisor = get_object_or_404(Supervisor, pk=pk)
|
||||
|
||||
if not request.user.supervisor == supervisor:
|
||||
raise PermissionDenied()
|
||||
|
||||
trainees = User.objects.filter(userprofile__supervisor=supervisor)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"generic/supervisor_overview.html",
|
||||
{"supervisor": supervisor, "trainees": trainees},
|
||||
)
|
||||
|
||||
|
||||
def supervisor_trainee(request, pk, trainee_id):
|
||||
supervisor = get_object_or_404(Supervisor, pk=pk)
|
||||
|
||||
if not request.user.supervisor == supervisor:
|
||||
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)
|
||||
|
||||
exams = get_user_exams(trainee, supervisor_view=True)
|
||||
|
||||
print(trainee)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"generic/supervisor_trainee.html",
|
||||
{"supervisor": supervisor, "trainee": trainee, "all_exams": exams},
|
||||
)
|
||||
|
||||
def supervisor_request_account(request):
|
||||
if request.method == "POST":
|
||||
email = request.POST.get("email")
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError:
|
||||
return HttpResponse(f"Invalid email address ({email})")
|
||||
|
||||
try:
|
||||
supervisor = Supervisor.objects.get(email=email)
|
||||
except Supervisor.DoesNotExist:
|
||||
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")))
|
||||
|
||||
try:
|
||||
user_account = User.objects.get(email=email)
|
||||
|
||||
except User.DoesNotExist:
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
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")))
|
||||
|
||||
else:
|
||||
|
||||
return render(request, "generic/supervisor_request_account.html", {})
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 11:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('longs', '0030_exam_exam_open_access'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='results_supervisor_visible',
|
||||
field=models.BooleanField(default=False, help_text='If true the results will be visible to supervisors without candidate approval.'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 11:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('physics', '0016_exam_exam_open_access'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='results_supervisor_visible',
|
||||
field=models.BooleanField(default=False, help_text='If true the results will be visible to supervisors without candidate approval.'),
|
||||
),
|
||||
]
|
||||
+7
-21
@@ -7,7 +7,7 @@ from atlas.models import CaseCollection, CidReportAnswer
|
||||
from generic.decorators import user_is_cid_user_manager
|
||||
from generic.filters import UserUserFilter
|
||||
from generic.tables import UserUserTable
|
||||
from generic.views import CidManagerRequiredMixin, RedirectMixin, get_question_and_content_type
|
||||
from generic.views import CidManagerRequiredMixin, RedirectMixin, get_question_and_content_type, get_user_exams
|
||||
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@@ -39,9 +39,8 @@ from django.http import Http404, JsonResponse
|
||||
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
|
||||
from physics.models import UserAnswer as PhysicsUserAnswer, Question
|
||||
from physics.models import UserAnswer as PhysicsUserAnswer
|
||||
from physics.models import Exam as PhysicsExam
|
||||
from physics.models import Question as SbasQuestion
|
||||
|
||||
from anatomy.models import UserAnswer as AnatomyUserAnswer
|
||||
from anatomy.models import Exam as AnatomyExam
|
||||
@@ -60,7 +59,6 @@ from longs.models import Long
|
||||
|
||||
from sbas.models import UserAnswer as SbasUserAnswer
|
||||
from sbas.models import Exam as SbasExam
|
||||
from sbas.models import Question as SbasQuestion
|
||||
|
||||
from anatomy.views import GenericExamViews as AnatomyExamViews
|
||||
from rapids.views import GenericExamViews as RapidsExamsViews
|
||||
@@ -148,6 +146,7 @@ def user_scores_admin(request, user_id):
|
||||
|
||||
return user_scores(request, user=user)
|
||||
|
||||
|
||||
@login_required
|
||||
def user_scores(request, user=None):
|
||||
# exam = get_object_or_404(Exam, pk=pk)
|
||||
@@ -164,23 +163,7 @@ def user_scores(request, user=None):
|
||||
user = request.user
|
||||
admin = False
|
||||
|
||||
EXAM_ANSWER_MAP = {
|
||||
"physics": (PhysicsUserAnswer, PhysicsExam),
|
||||
"anatomy": (AnatomyUserAnswer, AnatomyExam),
|
||||
"rapids": (RapidsUserAnswer, RapidsExam),
|
||||
"longs": (LongsUserAnswer, LongsExam),
|
||||
"sbas": (SbasUserAnswer, SbasExam),
|
||||
}
|
||||
kwargs = {"exam_mode": True, "archive": False}
|
||||
|
||||
exams = []
|
||||
for exam_type in EXAM_ANSWER_MAP:
|
||||
UserAnswer, Exam = 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 exams_to_add:
|
||||
exams.append((exam_type, exams_to_add))
|
||||
exams = get_user_exams(user)
|
||||
|
||||
# case_collections = request.user.casecollection_exams.all()
|
||||
|
||||
@@ -737,6 +720,9 @@ def accounts_bulk_create_check(request):
|
||||
|
||||
user = json.loads(request.POST.get("user"))
|
||||
|
||||
if "email" not in user:
|
||||
return HttpResponse(format_html("<span class='error'>{}</span>", "No email provided"))
|
||||
|
||||
if User.objects.filter(username=user["email"]).exists():
|
||||
return HttpResponse(format_html("<span class='error'>{}</span>", "User already exists"))
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 11:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rapids', '0015_alter_rapid_normal'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='results_supervisor_visible',
|
||||
field=models.BooleanField(default=False, help_text='If true the results will be visible to supervisors without candidate approval.'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-10-28 11:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sbas', '0016_exam_exam_open_access'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='results_supervisor_visible',
|
||||
field=models.BooleanField(default=False, help_text='If true the results will be visible to supervisors without candidate approval.'),
|
||||
),
|
||||
]
|
||||
@@ -32,6 +32,9 @@
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.supervisor %}
|
||||
<a href='{% url "generic:supervisor_overview" request.user.supervisor.pk %}'>Supervisor overview</a>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<a href="{% url 'user_scores'%}"><button>Exams</button></a>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
{% if view_all_results %}
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
Exam state:
|
||||
|
||||
@@ -24,6 +25,15 @@
|
||||
<div class="alert alert-info" role="alert">Results are not currently published. You can see your own answers below.</a></div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if supervisor %}
|
||||
<div class="alert alert-info" role="alert">Viewing results as a <a href='{% url "generic:supervisor_overview" supervisor.supervisor.pk %}'>supervisor</a>.</div>
|
||||
{% 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/>
|
||||
{% endif %}
|
||||
Answers:
|
||||
Reference in New Issue
Block a user