diff options
author | King | 2018-07-13 01:44:00 -0700 |
---|---|---|
committer | GitHub | 2018-07-13 01:44:00 -0700 |
commit | f0f4a882a796319f766ff67cd3f8133a04054dfd (patch) | |
tree | a5c8a3ab617107743864f9faacad341d0e74da00 | |
parent | 661c9d82bb680e745cc6b498131a0793b954c436 (diff) | |
parent | 0af47ee9292132ab472e3e0bbae617d77437ff72 (diff) | |
download | online_test-f0f4a882a796319f766ff67cd3f8133a04054dfd.tar.gz online_test-f0f4a882a796319f766ff67cd3f8133a04054dfd.tar.bz2 online_test-f0f4a882a796319f766ff67cd3f8133a04054dfd.zip |
Merge pull request #480 from ankitjavalkar/toggle-mod
[Role based implementation] Allow moderator to switch between student and moderator roles
-rw-r--r-- | yaksh/evaluator_tests/test_simple_question_types.py | 4 | ||||
-rw-r--r-- | yaksh/live_server_tests/load_test.py | 12 | ||||
-rw-r--r-- | yaksh/management/commands/create_moderator.py | 20 | ||||
-rw-r--r-- | yaksh/models.py | 34 | ||||
-rw-r--r-- | yaksh/templates/yaksh/moderator_dashboard.html | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/quizzes_user.html | 7 | ||||
-rw-r--r-- | yaksh/templatetags/test_custom_filters.py | 5 | ||||
-rw-r--r-- | yaksh/test_models.py | 14 | ||||
-rw-r--r-- | yaksh/test_views.py | 320 | ||||
-rw-r--r-- | yaksh/urls.py | 1 | ||||
-rw-r--r-- | yaksh/views.py | 54 |
11 files changed, 418 insertions, 58 deletions
diff --git a/yaksh/evaluator_tests/test_simple_question_types.py b/yaksh/evaluator_tests/test_simple_question_types.py index f7a6cf6..5edd545 100644 --- a/yaksh/evaluator_tests/test_simple_question_types.py +++ b/yaksh/evaluator_tests/test_simple_question_types.py @@ -1,6 +1,7 @@ import unittest from datetime import datetime, timedelta from django.utils import timezone +from django.contrib.auth.models import Group from textwrap import dedent import pytz from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ @@ -8,7 +9,9 @@ from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ StringTestCase, McqTestCase, ArrangeTestCase + def setUpModule(): + mod_group = Group.objects.create(name='moderator') # Create user profile # Create User 1 user = User.objects.create_user(username='demo_user_100', @@ -46,6 +49,7 @@ def setUpModule(): def tearDownModule(): User.objects.filter(username__in=["demo_user_100", "demo_user_101"])\ .delete() + Group.objects.all().delete() class IntegerQuestionTestCases(unittest.TestCase): diff --git a/yaksh/live_server_tests/load_test.py b/yaksh/live_server_tests/load_test.py index 520bebe..e5ac068 100644 --- a/yaksh/live_server_tests/load_test.py +++ b/yaksh/live_server_tests/load_test.py @@ -1,8 +1,10 @@ from threading import Thread from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from django.contrib.auth.models import User, Group, Permission +from django.contrib.contenttypes.models import ContentType # Local imports -from yaksh.models import User, Profile, Course +from yaksh.models import User, Profile, Course, create_group from yaksh.code_server import ServerPool from yaksh import settings from .selenium_test import SeleniumTest @@ -26,6 +28,10 @@ class YakshSeleniumTests(StaticLiveServerTestCase): cls.code_server_thread = t = Thread(target=code_server_pool.run) t.start() + app_label = 'yaksh' + group_name = 'moderator' + cls.group = create_group(group_name, app_label) + cls.demo_student = User.objects.create_user( username='demo_student', password='demo_student', @@ -45,7 +51,8 @@ class YakshSeleniumTests(StaticLiveServerTestCase): cls.demo_mod_profile = Profile.objects.create( user=cls.demo_mod, roll_number=0, institute='IIT', - department='Chemical', position='Moderator' + department='Chemical', position='Moderator', + is_moderator=True ) course_obj = Course() @@ -61,6 +68,7 @@ class YakshSeleniumTests(StaticLiveServerTestCase): cls.demo_mod.delete() cls.demo_mod_profile.delete() cls.demo_course.delete() + cls.group.delete() cls.code_server_pool.stop() cls.code_server_thread.join() diff --git a/yaksh/management/commands/create_moderator.py b/yaksh/management/commands/create_moderator.py index 86489d5..96276b5 100644 --- a/yaksh/management/commands/create_moderator.py +++ b/yaksh/management/commands/create_moderator.py @@ -7,7 +7,9 @@ # django imports from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.models import User, Group, Permission -from django.contrib.contenttypes.models import ContentType + +# local imports +from yaksh.models import create_group class Command(BaseCommand): @@ -19,19 +21,9 @@ class Command(BaseCommand): def handle(self, *args, **options): app_label = 'yaksh' - - try: - group = Group.objects.get(name='moderator') - except Group.DoesNotExist: - group = Group(name='moderator') - group.save() - # Get the models for the given app - content_types = ContentType.objects.filter(app_label=app_label) - # Get list of permissions for the models - permission_list = Permission.objects.filter( - content_type__in=content_types) - group.permissions.add(*permission_list) - group.save() + group_name = 'moderator' + group = create_group(group_name, app_label) + if group and isinstance(group, Group): self.stdout.write('Moderator group added successfully') if options['usernames']: diff --git a/yaksh/models.py b/yaksh/models.py index 1eca721..152289f 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -8,7 +8,7 @@ from ruamel.yaml.comments import CommentedMap from random import sample from collections import Counter from django.db import models -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Group, Permission from django.contrib.contenttypes.models import ContentType from taggit.managers import TaggableManager from django.utils import timezone @@ -92,6 +92,8 @@ test_status = ( FIXTURES_DIR_PATH = os.path.join(settings.BASE_DIR, 'yaksh', 'fixtures') +MOD_GROUP_NAME = 'moderator' + def get_assignment_dir(instance, filename): upload_dir = instance.question_paper.quiz.description.replace(" ", "_") @@ -135,6 +137,21 @@ def get_file_dir(instance, filename): upload_dir = instance.name.replace(" ", "_") return os.sep.join((upload_dir, filename)) +def create_group(group_name, app_label): + try: + group = Group.objects.get(name=group_name) + except Group.DoesNotExist: + group = Group(name=group_name) + group.save() + # Get the models for the given app + content_types = ContentType.objects.filter(app_label=app_label) + # Get list of permissions for the models + permission_list = Permission.objects.filter( + content_type__in=content_types) + group.permissions.add(*permission_list) + group.save() + return group + ############################################################################### class CourseManager(models.Manager): @@ -982,6 +999,7 @@ class Profile(models.Model): institute = models.CharField(max_length=128) department = models.CharField(max_length=64) position = models.CharField(max_length=64) + is_moderator = models.BooleanField(default=False) timezone = models.CharField( max_length=64, default=pytz.utc.zone, @@ -1000,6 +1018,20 @@ class Profile(models.Model): os.chmod(user_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) return user_dir + def _toggle_moderator_group(self, group_name): + group = Group.objects.get(name=group_name) + if self.is_moderator: + self.user.groups.add(group) + else: + self.user.groups.remove(group) + + def save(self, *args, **kwargs): + if self.pk is not None: + old_profile = Profile.objects.get(pk=self.pk) + if old_profile.is_moderator != self.is_moderator: + self._toggle_moderator_group(group_name=MOD_GROUP_NAME) + super(Profile, self).save(*args, **kwargs) + def __str__(self): return '%s' % (self.user.get_full_name()) diff --git a/yaksh/templates/yaksh/moderator_dashboard.html b/yaksh/templates/yaksh/moderator_dashboard.html index 17a4924..c848074 100644 --- a/yaksh/templates/yaksh/moderator_dashboard.html +++ b/yaksh/templates/yaksh/moderator_dashboard.html @@ -8,7 +8,10 @@ {% block content %} -<center><h4>List of quizzes! Click on the given links to have a look at answer papers for a quiz.</h4></center> +<center><h4 class="pull-left">List of quizzes! Click on the given links to have a look at answer papers for a quiz.</h4></center> +<a href="{{URL_ROOT}}/exam/toggle_moderator/" class="btn btn-primary pull-right"> + Switch To Student +</a> <table class="table table-bordered"> <th>Courses</th> <th>Quizzes</th> diff --git a/yaksh/templates/yaksh/quizzes_user.html b/yaksh/templates/yaksh/quizzes_user.html index 78cdc48..68044da 100644 --- a/yaksh/templates/yaksh/quizzes_user.html +++ b/yaksh/templates/yaksh/quizzes_user.html @@ -7,6 +7,13 @@ <center>{{ msg }}</center> </div> {% endif %} + {% if user.profile.is_moderator %} + <div class="row"> + <a href="{{URL_ROOT}}/exam/toggle_moderator/" class="btn btn-primary pull-right" style="margin-top: 20px; margin-bottom: 20px;"> + Switch To Moderator + </a> + </div> + {% endif %} {% if 'Enrolled Courses' not in title%} <div class="row well"> <form action="{{ URL_ROOT }}/exam/quizzes/" method="post" id="custom-search-form" class="form-search form-horizontal"> diff --git a/yaksh/templatetags/test_custom_filters.py b/yaksh/templatetags/test_custom_filters.py index e8d1d61..eb1f0fb 100644 --- a/yaksh/templatetags/test_custom_filters.py +++ b/yaksh/templatetags/test_custom_filters.py @@ -1,6 +1,7 @@ import unittest from datetime import datetime, timedelta from django.utils import timezone +from django.contrib.auth.models import Group import pytz # local imports @@ -15,6 +16,8 @@ from yaksh.templatetags.custom_filters import (completed, inprogress, def setUpModule(): + mod_group = Group.objects.create(name='moderator') + # Create user profile teacher = User.objects.create_user( username='teacher2000', password='demo', @@ -52,7 +55,7 @@ def setUpModule(): def tearDownModule(): User.objects.get(username="teacher2000").delete() - + Group.objects.all().delete() class CustomFiltersTestCases(unittest.TestCase): diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 403dfb4..14d5197 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1,9 +1,10 @@ import unittest +from django.contrib.auth.models import Group from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload,\ LearningModule, LearningUnit, Lesson, LessonFile, CourseStatus, \ - TestCaseOrder + TestCaseOrder, create_group from yaksh.code_server import ( ServerPool, get_result as get_result_from_code_server ) @@ -24,6 +25,8 @@ from yaksh import settings def setUpModule(): + mod_group = Group.objects.create(name='moderator') + # create user profile user = User.objects.create_user(username='creator', password='demo', @@ -111,7 +114,16 @@ def tearDownModule(): LearningUnit.objects.all().delete() LearningModule.objects.all().delete() AnswerPaper.objects.all().delete() + Group.objects.all().delete() + +############################################################################### +class GlobalMethodsTestCases(unittest.TestCase): + def test_create_group_when_group_exists(self): + self.assertEqual( + create_group('moderator', 'yaksh'), + Group.objects.get(name='moderator') + ) ############################################################################### class LessonTestCases(unittest.TestCase): diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 06a4fa3..899ed31 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -14,6 +14,7 @@ from django.contrib.auth import authenticate from django.core.urlresolvers import reverse from django.test import TestCase from django.test import Client +from django.http import Http404 from django.utils import timezone from django.core import mail from django.conf import settings @@ -27,15 +28,18 @@ from yaksh.models import ( FloatTestCase, FIXTURES_DIR_PATH, LearningModule, LearningUnit, Lesson, LessonFile, CourseStatus, dict_to_yaml ) +from yaksh.views import add_as_moderator from yaksh.decorators import user_has_profile class TestUserRegistration(TestCase): def setUp(self): self.client = Client() + self.mod_group = Group.objects.create(name='moderator') def tearDown(self): self.registered_user.delete() + self.mod_group.delete() def test_register_user_post(self): self.client.post( @@ -63,6 +67,7 @@ class TestUserRegistration(TestCase): class TestProfile(TestCase): def setUp(self): self.client = Client() + self.mod_group = Group.objects.create(name='moderator') # Create User without profile self.user1_plaintext_pass = 'demo1' @@ -95,6 +100,7 @@ class TestProfile(TestCase): self.client.logout() self.user1.delete() self.user2.delete() + self.mod_group.delete() def test_user_has_profile_for_user_without_profile(self): """ @@ -302,6 +308,7 @@ class TestProfile(TestCase): class TestStudentDashboard(TestCase): def setUp(self): self.client = Client() + self.mod_group = Group.objects.create(name='moderator') # student self.student_plaintext_pass = 'student' @@ -365,6 +372,7 @@ class TestStudentDashboard(TestCase): self.client.logout() self.user.delete() self.course.delete() + self.mod_group.delete() def test_student_dashboard_denies_anonymous_user(self): """ @@ -467,7 +475,7 @@ class TestMonitor(TestCase): password=self.user_plaintext_pass, first_name='first_name', last_name='last_name', - email='demo@test.com' + email='demo@test.com', ) Profile.objects.create( @@ -476,7 +484,8 @@ class TestMonitor(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -561,6 +570,7 @@ class TestMonitor(TestCase): self.new_answer.delete() self.learning_module.delete() self.learning_unit.delete() + self.mod_group.delete() def test_monitor_denies_student(self): """ @@ -661,7 +671,8 @@ class TestGradeUser(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -877,7 +888,8 @@ class TestDownloadAssignment(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Add to moderator group @@ -957,6 +969,7 @@ class TestDownloadAssignment(TestCase): self.course.delete() self.learning_module.delete() self.learning_unit.delete() + self.mod_group.delete() dir_name = self.quiz.description.replace(" ", "_") file_path = os.sep.join((settings.MEDIA_ROOT, dir_name)) if os.path.exists(file_path): @@ -1052,7 +1065,8 @@ class TestAddQuiz(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -1096,6 +1110,7 @@ class TestAddQuiz(TestCase): self.quiz.delete() self.exercise.delete() self.course.delete() + self.mod_group.delete() def test_add_quiz_denies_anonymous(self): """ @@ -1330,6 +1345,141 @@ class TestAddQuiz(TestCase): self.assertEqual(response.context['quizzes'][0], self.quiz) self.assertTemplateUsed(response, "yaksh/courses.html") +class TestAddAsModerator(TestCase): + def setUp(self): + self.client = Client() + self.mod_group = Group.objects.create(name='moderator') + # 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', + is_moderator=True + ) + + self.course = Course.objects.create( + name="Python Course", + enrollment="Enroll Request", creator=self.user + ) + + self.mod_group.delete() + + def tearDown(self): + self.client.logout() + self.user.delete() + + def test_add_as_moderator_group_does_not_exist(self): + """ + If group does not exist return 404 + """ + 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} + ), + follow=True + ) + self.assertEqual(response.status_code, 404) + with self.assertRaises(Http404): + add_as_moderator(self.user, 'moderator') + +class TestToggleModerator(TestCase): + def setUp(self): + self.client = Client() + + self.mod_group = Group.objects.create(name='moderator') + + # 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', + is_moderator=True + ) + + # 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' + ) + + Profile.objects.create( + user=self.student, + roll_number=10, + institute='IIT', + department='Chemical', + position='Student', + timezone='UTC', + ) + + # 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 + ) + + def tearDown(self): + self.client.logout() + self.user.delete() + self.student.delete() + self.course.delete() + self.mod_group.delete() + + def test_toggle_for_moderator(self): + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get( + reverse('yaksh:toggle_moderator') + ) + self.assertEqual(response.status_code, 302) + self.assertEquals(self.user.groups.all().count(), 0) + + def test_toggle_for_student(self): + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + response = self.client.get( + reverse('yaksh:toggle_moderator') + ) + + self.assertEqual(response.status_code, 404) class TestAddTeacher(TestCase): def setUp(self): @@ -1338,6 +1488,16 @@ class TestAddTeacher(TestCase): self.mod_group = Group.objects.create(name='moderator') tzone = pytz.timezone('UTC') + # Create User with no profile + self.user_no_profile_plaintext_pass = 'demo_no_profile' + self.user_no_profile = User.objects.create_user( + username='demo_user_no_profile', + password=self.user_no_profile_plaintext_pass, + first_name='first_name_no_profile', + last_name='last_name_no_profile', + email='demo_no_profile@test.com' + ) + # Create Moderator with profile self.user_plaintext_pass = 'demo' self.user = User.objects.create_user( @@ -1354,7 +1514,8 @@ class TestAddTeacher(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -1400,6 +1561,24 @@ class TestAddTeacher(TestCase): self.quiz.delete() self.pre_req_quiz.delete() self.course.delete() + self.mod_group.delete() + + def test_add_teacher_denies_no_profile(self): + """ + If not moderator redirect to login page + """ + self.client.login( + username=self.user_no_profile.username, + password=self.user_no_profile_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_denies_anonymous(self): """ @@ -1565,6 +1744,7 @@ class TestRemoveTeacher(TestCase): self.quiz.delete() self.pre_req_quiz.delete() self.course.delete() + self.mod_group.delete() def test_remove_teacher_denies_anonymous(self): """ @@ -1668,7 +1848,8 @@ class TestCourses(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.user2_plaintext_pass = 'demo2' @@ -1686,7 +1867,8 @@ class TestCourses(TestCase): institute='IIT', department='Aeronautical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -1716,6 +1898,16 @@ class TestCourses(TestCase): email='demo_teacher@test.com' ) + Profile.objects.create( + user=self.teacher, + roll_number=10, + institute='IIT', + department='Aeronautical', + position='Moderator', + timezone='UTC', + is_moderator=True + ) + # Add to moderator group self.mod_group.user_set.add(self.user1) self.mod_group.user_set.add(self.user2) @@ -1771,6 +1963,7 @@ class TestCourses(TestCase): self.user2.delete() self.student.delete() self.teacher.delete() + self.mod_group.delete() def test_courses_denies_anonymous(self): """ @@ -2087,7 +2280,8 @@ class TestAddCourse(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create a teacher @@ -2106,7 +2300,8 @@ class TestAddCourse(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -2154,6 +2349,7 @@ class TestAddCourse(TestCase): self.quiz.delete() self.pre_req_quiz.delete() self.course.delete() + self.mod_group.delete() def test_add_course_denies_anonymous(self): """ @@ -2266,7 +2462,8 @@ class TestCourseDetail(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.user2_plaintext_pass = 'demo2' @@ -2284,7 +2481,8 @@ class TestCourseDetail(TestCase): institute='IIT', department='Aeronautical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -2331,6 +2529,7 @@ class TestCourseDetail(TestCase): self.user2.delete() self.student.delete() self.user1_course.delete() + self.mod_group.delete() def test_upload_users_with_correct_csv(self): # Given @@ -2849,7 +3048,8 @@ class TestEnrollRequest(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.user2_plaintext_pass = 'demo2' @@ -2867,7 +3067,8 @@ class TestEnrollRequest(TestCase): institute='IIT', department='Aeronautical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -2895,6 +3096,7 @@ class TestEnrollRequest(TestCase): self.user2.delete() self.student.delete() self.course.delete() + self.mod_group.delete() def test_enroll_request_denies_anonymous(self): """ @@ -3118,7 +3320,8 @@ class TestSelfEnroll(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.user2_plaintext_pass = 'demo2' @@ -3136,7 +3339,8 @@ class TestSelfEnroll(TestCase): institute='IIT', department='Aeronautical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -3164,6 +3368,7 @@ class TestSelfEnroll(TestCase): self.user2.delete() self.student.delete() self.course.delete() + self.mod_group.delete() def test_self_enroll_denies_anonymous(self): response = self.client.get( @@ -3228,7 +3433,8 @@ class TestGrader(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.user2_plaintext_pass = 'demo2' @@ -3246,7 +3452,8 @@ class TestGrader(TestCase): institute='IIT', department='Aeronautical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -3295,6 +3502,7 @@ class TestGrader(TestCase): Quiz.objects.all().delete() QuestionPaper.objects.all().delete() AnswerPaper.objects.all().delete() + self.mod_group.delete() def test_grader_denies_anonymous(self): # Given @@ -3444,6 +3652,8 @@ class TestGrader(TestCase): class TestPasswordReset(TestCase): def setUp(self): + self.mod_group = Group.objects.create(name='moderator') + # Create User with profile self.user1_plaintext_pass = 'demo1' self.user1 = User.objects.create_user( @@ -3465,6 +3675,7 @@ class TestPasswordReset(TestCase): def tearDown(self): self.user1.delete() + self.mod_group.delete() def test_password_reset_post(self): """ @@ -3553,7 +3764,8 @@ class TestModeratorDashboard(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.mod_no_profile_plaintext_pass = 'demo2' @@ -3654,6 +3866,7 @@ class TestModeratorDashboard(TestCase): self.question_paper.delete() self.answerpaper.delete() self.new_answer.delete() + self.mod_group.delete() def test_moderator_dashboard_denies_student(self): """ @@ -3740,6 +3953,8 @@ class TestUserLogin(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( @@ -3763,6 +3978,7 @@ class TestUserLogin(TestCase): self.client.logout() settings.IS_DEVELOPMENT = True self.user1.delete() + self.mod_group.delete() def test_successful_user_login(self): """ @@ -3801,7 +4017,7 @@ class TestUserLogin(TestCase): self.assertTemplateUsed(response, "yaksh/activation_status.html") -class TestDownloadcsv(TestCase): +class TestDownloadCsv(TestCase): def setUp(self): self.client = Client() tzone = pytz.timezone("utc") @@ -3841,7 +4057,8 @@ class TestDownloadcsv(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.mod_group.user_set.add(self.user) self.course = Course.objects.create( @@ -3895,6 +4112,7 @@ class TestDownloadcsv(TestCase): self.student.delete() self.quiz.delete() self.course.delete() + self.mod_group.delete() def test_download_csv_denies_student(self): """ @@ -4036,7 +4254,8 @@ class TestShowQuestions(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.mod_group.user_set.add(self.user) self.question = Question.objects.create( @@ -4080,6 +4299,13 @@ class TestShowQuestions(TestCase): yaml_question_2.encode("utf-8") ) + def tearDown(self): + self.client.logout() + User.objects.all().delete() + Profile.objects.all().delete() + Question.objects.all().delete() + Group.objects.all().delete() + def test_show_questions_denies_student(self): """ Check show questions denies student @@ -4352,7 +4578,8 @@ class TestShowStatistics(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create Student @@ -4429,6 +4656,7 @@ class TestShowStatistics(TestCase): self.question.delete() self.question_paper.delete() self.new_answer.delete() + self.mod_group.delete() def test_show_statistics_denies_student(self): """ @@ -4514,7 +4742,8 @@ class TestQuestionPaper(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) self.student_plaintext_pass = 'demo' @@ -4568,7 +4797,8 @@ class TestQuestionPaper(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Add to moderator group @@ -4739,6 +4969,7 @@ class TestQuestionPaper(TestCase): self.question_paper.delete() self.learning_module.delete() self.learning_unit.delete() + self.mod_group.delete() def test_preview_questionpaper_correct(self): self.client.login( @@ -5262,7 +5493,8 @@ class TestLearningModule(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create a student @@ -5282,7 +5514,17 @@ class TestLearningModule(TestCase): password=self.teacher_plaintext_pass, first_name='first_name', last_name='last_name', - email='demo@student.com' + email='demo@teacher.com', + ) + + Profile.objects.create( + user=self.teacher, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC', + is_moderator=True ) # Add to moderator group @@ -5345,6 +5587,7 @@ class TestLearningModule(TestCase): self.course.delete() self.learning_unit.delete() self.learning_module.delete() + self.mod_group.delete() def test_add_new_module_denies_non_moderator(self): self.client.login( @@ -5643,7 +5886,8 @@ class TestLessons(TestCase): institute='IIT', department='Chemical', position='Moderator', - timezone='UTC' + timezone='UTC', + is_moderator=True ) # Create a student @@ -5656,6 +5900,15 @@ class TestLessons(TestCase): email='demo@student.com' ) + Profile.objects.create( + user=self.student, + roll_number=10, + institute='IIT', + department='Chemical', + position='student', + timezone='UTC' + ) + # Create a teacher to add to the course self.teacher_plaintext_pass = 'demo_teacher' self.teacher = User.objects.create_user( @@ -5666,6 +5919,16 @@ class TestLessons(TestCase): email='demo@student.com' ) + Profile.objects.create( + user=self.teacher, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC', + is_moderator=True + ) + # Add to moderator group self.mod_group.user_set.add(self.user) self.mod_group.user_set.add(self.teacher) @@ -5713,6 +5976,7 @@ class TestLessons(TestCase): self.learning_module2.delete() self.lesson.delete() self.lesson2.delete() + self.mod_group.delete() def test_edit_lesson_denies_non_moderator(self): """ Student should not be allowed to edit lesson """ diff --git a/yaksh/urls.py b/yaksh/urls.py index 6e6b8c1..8f3401b 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ url(r'^update_email/$', views.update_email, name="update_email"), url(r'^activate/(?P<key>.+)$', views.activate_user, name="activate"), url(r'^new_activation/$', views.new_activation, name='new_activation'), + url(r'^toggle_moderator/$', views.toggle_moderator_role, name='toggle_moderator'), url(r'^quizzes/$', views.quizlist_user, name='quizlist_user'), url(r'^quizzes/(?P<enrolled>\w+)/$', views.quizlist_user, name='quizlist_user'), diff --git a/yaksh/views.py b/yaksh/views.py index c6b1184..ecd7efd 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -30,7 +30,7 @@ from yaksh.code_server import get_result as get_result_from_code_server from yaksh.models import ( Answer, AnswerPaper, AssignmentUpload, Course, FileUpload, Profile, QuestionPaper, QuestionSet, Quiz, Question, TestCase, User, - FIXTURES_DIR_PATH, Lesson, LessonFile, LearningUnit, LearningModule, + FIXTURES_DIR_PATH, MOD_GROUP_NAME, Lesson, LessonFile, LearningUnit, LearningModule, CourseStatus ) from yaksh.forms import ( @@ -63,18 +63,27 @@ def my_render_to_response(request, template, context=None, **kwargs): return render(request, template, context, **kwargs) -def is_moderator(user): +def is_moderator(user, group_name=MOD_GROUP_NAME): """Check if the user is having moderator rights""" - if user.groups.filter(name='moderator').exists(): - return True + try: + group = Group.objects.get(name=group_name) + return user.profile.is_moderator and user in group.user_set.all() + except Profile.DoesNotExist: + return False + except Group.DoesNotExist: + return False -def add_to_group(users): +def add_as_moderator(users, group_name=MOD_GROUP_NAME): """ add users to moderator group """ - group = Group.objects.get(name="moderator") + try: + group = Group.objects.get(name=group_name) + except Group.DoesNotExist: + raise Http404('The Group {0} does not exist.'.format(group_name)) for user in users: if not is_moderator(user): - user.groups.add(group) + user.profile.is_moderator = True + user.profile.save() CSV_FIELDS = ['name', 'username', 'roll_number', 'institute', 'department', @@ -163,8 +172,10 @@ def quizlist_user(request, enrolled=None, msg=None): ) title = 'All Courses' - context = {'user': user, 'courses': courses, 'title': title, - 'msg': msg} + context = { + 'user': user, 'courses': courses, + 'title': title, 'msg': msg + } return my_render_to_response(request, "yaksh/quizzes_user.html", context) @@ -1757,6 +1768,29 @@ def search_teacher(request, course_id): @login_required @email_verified +def toggle_moderator_role(request): + """ Allow moderator to switch to student and back """ + + user = request.user + + try: + group = Group.objects.get(name='moderator') + except Group.DoesNotExist: + raise Http404('The Moderator group does not exist') + + if not user.profile.is_moderator: + raise Http404('You are not allowed to view this page!') + + if user not in group.user_set.all(): + group.user_set.add(user) + else: + group.user_set.remove(user) + + return my_redirect('/exam/') + + +@login_required +@email_verified def add_teacher(request, course_id): """ add teachers to the course """ @@ -1775,7 +1809,7 @@ def add_teacher(request, course_id): if request.method == 'POST': teacher_ids = request.POST.getlist('check') teachers = User.objects.filter(id__in=teacher_ids) - add_to_group(teachers) + add_as_moderator(teachers) course.add_teachers(*teachers) context['status'] = True context['teachers_added'] = teachers |