summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/models.py16
-rw-r--r--yaksh/templates/yaksh/monitor.html44
-rw-r--r--yaksh/test_models.py88
-rw-r--r--yaksh/test_views.py20
-rw-r--r--yaksh/urls.py4
-rw-r--r--yaksh/views.py115
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">&times;</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