summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.txt7
-rw-r--r--online_test/__init__.py2
-rw-r--r--yaksh/migrations/0028_auto_20210112_1039.py25
-rw-r--r--yaksh/models.py57
-rw-r--r--yaksh/templates/yaksh/statistics_question.html23
-rw-r--r--yaksh/templatetags/custom_filters.py7
-rw-r--r--yaksh/test_views.py19
-rw-r--r--yaksh/views.py3
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: