diff options
-rw-r--r-- | yaksh/forms.py | 2 | ||||
-rw-r--r-- | yaksh/models.py | 26 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_exercise.html | 46 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_quiz.html | 43 | ||||
-rw-r--r-- | yaksh/test_views.py | 159 | ||||
-rw-r--r-- | yaksh/urls.py | 2 | ||||
-rw-r--r-- | yaksh/views.py | 36 |
7 files changed, 242 insertions, 72 deletions
diff --git a/yaksh/forms.py b/yaksh/forms.py index 8399bc9..9fd2eaa 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -176,7 +176,7 @@ class UserLoginForm(forms.Form): class ExerciseForm(forms.ModelForm): class Meta: model = Quiz - fields = ['description', 'view_answerpaper'] + fields = ['description', 'view_answerpaper', 'active'] class QuizForm(forms.ModelForm): diff --git a/yaksh/models.py b/yaksh/models.py index f0d1b2e..1e45851 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1196,13 +1196,14 @@ class QuestionPaper(models.Model): return ans_paper - def _is_attempt_allowed(self, user): + def _is_attempt_allowed(self, user, course_id): attempts = AnswerPaper.objects.get_total_attempt(questionpaper=self, - user=user) + user=user, + course_id=course_id) return attempts != self.quiz.attempts_allowed def can_attempt_now(self, user, course_id): - if self._is_attempt_allowed(user): + if self._is_attempt_allowed(user, course_id): last_attempt = AnswerPaper.objects.get_user_last_attempt( user=user, questionpaper=self, course_id=course_id ) @@ -1280,10 +1281,11 @@ class QuestionSet(models.Model): ############################################################################### class AnswerPaperManager(models.Manager): - def get_all_questions(self, questionpaper_id, attempt_number, + def get_all_questions(self, questionpaper_id, attempt_number, course_id, status='completed'): ''' Return a dict of question id as key and count as value''' papers = self.filter(question_paper_id=questionpaper_id, + course_id=course_id, attempt_number=attempt_number, status=status) all_questions = list() questions = list() @@ -1294,9 +1296,10 @@ class AnswerPaperManager(models.Manager): return Counter(questions) def get_all_questions_answered(self, questionpaper_id, attempt_number, - status='completed'): + course_id, status='completed'): ''' Return a dict of answered question id as key and count as value''' papers = self.filter(question_paper_id=questionpaper_id, + course_id=course_id, attempt_number=attempt_number, status=status) questions_answered = list() for paper in papers: @@ -1334,15 +1337,17 @@ class AnswerPaperManager(models.Manager): ).count() def get_question_statistics(self, questionpaper_id, attempt_number, - status='completed'): + course_id, status='completed'): ''' Return dict with question object as key and list as value The list contains two value, first the number of times a question was answered correctly, and second the number of times a question appeared in a quiz''' question_stats = {} questions_answered = self.get_all_questions_answered(questionpaper_id, - attempt_number) - questions = self.get_all_questions(questionpaper_id, attempt_number) + attempt_number, + course_id) + questions = self.get_all_questions(questionpaper_id, attempt_number, + course_id) all_questions = Question.objects.filter( id__in=set(questions), active=True @@ -1391,8 +1396,9 @@ class AnswerPaperManager(models.Manager): def get_user_answerpapers(self, user): return self.filter(user=user) - def get_total_attempt(self, questionpaper, user): - return self.filter(question_paper=questionpaper, user=user).count() + def get_total_attempt(self, questionpaper, user, course_id): + return self.filter(question_paper=questionpaper, user=user, + course_id=course_id).count() def get_users_for_questionpaper(self, questionpaper_id, course_id): return self._get_answerpapers_for_quiz(questionpaper_id, course_id, diff --git a/yaksh/templates/yaksh/add_exercise.html b/yaksh/templates/yaksh/add_exercise.html index dac35d4..77e3ee8 100644 --- a/yaksh/templates/yaksh/add_exercise.html +++ b/yaksh/templates/yaksh/add_exercise.html @@ -14,17 +14,41 @@ {% block content %} <form name=frm id=frm action="" method="post" > - {% csrf_token %} - <center> - <table class="span1 table"> - {{ form.as_table }} - </table> - <br/><br/> - </center> + {% csrf_token %} + <center> + <table class="span1 table"> + {{ form.as_table }} + </table> + <br/><br/> + </center> + <center><button class="btn" type="submit" id="submit" name="save_exercise"> Save + </button> - <center><button class="btn" type="submit" id="submit" name="questionpaper"> Save - </button> - - <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center> + <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center> </form> +{% if exercise and course_id %} + {% if exercise.questionpaper_set.get.id %} + <center> + <h4>You can check the quiz by attempting it in the following modes:</h4> + <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ exercise.id }}/{{exercise.questionpaper_set.get.id}}/{{course_id}}" class="btn btn-primary">View Question Paper</a> + <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{exercise.id}}/{{course_id}}/");'>User Mode</button> + + <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{exercise.id}}/{{course_id}}/");'> + God Mode</button> + <a data-toggle="collapse" data-target="#help"> + <span class="glyphicon glyphicon-info-sign">Help</span></a> + <div id="help" class="collapse"> + <br/> + <ul> + <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. - + <ul> + <li><i>Quiz will have the same duration as that of the original quiz.</li> + <li>Quiz won't start if the course is inactive or the quiz time has expired.</li> + <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li> + </ul> + </p> + <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p> + </div> + {% endif %} +{% endif %} {% endblock %} diff --git a/yaksh/templates/yaksh/add_quiz.html b/yaksh/templates/yaksh/add_quiz.html index d3705e3..684f804 100644 --- a/yaksh/templates/yaksh/add_quiz.html +++ b/yaksh/templates/yaksh/add_quiz.html @@ -35,31 +35,34 @@ </form> <br> -{% if quiz_id and course_id %} +{% if quiz and course_id %} + {% if quiz.questionpaper_set.get.id %} <center> <h4>You can check the quiz by attempting it in the following modes:</h4> - <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz_id}}/{{course_id}}/");'>User Mode</button> - - <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz_id}}/{{course_id}}/");'> - God Mode</button> + <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ quiz.id }}/{{quiz.questionpaper_set.get.id}}/{{course_id}}" class="btn btn-primary">View Question Paper</a> + <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz.id}}/{{course_id}}/");'>User Mode</button> + + <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz.id}}/{{course_id}}/");'> + God Mode</button> <a data-toggle="collapse" data-target="#help"> <span class="glyphicon glyphicon-info-sign">Help</span></a> - <div id="help" class="collapse"> - <br/> - <ul> - <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. - - <ul> - <li><i>Quiz will have the same duration as that of the original quiz.</li> - <li>Quiz won't start if the course is inactive or the quiz time has expired.</li> - <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li> - </ul> - </p> - <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p> - </div> - {% endif %} - <style type="text/css"> + <div id="help" class="collapse"> + <br/> + <ul> + <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. - + <ul> + <li><i>Quiz will have the same duration as that of the original quiz.</li> + <li>Quiz won't start if the course is inactive or the quiz time has expired.</li> + <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li> + </ul> + </p> + <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p> + </div> + {% endif %} +{% endif %} +<style type="text/css"> #rendered_text{ width: 550px; } - </style> +</style> {% endblock %} diff --git a/yaksh/test_views.py b/yaksh/test_views.py index ff2b5a7..9be8d13 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1230,6 +1230,7 @@ class TestAddQuiz(TestCase): kwargs={'quiz_id': self.exercise.id}), data={ 'description': 'updated demo exercise', + 'active': True } ) @@ -1257,6 +1258,7 @@ class TestAddQuiz(TestCase): response = self.client.post(reverse('yaksh:add_exercise'), data={ 'description': "Demo Exercise", + 'active': True } ) quiz_list = Quiz.objects.all().order_by('-id') @@ -1916,6 +1918,25 @@ class TestAddCourse(TestCase): timezone='UTC' ) + # Create a teacher + self.teacher_plaintext_pass = 'demo_teacher' + self.teacher = User.objects.create_user( + username='demo_teacher', + password=self.teacher_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.teacher, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + # Create Student self.student_plaintext_pass = 'demo_student' self.student = User.objects.create_user( @@ -1928,10 +1949,13 @@ class TestAddCourse(TestCase): # Add to moderator group self.mod_group.user_set.add(self.user) + self.mod_group.user_set.add(self.teacher) self.course = Course.objects.create(name="Python Course", enrollment="Enroll Request", creator=self.user) + self.course.teachers.add(self.teacher) + self.pre_req_quiz = Quiz.objects.create( start_date_time=datetime(2014, 2, 1, 5, 8, 15, 0, tzone), end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone), @@ -2021,6 +2045,33 @@ class TestAddCourse(TestCase): self.assertRedirects(response, '/exam/manage/courses', target_status_code=301) + def test_add_course_teacher_cannot_be_creator(self): + """ + Teacher editing the course should not become creator + """ + self.client.login( + username=self.teacher.username, + password=self.teacher_plaintext_pass + ) + + response = self.client.post(reverse('yaksh:edit_course', + kwargs={"course_id": self.course.id}), + data={'name': 'Teacher_course', + 'active': True, + 'enrollment': 'open', + 'start_enroll_time': '2016-01-10 09:00:15', + 'end_enroll_time': '2016-01-15 09:00:15', + } + ) + updated_course = Course.objects.get(id=self.course.id) + self.assertEqual(updated_course.name, 'Teacher_course') + self.assertEqual(updated_course.enrollment, 'open') + self.assertEqual(updated_course.active, True) + self.assertEqual(response.status_code, 302) + self.assertEqual(updated_course.creator, self.user) + self.assertRedirects(response, '/exam/manage/courses', + target_status_code=301) + class TestCourseDetail(TestCase): def setUp(self): @@ -3989,8 +4040,27 @@ class TestQuestionPaper(TestCase): timezone='UTC' ) + self.teacher_plaintext_pass = 'demo_teacher' + self.teacher = User.objects.create_user( + username='demo_teacher', + password=self.teacher_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.teacher, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + # Add to moderator group self.mod_group.user_set.add(self.user) + self.mod_group.user_set.add(self.teacher) self.course = Course.objects.create( name="Python Course", @@ -4005,6 +4075,15 @@ class TestQuestionPaper(TestCase): creator=self.user ) + self.demo_quiz = Quiz.objects.create( + start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone), + end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone), + duration=30, active=True, instructions="Demo Instructions", + attempts_allowed=-1, time_between_attempts=0, + description='demo quiz 2', pass_criteria=40, + creator=self.user + ) + self.learning_unit = LearningUnit.objects.create( order=1, type="quiz", quiz=self.quiz) self.learning_module = LearningModule.objects.create( @@ -4026,8 +4105,6 @@ class TestQuestionPaper(TestCase): ) self.mcq_based_testcase.save() - ordered_questions = str(self.question_mcq.id) - # Mcc Question self.question_mcc = Question.objects.create( summary="Test_mcc_question", description="Test MCC", @@ -4041,8 +4118,6 @@ class TestQuestionPaper(TestCase): ) self.mcc_based_testcase.save() - ordered_questions = ordered_questions + str(self.question_mcc.id) - # Integer Question self.question_int = Question.objects.create( summary="Test_mcc_question", description="Test MCC", @@ -4055,8 +4130,6 @@ class TestQuestionPaper(TestCase): ) self.int_based_testcase.save() - ordered_questions = ordered_questions + str(self.question_int.id) - # String Question self.question_str = Question.objects.create( summary="Test_mcc_question", description="Test MCC", @@ -4083,17 +4156,19 @@ class TestQuestionPaper(TestCase): ) self.float_based_testcase.save() - ordered_questions = ordered_questions + str(self.question_float.id) - - questions_list = [self.question_mcq, self.question_mcc, - self.question_int, self.question_str, - self.question_float] - + self.questions_list = [self.question_mcq, self.question_mcc, + self.question_int, self.question_str, + self.question_float] + questions_order = ",".join([ + str(self.question_mcq.id), str(self.question_mcc.id), + str(self.question_int.id), str(self.question_str.id), + str(self.question_float.id) + ]) self.question_paper = QuestionPaper.objects.create( quiz=self.quiz, - total_marks=5.0, fixed_question_order=ordered_questions + total_marks=5.0, fixed_question_order=questions_order ) - self.question_paper.fixed_questions.add(*questions_list) + self.question_paper.fixed_questions.add(*self.questions_list) self.answerpaper = AnswerPaper.objects.create( user=self.user, question_paper=self.question_paper, attempt_number=1, @@ -4102,12 +4177,14 @@ class TestQuestionPaper(TestCase): user_ip="127.0.0.1", status="inprogress", passed=False, percent=0, marks_obtained=0, course=self.course ) - self.answerpaper.questions.add(*questions_list) + self.answerpaper.questions.add(*self.questions_list) def tearDown(self): self.client.logout() self.user.delete() + self.teacher.delete() self.quiz.delete() + self.demo_quiz.delete() self.course.delete() self.answerpaper.delete() self.question_mcq.delete() @@ -4381,6 +4458,58 @@ class TestQuestionPaper(TestCase): wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id) self.assertEqual(wrong_answer_paper.marks_obtained, 0) + def test_design_questionpaper(self): + """ Test design Question Paper """ + + # Should fail if Question paper is not the one which is associated + # with a quiz + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.demo_quiz.id, + "questionpaper_id": self.question_paper.id})) + self.assertEqual(response.status_code, 404) + + self.client.login( + username=self.teacher.username, + password=self.teacher_plaintext_pass + ) + + # Should not allow teacher to view question paper + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.quiz.id, + "questionpaper_id": self.question_paper.id})) + + self.assertEqual(response.status_code, 404) + + # Should not allow teacher to view question paper + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.quiz.id, + "course_id": self.course.id, + "questionpaper_id": self.question_paper.id})) + + self.assertEqual(response.status_code, 404) + + # Should allow course teacher to view question paper + # Add teacher to the course + self.course.teachers.add(self.teacher) + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.quiz.id, + "course_id": self.course.id, + "questionpaper_id": self.question_paper.id})) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/design_questionpaper.html') + self.assertEqual(response.context['fixed_questions'], + self.questions_list) + self.assertEqual(response.context['qpaper'], self.question_paper) + class TestLearningModule(TestCase): def setUp(self): diff --git a/yaksh/urls.py b/yaksh/urls.py index 7eca5af..08c2091 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -78,6 +78,8 @@ urlpatterns = [ name='design_questionpaper'), url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/(?P<questionpaper_id>\d+)/$', views.design_questionpaper, name='designquestionpaper'), + url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$', + views.design_questionpaper, name='designquestionpaper'), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$', views.show_statistics, name="show_statistics"), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/(?P<course_id>\d+)/$', diff --git a/yaksh/views.py b/yaksh/views.py index 637b35c..2bc5dfe 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -320,10 +320,9 @@ def add_quiz(request, quiz_id=None, course_id=None): return my_redirect("/exam/manage/courses/") else: - quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None form = QuizForm(instance=quiz) - context["quiz_id"] = quiz_id context["course_id"] = course_id + context["quiz"] = quiz context["form"] = form return my_render_to_response( 'yaksh/add_quiz.html', context, context_instance=ci @@ -346,7 +345,7 @@ def add_exercise(request, quiz_id=None, course_id=None): if course_id: course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): - raise Http404('This quiz does not belong to you') + raise Http404('This Course does not belong to you') context = {} if request.method == "POST": @@ -370,9 +369,8 @@ def add_exercise(request, quiz_id=None, course_id=None): return my_redirect("/exam/manage/courses/") else: - quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None form = ExerciseForm(instance=quiz) - context["quiz_id"] = quiz_id + context["exercise"] = quiz context["course_id"] = course_id context["form"] = form return my_render_to_response( @@ -941,14 +939,14 @@ def add_course(request, course_id=None): raise Http404("You are not allowed to view this course") else: course = None - if not is_moderator(user): raise Http404('You are not allowed to view this page') if request.method == 'POST': form = CourseForm(request.POST, instance=course) if form.is_valid(): new_course = form.save(commit=False) - new_course.creator = user + if course_id is None: + new_course.creator = user new_course.save() return my_redirect('/exam/manage/courses') else: @@ -1167,7 +1165,7 @@ def show_statistics(request, questionpaper_id, attempt_number=None, course_id): return my_redirect('/exam/manage/') question_stats = AnswerPaper.objects.get_question_statistics( - questionpaper_id, attempt_number + questionpaper_id, attempt_number, course_id ) context = {'question_stats': question_stats, 'quiz': quiz, 'questionpaper_id': questionpaper_id, @@ -1301,14 +1299,21 @@ def _remove_already_present(questionpaper_id, questions): @login_required @email_verified -def design_questionpaper(request, quiz_id, questionpaper_id=None): +def design_questionpaper(request, quiz_id, questionpaper_id=None, + course_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') - quiz = Quiz.objects.get(id=quiz_id) - if not quiz.creator == user: - raise Http404('This course does not belong to you') + if quiz_id: + quiz = get_object_or_404(Quiz, pk=quiz_id) + if quiz.creator != user and not course_id: + raise Http404('This quiz does not belong to you') + if course_id: + course = get_object_or_404(Course, pk=course_id) + if not course.is_creator(user) and not course.is_teacher(user): + raise Http404('This Course does not belong to you') + filter_form = QuestionFilterForm(user=user) questions = None marks = None @@ -1316,7 +1321,8 @@ def design_questionpaper(request, quiz_id, questionpaper_id=None): if questionpaper_id is None: question_paper = QuestionPaper.objects.get_or_create(quiz_id=quiz_id)[0] else: - question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id) + question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id, + quiz_id=quiz_id) qpaper_form = QuestionPaperForm(instance=question_paper) if request.method == 'POST': @@ -1533,9 +1539,9 @@ def download_quiz_csv(request, course_id, quiz_id): questions = question_paper.get_question_bank() answerpapers = AnswerPaper.objects.filter(question_paper=question_paper, - attempt_number=attempt_number) + attempt_number=attempt_number, course_id=course_id) if not answerpapers: - return monitor(request, quiz_id) + return monitor(request, quiz_id, course_id) response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="{0}-{1}-attempt{2}.csv"'.format( course.name.replace('.', ''), quiz.description.replace('.', ''), |