Merge pull request #1316 from FahidLatheef/fix/quiz-maximum-attempts

fix: fixed bug in which user can submit quiz over the maximum limit allowed
This commit is contained in:
Jannat Patel
2025-02-17 19:59:57 +05:30
committed by GitHub
19 changed files with 62 additions and 21 deletions

View File

@@ -207,7 +207,7 @@
</Button> </Button>
<Button <Button
v-else-if="activeQuestion != questions.length" v-else-if="activeQuestion != questions.length"
@click="nextQuetion()" @click="nextQuestion()"
> >
<span> <span>
{{ __('Next') }} {{ __('Next') }}
@@ -258,14 +258,22 @@
</Button> </Button>
</div> </div>
<div <div
v-if="quiz.data.show_submission_history && attempts?.data" v-if="
quiz.data.show_submission_history &&
attempts?.data &&
attempts.data.length > 0
"
class="mt-10" class="mt-10"
> >
<ListView <ListView
:columns="getSubmissionColumns()" :columns="getSubmissionColumns()"
:rows="attempts?.data" :rows="attempts?.data"
row-key="name" row-key="name"
:options="{ selectable: false, showTooltip: false }" :options="{
selectable: false,
showTooltip: false,
emptyState: { title: __('No Quiz submissions found') },
}"
> >
</ListView> </ListView>
</div> </div>
@@ -282,7 +290,7 @@ import {
FormControl, FormControl,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, watch, reactive, inject, computed } from 'vue' import { ref, watch, reactive, inject, computed } from 'vue'
import { createToast } from '@/utils/' import { createToast, showToast } from '@/utils/'
import { CheckCircle, XCircle, MinusCircle } from 'lucide-vue-next' import { CheckCircle, XCircle, MinusCircle } from 'lucide-vue-next'
import { timeAgo } from '@/utils' import { timeAgo } from '@/utils'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -536,7 +544,7 @@ const addToLocalStorage = () => {
localStorage.setItem(quiz.data.title, JSON.stringify(quizData)) localStorage.setItem(quiz.data.title, JSON.stringify(quizData))
} }
const nextQuetion = () => { const nextQuestion = () => {
if (!quiz.data.show_answers && questionDetails.data?.type != 'Open Ended') { if (!quiz.data.show_answers && questionDetails.data?.type != 'Open Ended') {
checkAnswer() checkAnswer()
} else { } else {
@@ -574,6 +582,16 @@ const createSubmission = () => {
if (quiz.data && quiz.data.max_attempts) attempts.reload() if (quiz.data && quiz.data.max_attempts) attempts.reload()
if (quiz.data.duration) clearInterval(timerInterval) if (quiz.data.duration) clearInterval(timerInterval)
}, },
onError(err) {
const errorTitle = err?.message || ''
if (errorTitle.includes('MaximumAttemptsExceededError')) {
const errorMessage = err.messages?.[0] || err
showToast(__('Error'), __(errorMessage), 'x')
setTimeout(() => {
window.location.reload()
}, 3000)
}
},
} }
) )
} }

View File

@@ -55,7 +55,7 @@
<FormControl <FormControl
type="number" type="number"
v-model="quiz.max_attempts" v-model="quiz.max_attempts"
:label="__('Maximun Attempts')" :label="__('Maximum Attempts')"
/> />
<FormControl <FormControl
type="number" type="number"

View File

@@ -219,7 +219,7 @@ let router = createRouter({
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
const { userResource } = usersStore() const { userResource } = usersStore()
const { isLoggedIn } = sessionStore() let { isLoggedIn } = sessionStore()
const { allowGuestAccess } = useSettings() const { allowGuestAccess } = useSettings()
try { try {

View File

@@ -4,6 +4,7 @@ import { createApp, h } from 'vue'
import { usersStore } from '../stores/user' import { usersStore } from '../stores/user'
import translationPlugin from '../translation' import translationPlugin from '../translation'
import { CircleHelp } from 'lucide-vue-next' import { CircleHelp } from 'lucide-vue-next'
import router from '@/router'
export class Quiz { export class Quiz {
constructor({ data, api, readOnly }) { constructor({ data, api, readOnly }) {
@@ -46,6 +47,7 @@ export class Quiz {
quiz: quiz, quiz: quiz,
}) })
app.use(translationPlugin) app.use(translationPlugin)
app.use(router)
const { userResource } = usersStore() const { userResource } = usersStore()
app.provide('$user', userResource) app.provide('$user', userResource)
app.mount(this.wrapper) app.mount(this.wrapper)

View File

@@ -10,12 +10,29 @@ from frappe.desk.doctype.notification_log.notification_log import make_notificat
class LMSQuizSubmission(Document): class LMSQuizSubmission(Document):
def validate(self): def validate(self):
self.validate_if_max_attempts_exceeded()
self.validate_marks() self.validate_marks()
self.set_percentage() self.set_percentage()
def on_update(self): def on_update(self):
self.notify_member() self.notify_member()
def validate_if_max_attempts_exceeded(self):
max_attempts = frappe.db.get_value("LMS Quiz", self.quiz, ["max_attempts"])
if max_attempts == 0:
return
current_user_submission_count = frappe.db.count(
self.doctype, filters={"quiz": self.quiz, "member": frappe.session.user}
)
if current_user_submission_count >= max_attempts:
frappe.throw(
_("You have exceeded the maximum number of attempts ({0}) for this quiz").format(
max_attempts
),
MaximumAttemptsExceededError,
)
def validate_marks(self): def validate_marks(self):
self.score = 0 self.score = 0
for row in self.result: for row in self.result:
@@ -52,3 +69,7 @@ class LMSQuizSubmission(Document):
) )
make_notification_logs(notification, [self.member]) make_notification_logs(notification, [self.member])
class MaximumAttemptsExceededError(frappe.DuplicateEntryError):
pass

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "Max. Versuche" msgstr "Max. Versuche"
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "crwdns150150:0crwdne150150:0" msgstr "crwdns150150:0crwdne150150:0"
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "crwdns150152:0crwdne150152:0" msgstr "crwdns150152:0crwdne150152:0"
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "Intentos máximos" msgstr "Intentos máximos"
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "Intentos máximos" msgstr "Intentos máximos"
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3042,7 +3042,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "Максимум попыток" msgstr "Максимум попыток"
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "Maximalt antal försök" msgstr "Maximalt antal försök"
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "Maximalt antal försök" msgstr "Maximalt antal försök"
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "Maksimum Deneme" msgstr "Maksimum Deneme"
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'

View File

@@ -3031,7 +3031,7 @@ msgid "Max Attempts"
msgstr "" msgstr ""
#: frontend/src/pages/QuizForm.vue:58 #: frontend/src/pages/QuizForm.vue:58
msgid "Maximun Attempts" msgid "Maximum Attempts"
msgstr "" msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch' #. Label of the medium (Select) field in DocType 'LMS Batch'