feat: Quiz attempt limits is configurable
- Added a 'no limit' check to `check_quiz_completion` in utils.py - Added disabled flag for questions in quiz component - Refactored `get_quiz_without_answers` to send attempt limit data to client
This commit is contained in:
parent
d4ed56af02
commit
2b7e158e50
@ -135,7 +135,7 @@ def check_content_completion(content_name, content_type, enrollment_name):
|
|||||||
|
|
||||||
def check_quiz_completion(quiz, enrollment_name):
|
def check_quiz_completion(quiz, enrollment_name):
|
||||||
attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
|
attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
|
||||||
status = bool(len(attempts) == quiz.max_attempts)
|
status = False if quiz.max_attempts == 0 else bool(len(attempts) == quiz.max_attempts)
|
||||||
score = None
|
score = None
|
||||||
result = None
|
result = None
|
||||||
if attempts:
|
if attempts:
|
||||||
|
@ -10,21 +10,21 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<div id="quiz" :name="content">
|
<div id="quiz" :name="content">
|
||||||
<div id="quiz-body">
|
<div id="quiz-body">
|
||||||
<component v-for="question in quizData" :key="question.name" v-bind:is="question.type" :question="question" @updateResponse="updateResponse"></component>
|
<component v-for="question in quizData" :key="question.name" v-bind:is="question.type" :question="question" @updateResponse="updateResponse" :isDisabled="isDisabled"></component>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div>
|
<div>
|
||||||
<div v-if="submitted" id="post-quiz-actions" class="row">
|
<div v-if="isDisabled || submitted" id="post-quiz-actions" class="row">
|
||||||
<div class="col-md-8 text-left">
|
<div class="col-md-8 text-left">
|
||||||
<h3>Your Score: <span id="result">{{ score }}</span></h3>
|
<span v-html="message"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-right">
|
<div class="col-md-4 text-right">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else id="quiz-actions" class="text-right">
|
<div v-else id="quiz-actions" class="text-right">
|
||||||
<button class='btn btn-outline-secondary' type="reset">Reset</button>
|
<button class='btn btn-outline-secondary' type="reset" :disabled="isDisabled">Reset</button>
|
||||||
<button class='btn btn-primary' @click="submitQuiz" type="button">Submit</button>
|
<button class='btn btn-primary' @click="submitQuiz" type="button" :disabled="isDisabled">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -50,12 +50,16 @@ export default {
|
|||||||
quizData: '',
|
quizData: '',
|
||||||
quizResponse: {},
|
quizResponse: {},
|
||||||
score: '',
|
score: '',
|
||||||
submitted: false
|
submitted: false,
|
||||||
|
isDisabled: false,
|
||||||
|
quizStatus: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getQuizWithoutAnswers().then(data => {
|
this.getQuizWithoutAnswers().then(data => {
|
||||||
this.quizData = data
|
this.quizData = data.quizData
|
||||||
|
this.quizStatus = data.status
|
||||||
|
this.isDisabled = data.status.is_complete
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@ -67,6 +71,7 @@ export default {
|
|||||||
return lms.call("get_quiz_without_answers",
|
return lms.call("get_quiz_without_answers",
|
||||||
{
|
{
|
||||||
quiz_name: this.content,
|
quiz_name: this.content,
|
||||||
|
course_name: this.$route.params.course_name
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -81,8 +86,8 @@ export default {
|
|||||||
course: this.$route.params.course_name
|
course: this.$route.params.course_name
|
||||||
}
|
}
|
||||||
).then(data => {
|
).then(data => {
|
||||||
this.score = data,
|
this.score = data
|
||||||
this.submitted = true,
|
this.submitted = true
|
||||||
this.quizResponse = null
|
this.quizResponse = null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -96,6 +101,16 @@ export default {
|
|||||||
return 'QuizSingleChoice'
|
return 'QuizSingleChoice'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
message: function() {
|
||||||
|
if(this.submitted) {
|
||||||
|
return '<h3>Your Score: <span id="result">'+ this.score +'</span></h3>'
|
||||||
|
}
|
||||||
|
let message = '<h4>You have exhausted all attempts for this quiz.</h4>'
|
||||||
|
if(this.quizStatus.result == 'Pass') {
|
||||||
|
message = "<h4>You have successfully completed this quiz.</h4>Score: " + this.quizStatus.score
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<h5>{{ question.question }}</h5>
|
<h5>{{ question.question }}</h5>
|
||||||
<div class="options ml-2">
|
<div class="options ml-2">
|
||||||
<div v-for="option in question.options" :key="option.name" class="form-check pb-1">
|
<div v-for="option in question.options" :key="option.name" class="form-check pb-1">
|
||||||
<input v-model="checked" class="form-check-input" type="checkbox" :name="question.name" :id="option.name" :value="option.name" @change="emitResponse(question.name, option.name)">
|
<input v-model="checked" class="form-check-input" type="checkbox" :name="question.name" :id="option.name" :value="option.name" @change="emitResponse(question.name, option.name)" :disabled="isDisabled">
|
||||||
<label class="form-check-label" :for="option.name">
|
<label class="form-check-label" :for="option.name">
|
||||||
{{ option.option }}
|
{{ option.option }}
|
||||||
</label>
|
</label>
|
||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: ['question'],
|
props: ['question', 'isDisabled'],
|
||||||
name: 'QuizSingleChoice',
|
name: 'QuizSingleChoice',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<h5>{{ question.question }}</h5>
|
<h5>{{ question.question }}</h5>
|
||||||
<div class="options ml-2">
|
<div class="options ml-2">
|
||||||
<div v-for="option in question.options" :key="option.name" class="form-check pb-1">
|
<div v-for="option in question.options" :key="option.name" class="form-check pb-1">
|
||||||
<input class="form-check-input" type="radio" :name="question.name" :id="option.name" :value="option.name" @change="emitResponse(question.name, option.name)">
|
<input class="form-check-input" type="radio" :name="question.name" :id="option.name" :value="option.name" @change="emitResponse(question.name, option.name)" :disabled="isDisabled">
|
||||||
<label class="form-check-label" :for="option.name">
|
<label class="form-check-label" :for="option.name">
|
||||||
{{ option.option }}
|
{{ option.option }}
|
||||||
</label>
|
</label>
|
||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: ['question'],
|
props: ['question', 'isDisabled'],
|
||||||
name: 'QuizSingleChoice',
|
name: 'QuizSingleChoice',
|
||||||
methods: {
|
methods: {
|
||||||
emitResponse(q, o) {
|
emitResponse(q, o) {
|
||||||
|
@ -93,15 +93,20 @@ def get_quiz_with_answers(quiz_name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_quiz_without_answers(quiz_name):
|
def get_quiz_without_answers(quiz_name, course_name):
|
||||||
try:
|
try:
|
||||||
quiz = frappe.get_doc("Quiz", quiz_name).get_questions()
|
quiz = frappe.get_doc("Quiz", quiz_name)
|
||||||
quiz_output = [{'name':question.name, 'question':question.question, 'type': question.type, 'options':[{'name': option.name, 'option':option.option} for option in question.options]} for question in quiz]
|
questions = quiz.get_questions()
|
||||||
return quiz_output
|
|
||||||
except:
|
except:
|
||||||
frappe.throw("Quiz {0} does not exist".format(quiz_name))
|
frappe.throw("Quiz {0} does not exist".format(quiz_name))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
enrollment = utils.get_course_enrollment(course_name).name
|
||||||
|
quiz_status = {}
|
||||||
|
quiz_status['is_complete'], quiz_status['score'], quiz_status['result'] = utils.check_quiz_completion(quiz, enrollment)
|
||||||
|
quiz_output = [{'name':question.name, 'question':question.question, 'type': question.type, 'options':[{'name': option.name, 'option':option.option} for option in question.options]} for question in questions]
|
||||||
|
return { 'quizData': quiz_output, 'status': quiz_status}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def evaluate_quiz(course, quiz_response, quiz_name):
|
def evaluate_quiz(course, quiz_response, quiz_name):
|
||||||
"""LMS Function: Evaluates a simple multiple choice quiz.
|
"""LMS Function: Evaluates a simple multiple choice quiz.
|
||||||
|
Loading…
Reference in New Issue
Block a user