diff --git a/rad/urls.py b/rad/urls.py index e308bb65..8cb9625d 100644 --- a/rad/urls.py +++ b/rad/urls.py @@ -64,6 +64,7 @@ urlpatterns = [ path("cid/results//", views.cid_results, name="cid_results"), path("cid//", views.cid_scores, name="cid_scores"), path("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"), # Global url that registers RTS compatible exams path("exam/json/", views.active_exams, name="active_exams"), diff --git a/rad/views.py b/rad/views.py index 6d7d89b7..83b3648c 100644 --- a/rad/views.py +++ b/rad/views.py @@ -1,3 +1,4 @@ +import secrets from atlas.models import CaseCollection, CidReportAnswer from generic.decorators import user_is_cid_user_manager 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): # exam = get_object_or_404(Exam, pk=pk) @@ -459,8 +528,11 @@ class UpdateUserView(CidManagerRequiredMixin, UpdateView): template_name = "user_update.html" slug_field = "username" 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): model = UserProfile @@ -474,7 +546,11 @@ class UpdateUserProfileView(CidManagerRequiredMixin, UpdateView): template_name = "user_update.html" slug_field = "user__username" 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): @@ -534,6 +610,7 @@ def accounts_bulk_create(request): "first_name": user["first_name"], "last_name": user["last_name"], "email": user["email"], + "password": secrets.token_hex(nbytes=16) } new_user = User.objects.create_user(**user_dict) except Exception as error: diff --git a/rapids/templates/rapids/exam_scores_user.html b/rapids/templates/rapids/exam_scores_user.html index 9f059ef7..d24275cb 100644 --- a/rapids/templates/rapids/exam_scores_user.html +++ b/rapids/templates/rapids/exam_scores_user.html @@ -17,20 +17,23 @@

Exam: {{ exam.name }}

-

Candidate: {{ cid }}

+

Candidate: {{ cid|default_if_none:request.user.username }}

Answers: -
    {% for ans, score, correct_answer in answers_and_marks %} -
  • Question {{forloop.counter}} - Correct answer: {{ correct_answer }}
  • - -
    {{ans}}
    ({{score}}) -
    - View - {% endfor %} +
      + {% for ans, score, correct_answer in answers_and_marks %} +
    • Question {{forloop.counter}} - Correct answer: {{ correct_answer }}
    • + +
      {{ans}}
      ({{score}}) +
      + View + {% endfor %}

    Total mark: {{ total_score }} / {{max_score}}
    Normalised score: {{ normalised_score }}
    + {% if cid %} Other exams + {% endif %}
{% endblock %} diff --git a/rapids/urls.py b/rapids/urls.py index 406a0687..b62b1d6d 100755 --- a/rapids/urls.py +++ b/rapids/urls.py @@ -1,4 +1,6 @@ from django.urls import path, include + +from rapids.models import Answer from . import views from django.views.decorators.cache import cache_page, cache_control @@ -92,6 +94,11 @@ urlpatterns = [ views.exam_scores_cid_user, name="exam_scores_cid_user", ), + path( + "exam//scores/", + views.exam_scores_user, + name="exam_scores_user", + ), path( "exam//toggle_active", views.GenericExamViews.exam_toggle_active, @@ -177,4 +184,11 @@ urlpatterns = [ views.GenericViews.user_answer_delete_multiple, name="user_answer_delete_multiple", ), + path( + "answer-autocomplete", + views.AnswerAutocomplete.as_view( + model=Answer, create_field="answer" + ), + name="answer-autocomplete", + ), ] diff --git a/rapids/views.py b/rapids/views.py index fe1f411d..9b6adabd 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -91,6 +91,8 @@ from .serializers import ( from generic.mixins import SuperuserRequiredMixin +from dal import autocomplete + 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) if not exam.exam_mode: @@ -1009,3 +1014,17 @@ def question_viewer(request): "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 \ No newline at end of file diff --git a/templates/user_scores.html b/templates/user_scores.html new file mode 100644 index 00000000..eb8d04d6 --- /dev/null +++ b/templates/user_scores.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% block content %} +
+

User: {{ request.user.username }}

+

Assigned exams

+

The following exams will be available to take when active. + {% for exam_type, exams in available_exams %} +

{{exam_type}}

+ + {% endfor %} + + {% if all_exams %} +

Exam results

+
+ 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 %} +

{{exam_type|title}}

+
    + {% for exam in exams %} +
  • {{exam.name}} {% if exam.active %}[Active]{% endif %} {% if exam.publish_results %}[Results Published]{% endif %}
  • + {% endfor %} +
+ {% endif %} + {% endfor %} +
+ {% endif %} + + {% if case_collections %} +
+

Case Collection

+ The following Case Collections have been found. + {% for collection in case_collections %} +
  • {{collection.name}} {% if collection.active %}[Active]{% endif %} {% if collection.publish_results %}[Results Published]{% endif %}
  • + + {% endfor %} +
    + {% endif %} + +
    +{% endblock %}