Skip to content

Commit

Permalink
implement basic features trainings for the user
Browse files Browse the repository at this point in the history
This commit allows users to take the test from settings.

The quiz.html file loads all the content and questions for a module based on the order.
All the data is hidden from user and gradually displayed as users go through the training

We have two level of verifications of answers. the first one is done on quiz.html

1. When users go through the content and they will see the related questions and answer
the questions. Users can go to next section or skip the question only if they have answered the question correctly previously.

2. We also have another verification done on view. Here if the users had correctly answered the questions a json string
   is generated at the end of module and sent via POST, we will then check the database to verify that the data in json
   string(question,answer) and match it with the database. This should help us stop someone from just sending a post
   request to complete the training.

How the resume works?
1. When users go through the training module, we will track the contents, quiz that they have completed and store it in the database.
2. If the user clicks Next after completing a section or quiz, we will check if it was completed in previous session,
   1. If not we will send a post request to the server to update the database, and then load the next section or quiz.
   2. If yes we will just load the next section or quiz.
3. One the user reaches the end of the module, we will send submit the form and the server will check if the user has completed
   all the sections and quizzes. If yes, we will update the database and redirect the user to the training dashboard.

Quick note:
1. Only multiple choice questions is supported yet.
  • Loading branch information
superryeti committed Mar 28, 2023
1 parent f2e16c1 commit 7409f5b
Show file tree
Hide file tree
Showing 5 changed files with 399 additions and 2 deletions.
14 changes: 14 additions & 0 deletions physionet-django/static/custom/css/quiz.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.eachQuiz{
display: none
}

.quizContainer {
max-width: 100%;
position: relative;
margin: auto;
display: block;
}

.alert {
display: none
}
138 changes: 138 additions & 0 deletions physionet-django/static/custom/js/quiz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@


function changeSlide(n) {
showSlide(slideIndex += n);
}

$('.next').on('click', function () {
// move to next slide but keep track of start of partaining training

// when next is clicked, update the progress of the user(for content and quiz) only if the content or quiz was not completed before
if($(this).hasClass('completed') == false){
$(this).addClass('disabled');
const url = document.querySelector('input[name="data-update-url"]').value;
const update_type = $(this).attr('data-update-type');
const update_type_id = $(this).attr('data-update-type-id');
const csrf_token = $(this).find('input[name="csrfmiddlewaretoken"]').val();
const module_id = document.querySelector('input[name="data-module-id"]').value;
const training_id = document.querySelector('input[name="data-training-id"]').value;

const data = {
'update_type': update_type,
'update_type_id': parseInt(update_type_id),
'module_id': parseInt(module_id),
'training_id': parseInt(training_id),
}
const $btn = $(this);
$.ajax({
url: url,
type: 'POST',
data: data,
headers: {
'X-CSRFToken': csrf_token
},
success: function (data) {
console.log(data);
$btn.addClass('completed');
console.log('ajax done');
let slidePosition = parseInt(sessionStorage.getItem("slidePosition"));
sessionStorage.setItem("slidePosition", slidePosition - 1)
changeSlide(1);
},
error: function (data) {
console.log(data);
alertBox("<strong>Oops!</strong> Something went wrong, kindly try again.", "danger")
}
});
$(this).removeClass('disabled');
}
else{
let slidePosition = parseInt(sessionStorage.getItem("slidePosition"));
sessionStorage.setItem("slidePosition", slidePosition - 1)
changeSlide(1);
}
})

$('.previous').on('click', function () {
// move to next slide but keep track of start of partaining training
let slidePosition = parseInt(sessionStorage.getItem("slidePosition"));
sessionStorage.setItem("slidePosition", slidePosition - 2);
changeSlide(-1);
})

function showSlide(n) {

let i;
let slides = document.getElementsByClassName("eachQuiz");
if (n > slides.length) {
alertBox("<strong>Congratulations</strong> You have come to the end of this module.");
sessionStorage.clear();

let question_answers = {};
document.querySelectorAll('.question input[type="radio"]:checked').forEach(function (eachQuiz) {
let question_id = eachQuiz.getAttribute("name");
let value = eachQuiz.value;
question_answers[question_id] = parseInt(value);
});

$('[name="question_answers"]').val(JSON.stringify(question_answers));

$('form').submit();
}
if (n < 1) { slideIndex = slides.length }
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
}

slides[slideIndex - 1].style.display = "block";
}
//
// $("input[type=radio]").on("click", function () {
// // check if answer is correct and proceed, else take back to training
// let id = $(this).attr("name")
// let value = $(this).val();
// var slidePosition = parseInt(sessionStorage.getItem("slidePosition"))
//
// sessionStorage.setItem("slidePosition", 0)
//
// if (sessionStorage.getItem(id) == value) {
// alertBox("<strong>Yay!</strong> You chose the correct answer.", "success")
// changeSlide(1)
// } else {
// alertBox("<strong>Oops!</strong> You chose a wrong answer, kindly go through the training again.", "danger")
// changeSlide(slidePosition)
// }
// })

$('.checkAnswer').on('click', function () {

let question_id = $('input[type="radio"]:checked', $(this).parent()).attr("name");
if (question_id == undefined) {
alertBox("<strong>Oops!</strong> You did not choose an answer, kindly choose an answer.", "danger");
return
}
let value = $('input[type="radio"]:checked', $(this).parent()).val();
// let slidePosition = parseInt(sessionStorage.getItem("slidePosition"));

sessionStorage.setItem("slidePosition", 0);

if (sessionStorage.getItem(question_id) == value) {
alertBox("<strong>Yay!</strong> You chose the correct answer.", "success");
$('.next', $(this).parent().parent()).removeClass('disabled');
// changeSlide(1);
} else {
alertBox("<strong>Oops!</strong> You chose a wrong answer, kindly go through the previous sections again.", "danger");
// changeSlide(slidePosition);
$('.next', $(this).parent().parent()).addClass('disabled');
}
});

function alertBox(message, _class) {
let alert = document.getElementById("alert");
alert.className = "alert alert-" + _class;
alert.innerHTML = message;
alert.style.display = "block";
setTimeout(function () {
alert.style.display = "none";
}, 3000);
};
99 changes: 99 additions & 0 deletions physionet-django/training/templates/training/quiz.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{% extends "base.html" %}
{% load static %}
{% load physionet_templatetags %}
{% block title %}{{ training.training_type.name }}{% endblock %}
{% block local_css %}
<link rel="stylesheet"
type="text/css"
href="{% static 'custom/css/quiz.css' %}">
{% endblock %}
{% block content %}
<div class="container my-3">
<div class="quizContainer">
<div class="quizHeader">
<h1><a href="{% url 'platform_training' training.training_type.id %}">{{ training.training_type.name }}</a></h1>
</div>
<hr>
<h2>{{ module.name }}</h2>

<form method="POST" class="training">
{% csrf_token %}
<input type="text" hidden name="resume-from" value="{{ resume_content_or_quiz_from }}"/>
<input type="text" hidden name="question_answers" value=""/>
<input type="text" hidden name="data-update-url" value="{% url 'update_module_progress' %}"/>
<input type="text" hidden name="data-module-id" value="{{ module.id }}"/>
<input type="text" hidden name="data-training-id" value="{{ training.id }}"/>
{% for item in quiz_content %}
{% if item.body %}
<div class="eachQuiz">
<div>{{ item.body | safe }}</div>
<div class="row ml-1">
{% if forloop.counter != 1 %}<a class="btn btn-warning previous">Back</a>{% endif %}
<a class="btn btn-success next text-white ml-auto mr-3 {% if item.id in completed_contents_ids %}completed{% endif %}" data-update-type="content" data-update-type-id="{{ item.id }}">
{% if forloop.last %}
Submit Module
{% else %}
Next
{% endif %}
{% csrf_token %}
</a>
</div>
</div>
{% else %}
<div class="eachQuiz question">
<div class="list-group">
<div class="list-group-item py-4">{{ item.question | safe }}</div>
{% for choice in item.choices.all %}
<label
class="list-group-item list-group-item-action q_{{ item.id }} {{ item.id }}_{{ choice.id }} mb-0"
for="{{ item.id }}_{{ choice.id }}">
<input type="radio"
id="{{ item.id }}_{{ choice.id }}"
name="{{ item.id }}"
value="{{ choice.id }}">
{{ choice.body | safe }}
</label>
{# add a check answer button #}
{% endfor %}
<a type="button"
class="btn btn-secondary text-white checkAnswer"
data-question="{{ item.id }}"
data-answer="{{ item.answer.id }}">Check Answer</a>
</div>
<br>
<div class="row ml-1">
{% if forloop.counter != 1 %}<a class="btn btn-warning previous">Back</a>{% endif %}
<a class="btn btn-success next text-white ml-auto mr-3 {% if item.id in completed_quizzes_ids %}completed{% else %}disabled{% endif %}" data-update-type="quiz" data-update-type-id="{{ item.id }}">
{% if forloop.last %}
Submit Module
{% else %}
Next
{% endif %}
{% csrf_token %}
</a>
</div>
</div>
{% endif %}
{% endfor %}
</form>
</div>
<br>
<div id="alert" class="alert" role="alert"></div>
</div>
{% endblock %}
{% block local_js_bottom %}
{% for quiz, answer in quiz_answer %}
<script>
sessionStorage.setItem({{quiz }}, {{answer }});
{% if quiz in completed_quizzes_ids %}
document.querySelector("input[type='radio'][id='{{ quiz }}_{{ answer }}']").checked = true;
{% endif %}
</script>
{% endfor %}
<script src="{% static 'custom/js/quiz.js' %}"></script>
<script>
sessionStorage.setItem("slidePosition", 0);
var slideIndex = parseInt(document.querySelector("input[name='resume-from']").value) || 0;
showSlide(slideIndex);
</script>
{% endblock %}
3 changes: 2 additions & 1 deletion physionet-django/training/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
path('settings/platform-training/<int:training_id>/', views.take_training, name='platform_training'),
path('settings/platform-training/<int:training_id>/module/<int:module_id>/', views.take_module_training,
name='platform_training_module'),

path('settings/platform-training/update-module-progress/', views.update_module_progress,
name='update_module_progress'),
path('settings/platform-training/', views.take_training, name='start_training')
]
Loading

0 comments on commit 7409f5b

Please sign in to comment.