diff options
-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 |