Commit c0a131e5 by Amir Qayyum Khan

Allowed ccx coaches to see individual students progress from grade book

parent 8d3fe518
"""
This file has custom exceptions for ccx
"""
class CCXUserValidationException(Exception):
"""
Custom Exception for validation of users in CCX
"""
pass
class CCXLocatorValidationException(Exception):
"""
Custom Exception to validate CCX locator
"""
pass
...@@ -29,17 +29,11 @@ from student.roles import CourseCcxCoachRole ...@@ -29,17 +29,11 @@ from student.roles import CourseCcxCoachRole
from lms.djangoapps.ccx.models import CustomCourseForEdX from lms.djangoapps.ccx.models import CustomCourseForEdX
from lms.djangoapps.ccx.overrides import get_override_for_ccx from lms.djangoapps.ccx.overrides import get_override_for_ccx
from lms.djangoapps.ccx.custom_exception import CCXUserValidationException
log = logging.getLogger("edx.ccx") log = logging.getLogger("edx.ccx")
class CCXUserValidationException(Exception):
"""
Custom Exception for validation of users in CCX
"""
pass
def get_ccx_from_ccx_locator(course_id): def get_ccx_from_ccx_locator(course_id):
""" helper function to allow querying ccx fields from templates """ """ helper function to allow querying ccx fields from templates """
ccx_id = getattr(course_id, 'ccx', None) ccx_id = getattr(course_id, 'ccx', None)
......
...@@ -39,6 +39,7 @@ from student import auth ...@@ -39,6 +39,7 @@ from student import auth
from student.models import CourseEnrollmentAllowed from student.models import CourseEnrollmentAllowed
from student.roles import ( from student.roles import (
CourseBetaTesterRole, CourseBetaTesterRole,
CourseCcxCoachRole,
CourseInstructorRole, CourseInstructorRole,
CourseStaffRole, CourseStaffRole,
GlobalStaff, GlobalStaff,
...@@ -62,9 +63,41 @@ from courseware.access_response import ( ...@@ -62,9 +63,41 @@ from courseware.access_response import (
) )
from courseware.access_utils import adjust_start_date, check_start_date, debug, ACCESS_GRANTED, ACCESS_DENIED from courseware.access_utils import adjust_start_date, check_start_date, debug, ACCESS_GRANTED, ACCESS_DENIED
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
from lms.djangoapps.ccx.models import CustomCourseForEdX
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def has_ccx_coach_role(user, course_key):
"""
Check if user is a coach on this ccx.
Arguments:
user (User): the user whose descriptor access we are checking.
course_key (CCXLocator): Key to CCX.
Returns:
bool: whether user is a coach on this ccx or not.
"""
if hasattr(course_key, 'ccx'):
ccx_id = course_key.ccx
role = CourseCcxCoachRole(course_key)
if role.has_user(user):
list_ccx = CustomCourseForEdX.objects.filter(
course_id=course_key.to_course_locator(),
coach=user
)
if list_ccx.exists():
coach_ccx = list_ccx[0]
return str(coach_ccx.id) == ccx_id
else:
raise CCXLocatorValidationException("Invalid CCX key. To verify that "
"user is a coach on CCX, you must provide key to CCX")
return False
def has_access(user, action, obj, course_key=None): def has_access(user, action, obj, course_key=None):
""" """
Check whether a user has the access to do action on obj. Handles any magic Check whether a user has the access to do action on obj. Handles any magic
......
...@@ -8,6 +8,9 @@ import itertools ...@@ -8,6 +8,9 @@ import itertools
import pytz import pytz
from django.contrib.auth.models import User from django.contrib.auth.models import User
from ccx_keys.locator import CCXLocator
from django.http import Http404
from django.test.client import RequestFactory
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from mock import Mock, patch from mock import Mock, patch
...@@ -24,9 +27,14 @@ from courseware.tests.factories import ( ...@@ -24,9 +27,14 @@ from courseware.tests.factories import (
StaffFactory, StaffFactory,
UserFactory, UserFactory,
) )
import courseware.views as views
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from edxmako.tests import mako_middleware_process_request
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.models import CourseEnrollment
from student.roles import CourseCcxCoachRole
from student.tests.factories import ( from student.tests.factories import (
AdminFactory,
AnonymousUserFactory, AnonymousUserFactory,
CourseEnrollmentAllowedFactory, CourseEnrollmentAllowedFactory,
CourseEnrollmentFactory, CourseEnrollmentFactory,
...@@ -37,7 +45,11 @@ from xmodule.course_module import ( ...@@ -37,7 +45,11 @@ from xmodule.course_module import (
CATALOG_VISIBILITY_NONE, CATALOG_VISIBILITY_NONE,
) )
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
SharedModuleStoreTestCase,
TEST_DATA_SPLIT_MODULESTORE
)
from util.milestones_helpers import ( from util.milestones_helpers import (
set_prerequisite_courses, set_prerequisite_courses,
...@@ -45,9 +57,98 @@ from util.milestones_helpers import ( ...@@ -45,9 +57,98 @@ from util.milestones_helpers import (
seed_milestone_relationship_types, seed_milestone_relationship_types,
) )
from lms.djangoapps.ccx.models import CustomCourseForEdX
# pylint: disable=protected-access # pylint: disable=protected-access
class CoachAccessTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
"""
Test if user is coach on ccx.
"""
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
@classmethod
def setUpClass(cls):
"""
Set up course for tests
"""
super(CoachAccessTestCaseCCX, cls).setUpClass()
cls.course = CourseFactory.create()
def setUp(self):
"""
Set up tests
"""
super(CoachAccessTestCaseCCX, self).setUp()
# Create ccx coach account
self.coach = AdminFactory.create(password="test")
self.client.login(username=self.coach.username, password="test")
# assign role to coach
role = CourseCcxCoachRole(self.course.id)
role.add_users(self.coach)
self.request_factory = RequestFactory()
def make_ccx(self):
"""
create ccx
"""
ccx = CustomCourseForEdX(
course_id=self.course.id,
coach=self.coach,
display_name="Test CCX"
)
ccx.save()
ccx_locator = CCXLocator.from_course_locator(self.course.id, unicode(ccx.id))
role = CourseCcxCoachRole(ccx_locator)
role.add_users(self.coach)
CourseEnrollment.enroll(self.coach, ccx_locator)
return ccx_locator
def test_has_ccx_coach_role(self):
"""
Assert that user has coach access on ccx.
"""
ccx_locator = self.make_ccx()
# user have access as coach on ccx
self.assertTrue(access.has_ccx_coach_role(self.coach, ccx_locator))
# user dont have access as coach on ccx
self.setup_user()
self.assertFalse(access.has_ccx_coach_role(self.user, ccx_locator))
def test_access_student_progress_ccx(self):
"""
Assert that only a coach can see progress of student.
"""
ccx_locator = self.make_ccx()
student = UserFactory()
# Enroll user
CourseEnrollment.enroll(student, ccx_locator)
# Test for access of a coach
request = self.request_factory.get(reverse('about_course', args=[unicode(ccx_locator)]))
request.user = self.coach
mako_middleware_process_request(request)
resp = views.progress(request, course_id=unicode(ccx_locator), student_id=student.id)
self.assertEqual(resp.status_code, 200)
# Assert access of a student
request = self.request_factory.get(reverse('about_course', args=[unicode(ccx_locator)]))
request.user = student
mako_middleware_process_request(request)
with self.assertRaises(Http404) as context:
views.progress(request, course_id=unicode(ccx_locator), student_id=self.coach.id)
self.assertIsNotNone(context.exception)
@attr('shard_1') @attr('shard_1')
@ddt.ddt @ddt.ddt
class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
......
...@@ -33,7 +33,7 @@ from rest_framework import status ...@@ -33,7 +33,7 @@ from rest_framework import status
import newrelic.agent import newrelic.agent
from courseware import grades from courseware import grades
from courseware.access import has_access, _adjust_start_date_for_beta_testers from courseware.access import has_access, has_ccx_coach_role, _adjust_start_date_for_beta_testers
from courseware.access_response import StartDateError from courseware.access_response import StartDateError
from courseware.access_utils import in_preview_mode from courseware.access_utils import in_preview_mode
from courseware.courses import ( from courseware.courses import (
...@@ -97,6 +97,7 @@ from util.views import ensure_valid_course_key ...@@ -97,6 +97,7 @@ from util.views import ensure_valid_course_key
from eventtracking import tracker from eventtracking import tracker
import analytics import analytics
from courseware.url_helpers import get_redirect_url from courseware.url_helpers import get_redirect_url
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
from lang_pref import LANGUAGE_KEY from lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
...@@ -918,7 +919,7 @@ def course_about(request, course_id): ...@@ -918,7 +919,7 @@ def course_about(request, course_id):
def progress(request, course_id, student_id=None): def progress(request, course_id, student_id=None):
""" Display the progress page. """ """ Display the progress page. """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = CourseKey.from_string(course_id)
with modulestore().bulk_operations(course_key): with modulestore().bulk_operations(course_key):
return _progress(request, course_key, student_id) return _progress(request, course_key, student_id)
...@@ -940,13 +941,19 @@ def _progress(request, course_key, student_id): ...@@ -940,13 +941,19 @@ def _progress(request, course_key, student_id):
return redirect(reverse('course_survey', args=[unicode(course.id)])) return redirect(reverse('course_survey', args=[unicode(course.id)]))
staff_access = bool(has_access(request.user, 'staff', course)) staff_access = bool(has_access(request.user, 'staff', course))
try:
coach_access = has_ccx_coach_role(request.user, course_key)
except CCXLocatorValidationException:
coach_access = False
has_access_on_students_profiles = staff_access or coach_access
if student_id is None or student_id == request.user.id: if student_id is None or student_id == request.user.id:
# always allowed to see your own profile # always allowed to see your own profile
student = request.user student = request.user
else: else:
# Requesting access to a different student's profile # Requesting access to a different student's profile
if not staff_access: if not has_access_on_students_profiles:
raise Http404 raise Http404
try: try:
student = User.objects.get(id=student_id) student = User.objects.get(id=student_id)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment