diff options
-rw-r--r-- | CHANGELOG.txt | 7 | ||||
-rw-r--r-- | online_test/__init__.py | 2 | ||||
-rw-r--r-- | yaksh/migrations/0028_auto_20210112_1039.py | 25 | ||||
-rw-r--r-- | yaksh/models.py | 57 | ||||
-rw-r--r-- | yaksh/templates/yaksh/statistics_question.html | 23 | ||||
-rw-r--r-- | yaksh/templatetags/custom_filters.py | 7 | ||||
-rw-r--r-- | yaksh/test_views.py | 19 | ||||
-rw-r--r-- | yaksh/views.py | 3 |
8 files changed, 115 insertions, 28 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 56b058b..f6ef277 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,10 @@ +=== 0.30.0 (12-01-2021) === + +* Add new UI skin. +* Add feature to count number of times MCQ option has been selected. +* Fix bug to delete and recreate fresh question papers when using god mode or user mode. +* Add feature to show stats for number of times MCQ option has been selected. + === 0.29.3 (10-12-2020) === * Show the graphs for the lesson video statistics page. diff --git a/online_test/__init__.py b/online_test/__init__.py index 267ddf3..09a303b 100644 --- a/online_test/__init__.py +++ b/online_test/__init__.py @@ -4,4 +4,4 @@ from online_test.celery_settings import app as celery_app __all__ = ('celery_app',) -__version__ = '0.29.3' +__version__ = '0.30.0' diff --git a/yaksh/migrations/0028_auto_20210112_1039.py b/yaksh/migrations/0028_auto_20210112_1039.py new file mode 100644 index 0000000..448de98 --- /dev/null +++ b/yaksh/migrations/0028_auto_20210112_1039.py @@ -0,0 +1,25 @@ +# Generated by Django 3.0.7 on 2021-01-12 05:09 + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('yaksh', '0027_release_0_28_0'), + ] + + operations = [ + migrations.AlterField( + model_name='course', + name='end_enroll_time', + field=models.DateTimeField(default=datetime.datetime(2198, 12, 31, 18, 7, tzinfo=utc), null=True, verbose_name='End Date and Time for enrollment of course'), + ), + migrations.AlterField( + model_name='quiz', + name='end_date_time', + field=models.DateTimeField(default=datetime.datetime(2198, 12, 31, 18, 7, tzinfo=utc), null=True, verbose_name='End Date and Time of the quiz'), + ), + ] diff --git a/yaksh/models.py b/yaksh/models.py index 00bca5b..a475493 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -321,7 +321,6 @@ class Lesson(models.Model): lesson_files = self.get_files() new_lesson = self new_lesson.id = None - new_lesson.name = "Copy of {0}".format(self.name) new_lesson.creator = user new_lesson.save() for _file in lesson_files: @@ -600,7 +599,6 @@ class Quiz(models.Model): question_papers = self.questionpaper_set.all() new_quiz = self new_quiz.id = None - new_quiz.description = "Copy of {0}".format(self.description) new_quiz.creator = user new_quiz.save() for qp in question_papers: @@ -846,12 +844,13 @@ class LearningModule(models.Model): percent = round((count / units.count()) * 100) return percent - def _create_module_copy(self, user, module_name): + def _create_module_copy(self, user, module_name=None): learning_units = self.learning_unit.order_by("order") new_module = self new_module.id = None - new_module.name = module_name new_module.creator = user + if module_name: + new_module.name = module_name new_module.save() for unit in learning_units: new_unit = unit._create_unit_copy(user) @@ -957,8 +956,8 @@ class Course(models.Model): copy_course_name = "Copy Of {0}".format(self.name) new_course = self._create_duplicate_instance(user, copy_course_name) for module in learning_modules: - copy_module_name = "Copy of {0}".format(module.name) - new_module = module._create_module_copy(user, copy_module_name) + copy_module_name = module.name + new_module = module._create_module_copy(user) new_course.learning_module.add(new_module) return new_course @@ -1754,7 +1753,8 @@ class QuestionPaperManager(models.Manager): def create_trial_paper_to_test_quiz(self, trial_quiz, original_quiz_id): """Creates a trial question paper to test quiz.""" - trial_quiz.questionpaper_set.all().delete() + if self.filter(quiz=trial_quiz).exists(): + self.get(quiz=trial_quiz).delete() trial_questionpaper, trial_questions = \ self._create_trial_from_questionpaper(original_quiz_id) trial_questionpaper.quiz = trial_quiz @@ -1993,6 +1993,32 @@ class AnswerPaperManager(models.Manager): questions.append(question.id) return Counter(questions) + def get_per_answer_stats(self, questionpaper_id, attempt_number, + course_id, status='completed'): + papers = self.filter(question_paper_id=questionpaper_id, + course_id=course_id, + attempt_number=attempt_number, status=status) + questions = Question.objects.filter( + questions__id__in=papers, + ).distinct() + + stats = {} + for question in questions: + answers = Answer.objects.filter( + answerpaper__id__in=papers, question=question.id + ).values('answer', 'question__id', 'answerpaper__id') + question_ans_count = {} + answerpaper_count = [] + for ans in answers: + if ans.get('answerpaper__id'): + if ans.get('answer') not in question_ans_count: + question_ans_count[ans.get('answer')] = 1 + else: + question_ans_count[ans.get('answer')] += 1 + answerpaper_count.append(ans.get('answerpaper__id')) + stats[question] = question_ans_count + return stats + def get_all_questions_answered(self, questionpaper_id, attempt_number, course_id, status='completed'): ''' Return a dict of answered question id as key and count as value''' @@ -2046,16 +2072,27 @@ class AnswerPaperManager(models.Manager): course_id) questions = self.get_all_questions(questionpaper_id, attempt_number, course_id) + per_answer_stats = self.get_per_answer_stats( + questionpaper_id, attempt_number, course_id + ) all_questions = Question.objects.filter( id__in=set(questions), active=True ).order_by('type') for question in all_questions: if question.id in questions_answered: - question_stats[question] = [questions_answered[question.id], - questions[question.id]] + question_stats[question] = { + 'answered': [questions_answered[question.id], + questions[question.id]], + 'per_answer': per_answer_stats[question], + } + else: - question_stats[question] = [0, questions[question.id]] + question_stats[question] = { + 'answered': [0, questions[question.id]], + 'per_answer': per_answer_stats[question], + } + return question_stats def _get_answerpapers_for_quiz(self, questionpaper_id, course_id, diff --git a/yaksh/templates/yaksh/statistics_question.html b/yaksh/templates/yaksh/statistics_question.html index 5983835..d70256b 100644 --- a/yaksh/templates/yaksh/statistics_question.html +++ b/yaksh/templates/yaksh/statistics_question.html @@ -1,4 +1,5 @@ {% extends "manage.html" %} +{% load custom_filters %} {% block title %} Question Statistics {% endblock %} {% block pagetitle %} Statistics for {{ quiz.description }}{% endblock pagetitle %} @@ -20,8 +21,8 @@ {% if question_stats %} <p><b>Total number of participants: {{ total }}</b></p> <table class="table table-responsive-sm"> - <tr class="bg-light yakshred"><th>Question</th><th>Type</th><th>Total</th><th>Answered Correctly</th></tr> - {% for question, value in question_stats.items %} + <tr class="bg-light yakshred"><th>Question</th><th></th><th>Type</th><th>Total</th><th>Answered Correctly</th></tr> + {% for question, data in question_stats.items %} <tr> <td style="width: 45%"> <a href="#collapse_question_{{question.id}}" data-toggle="collapse"> @@ -61,12 +62,14 @@ <p> <ol> {% for tc in question.testcase_set.all %} - <li> - {{ tc.mcqtestcase.options|safe }} - {% if tc.mcqtestcase.correct %} - <span class="badge badge-success">Correct</span> - {% endif %} - </li> + <li> + {{ tc.mcqtestcase.options }} + {% if tc.mcqtestcase.correct %} + <span class="badge badge-primary">Correct</span> + {% endif %} + {% get_dict_value data.per_answer tc.id|stringformat:"i" as num %} + <span class="badge badge-info">Answered: {{ num }}</span> + </li> {% endfor %} </ol> </p> @@ -76,7 +79,9 @@ </div> </td> <td>{{ question.type }}</td> - <td>{{value.1}}</td><td>{{ value.0 }} ({% widthratio value.0 value.1 100 %}%)</td> + <td>{{data.answered.1}}</td><td>{{ data.answered.0 }} ({% widthratio data.answered.0 data.answered.1 100 %}%)</td> + + </tr> {% endfor %} </table> diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py index 7eba939..81572a7 100644 --- a/yaksh/templatetags/custom_filters.py +++ b/yaksh/templatetags/custom_filters.py @@ -211,3 +211,10 @@ def get_lesson_views(course_id, lesson_id): return TrackLesson.objects.filter( course_id=course_id, lesson_id=lesson_id, watched=True ).count(), course.students.count() + + +@register.simple_tag +def get_dict_value(dictionary, key): + return dictionary.get(key, None) + + diff --git a/yaksh/test_views.py b/yaksh/test_views.py index c2fa4ac..31066d1 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -2168,7 +2168,8 @@ class TestCourses(TestCase): # Teacher Login # Given # Add files to a lesson - lesson_file = SimpleUploadedFile("file1.txt", b"Test") + file_content = b"Test" + lesson_file = SimpleUploadedFile("file1.txt", file_content) django_file = File(lesson_file) lesson_file_obj = LessonFile() lesson_file_obj.lesson = self.lesson @@ -2203,18 +2204,18 @@ class TestCourses(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(courses.last().creator, self.teacher) self.assertEqual(courses.last().name, "Copy Of Python Course") - self.assertEqual(module.name, "Copy of demo module") + self.assertEqual(module.name, "demo module") self.assertEqual(module.creator, self.teacher) self.assertEqual(module.order, 0) self.assertEqual(len(units), 2) - self.assertEqual(cloned_lesson.name, "Copy of demo lesson") + self.assertEqual(cloned_lesson.name, "demo lesson") self.assertEqual(cloned_lesson.creator, self.teacher) - self.assertEqual(cloned_quiz.description, "Copy of demo quiz") + self.assertEqual(cloned_quiz.description, "demo quiz") self.assertEqual(cloned_quiz.creator, self.teacher) self.assertEqual(cloned_qp.__str__(), - "Question Paper for Copy of demo quiz") - self.assertEqual(os.path.basename(expected_lesson_files[0].file.name), - os.path.basename(actual_lesson_files[0].file.name)) + "Question Paper for demo quiz") + self.assertTrue(expected_lesson_files.exists()) + self.assertEquals(expected_lesson_files[0].file.read(), file_content) for lesson_file in self.all_files: file_path = lesson_file.file.path @@ -5655,10 +5656,12 @@ class TestShowStatistics(TestCase): "course_id": self.course.id}), follow=True ) + question_stats = response.context['question_stats'] self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/statistics_question.html') + self.assertIn(self.question, list(question_stats.keys())) self.assertSequenceEqual( - response.context['question_stats'][self.question], [1, 1] + list(question_stats.values())[0]['answered'], [1, 1] ) self.assertEqual(response.context['attempts'][0], 1) self.assertEqual(response.context['total'], 1) diff --git a/yaksh/views.py b/yaksh/views.py index f3e8668..50f9ded 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -230,6 +230,9 @@ def results_user(request): @email_verified def add_question(request, question_id=None): user = request.user + if not is_moderator(user): + raise Http404('You are not allowed to view this page !') + test_case_type = None if question_id is not None: |