diff options
-rw-r--r-- | .travis.yml | 4 | ||||
-rw-r--r-- | online_test/urls.py | 2 | ||||
-rw-r--r-- | requirements.txt | 6 | ||||
-rw-r--r-- | yaksh/models.py | 8 | ||||
-rw-r--r-- | yaksh/templates/yaksh/question.html | 5 | ||||
-rw-r--r-- | yaksh/test_views.py | 1085 | ||||
-rw-r--r-- | yaksh/urls.py | 33 | ||||
-rw-r--r-- | yaksh/views.py | 49 |
8 files changed, 1133 insertions, 59 deletions
diff --git a/.travis.yml b/.travis.yml index 6a4c8cf..508336d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ python: - "2.7" env: - - DJANGO=1.6.4 - - DJANGO=1.9 + - DJANGO=1.8.13 + - DJANGO=1.9.5 # command to install dependencies install: diff --git a/online_test/urls.py b/online_test/urls.py index 9c55574..4d8c042 100644 --- a/online_test/urls.py +++ b/online_test/urls.py @@ -9,5 +9,5 @@ urlpatterns = [ # url(r'^blog/', include('blog.urls')), url(r'^admin/', include(admin.site.urls)), - url(r'^exam/', include('yaksh.urls')), + url(r'^exam/', include('yaksh.urls', namespace='yaksh', app_name='yaksh')), ] diff --git a/requirements.txt b/requirements.txt index 2ace8a9..d3aa19a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django==1.9.5, -mysql-python==1.2.5, -django-taggit==0.18.1, +django==1.9.5 +mysql-python==1.2.5 +django-taggit==0.18.1 pytz==2016.4 diff --git a/yaksh/models.py b/yaksh/models.py index 7035d1e..c48eef1 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -42,7 +42,7 @@ test_case_types = ( attempts = [(i, i) for i in range(1, 6)] attempts.append((-1, 'Infinite')) -days_between_attempts = ((j, j) for j in range(401)) +days_between_attempts = [(j, j) for j in range(401)] test_status = ( ('inprogress', 'Inprogress'), @@ -58,6 +58,10 @@ def get_model_class(model): return model_class +def has_profile(user): + """ check if user has profile """ + return True if hasattr(user, 'profile') else False + ############################################################################### class CourseManager(models.Manager): @@ -159,7 +163,7 @@ class Profile(models.Model): department = models.CharField(max_length=64) position = models.CharField(max_length=64) timezone = models.CharField(max_length=64, - choices=[(tz, tz) for tz in pytz.common_timezones]) + choices=[(tz, tz) for tz in pytz.common_timezones]) ############################################################################### diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index b8be99f..54fb30b 100644 --- a/yaksh/templates/yaksh/question.html +++ b/yaksh/templates/yaksh/question.html @@ -189,13 +189,14 @@ function call_skip(url) <br><button class="btn" type="submit" name="check" id="check" onClick="return validate();">Upload</button> {% else %} <button class="btn" type="submit" name="check" id="check" onClick="submitCode();">Check Answer</button> + <button class="btn" type="button" onclick="reset_editor()" name="reset" id="reset">Reset Answer</button> + {% endif %} {% if paper.unanswered.all|length != 1 %} <button class="btn" onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')" name="skip" id="skip">Attempt Later</button> {% endif %} - <button class="btn" type="button" onclick="reset_editor()" name="reset" id="reset">Reset Answer</button> </form> -</div> +</div> <!-- Modal --> <div class="modal fade " id="upload_alert" > diff --git a/yaksh/test_views.py b/yaksh/test_views.py new file mode 100644 index 0000000..d673ac3 --- /dev/null +++ b/yaksh/test_views.py @@ -0,0 +1,1085 @@ +from datetime import datetime +import pytz + +from django.contrib.auth.models import Group +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.test import Client + +from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ + QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ + StdoutBasedTestCase, has_profile + + +class TestProfile(TestCase): + def setUp(self): + self.client = Client() + + # Create User without profile + self.user1_plaintext_pass = 'demo1' + self.user1 = User.objects.create_user( + username='demo_user1', + password=self.user1_plaintext_pass, + email='demo1@test.com' + ) + + # Create User with profile + self.user2_plaintext_pass = 'demo2' + self.user2 = User.objects.create_user( + username='demo_user2', + password=self.user2_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo2@test.com' + ) + + Profile.objects.create( + user=self.user2, + roll_number=10, + institute='IIT', + department='Chemical', + position='Student', + timezone='UTC' + ) + + def tearDown(self): + self.client.logout() + self.user1.delete() + self.user2.delete() + + def test_has_profile_for_user_without_profile(self): + """ + If no profile exists for user passed as argument return False + """ + has_profile_status = has_profile(self.user1) + self.assertFalse(has_profile_status) + + def test_has_profile_for_user_with_profile(self): + """ + If profile exists for user passed as argument return True + """ + has_profile_status = has_profile(self.user2) + self.assertTrue(has_profile_status) + + + def test_view_profile_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:view_profile'), follow=True) + redirect_destination = '/exam/login/?next=%2Fexam%2Fviewprofile%2F' + self.assertRedirects(response, redirect_destination) + + def test_view_profile_get_for_user_without_profile(self): + """ + If no profile exists a blank profile form will be displayed + """ + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + response = self.client.get(reverse('yaksh:view_profile')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/editprofile.html') + + def test_view_profile_get_for_user_with_profile(self): + """ + If profile exists a viewprofile.html template will be rendered + """ + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + response = self.client.get(reverse('yaksh:view_profile')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/view_profile.html') + + def test_edit_profile_post(self): + """ + POST request to edit_profile view should update the user's profile + """ + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + response = self.client.post(reverse('yaksh:edit_profile'), + data={ + 'user': self.user2, + 'first_name': 'new_first_name', + 'last_name': 'new_last_name', + 'roll_number': 20, + 'institute': 'new_institute', + 'department': 'Aerospace', + 'position': 'new_position', + 'timezone': 'UTC' + } + ) + updated_profile_user = User.objects.get(id=self.user2.id) + updated_profile = Profile.objects.get(user=updated_profile_user) + self.assertEqual(updated_profile_user.first_name, 'new_first_name') + self.assertEqual(updated_profile_user.last_name, 'new_last_name') + self.assertEqual(updated_profile.roll_number, '20') + self.assertEqual(updated_profile.institute, 'new_institute') + self.assertEqual(updated_profile.department, 'Aerospace') + self.assertEqual(updated_profile.position, 'new_position') + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/profile_updated.html') + + def test_edit_profile_get(self): + """ + GET request to edit profile should display profile form + """ + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + response = self.client.get(reverse('yaksh:edit_profile')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/editprofile.html') + +class TestAddQuiz(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + tzone = pytz.timezone('UTC') + + # Create Moderator with profile + self.user_plaintext_pass = 'demo' + self.user = User.objects.create_user( + username='demo_user', + password=self.user_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user, + 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( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user) + + self.course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user) + + 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), + duration=30, active=True, + attempts_allowed=-1, time_between_attempts=0, + description='pre requisite quiz', pass_criteria=40, + language='Python', prerequisite=None, + course=self.course + ) + + self.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, + attempts_allowed=-1, time_between_attempts=0, + description='demo quiz', pass_criteria=40, + language='Python', prerequisite=self.pre_req_quiz, + course=self.course + ) + + def tearDown(self): + self.client.logout() + self.user.delete() + self.student.delete() + self.quiz.delete() + self.pre_req_quiz.delete() + self.course.delete() + + def test_view_profile_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:add_quiz'), follow=True) + redirect_destination = '/exam/login/?next=%2Fexam%2Fmanage%2Faddquiz%2F' + self.assertRedirects(response, redirect_destination) + + def test_view_profile_denies_non_moderator(self): + """ + If not moderator in redirect to login page + """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:add_quiz'), follow=True) + self.assertEqual(response.status_code, 404) + + def test_add_quiz_get(self): + """ + GET request to add question should display add quiz form + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get(reverse('yaksh:add_quiz')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/add_quiz.html') + self.assertIsNotNone(response.context['form']) + + def test_add_quiz_post_existing_quiz(self): + """ + POST request to add quiz should edit quiz if quiz exists + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + tzone = pytz.timezone('UTC') + response = self.client.post(reverse('yaksh:edit_quiz', + kwargs={'quiz_id': self.quiz.id}), + data={ + 'start_date_time': '2016-01-10 09:00:15', + 'end_date_time': '2016-01-15 09:00:15', + 'duration': 30, + 'active': False, + 'attempts_allowed': 5, + 'time_between_attempts': 1, + 'description': 'updated demo quiz', + 'pass_criteria': 40, + 'language': 'java', + 'prerequisite': self.pre_req_quiz.id, + 'course': self.course.id + } + ) + + updated_quiz = Quiz.objects.get(id=self.quiz.id) + self.assertEqual(updated_quiz.start_date_time, + datetime(2016, 1, 10, 9, 0, 15, 0, tzone) + ) + self.assertEqual(updated_quiz.end_date_time, + datetime(2016, 1, 15, 9, 0, 15, 0, tzone) + ) + self.assertEqual(updated_quiz.duration, 30) + self.assertEqual(updated_quiz.active, False) + self.assertEqual(updated_quiz.attempts_allowed, 5) + self.assertEqual(updated_quiz.time_between_attempts, 1) + self.assertEqual(updated_quiz.description, 'updated demo quiz') + self.assertEqual(updated_quiz.pass_criteria, 40) + self.assertEqual(updated_quiz.language, 'java') + self.assertEqual(updated_quiz.prerequisite, self.pre_req_quiz) + self.assertEqual(updated_quiz.course, self.course) + + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/exam/manage/') + + def test_add_quiz_post_new_quiz(self): + """ + POST request to add quiz should add new quiz if no quiz exists + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + + tzone = pytz.timezone('UTC') + response = self.client.post(reverse('yaksh:add_quiz'), + data={ + 'start_date_time': '2016-01-10 09:00:15', + 'end_date_time': '2016-01-15 09:00:15', + 'duration': 50, + 'active': True, + 'attempts_allowed': -1, + 'time_between_attempts': 2, + 'description': 'new demo quiz', + 'pass_criteria': 50, + 'language': 'python', + 'prerequisite': self.pre_req_quiz.id, + 'course': self.course.id + } + ) + quiz_list = Quiz.objects.all().order_by('-id') + new_quiz = quiz_list[0] + self.assertEqual(new_quiz.start_date_time, + datetime(2016, 1, 10, 9, 0, 15, 0, tzone) + ) + self.assertEqual(new_quiz.end_date_time, + datetime(2016, 1, 15, 9, 0, 15, 0, tzone) + ) + self.assertEqual(new_quiz.duration, 50) + self.assertEqual(new_quiz.active, True) + self.assertEqual(new_quiz.attempts_allowed, -1) + self.assertEqual(new_quiz.time_between_attempts, 2) + self.assertEqual(new_quiz.description, 'new demo quiz') + self.assertEqual(new_quiz.pass_criteria, 50) + self.assertEqual(new_quiz.language, 'python') + self.assertEqual(new_quiz.prerequisite, self.pre_req_quiz) + self.assertEqual(new_quiz.course, self.course) + + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/exam/manage/designquestionpaper/') + +class TestAddTeacher(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + tzone = pytz.timezone('UTC') + + # Create Moderator with profile + self.user_plaintext_pass = 'demo' + self.user = User.objects.create_user( + username='demo_user', + password=self.user_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user, + 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( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user) + + self.course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user) + + 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), + duration=30, active=True, + attempts_allowed=-1, time_between_attempts=0, + description='pre requisite quiz', pass_criteria=40, + language='Python', prerequisite=None, + course=self.course + ) + + self.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, + attempts_allowed=-1, time_between_attempts=0, + description='demo quiz', pass_criteria=40, + language='Python', prerequisite=self.pre_req_quiz, + course=self.course + ) + + def tearDown(self): + self.client.logout() + self.user.delete() + self.student.delete() + self.quiz.delete() + self.pre_req_quiz.delete() + self.course.delete() + + def test_add_teacher_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:add_teacher', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + redirect_destination = ('/exam/login/?next=%2Fexam' + '%2Fmanage%2Faddteacher%2F{0}%2F'.format(self.course.id)) + self.assertRedirects(response, redirect_destination) + + def test_add_teacher_denies_non_moderator(self): + """ + If not moderator redirect to login page + """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:add_teacher', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + self.assertEqual(response.status_code, 404) + + def test_add_teacher_get(self): + """ + GET request to add teacher should display list of teachers + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get(reverse('yaksh:add_teacher', + kwargs={'course_id': self.course.id} + ) + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/addteacher.html') + self.assertEqual(response.context['course'], self.course) + + def test_add_teacher_post(self): + """ + POST request to add teacher should add teachers to a course + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + teacher_id_list = [] + + for i in range(5): + teacher = User.objects.create_user( + username='demo_teacher{}'.format(i), + password='demo_teacher_pass{}'.format(i), + first_name='teacher_first_name{}'.format(i), + last_name='teacher_last_name{}'.format(i), + email='demo{}@test.com'.format(i) + ) + + teacher_profile = Profile.objects.create( + user=teacher, + roll_number='T{}'.format(i), + institute='IIT', + department='Chemical', + position='Teacher', + timezone='UTC' + ) + teacher_id_list.append(teacher.id) + + response = self.client.post(reverse('yaksh:add_teacher', + kwargs={'course_id': self.course.id} + ), + data={'check': teacher_id_list} + ) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/addteacher.html') + self.assertEqual(response.context['status'], True) + for t_id in teacher_id_list: + teacher_object = User.objects.get(id=t_id) + self.assertIn(teacher_object, response.context['teachers_added']) + self.assertIn(teacher_object, self.course.teachers.all()) + +class TestRemoveTeacher(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + tzone = pytz.timezone('UTC') + + # Create Moderator with profile + self.user_plaintext_pass = 'demo' + self.user = User.objects.create_user( + username='demo_user', + password=self.user_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user, + 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( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user) + + self.course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user) + + 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), + duration=30, active=True, + attempts_allowed=-1, time_between_attempts=0, + description='pre requisite quiz', pass_criteria=40, + language='Python', prerequisite=None, + course=self.course + ) + + self.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, + attempts_allowed=-1, time_between_attempts=0, + description='demo quiz', pass_criteria=40, + language='Python', prerequisite=self.pre_req_quiz, + course=self.course + ) + def tearDown(self): + self.client.logout() + self.user.delete() + self.student.delete() + self.quiz.delete() + self.pre_req_quiz.delete() + self.course.delete() + + def test_remove_teacher_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:remove_teacher', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + redirect_destination = ('/exam/login/?next=%2Fexam' + '%2Fmanage%2Fremove_teachers%2F{0}%2F'.format(self.course.id)) + self.assertRedirects(response, redirect_destination) + + def test_remove_teacher_denies_non_moderator(self): + """ + If not moderator redirect to login page + """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:remove_teacher', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + self.assertEqual(response.status_code, 404) + + def test_remove_teacher_post(self): + """ + POST request should remove moderator from course + """ + teacher_id_list = [] + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + + for i in range(5): + teacher = User.objects.create_user( + username='remove_teacher{}'.format(i), + password='remove_teacher_pass{}'.format(i), + first_name='remove_teacher_first_name{}'.format(i), + last_name='remove_teacher_last_name{}'.format(i), + email='remove_teacher{}@test.com'.format(i) + ) + + teacher_profile = Profile.objects.create( + user=teacher, + roll_number='RT{}'.format(i), + institute='IIT', + department='Aeronautical', + position='Teacher', + timezone='UTC' + ) + teacher_id_list.append(teacher.id) + self.course.teachers.add(teacher) + + response = self.client.post(reverse('yaksh:remove_teacher', + kwargs={'course_id': self.course.id} + ), + data={'remove': teacher_id_list} + ) + + self.assertEqual(response.status_code, 302) + redirect_destination = '/exam/manage/courses' + self.assertRedirects(response, redirect_destination, + status_code=302, + target_status_code=301 + ) + for t_id in teacher_id_list: + teacher = User.objects.get(id=t_id) + self.assertNotIn(teacher, self.course.teachers.all()) + + +class TestCourses(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + + # Create Moderator with profile + self.user1_plaintext_pass = 'demo1' + self.user1 = User.objects.create_user( + username='demo_user1', + password=self.user1_plaintext_pass, + first_name='user1_first_name', + last_name='user1_last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user1, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + + self.user2_plaintext_pass = 'demo2' + self.user2 = User.objects.create_user( + username='demo_user2', + password=self.user2_plaintext_pass, + first_name='user2_first_name', + last_name='user2_last_name', + email='demo2@test.com' + ) + + Profile.objects.create( + user=self.user2, + roll_number=10, + institute='IIT', + department='Aeronautical', + position='Moderator', + timezone='UTC' + ) + + # Create Student + self.student_plaintext_pass = 'demo_student' + self.student = User.objects.create_user( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user1) + self.mod_group.user_set.add(self.user2) + + self.user1_course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user1) + + self.user2_course = Course.objects.create(name="Java Course", + enrollment="Enroll Request", creator=self.user2) + + def tearDown(self): + self.client.logout() + self.user1.delete() + self.user2.delete() + self.student.delete() + self.user1_course.delete() + self.user2_course.delete() + + def test_courses_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:courses'), + follow=True + ) + redirect_destination = ('/exam/login/?next=%2Fexam' + '%2Fmanage%2Fcourses%2F') + self.assertRedirects(response, redirect_destination) + + def test_courses_denies_non_moderator(self): + """ + If not moderator redirect to login page + """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:courses'), + follow=True + ) + self.assertEqual(response.status_code, 404) + + def test_courses_get(self): + """ + GET request should return courses page + """ + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + response = self.client.get(reverse('yaksh:courses'), + follow=True + ) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/courses.html') + self.assertIn(self.user1_course, response.context['courses']) + self.assertNotIn(self.user2_course, response.context['courses']) + + +class TestCourseDetail(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + + # Create Moderator with profile + self.user1_plaintext_pass = 'demo1' + self.user1 = User.objects.create_user( + username='demo_user1', + password=self.user1_plaintext_pass, + first_name='user1_first_name', + last_name='user1_last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user1, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + + self.user2_plaintext_pass = 'demo2' + self.user2 = User.objects.create_user( + username='demo_user2', + password=self.user2_plaintext_pass, + first_name='user2_first_name', + last_name='user2_last_name', + email='demo2@test.com' + ) + + Profile.objects.create( + user=self.user2, + roll_number=10, + institute='IIT', + department='Aeronautical', + position='Moderator', + timezone='UTC' + ) + + # Create Student + self.student_plaintext_pass = 'demo_student' + self.student = User.objects.create_user( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user1) + self.mod_group.user_set.add(self.user2) + + self.user1_course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user1) + + def tearDown(self): + self.client.logout() + self.user1.delete() + self.user2.delete() + self.student.delete() + self.user1_course.delete() + + def test_course_detail_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:add_course'), + follow=True + ) + redirect_destination = ('/exam/login/?next=%2Fexam' + '%2Fmanage%2Fadd_course%2F') + self.assertRedirects(response, redirect_destination) + + def test_course_detail_denies_non_moderator(self): + """ + If not moderator redirect to 404 + """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:add_course'), + follow=True + ) + self.assertEqual(response.status_code, 404) + + def test_course_detail_denies_unrelated_moderators(self): + """ + If not creator of course or related teacher redirect to 404 + """ + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + response = self.client.get(reverse('yaksh:course_detail', + kwargs={'course_id': self.user1_course.id} + ), + follow=True + ) + self.assertEqual(response.status_code, 404) + + def test_course_detail_get(self): + """ + If not creator of course or related teacher redirect to 404 + """ + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + response = self.client.get(reverse('yaksh:course_detail', + kwargs={'course_id': self.user1_course.id} + ), + follow=True + ) + self.assertEqual(self.user1_course, response.context['course']) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/course_detail.html') + +class TestEnrollRequest(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + + # Create Moderator with profile + self.user1_plaintext_pass = 'demo1' + self.user1 = User.objects.create_user( + username='demo_user1', + password=self.user1_plaintext_pass, + first_name='user1_first_name', + last_name='user1_last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user1, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + + self.user2_plaintext_pass = 'demo2' + self.user2 = User.objects.create_user( + username='demo_user2', + password=self.user2_plaintext_pass, + first_name='user2_first_name', + last_name='user2_last_name', + email='demo2@test.com' + ) + + Profile.objects.create( + user=self.user2, + roll_number=10, + institute='IIT', + department='Aeronautical', + position='Moderator', + timezone='UTC' + ) + + # Create Student + self.student_plaintext_pass = 'demo_student' + self.student = User.objects.create_user( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user1) + self.mod_group.user_set.add(self.user2) + + self.course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user1) + + def tearDown(self): + self.client.logout() + self.user1.delete() + self.user2.delete() + self.student.delete() + self.course.delete() + + def test_enroll_request_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:enroll_request', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + redirect_destination = ('/exam/login/?next=%2Fexam' + '%2Fenroll_request%2F{}%2F'.format(self.course.id)) + self.assertRedirects(response, redirect_destination) + + def test_enroll_request_get_for_student(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:enroll_request', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + self.assertRedirects(response, '/exam/quizzes/') + + def test_enroll_request_get_for_moderator(self): + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:enroll_request', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + self.assertRedirects(response, '/exam/manage/') + + +class TestSelfEnroll(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + + # Create Moderator with profile + self.user1_plaintext_pass = 'demo1' + self.user1 = User.objects.create_user( + username='demo_user1', + password=self.user1_plaintext_pass, + first_name='user1_first_name', + last_name='user1_last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user1, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + + self.user2_plaintext_pass = 'demo2' + self.user2 = User.objects.create_user( + username='demo_user2', + password=self.user2_plaintext_pass, + first_name='user2_first_name', + last_name='user2_last_name', + email='demo2@test.com' + ) + + Profile.objects.create( + user=self.user2, + roll_number=10, + institute='IIT', + department='Aeronautical', + position='Moderator', + timezone='UTC' + ) + + # Create Student + self.student_plaintext_pass = 'demo_student' + self.student = User.objects.create_user( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user1) + self.mod_group.user_set.add(self.user2) + + self.course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user1) + + def tearDown(self): + self.client.logout() + self.user1.delete() + self.user2.delete() + self.student.delete() + self.course.delete() + + def test_self_enroll_denies_anonymous(self): + response = self.client.get(reverse('yaksh:self_enroll', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + redirect_destination = ('/exam/login/?next=%2Fexam' + '%2Fself_enroll%2F{}%2F'.format(self.course.id)) + self.assertRedirects(response, redirect_destination) + + def test_enroll_request_get_for_student(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:self_enroll', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + self.assertRedirects(response, '/exam/quizzes/') + + def test_enroll_request_get_for_moderator(self): + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + + response = self.client.get(reverse('yaksh:self_enroll', + kwargs={'course_id': self.course.id} + ), + follow=True + ) + self.assertRedirects(response, '/exam/manage/') diff --git a/yaksh/urls.py b/yaksh/urls.py index d7b56e4..7a41f95 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -4,6 +4,7 @@ from django.contrib.auth.views import password_reset, password_reset_confirm,\ password_reset_done, password_reset_complete, password_change,\ password_change_done +# app_name = 'yaksh' urlpatterns = [ url(r'^forgotpassword/$', password_reset, name="password_reset"), url(r'^password_reset/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', @@ -19,8 +20,8 @@ urlpatterns = [ ] urlpatterns += [ url(r'^$', views.index), - url(r'^login/$', views.user_login), - url(r'^quizzes/$', views.quizlist_user), + url(r'^login/$', views.user_login, name='login'), + url(r'^quizzes/$', views.quizlist_user, name='quizlist_user'), url(r'^results/$', views.results_user), url(r'^start/$', views.start), url(r'^start/(?P<questionpaper_id>\d+)/$', views.start), @@ -37,13 +38,13 @@ urlpatterns += [ views.skip), url(r'^(?P<q_id>\d+)/skip/(?P<next_q>\d+)/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$', views.skip), - url(r'^enroll_request/(?P<course_id>\d+)/$', views.enroll_request), - url(r'^self_enroll/(?P<course_id>\d+)/$', views.self_enroll), - url(r'^manage/$', views.prof_manage), + url(r'^enroll_request/(?P<course_id>\d+)/$', views.enroll_request, name='enroll_request'), + url(r'^self_enroll/(?P<course_id>\d+)/$', views.self_enroll, name='self_enroll'), + url(r'^manage/$', views.prof_manage, name='manage'), url(r'^manage/addquestion/$', views.add_question), url(r'^manage/addquestion/(?P<question_id>\d+)/$', views.edit_question), - url(r'^manage/addquiz/$', views.add_quiz), - url(r'^manage/addquiz/(?P<quiz_id>\d+)/$', views.add_quiz), + url(r'^manage/addquiz/$', views.add_quiz, name='add_quiz'), + url(r'^manage/addquiz/(?P<quiz_id>\d+)/$', views.add_quiz, name='edit_quiz'), url(r'^manage/gradeuser/$', views.grade_user), url(r'^manage/gradeuser/(?P<quiz_id>\d+)/$',views.grade_user), url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/$',views.grade_user), @@ -57,7 +58,7 @@ urlpatterns += [ url(r'^manage/user_data/(?P<user_id>\d+)/(?P<questionpaper_id>\d+)/$', views.user_data), url(r'^manage/user_data/(?P<user_id>\d+)/$', views.user_data), - url(r'^manage/designquestionpaper/$', views.design_questionpaper), + url(r'^manage/designquestionpaper/$', views.design_questionpaper, name='design_questionpaper'), url(r'^manage/designquestionpaper/(?P<questionpaper_id>\d+)/$',\ views.design_questionpaper), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/$', @@ -66,9 +67,9 @@ urlpatterns += [ views.show_statistics), url(r'^manage/monitor/download_csv/(?P<questionpaper_id>\d+)/$', views.download_csv), - url(r'manage/courses/$', views.courses), - url(r'manage/add_course/$', views.add_course), - url(r'manage/course_detail/(?P<course_id>\d+)/$', views.course_detail), + url(r'manage/courses/$', views.courses, name='courses'), + url(r'manage/add_course/$', views.add_course, name='add_course'), + url(r'manage/course_detail/(?P<course_id>\d+)/$', views.course_detail, name='course_detail'), url(r'manage/enroll/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.enroll), url(r'manage/enroll/rejected/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.enroll, {'was_rejected': True}), @@ -78,20 +79,18 @@ urlpatterns += [ url(r'manage/toggle_status/(?P<course_id>\d+)/$', views.toggle_course_status), url(r'^ajax/questionpaper/(?P<query>.+)/$', views.ajax_questionpaper), url(r'^ajax/questions/filter/$', views.ajax_questions_filter), - url(r'^editprofile/$', views.edit_profile), - url(r'^viewprofile/$', views.view_profile), + url(r'^editprofile/$', views.edit_profile, name='edit_profile'), + url(r'^viewprofile/$', views.view_profile, name='view_profile'), url(r'^manage/enroll/(?P<course_id>\d+)/$', views.enroll), url(r'manage/enroll/rejected/(?P<course_id>\d+)/$', views.enroll, {'was_rejected': True}), url(r'manage/enrolled/reject/(?P<course_id>\d+)/$', views.reject, {'was_enrolled': True}), url(r'^manage/searchteacher/(?P<course_id>\d+)/$', views.search_teacher), - url(r'^manage/addteacher/(?P<course_id>\d+)/$', views.add_teacher), + url(r'^manage/addteacher/(?P<course_id>\d+)/$', views.add_teacher, name='add_teacher'), url(r'^manage/allotted_course/$', views.allotted_courses), - url(r'^manage/remove_teachers/(?P<course_id>\d+)/$', views.remove_teachers), + url(r'^manage/remove_teachers/(?P<course_id>\d+)/$', views.remove_teachers, name='remove_teacher'), url(r'^manage/download_questions/$', views.show_all_questions), url(r'^manage/upload_questions/$', views.show_all_questions), url(r'^manage/(?P<mode>[\w\-]+)/(?P<quiz_id>\d+)/$', views.test_quiz) ] - - diff --git a/yaksh/views.py b/yaksh/views.py index e2ff84f..56746b0 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -7,6 +7,7 @@ from datetime import datetime import collections import csv from django.http import HttpResponse +from django.core.urlresolvers import reverse from django.contrib.auth import login, logout, authenticate from django.shortcuts import render_to_response, get_object_or_404, redirect from django.template import RequestContext @@ -22,12 +23,13 @@ from taggit.models import Tag from itertools import chain import json # Local imports. -from yaksh.models import get_model_class, Quiz, Question, QuestionPaper, QuestionSet, Course -from yaksh.models import Profile, Answer, AnswerPaper, User, TestCase +from yaksh.models import get_model_class, Quiz, Question, QuestionPaper,\ + QuestionSet, Course, Profile, Answer, AnswerPaper, User, TestCase,\ + has_profile from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ - QuestionForm, RandomQuestionForm,\ - QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm,\ - get_object_form + QuestionForm, RandomQuestionForm,\ + QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm,\ + get_object_form from yaksh.xmlrpc_clients import code_server from settings import URL_ROOT from yaksh.models import AssignmentUpload @@ -67,13 +69,9 @@ def get_user_dir(user): def is_moderator(user): """Check if the user is having moderator rights""" - if user.groups.filter(name='moderator').count() == 1: + if user.groups.filter(name='moderator').exists(): return True -def has_profile(user): - """ check if user has profile """ - return True if hasattr(user, 'profile') else False - def add_to_group(users): """ add users to moderator group """ group = Group.objects.get(name="moderator") @@ -221,7 +219,6 @@ def edit_question(request, question_id=None): def add_quiz(request, quiz_id=None): """To add a new quiz in the database. Create a new quiz and store it.""" - user = request.user ci = RequestContext(request) if not is_moderator(user): @@ -232,7 +229,7 @@ def add_quiz(request, quiz_id=None): form = QuizForm(request.POST, user=user) if form.is_valid(): form.save() - return my_redirect("/exam/manage/designquestionpaper") + return my_redirect(reverse('yaksh:design_questionpaper')) else: quiz = Quiz.objects.get(id=quiz_id) form = QuizForm(request.POST, user=user, instance=quiz) @@ -240,10 +237,6 @@ def add_quiz(request, quiz_id=None): form.save() context["quiz_id"] = quiz_id return my_redirect("/exam/manage/") - - context["form"] = form - return my_render_to_response('yaksh/add_quiz.html', context, - context_instance=ci) else: if quiz_id is None: form = QuizForm(user=user) @@ -618,7 +611,10 @@ def enroll_request(request, course_id): ci = RequestContext(request) course = get_object_or_404(Course, pk=course_id) course.request(user) - return my_redirect('/exam/manage/') + if is_moderator(user): + return my_redirect('/exam/manage/') + else: + return my_redirect('/exam/quizzes/') @login_required @@ -629,7 +625,10 @@ def self_enroll(request, course_id): if course.is_self_enroll(): was_rejected = False course.enroll(was_rejected, user) - return my_redirect('/exam/manage/') + if is_moderator(user): + return my_redirect('/exam/manage/') + else: + return my_redirect('/exam/quizzes/') @login_required @@ -794,20 +793,6 @@ def monitor(request, questionpaper_id=None): context_instance=ci) -@login_required -def show_all_users(request): - """Shows all the users who have taken various exams/quiz.""" - - user = request.user - if not user.is_authenticated() or not is_moderator(user): - raise Http404('You are not allowed to view this page !') - user = User.objects.filter(username__contains="") - questionpaper = AnswerPaper.objects.all() - context = {'question': questionpaper} - return my_render_to_response('yaksh/showusers.html', context, - context_instance=RequestContext(request)) - - @csrf_exempt def ajax_questions_filter(request): """Ajax call made when filtering displayed questions.""" |