diff options
author | prathamesh | 2017-10-26 14:35:08 +0530 |
---|---|---|
committer | prathamesh | 2017-10-26 14:35:08 +0530 |
commit | 1f554e7505f5a6aa1b796b2e31e1541188af56da (patch) | |
tree | cbe0f88b2dd7dd8ef37f391f89bb8f578cae40e0 | |
parent | 97a277cf5b86f60525f94302d5b04de420ad7212 (diff) | |
download | online_test-1f554e7505f5a6aa1b796b2e31e1541188af56da.tar.gz online_test-1f554e7505f5a6aa1b796b2e31e1541188af56da.tar.bz2 online_test-1f554e7505f5a6aa1b796b2e31e1541188af56da.zip |
CSV download for quiz enhanced
CSV download for a quiz now shows question wise grades.
Also, for a given attempt all the users from the course are entered in
the CSV. If the user has not attempted then a dash '-' is put under the
grades.
Also, handles random questions, if a question paper has questions
selected from pool of questions then all the questions are entered in
the CSV. 'NA' is put under the question grade if that question has not
come in the question/answer paper for that given user.
-rw-r--r-- | yaksh/models.py | 16 | ||||
-rw-r--r-- | yaksh/templates/yaksh/monitor.html | 44 | ||||
-rw-r--r-- | yaksh/test_models.py | 88 | ||||
-rw-r--r-- | yaksh/test_views.py | 20 | ||||
-rw-r--r-- | yaksh/urls.py | 4 | ||||
-rw-r--r-- | yaksh/views.py | 115 |
6 files changed, 238 insertions, 49 deletions
diff --git a/yaksh/models.py b/yaksh/models.py index 787daa6..97f2f0b 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -826,6 +826,13 @@ class QuestionPaper(models.Model): objects = QuestionPaperManager() + def get_question_bank(self): + ''' Gets all the questions in the question paper''' + questions = list(self.fixed_questions.all()) + for random_set in self.random_questions.all(): + questions += list(random_set.questions.all()) + return questions + def _create_duplicate_questionpaper(self, quiz): new_questionpaper = QuestionPaper.objects.create(quiz=quiz, shuffle_questions=self.shuffle_questions, @@ -1185,6 +1192,15 @@ class AnswerPaper(models.Model): objects = AnswerPaperManager() + def get_per_question_score(self, question_id): + if question_id not in self.get_questions().values_list('id', flat=True): + return 'NA' + answer = self.get_latest_answer(question_id) + if answer: + return answer.marks + else: + return 0 + def current_question(self): """Returns the current active question to display.""" unanswered_questions = self.questions_unanswered.all() diff --git a/yaksh/templates/yaksh/monitor.html b/yaksh/templates/yaksh/monitor.html index 9ce0dc4..2e7097f 100644 --- a/yaksh/templates/yaksh/monitor.html +++ b/yaksh/templates/yaksh/monitor.html @@ -76,7 +76,10 @@ $(document).ready(function() <p>Papers in progress:<b> {{ inprogress_papers }} </b></p> <p><a href="{{URL_ROOT}}/exam/manage/statistics/question/{{papers.0.question_paper.id}}">Question Statisitics</a></p> -<p><a href="{{URL_ROOT}}/exam/manage/monitor/download_csv/{{papers.0.question_paper.id}}">Download CSV</a></p> +<p> + <button type="button" class="btn btn-info btn-lg" data-toggle="modal" data-target="#csvModal"> + Download CSV <span class="glyphicon glyphicon-save"></span> + </button></p> <table id="result-table" class="tablesorter table table table-striped"> <thead> <tr> @@ -115,4 +118,43 @@ $(document).ready(function() <h4>No Quiz Found</h4> {% endif %} {% endif %} + +<!-- CSV Modal --> +<div class="modal fade" id="csvModal" role="dialog"> + <div class="modal-dialog"> + + <!-- Modal content--> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal">×</button> + <h3 class="modal-title">Uncheck unwanted columns</h3> + </div> + <form action="{{URL_ROOT}}/exam/manage/download_quiz_csv/{{ quiz.course.id }}/{{ quiz.id }}/" method="post"> + {% csrf_token %} + <div class="modal-body"> + {% for field in csv_fields %} + <div class="form-check form-check-inline"> + <label class="form-check-label"> + <input class="form-check-input" name="csv_fields" type="checkbox" value="{{ field }}" checked> {{ field }} + </label> + </div> + {% endfor %} + <b>Select Attempt Number: Default latest attempt</b> + <select class="form-control" id = "attempt_number"> + <option selected="">Latest</option> + {%for attempt_number in attempt_numbers %} + <option value = "{{ attempt_number }}"> {{ attempt_number }}</option> + {% endfor %} + </select> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary"> Download <span class="glyphicon glyphicon-save"></span></button> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + </div> + </form> + </div> + </div> +</div> + + {% endblock %} diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 00506cd..d3c4f6f 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -342,6 +342,26 @@ class QuestionPaperTestCases(unittest.TestCase): self.questions = Question.objects.filter(active=True) self.quiz = Quiz.objects.get(description="demo quiz 1") + # create question paper with only fixed questions + self.question_paper_fixed_questions = QuestionPaper.objects.create( + quiz=self.quiz) + self.question_paper_fixed_questions.fixed_questions.add( + self.questions.get(id=1), self.questions.get(id=10)) + + # create question paper with only random questions + self.question_paper_random_questions = QuestionPaper.objects.create( + quiz=self.quiz) + self.question_set_random = QuestionSet.objects.create(marks=2, + num_questions=2) + self.question_set_random.questions.add(self.questions.get(id=3), + self.questions.get(id=5), self.questions.get(id=7)) + self.question_paper_random_questions.random_questions.add( + self.question_set_random) + + # create question paper with no questions + self.question_paper_no_questions = QuestionPaper.objects.create( + quiz=self.quiz) + # create question paper self.question_paper = QuestionPaper.objects.create(quiz=self.quiz, total_marks=0.0, @@ -399,6 +419,39 @@ class QuestionPaperTestCases(unittest.TestCase): self.trial_course = Course.objects.create_trial_course(self.user) self.trial_quiz = Quiz.objects.create_trial_quiz(self.trial_course, self.user) + + def test_get_question_bank(self): + # Given + ids = [4, 6, 7, 8, 9, 10, 12, 13, 14, 15] + questions = list(Question.objects.filter(id__in=ids)) + # When + question_bank = self.question_paper.get_question_bank() + # Then + self.assertSequenceEqual(questions, question_bank) + + # Given + ids = [1, 10] + questions = list(Question.objects.filter(id__in=ids)) + # When + question_bank = self.question_paper_fixed_questions.get_question_bank() + # Then + self.assertSequenceEqual(questions, question_bank) + + # Given + ids = [3, 5, 7] + questions = list(Question.objects.filter(id__in=ids)) + # When + question_bank = self.question_paper_random_questions.get_question_bank() + # Then + self.assertSequenceEqual(questions, question_bank) + + # Given + questions = [] + # When + question_bank = self.question_paper_no_questions.get_question_bank() + # Then + self.assertSequenceEqual(questions, question_bank) + def test_questionpaper(self): """ Test question paper""" self.assertEqual(self.question_paper.quiz.description, 'demo quiz 1') @@ -507,12 +560,13 @@ class AnswerPaperTestCases(unittest.TestCase): self.quiz = Quiz.objects.get(description='demo quiz 1') self.question_paper = QuestionPaper(quiz=self.quiz, total_marks=3) self.question_paper.save() - self.questions = Question.objects.all()[0:3] + self.questions = Question.objects.all()[0:4] self.start_time = timezone.now() self.end_time = self.start_time + timedelta(minutes=20) self.question1 = self.questions[0] self.question2 = self.questions[1] self.question3 = self.questions[2] + self.question4 = self.questions[3] # create answerpaper self.answerpaper = AnswerPaper(user=self.user, @@ -544,10 +598,17 @@ class AnswerPaperTestCases(unittest.TestCase): marks=0, error=json.dumps(['error1', 'error2']) ) + self.answer_correct = Answer(question=self.question4, + answer="correct answer", + correct=True, marks=1, + error=json.dumps([]) + ) self.answer_right.save() self.answer_wrong.save() + self.answer_correct.save() self.answerpaper.answers.add(self.answer_right) self.answerpaper.answers.add(self.answer_wrong) + self.answerpaper.answers.add(self.answer_correct) self.answer1 = Answer.objects.create( question=self.question1, @@ -614,6 +675,31 @@ class AnswerPaperTestCases(unittest.TestCase): self.user2, self.ip, 1 ) + def test_get_per_question_score(self): + # Given + question_id = self.question4.id + expected_score = 1 + # When + score = self.answerpaper.get_per_question_score(question_id) + # Then + self.assertEqual(score, expected_score) + + # Given + question_id = self.question2.id + expected_score = 0 + # When + score = self.answerpaper.get_per_question_score(question_id) + # Then + self.assertEqual(score, expected_score) + + # Given + question_id = 131 + expected_score = 'NA' + # When + score = self.answerpaper.get_per_question_score(question_id) + # Then + self.assertEqual(score, expected_score) + def test_validate_and_regrade_mcc_correct_answer(self): # Given mcc_answer = [str(self.mcc_based_testcase.id)] diff --git a/yaksh/test_views.py b/yaksh/test_views.py index dc06126..af9ac7d 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -2904,6 +2904,7 @@ class TestDownloadcsv(TestCase): self.mod_group.user_set.add(self.user) self.course = Course.objects.create(name="Python Course", enrollment="Enroll Request", creator=self.user) + self.course.students.add(self.student) self.quiz = Quiz.objects.create( start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone), @@ -2956,8 +2957,9 @@ class TestDownloadcsv(TestCase): username=self.student.username, password=self.student_plaintext_pass ) - response = self.client.get(reverse('yaksh:download_csv', - kwargs={"questionpaper_id": self.question_paper.id}), + response = self.client.get(reverse('yaksh:download_quiz_csv', + kwargs={"course_id": self.course.id, + "quiz_id": self.quiz.id}), follow=True ) self.assertEqual(response.status_code, 404) @@ -2985,8 +2987,9 @@ class TestDownloadcsv(TestCase): username=self.student.username, password=self.student_plaintext_pass ) - response = self.client.get(reverse('yaksh:download_csv', - kwargs={"questionpaper_id": self.question_paper.id}), + response = self.client.get(reverse('yaksh:download_quiz_csv', + kwargs={"course_id": self.course.id, + "quiz_id": self.quiz.id}), follow=True ) self.assertEqual(response.status_code, 404) @@ -3031,11 +3034,14 @@ class TestDownloadcsv(TestCase): username=self.user.username, password=self.user_plaintext_pass ) - response = self.client.get(reverse('yaksh:download_csv', - kwargs={'questionpaper_id': self.question_paper.id}), + response = self.client.get(reverse('yaksh:download_quiz_csv', + kwargs={"course_id": self.course.id, + "quiz_id": self.quiz.id}), + follow=True ) - file_name = "{0}.csv".format(self.quiz.description) + file_name = "{0}-{1}-attempt{2}.csv".format(self.course.name.replace('.', ''), + self.quiz.description.replace('.', ''), 1) self.assertEqual(response.status_code, 200) self.assertEqual(response.get('Content-Disposition'), 'attachment; filename="{0}"'.format(file_name)) diff --git a/yaksh/urls.py b/yaksh/urls.py index 2e25bee..849a810 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -56,8 +56,8 @@ urlpatterns = [ views.show_statistics, name="show_statistics"), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/$', views.show_statistics, name="show_statistics"), - url(r'^manage/monitor/download_csv/(?P<questionpaper_id>\d+)/$', - views.download_csv, name="download_csv"), + url(r'^manage/download_quiz_csv/(?P<course_id>\d+)/(?P<quiz_id>\d+)/$', + views.download_quiz_csv, name="download_quiz_csv"), url(r'^manage/duplicate_course/(?P<course_id>\d+)/$', views.duplicate_course, name='duplicate_course'), url(r'manage/courses/$', views.courses, name='courses'), diff --git a/yaksh/views.py b/yaksh/views.py index b4cb844..8bd65bb 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -991,7 +991,12 @@ def monitor(request, quiz_id=None): papers = [] q_paper = None latest_attempts = [] + attempt_numbers = [] else: + if q_paper: + attempt_numbers = AnswerPaper.objects.get_attempt_numbers(q_paper.last().id) + else: + attempt_numbers = [] latest_attempts = [] papers = AnswerPaper.objects.filter(question_paper=q_paper).order_by( 'user__profile__roll_number' @@ -1007,11 +1012,15 @@ def monitor(request, quiz_id=None): attempt_number=last_attempt['last_attempt_num'] ) ) + csv_fields = ['name', 'username', 'roll_number', 'institute', + 'department', 'questions', 'total', 'out_of', 'percentage', 'status'] context = { "papers": papers, "quiz": quiz, "msg": "Quiz Results", - "latest_attempts": latest_attempts + "latest_attempts": latest_attempts, + "csv_fields": csv_fields, + "attempt_numbers": attempt_numbers } return my_render_to_response('yaksh/monitor.html', context, context_instance=ci) @@ -1264,49 +1273,79 @@ def user_data(request, user_id, questionpaper_id=None): context_instance=RequestContext(request)) +def _expand_questions(questions, field_list): + i = field_list.index('questions') + field_list.remove('questions') + for question in questions: + field_list.insert(i, '{0}-{1}'.format(question.summary, question.points)) + return field_list + + @login_required @email_verified -def download_csv(request, questionpaper_id): - user = request.user - if not is_moderator(user): +def download_quiz_csv(request, course_id, quiz_id): + current_user = request.user + if not is_moderator(current_user): raise Http404('You are not allowed to view this page!') - quiz = Quiz.objects.get(questionpaper=questionpaper_id) + course = get_object_or_404(Course, id=course_id) + quiz = get_object_or_404(Quiz, id=quiz_id) + if not course.is_creator(current_user) and not course.is_teacher(current_user): + raise Http404('The quiz does not belong to your course') + users = course.get_enrolled().order_by('first_name') + if not users: + return monitor(request, quiz_id) + csv_fields = [] + attempt_number = None + question_paper = quiz.questionpaper_set.last() + last_attempt_number =AnswerPaper.objects.get_attempt_numbers(question_paper.id).last() + if request.method == 'POST': + csv_fields = request.POST.getlist('csv_fields') + attempt_number = request.POST.get('attempt_number', last_attempt_number) + if not csv_fields: + csv_fields = ['name', 'username', 'roll_number', 'institute', + 'department', 'questions', 'total', 'out_of', 'percentage', 'status'] + if not attempt_number: + attempt_number = last_attempt_number + + questions = question_paper.get_question_bank() + answerpapers = AnswerPaper.objects.filter(question_paper=question_paper, + attempt_number=attempt_number) + if not answerpapers: + return monitor(request, quiz_id) - if not quiz.course.is_creator(user) and not quiz.course.is_teacher(user): - raise Http404('The question paper does not belong to your course') - papers = AnswerPaper.objects.get_latest_attempts(questionpaper_id) - if not papers: - return monitor(request, questionpaper_id) response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="{0}.csv"'.format( - (quiz.description).replace('.', '')) + response['Content-Disposition'] = 'attachment; filename="{0}-{1}-attempt{2}.csv"'.format( + course.name.replace('.', ''), quiz.description.replace('.', ''), + attempt_number) writer = csv.writer(response) - header = [ - 'name', - 'username', - 'roll_number', - 'institute', - 'marks_obtained', - 'total_marks', - 'percentage', - 'questions', - 'questions_answered', - 'status' - ] - writer.writerow(header) - for paper in papers: - row = [ - '{0} {1}'.format(paper.user.first_name, paper.user.last_name), - paper.user.username, - paper.user.profile.roll_number, - paper.user.profile.institute, - paper.marks_obtained, - paper.question_paper.total_marks, - paper.percent, - paper.questions.all(), - paper.questions_answered.all(), - paper.status - ] + if 'questions' in csv_fields: + csv_fields = _expand_questions(questions, csv_fields) + writer.writerow(csv_fields) + + csv_fields_values = {'name': 'user.get_full_name().title()', + 'roll_number': 'user.profile.roll_number', + 'institute': 'user.profile.institute', + 'department': 'user.profile.department', + 'username': 'user.username', 'total': 'answerpaper.marks_obtained', + 'out_of': 'question_paper.total_marks', + 'percentage': 'answerpaper.percent', 'status': 'answerpaper.status'} + questions_scores = {} + for question in questions: + questions_scores['{0}-{1}'.format(question.summary, question.points)] \ + = 'answerpaper.get_per_question_score({0})'.format(question.id) + csv_fields_values.update(questions_scores) + + for user in users: + row = [] + answerpaper = None + papers = answerpapers.filter(user=user) + if papers: + answerpaper = papers.first() + for field in csv_fields: + try: + row.append(eval(csv_fields_values[field])) + except AttributeError: + row.append('-') writer.writerow(row) return response |