.
This commit is contained in:
@@ -64,6 +64,7 @@ urlpatterns = [
|
|||||||
path("cid/results/<int:cid>/", views.cid_results, name="cid_results"),
|
path("cid/results/<int:cid>/", views.cid_results, name="cid_results"),
|
||||||
path("cid/<int:pk>/<str:passcode>", views.cid_scores, name="cid_scores"),
|
path("cid/<int:pk>/<str:passcode>", views.cid_scores, name="cid_scores"),
|
||||||
path("cid/<int:cid>/", views.cid_scores_admin, name="cid_scores_admin"),
|
path("cid/<int:cid>/", views.cid_scores_admin, name="cid_scores_admin"),
|
||||||
|
path("user/scores", views.user_scores, name="user_scores"),
|
||||||
path("cid/", views.cid_selector, name="cid_selector"),
|
path("cid/", views.cid_selector, name="cid_selector"),
|
||||||
# Global url that registers RTS compatible exams
|
# Global url that registers RTS compatible exams
|
||||||
path("exam/json/", views.active_exams, name="active_exams"),
|
path("exam/json/", views.active_exams, name="active_exams"),
|
||||||
|
|||||||
+79
-2
@@ -1,3 +1,4 @@
|
|||||||
|
import secrets
|
||||||
from atlas.models import CaseCollection, CidReportAnswer
|
from atlas.models import CaseCollection, CidReportAnswer
|
||||||
from generic.decorators import user_is_cid_user_manager
|
from generic.decorators import user_is_cid_user_manager
|
||||||
from generic.views import CidManagerRequiredMixin, get_question_and_content_type
|
from generic.views import CidManagerRequiredMixin, get_question_and_content_type
|
||||||
@@ -116,6 +117,74 @@ def cid_results(request, cid):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def user_scores(request):
|
||||||
|
# exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
|
#cid = pk
|
||||||
|
|
||||||
|
#cid_user = CidUser.objects.filter(cid=cid).first()
|
||||||
|
#print(cid_user)
|
||||||
|
#if not cid_user or cid_user.passcode != passcode:
|
||||||
|
# raise Http404("CID / Passcode combination not found")
|
||||||
|
#print(cid_user.passcode)
|
||||||
|
|
||||||
|
# questions = exam.exam_questions.all()
|
||||||
|
EXAM_ANSWER_MAP = {
|
||||||
|
"physics": (PhysicsCidUserAnswer, PhysicsExam),
|
||||||
|
"anatomy": (AnatomyCidUserAnswer, AnatomyExam),
|
||||||
|
"rapids": (RapidsCidUserAnswer, RapidsExam),
|
||||||
|
"longs": (LongsCidUserAnswer, LongsExam),
|
||||||
|
"sbas": (SbasCidUserAnswer, SbasExam),
|
||||||
|
}
|
||||||
|
kwargs = {"exam_mode": True, "archive": False}
|
||||||
|
|
||||||
|
exams = []
|
||||||
|
for exam_type in EXAM_ANSWER_MAP:
|
||||||
|
CidUserAnswer, Exam = EXAM_ANSWER_MAP[exam_type]
|
||||||
|
exam_answers = CidUserAnswer.objects.filter(user=request.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))
|
||||||
|
|
||||||
|
#case_collections = request.user.casecollection_exams.all()
|
||||||
|
|
||||||
|
|
||||||
|
USER_EXAM_TYPES = (
|
||||||
|
("Physics", "user_physics_exams"),
|
||||||
|
("Rapids", "user_rapid_exams"),
|
||||||
|
("SBAs", "user_sba_exams"),
|
||||||
|
("Anatomy", "user_anatomy_exams"),
|
||||||
|
("Longs", "user_longs_exams"),
|
||||||
|
#("CaseCollection", "user_casecollection_exams"),
|
||||||
|
)
|
||||||
|
available_exams = []
|
||||||
|
|
||||||
|
for n, t in USER_EXAM_TYPES:
|
||||||
|
exam_rel = getattr(request.user, t)
|
||||||
|
if exam_rel.exists():
|
||||||
|
temp_exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
|
||||||
|
available_exams.append((n, temp_exams))
|
||||||
|
|
||||||
|
#available_exams = request.user.get_cid_exams()
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"user_scores.html",
|
||||||
|
{
|
||||||
|
# "physics_exams": physics_exams,
|
||||||
|
# "anatomy_exams": anatomy_exams,
|
||||||
|
# "rapid_exams": rapid_exams,
|
||||||
|
# "longs_exams": longs_exams,
|
||||||
|
# "sba_exams": sba_exams,
|
||||||
|
"all_exams": exams,
|
||||||
|
#"passcode": passcode,
|
||||||
|
"cid_user": request.user,
|
||||||
|
"available_exams": available_exams,
|
||||||
|
"case_collections": [],#case_collections,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def cid_scores(request, pk, passcode):
|
def cid_scores(request, pk, passcode):
|
||||||
# exam = get_object_or_404(Exam, pk=pk)
|
# exam = get_object_or_404(Exam, pk=pk)
|
||||||
@@ -459,8 +528,11 @@ class UpdateUserView(CidManagerRequiredMixin, UpdateView):
|
|||||||
template_name = "user_update.html"
|
template_name = "user_update.html"
|
||||||
slug_field = "username"
|
slug_field = "username"
|
||||||
slug_url_kwarg = "slug"
|
slug_url_kwarg = "slug"
|
||||||
success_url = reverse_lazy("profile")
|
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
view_name = 'account_profile'
|
||||||
|
# No need for reverse_lazy here, because it's called inside the method
|
||||||
|
return reverse(view_name, kwargs={'slug': self.object.username})
|
||||||
|
|
||||||
class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView):
|
class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView):
|
||||||
model = UserProfile
|
model = UserProfile
|
||||||
@@ -474,7 +546,11 @@ class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView):
|
|||||||
template_name = "user_update.html"
|
template_name = "user_update.html"
|
||||||
slug_field = "user__username"
|
slug_field = "user__username"
|
||||||
slug_url_kwarg = "slug"
|
slug_url_kwarg = "slug"
|
||||||
success_url = reverse_lazy("profile")
|
|
||||||
|
def get_success_url(self):
|
||||||
|
view_name = 'account_profile'
|
||||||
|
# No need for reverse_lazy here, because it's called inside the method
|
||||||
|
return reverse(view_name, kwargs={'slug': self.object.username})
|
||||||
|
|
||||||
|
|
||||||
# class UpdateUser(TemplateView):
|
# class UpdateUser(TemplateView):
|
||||||
@@ -534,6 +610,7 @@ def accounts_bulk_create(request):
|
|||||||
"first_name": user["first_name"],
|
"first_name": user["first_name"],
|
||||||
"last_name": user["last_name"],
|
"last_name": user["last_name"],
|
||||||
"email": user["email"],
|
"email": user["email"],
|
||||||
|
"password": secrets.token_hex(nbytes=16)
|
||||||
}
|
}
|
||||||
new_user = User.objects.create_user(**user_dict)
|
new_user = User.objects.create_user(**user_dict)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
|||||||
@@ -17,20 +17,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="rapids">
|
<div class="rapids">
|
||||||
<h2>Exam: {{ exam.name }}</h2>
|
<h2>Exam: {{ exam.name }}</h2>
|
||||||
<h3>Candidate: {{ cid }}</h3>
|
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
|
||||||
Answers:
|
Answers:
|
||||||
<ul class="score-answer-list">{% for ans, score, correct_answer in answers_and_marks %}
|
<ul class="score-answer-list">
|
||||||
<li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{ correct_answer }}</span></li>
|
{% for ans, score, correct_answer in answers_and_marks %}
|
||||||
<span class="user-answer-score user-answer-score-{{score}} rapid-ans">
|
<li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{ correct_answer }}</span></li>
|
||||||
<pre>{{ans}}</pre> ({{score}})
|
<span class="user-answer-score user-answer-score-{{score}} rapid-ans">
|
||||||
</span>
|
<pre>{{ans}}</pre> ({{score}})
|
||||||
<span class="view-question-link" data-qn={{forloop.counter0}}>View</span>
|
</span>
|
||||||
{% endfor %}
|
<span class="view-question-link" data-qn={{forloop.counter0}}>View</span>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<br /> Total mark: {{ total_score }} / {{max_score}}
|
<br /> Total mark: {{ total_score }} / {{max_score}}
|
||||||
<br /> Normalised score: {{ normalised_score }}
|
<br /> Normalised score: {{ normalised_score }}
|
||||||
<div>
|
<div>
|
||||||
|
{% if cid %}
|
||||||
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
|
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from rapids.models import Answer
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
from django.views.decorators.cache import cache_page, cache_control
|
from django.views.decorators.cache import cache_page, cache_control
|
||||||
@@ -92,6 +94,11 @@ urlpatterns = [
|
|||||||
views.exam_scores_cid_user,
|
views.exam_scores_cid_user,
|
||||||
name="exam_scores_cid_user",
|
name="exam_scores_cid_user",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"exam/<int:pk>/scores/",
|
||||||
|
views.exam_scores_user,
|
||||||
|
name="exam_scores_user",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"exam/<int:pk>/toggle_active",
|
"exam/<int:pk>/toggle_active",
|
||||||
views.GenericExamViews.exam_toggle_active,
|
views.GenericExamViews.exam_toggle_active,
|
||||||
@@ -177,4 +184,11 @@ urlpatterns = [
|
|||||||
views.GenericViews.user_answer_delete_multiple,
|
views.GenericViews.user_answer_delete_multiple,
|
||||||
name="user_answer_delete_multiple",
|
name="user_answer_delete_multiple",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"answer-autocomplete",
|
||||||
|
views.AnswerAutocomplete.as_view(
|
||||||
|
model=Answer, create_field="answer"
|
||||||
|
),
|
||||||
|
name="answer-autocomplete",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
+20
-1
@@ -91,6 +91,8 @@ from .serializers import (
|
|||||||
|
|
||||||
from generic.mixins import SuperuserRequiredMixin
|
from generic.mixins import SuperuserRequiredMixin
|
||||||
|
|
||||||
|
from dal import autocomplete
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -684,8 +686,11 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def exam_scores_user(request, pk):
|
||||||
|
return exam_scores_cid_user(request, pk)
|
||||||
|
|
||||||
def exam_scores_cid_user(request, pk, cid, passcode):
|
def exam_scores_cid_user(request, pk, cid=None, passcode=""):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
if not exam.exam_mode:
|
if not exam.exam_mode:
|
||||||
@@ -1009,3 +1014,17 @@ def question_viewer(request):
|
|||||||
"ids": ids,
|
"ids": ids,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
|
def get_queryset(self):
|
||||||
|
# Don't forget to filter out results depending on the visitor !
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return Answer.objects.none()
|
||||||
|
|
||||||
|
qs = Answer.objects.all()
|
||||||
|
|
||||||
|
if self.q:
|
||||||
|
qs = qs.filter(answer__istartswith=self.q)
|
||||||
|
|
||||||
|
return qs
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="">
|
||||||
|
<h2>User: {{ request.user.username }}</h2>
|
||||||
|
<h3>Assigned exams</h3>
|
||||||
|
<p>The following exams will be available to take when active.
|
||||||
|
{% for exam_type, exams in available_exams %}
|
||||||
|
<h4>{{exam_type}}</h4>
|
||||||
|
<ul>
|
||||||
|
{% for exam in exams %}
|
||||||
|
<li><a href="{{exam.get_take_url}}" target="_blank">{{exam.name}} {% if exam.active %}[Active]{% endif %}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if all_exams %}
|
||||||
|
<h3>Exam results</h3>
|
||||||
|
<div>
|
||||||
|
The following exam results been found. Click to view answers (and scores when the results are published):
|
||||||
|
{% for exam_type, exams in all_exams %}
|
||||||
|
{% if exams %}
|
||||||
|
<h4>{{exam_type|title}}</h4>
|
||||||
|
<ul>
|
||||||
|
{% for exam in exams %}
|
||||||
|
<li><a href="{% url exam_type|add:':exam_scores_user' pk=exam.pk %}">{{exam.name}}</a> {% if exam.active %}[Active]{% endif %} {% if exam.publish_results %}[Results Published]{% endif %}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if case_collections %}
|
||||||
|
<div>
|
||||||
|
<h3>Case Collection</h3>
|
||||||
|
The following Case Collections have been found.
|
||||||
|
{% for collection in case_collections %}
|
||||||
|
<li><a href="{{collection.get_take_url}}?cid={{cid}}&passcode={{passcode}}">{{collection.name}}</a> {% if collection.active %}[Active]{% endif %} {% if collection.publish_results %}[Results Published]{% endif %}</li>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user