diff options
author | adityacp | 2020-09-12 11:44:01 +0530 |
---|---|---|
committer | adityacp | 2020-09-12 11:44:01 +0530 |
commit | b1d2b88746fc670d7362f9b4d175d5e570f3ac77 (patch) | |
tree | 7225713597b09645ce7b75ffe44271691e3e28b0 | |
parent | bee8d54d8ae094db5e3c9c04c5e28fb5b2abb1df (diff) | |
download | online_test-b1d2b88746fc670d7362f9b4d175d5e570f3ac77.tar.gz online_test-b1d2b88746fc670d7362f9b4d175d5e570f3ac77.tar.bz2 online_test-b1d2b88746fc670d7362f9b4d175d5e570f3ac77.zip |
Mulitple changes
- Remove all alerts and add toast messages
- Accept user submissions for the lesson quiz and evaluate
- Initial lesson statistics
-rw-r--r-- | yaksh/admin.py | 4 | ||||
-rw-r--r-- | yaksh/forms.py | 7 | ||||
-rw-r--r-- | yaksh/models.py | 20 | ||||
-rw-r--r-- | yaksh/static/yaksh/css/custom.css | 28 | ||||
-rw-r--r-- | yaksh/static/yaksh/css/toastr.min.css | 1 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/lesson.js | 64 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/show_toc.js | 78 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/toastr.min.js | 7 | ||||
-rw-r--r-- | yaksh/templates/base.html | 19 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_lesson_statistics.html | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_video.html | 11 | ||||
-rw-r--r-- | yaksh/urls.py | 2 | ||||
-rw-r--r-- | yaksh/views.py | 62 |
13 files changed, 244 insertions, 64 deletions
diff --git a/yaksh/admin.py b/yaksh/admin.py index 6fb05d1..e98c7c5 100644 --- a/yaksh/admin.py +++ b/yaksh/admin.py @@ -2,7 +2,7 @@ from yaksh.models import Question, Quiz, QuestionPaper, Profile from yaksh.models import (TestCase, StandardTestCase, StdIOBasedTestCase, Course, AnswerPaper, CourseStatus, LearningModule, Lesson, Post, Comment, Topic, TableOfContents, - VideoQuizAnswer, Answer + LessonQuizAnswer, Answer ) from django.contrib import admin @@ -62,5 +62,5 @@ admin.site.register(Lesson, LessonAdmin) admin.site.register(LearningModule, LearningModuleAdmin) admin.site.register(Topic) admin.site.register(TableOfContents) -admin.site.register(VideoQuizAnswer) +admin.site.register(LessonQuizAnswer) admin.site.register(Answer) diff --git a/yaksh/forms.py b/yaksh/forms.py index 440a535..cc5daaf 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -655,10 +655,13 @@ class TopicForm(forms.ModelForm): time = kwargs.pop("time") if "time" in kwargs else None super(TopicForm, self).__init__(*args, **kwargs) self.fields['name'].widget.attrs.update( - {'class': form_input_class, 'placeholder': 'Topic Name'} + {'class': form_input_class, 'placeholder': 'Name'} ) self.fields['timer'].widget.attrs.update( - {'class': form_input_class, 'placeholder': 'Topic Time'} + {'class': form_input_class, 'placeholder': 'Time'} + ) + self.fields['description'].widget.attrs.update( + {'class': form_input_class, 'placeholder': 'Description'} ) self.fields['timer'].initial = time diff --git a/yaksh/models.py b/yaksh/models.py index 570c4c6..19f3302 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -40,7 +40,7 @@ from django.contrib.contenttypes.models import ContentType from django.template import Context, Template from django.conf import settings from django.forms.models import model_to_dict - +from django.db.models import Count # Local Imports from yaksh.code_server import ( submit, get_result as get_result_from_code_server @@ -2745,6 +2745,18 @@ class Comment(ForumBase): self.post_field.title) +class TOCManager(models.Manager): + + def get_data(self, course_id, lesson_id): + toc = TableOfContents.objects.filter( + course_id=course_id, lesson_id=lesson_id, content__in=[2, 3, 4] + ) + answers = LessonQuizAnswer.objects.select_related("toc").filter( + toc__course_id=course_id, toc__lesson_id=lesson_id + ) + return answers + + class TableOfContents(models.Model): toc_types = ((1, "Topic"), (2, "Graded Quiz"), (3, "Exercise"), (4, "Poll")) course = models.ForeignKey(Course, on_delete=models.CASCADE, @@ -2757,6 +2769,8 @@ class TableOfContents(models.Model): object_id = models.PositiveIntegerField() content_object = GenericForeignKey() + objects = TOCManager() + class Meta: verbose_name_plural = "Table Of Contents" @@ -2773,13 +2787,14 @@ class TableOfContents(models.Model): class Topic(models.Model): name = models.CharField(max_length=255) + description = models.TextField(null=True, blank=True) content = GenericRelation(TableOfContents) def __str__(self): return f"{self.name}" -class VideoQuizAnswer(models.Model): +class LessonQuizAnswer(models.Model): toc = models.ForeignKey(TableOfContents, on_delete=models.CASCADE) student = models.ForeignKey(User, on_delete=models.CASCADE) answer = models.ForeignKey(Answer, on_delete=models.CASCADE) @@ -2849,6 +2864,7 @@ class VideoQuizAnswer(models.Model): if ans_status: self.answer.marks = self.answer.question.points self.answer.save() + return result def __str__(self): return f"Lesson answer of {self.toc} by {self.student.get_full_name()}" diff --git a/yaksh/static/yaksh/css/custom.css b/yaksh/static/yaksh/css/custom.css index c93b03c..9d4ab76 100644 --- a/yaksh/static/yaksh/css/custom.css +++ b/yaksh/static/yaksh/css/custom.css @@ -68,7 +68,7 @@ body, .dropdown-menu { } #sidebar.active { - margin-left: -350px; + margin-left: -370px; } #sidebar .sidebar-header { @@ -132,7 +132,6 @@ body, .dropdown-menu { border: none; } -<<<<<<< HEAD /* Simple MDE editor style */ .editor-toolbar.fullscreen, .editor-preview-side { z-index: 2000 !important; @@ -167,3 +166,28 @@ iframe { display:block; width:100%; } + +#loader { + position: fixed; + display: none; + width: 100%; + height: 100%; + top:0; + bottom: 0; + left:0; + right: 0; + background-color: rgba(0,0,0,0.5); + z-index: 1001; /* 1001 coz sidebar is 1000. So will be on top of sidebar*/ +} + +#state1 { + position: absolute; + top: 40%; + left: 45%; + font-size: 30px; + color: white; +} + +.toast-top-center { + padding-top: 5em; +}
\ No newline at end of file diff --git a/yaksh/static/yaksh/css/toastr.min.css b/yaksh/static/yaksh/css/toastr.min.css new file mode 100644 index 0000000..643135e --- /dev/null +++ b/yaksh/static/yaksh/css/toastr.min.css @@ -0,0 +1 @@ +/*
* Note that this is toastr v2.1.3, the "latest" version in url has no more maintenance,
* please go to https://cdnjs.com/libraries/toastr.js and pick a certain version you want to use,
* make sure you copy the url from the website since the url may change between versions.
* */
.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}
\ No newline at end of file diff --git a/yaksh/static/yaksh/js/lesson.js b/yaksh/static/yaksh/js/lesson.js index 0558bd0..60eff78 100644 --- a/yaksh/static/yaksh/js/lesson.js +++ b/yaksh/static/yaksh/js/lesson.js @@ -71,6 +71,7 @@ function add_topic() { if (!$("#id_timer").val()) { $("#id_timer").val($("#vtimer").val()); } + document.getElementById("id_timer").focus(); $("#topic-form").submit(function(e) { e.preventDefault(); lock_screen(); @@ -83,6 +84,7 @@ function add_question() { if (!$("#id_timer").val()) { $("#id_timer").val($("#vtimer").val()); } + document.getElementById("id_timer").focus(); $("#question-form").submit(function(e) { e.preventDefault(); lock_screen(); @@ -92,11 +94,11 @@ function add_question() { } function lock_screen() { - document.getElementById("ontop").style.display = "block"; + document.getElementById("loader").style.display = "block"; } function unlock_screen() { - document.getElementById("ontop").style.display = "none"; + document.getElementById("loader").style.display = "none"; } function show_error(error) { @@ -105,9 +107,40 @@ function show_error(error) { var value = err[key]; err_msg = err_msg + key + " : " + value[0].message + "\n"; }); - alert(err_msg); + show_message(err_msg, "error"); } +function show_message(message, msg_type) { + toastr.options = { + "positionClass": "toast-top-center", + "timeOut": "1500", + "showDuration": "300", + } + switch(msg_type) { + case "info": { + toastr.info(message); + break; + } + case "error": { + toastr.error(message); + break; + } + case "warning": { + toastr.warning(message); + break; + } + case "success": { + toastr.success(message); + break; + } + default: { + toastr.info(message); + break; + } + } +} + + function show_toc(toc) { $("#lesson-content").empty(); $("#toc").html(toc); @@ -131,17 +164,22 @@ function ajax_call(url, method, data, csrf) { }, success: function(msg) { unlock_screen(); - if (msg.success) { - if (msg.status) $("#lesson-content").html(msg.data); - if (parseInt(msg.content_type) === 1) { - add_topic(); + if (msg.status) $("#lesson-content").html(msg.data); + if (parseInt(msg.content_type) === 1) { + add_topic(); + } + else { + add_question(); + } + if (msg.toc) show_toc(msg.toc); + if (msg.message) { + if (msg.success) { + show_message(msg.message, "success"); } else { - add_question(); + show_message(msg.message, "warning"); } } - if (msg.toc) show_toc(msg.toc); - if (msg.message) alert(msg.message) }, error: function(xhr, data) { unlock_screen(); @@ -152,15 +190,15 @@ function ajax_call(url, method, data, csrf) { break; } case 500: { - alert('500 status code! server error'); + show_message('500 status code! server error', "error"); break; } case 404: { - alert('404 status code! server error'); + show_message('404 status code! server error', "error"); break; } default: { - alert('Unable to perform action. Please try again'); + show_message('Unable to perform action. Please try again', "error"); break; } } diff --git a/yaksh/static/yaksh/js/show_toc.js b/yaksh/static/yaksh/js/show_toc.js index f42346b..5fef923 100644 --- a/yaksh/static/yaksh/js/show_toc.js +++ b/yaksh/static/yaksh/js/show_toc.js @@ -12,6 +12,7 @@ $(document).ready(function() { loc += 1; if(content.content != 1) { player.pause(); + player.fullscreen.exit(); url = $("#toc_"+content.id).val(); ajax_call(url, "GET"); } @@ -33,26 +34,23 @@ function get_time_in_seconds(time) { } function lock_screen() { - document.getElementById("ontop").style.display = "block"; + document.getElementById("loader").style.display = "block"; + if ($("#check").is(":visible")) { + $("#check").attr("disabled", true); + } } function unlock_screen() { - document.getElementById("ontop").style.display = "none"; -} - -function show_error(error) { - var err_msg = ""; - Object.keys(err).forEach(function(key) { - var value = err[key]; - err_msg = err_msg + key + " : " + value[0].message + "\n"; - }); - alert(err_msg); + document.getElementById("loader").style.display = "none"; + if ($("#check").is(":visible")) { + $("#check").attr("disabled", false); + } } function show_question(data) { $("#dialog").html(data); $("#dialog").dialog({ - width: 600, + width: 800, height: 500, }); $("#submit-quiz-form").submit(function(e) { @@ -65,10 +63,13 @@ function show_question(data) { function select_toc(element) { var toc_id = element.getAttribute("data-toc"); + var content_type = element.getAttribute("data-toc-type"); var toc_time = $("#toc_time_"+toc_id).val(); player.currentTime = get_time_in_seconds(toc_time); - url = $("#toc_"+toc_id).val(); - ajax_call(url, "GET"); + if (content_type != 1) { + url = $("#toc_"+toc_id).val(); + ajax_call(url, "GET"); + } } function csrfSafeMethod(method) { @@ -76,6 +77,36 @@ function csrfSafeMethod(method) { return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } +function show_message(message, msg_type) { + toastr.options = { + "positionClass": "toast-top-center", + "timeOut": "1500", + "showDuration": "300", + } + switch(msg_type) { + case "info": { + toastr.info(message); + break; + } + case "error": { + toastr.error(message); + break; + } + case "warning": { + toastr.warning(message); + break; + } + case "success": { + toastr.success(message); + break; + } + default: { + toastr.info(message); + break; + } + } +} + function ajax_call(url, method, data, csrf) { lock_screen(); $.ajax({ @@ -93,26 +124,33 @@ function ajax_call(url, method, data, csrf) { if (msg.data) { show_question(msg.data); } - if(msg.message) alert(msg.message); + if (msg.message) { + if (msg.success) { + $("#dialog").dialog("close"); + show_message(msg.message, "success"); + } + else { + show_message(msg.message, "warning"); + } + } }, error: function(xhr, data) { unlock_screen(); switch(xhr.status) { case 400: { - err = JSON.parse(xhr.responseJSON.message); - show_error(err); + show_message("400 status code! server error", "error"); break; } case 500: { - alert('500 status code! server error'); + show_message("500 status code! server error", "error"); break; } case 404: { - alert('404 status code! server error'); + show_message("404 status code! server error", "error"); break; } default: { - alert('Unable to perform action. Please try again'); + show_message('Unable to perform action. Please try again', "error"); break; } } diff --git a/yaksh/static/yaksh/js/toastr.min.js b/yaksh/static/yaksh/js/toastr.min.js new file mode 100644 index 0000000..4b5f34a --- /dev/null +++ b/yaksh/static/yaksh/js/toastr.min.js @@ -0,0 +1,7 @@ +/* + * Note that this is toastr v2.1.3, the "latest" version in url has no more maintenance, + * please go to https://cdnjs.com/libraries/toastr.js and pick a certain version you want to use, + * make sure you copy the url from the website since the url may change between versions. + * */ +!function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("<div/>").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'<button type="button">×</button>',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("<div/>"),M=e("<div/>"),B=e("<div/>"),q=e("<div/>"),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); +//# sourceMappingURL=toastr.js.map diff --git a/yaksh/templates/base.html b/yaksh/templates/base.html index 53edbee..8bf7fbc 100644 --- a/yaksh/templates/base.html +++ b/yaksh/templates/base.html @@ -22,6 +22,7 @@ <link rel="stylesheet" href="{% static 'yaksh/css/ontop.css' %}" type="text/css" /> <link rel="stylesheet" href="{% static 'yaksh/css/plyr.css' %}" /> <link rel="stylesheet" type="text/css" href="{% static 'yaksh/css/simplemde.min.css' %}"> + <link rel="stylesheet" href="{% static 'yaksh/css/toastr.min.css' %}" />"> {% block meta %} @@ -42,6 +43,8 @@ <script src="{% static 'yaksh/js/plyr.js' %}"></script> <script type="text/javascript" src="{% static 'yaksh/js/simplemde.min.js' %}"> </script> + <script type="text/javascript" src="{% static 'yaksh/js/toastr.min.js' %}"> + </script> <script> new WOW().init(); @@ -57,6 +60,22 @@ Checking...<img src="{% static 'yaksh/images/check_answer.gif' %}"/> </div> </div> + <div id="loader"> + <div id="state1"> + <div class="spinner-grow text-success" role="status"> + <span class="sr-only">Loading...</span> + </div> + <div class="spinner-grow text-danger" role="status"> + <span class="sr-only">Loading...</span> + </div> + <div class="spinner-grow text-warning" role="status"> + <span class="sr-only">Loading...</span> + </div> + <div class="spinner-grow text-primary" role="status"> + <span class="sr-only">Loading...</span> + </div> + </div> + </div> {% block nav %} {% endblock %} diff --git a/yaksh/templates/yaksh/show_lesson_statistics.html b/yaksh/templates/yaksh/show_lesson_statistics.html new file mode 100644 index 0000000..36b1fbd --- /dev/null +++ b/yaksh/templates/yaksh/show_lesson_statistics.html @@ -0,0 +1,5 @@ +{% extends "manage.html" %} +{% block title %} Lesson Statistics {% endblock %} +{% block content %} +{{data}} +{% endblock %}
\ No newline at end of file diff --git a/yaksh/templates/yaksh/show_video.html b/yaksh/templates/yaksh/show_video.html index 8f8bbb2..f6e247c 100644 --- a/yaksh/templates/yaksh/show_video.html +++ b/yaksh/templates/yaksh/show_video.html @@ -90,11 +90,9 @@ <!-- Page Content --> <div id="content"> - <button type="button" id="sidebarCollapse" class="btn btn-outline-info"> <i class="fa fa-navicon fa-lg"></i> </button> - <br><br> <ol class="breadcrumb"> <li class="breadcrumb-item"> @@ -108,7 +106,6 @@ {% endif %} </ol> <br> - {% if msg %} <center> <div class="alert alert-dismissible alert-warning"> @@ -171,7 +168,9 @@ {% with content.get_toc_text as toc_name %} <tr> <td> - <a href="#" onclick="select_toc(this);" data-toc="{{content.id}}" data-content-type="{{content.content}}">{{ toc_name }}</a> + <a href="#" onclick="select_toc(this);" data-toc="{{content.id}}" data-toc-type="{{content.content}}"> + {{ toc_name }} + </a> </td> <td> {{content.get_content_display}} @@ -179,9 +178,7 @@ <td id="toc_time_{{content.id}}"> {{content.time}} </td> - <td> - <input type="hidden" id="toc_{{content.id}}" value="{% url 'yaksh:get_marker_quiz' course.id content.id %}" data-content="{{content.content}}"/> - </td> + <input type="hidden" id="toc_{{content.id}}" value="{% url 'yaksh:get_marker_quiz' course.id content.id %}" data-content="{{content.content}}"/> </tr> {% endwith %} {% empty %} diff --git a/yaksh/urls.py b/yaksh/urls.py index 2b9a04f..ada5829 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -263,4 +263,6 @@ urlpatterns = [ name='get_marker_quiz'), path('submit/marker/quiz/<int:course_id>/<int:toc_id>', views.submit_marker_quiz, name='submit_marker_quiz'), + path('manage/lesson/stats/<int:course_id>/<int:lesson_id>', + views.lessson_statistics, name='lessson_statistics'), ] diff --git a/yaksh/views.py b/yaksh/views.py index ff22a26..c817c51 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -38,7 +38,7 @@ from yaksh.models import ( StdIOBasedTestCase, StringTestCase, TestCase, User, get_model_class, FIXTURES_DIR_PATH, MOD_GROUP_NAME, Lesson, LessonFile, LearningUnit, LearningModule, CourseStatus, question_types, Post, Comment, - Topic, TableOfContents, VideoQuizAnswer, MicroManager + Topic, TableOfContents, LessonQuizAnswer, MicroManager ) from yaksh.forms import ( UserRegisterForm, UserLoginForm, QuizForm, QuestionForm, @@ -3772,6 +3772,7 @@ def delete_toc(request, course_id, toc_id): get_object_or_404(Topic, pk=toc.object_id).delete() else: get_object_or_404(Question, id=toc.object_id).delete() + messages.success(request, "Content deleted successfully") return redirect(redirect_url) @@ -3839,12 +3840,12 @@ def submit_marker_quiz(request, course_id, toc_id): try: user_answer = int(request.POST.get('answer')) except ValueError: - msg = "Please enter an Integer Value" + user_answer = None elif current_question.type == 'float': try: user_answer = float(request.POST.get('answer')) except ValueError: - msg = "Please enter a Float Value" + user_answer = None elif current_question.type == 'string': user_answer = str(request.POST.get('answer')) elif current_question.type == 'mcc': @@ -3853,30 +3854,59 @@ def submit_marker_quiz(request, course_id, toc_id): user_answer_ids = request.POST.get('answer').split(',') user_answer = [int(ids) for ids in user_answer_ids] - def is_valid_answer(user_answer): - success = True - if current_question.type == "mcc" and not user_answer: - success = False - elif not str(user_answer): - success = False - return success + def is_valid_answer(answer): + status = True + if ((current_question.type == "mcc" or + current_question.type == "arrange") and not answer): + status = False + elif answer is None or not str(answer): + status = False + return status if is_valid_answer(user_answer): - if not VideoQuizAnswer.objects.filter( - toc_id=toc_id, student_id=user.id).exists(): + success = True + # check if graded quiz and already attempted + has_attempts = LessonQuizAnswer.objects.filter( + toc_id=toc_id, student_id=user.id).exists() + if ((toc.content == 2 and not has_attempts) or + toc.content == 3 or toc.content == 4): answer = Answer.objects.create( question_id=current_question.id, answer=user_answer, correct=False, error=json.dumps([]) ) - lesson_ans = VideoQuizAnswer.objects.create( + lesson_ans = LessonQuizAnswer.objects.create( toc_id=toc_id, student=user, answer=answer ) - if toc.content == 2: - lesson_ans.check_answer(user_answer) msg = "Answer saved successfully" + # call check answer only for graded quiz and exercise + if toc.content == 3 or toc.content == 2: + result = lesson_ans.check_answer(user_answer) + # if exercise then show custom message + if toc.content == 3: + if result.get("success"): + msg = "You answered the question correctly" + else: + success = False + msg = "You have answered the question incorrectly. "\ + "Please refer the lesson again" else: msg = "You have already submitted the answer" else: + success = False msg = "Please submit a valid answer" - context = {"success": True, "message": msg} + context = {"success": success, "message": msg} return JsonResponse(context) + + +@login_required +@email_verified +def lessson_statistics(request, course_id, lesson_id): + user = request.user + course = get_object_or_404(Course, pk=course_id) + if (not is_moderator(user) or + not course.is_creator(user) or not course.is_creator(user)): + raise Http404("You are not allowed to view this page") + toc = TableOfContents.objects.get_data(course_id, lesson_id) + return render(request, 'yaksh/show_lesson_statistics.html', { + 'data': toc, + }) |