Commit dcb04cb0 by Peter Pinch

Merge pull request #10877 from mitocw/enhancement/aq/add_master_course_staff_in_ccx

Added staff and instructor roles on ccx to all the staff and instructors of the master course plus fixed view as student masquerade and display name of ccx on coach dashboard 
parents e8ccc2f3 91bf48fc
...@@ -27,6 +27,7 @@ from .component import ( ...@@ -27,6 +27,7 @@ from .component import (
) )
from .item import create_xblock_info from .item import create_xblock_info
from .library import LIBRARIES_ENABLED from .library import LIBRARIES_ENABLED
from ccx_keys.locator import CCXLocator
from contentstore import utils from contentstore import utils
from contentstore.course_group_config import ( from contentstore.course_group_config import (
COHORT_SCHEME, COHORT_SCHEME,
...@@ -389,6 +390,11 @@ def _accessible_courses_list(request): ...@@ -389,6 +390,11 @@ def _accessible_courses_list(request):
if isinstance(course, ErrorDescriptor): if isinstance(course, ErrorDescriptor):
return False return False
# Custom Courses for edX (CCX) is an edX feature for re-using course content.
# CCXs cannot be edited in Studio (aka cms) and should not be shown in this dashboard.
if isinstance(course, CCXLocator):
return False
# pylint: disable=fixme # pylint: disable=fixme
# TODO remove this condition when templates purged from db # TODO remove this condition when templates purged from db
if course.location.course == 'templates': if course.location.course == 'templates':
...@@ -433,8 +439,11 @@ def _accessible_courses_list_from_groups(request): ...@@ -433,8 +439,11 @@ def _accessible_courses_list_from_groups(request):
except ItemNotFoundError: except ItemNotFoundError:
# If a user has access to a course that doesn't exist, don't do anything with that course # If a user has access to a course that doesn't exist, don't do anything with that course
pass pass
if course is not None and not isinstance(course, ErrorDescriptor):
# ignore deleted or errored courses # Custom Courses for edX (CCX) is an edX feature for re-using course content.
# CCXs cannot be edited in Studio (aka cms) and should not be shown in this dashboard.
if course is not None and not isinstance(course, ErrorDescriptor) and not isinstance(course.id, CCXLocator):
# ignore deleted, errored or ccx courses
courses_list[course_key] = course courses_list[course_key] = course
return courses_list.values(), in_process_course_actions return courses_list.values(), in_process_course_actions
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ccx_keys.locator import CCXLocator
from django.db import migrations
from lms.djangoapps.ccx.models import CustomCourseForEdX
from lms.djangoapps.ccx.utils import (
add_master_course_staff_to_ccx,
reverse_add_master_course_staff_to_ccx
)
def add_master_course_staff_to_ccx_for_existing_ccx(apps, schema_editor):
"""
Add all staff and admin of master course to respective CCX(s).
"""
list_ccx = CustomCourseForEdX.objects.all()
for ccx in list_ccx:
ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id))
add_master_course_staff_to_ccx(ccx.course, ccx_locator, ccx.display_name)
def reverse_add_master_course_staff_to_ccx_for_existing_ccx(apps, schema_editor):
"""
Add all staff and admin of master course to respective CCX(s).
"""
list_ccx = CustomCourseForEdX.objects.all()
for ccx in list_ccx:
ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id))
reverse_add_master_course_staff_to_ccx(ccx.course, ccx_locator, ccx.display_name)
class Migration(migrations.Migration):
dependencies = [
('ccx', '0001_initial'),
]
operations = [
migrations.RunPython(
add_master_course_staff_to_ccx_for_existing_ccx,
reverse_code=reverse_add_master_course_staff_to_ccx_for_existing_ccx
)
]
...@@ -3,17 +3,30 @@ test utils ...@@ -3,17 +3,30 @@ test utils
""" """
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from lms.djangoapps.ccx.tests.factories import CcxFactory from ccx_keys.locator import CCXLocator
from student.roles import CourseCcxCoachRole from student.roles import (
CourseCcxCoachRole,
CourseInstructorRole,
CourseStaffRole,
)
from student.tests.factories import ( from student.tests.factories import (
AdminFactory, AdminFactory,
CourseEnrollmentFactory,
UserFactory
) )
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, ModuleStoreTestCase,
SharedModuleStoreTestCase,
TEST_DATA_SPLIT_MODULESTORE) TEST_DATA_SPLIT_MODULESTORE)
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.django import modulestore
from ccx_keys.locator import CCXLocator from lms.djangoapps.instructor.access import list_with_level, allow_access
from lms.djangoapps.ccx.utils import add_master_course_staff_to_ccx
from lms.djangoapps.ccx.views import ccx_course
from lms.djangoapps.ccx.tests.factories import CcxFactory
from lms.djangoapps.ccx.tests.utils import CcxTestCase
@attr('shard_1') @attr('shard_1')
...@@ -47,3 +60,51 @@ class TestGetCCXFromCCXLocator(ModuleStoreTestCase): ...@@ -47,3 +60,51 @@ class TestGetCCXFromCCXLocator(ModuleStoreTestCase):
course_key = CCXLocator.from_course_locator(self.course.id, ccx.id) course_key = CCXLocator.from_course_locator(self.course.id, ccx.id)
result = self.call_fut(course_key) result = self.call_fut(course_key)
self.assertEqual(result, ccx) self.assertEqual(result, ccx)
class TestStaffOnCCX(CcxTestCase, SharedModuleStoreTestCase):
"""
Tests for staff on ccx courses.
"""
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
def setUp(self):
super(TestStaffOnCCX, self).setUp()
# Create instructor account
self.client.login(username=self.coach.username, password="test")
# create an instance of modulestore
self.mstore = modulestore()
# adding staff to master course.
staff = UserFactory()
allow_access(self.course, staff, 'staff')
self.assertTrue(CourseStaffRole(self.course.id).has_user(staff))
# adding instructor to master course.
instructor = UserFactory()
allow_access(self.course, instructor, 'instructor')
self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor))
def test_add_master_course_staff_to_ccx(self):
"""
Test add staff of master course to ccx course
"""
self.make_coach()
ccx = self.make_ccx()
ccx_locator = CCXLocator.from_course_locator(self.course.id, ccx.id)
add_master_course_staff_to_ccx(self.course, ccx_locator, ccx.display_name)
# assert that staff and instructors of master course has staff and instructor roles on ccx
list_staff_master_course = list_with_level(self.course, 'staff')
list_instructor_master_course = list_with_level(self.course, 'instructor')
with ccx_course(ccx_locator) as course_ccx:
list_staff_ccx_course = list_with_level(course_ccx, 'staff')
self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course))
self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email)
list_instructor_ccx_course = list_with_level(course_ccx, 'instructor')
self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course))
self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
...@@ -15,6 +15,8 @@ from courseware.courses import get_course_by_id ...@@ -15,6 +15,8 @@ from courseware.courses import get_course_by_id
from courseware.tests.factories import StudentModuleFactory from courseware.tests.factories import StudentModuleFactory
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from courseware.tabs import get_course_tab_list from courseware.tabs import get_course_tab_list
from instructor.access import list_with_level, allow_access
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse, resolve from django.core.urlresolvers import reverse, resolve
from django.utils.timezone import UTC from django.utils.timezone import UTC
...@@ -23,7 +25,11 @@ from django.test import RequestFactory ...@@ -23,7 +25,11 @@ from django.test import RequestFactory
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from request_cache.middleware import RequestCache from request_cache.middleware import RequestCache
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from student.roles import CourseCcxCoachRole from student.roles import (
CourseCcxCoachRole,
CourseInstructorRole,
CourseStaffRole,
)
from student.models import ( from student.models import (
CourseEnrollment, CourseEnrollment,
CourseEnrollmentAllowed, CourseEnrollmentAllowed,
...@@ -49,6 +55,7 @@ from ccx_keys.locator import CCXLocator ...@@ -49,6 +55,7 @@ from ccx_keys.locator import CCXLocator
from lms.djangoapps.ccx.models import CustomCourseForEdX from lms.djangoapps.ccx.models import CustomCourseForEdX
from lms.djangoapps.ccx.overrides import get_override_for_ccx, override_field_for_ccx from lms.djangoapps.ccx.overrides import get_override_for_ccx, override_field_for_ccx
from lms.djangoapps.ccx.views import ccx_course
from lms.djangoapps.ccx.tests.factories import CcxFactory from lms.djangoapps.ccx.tests.factories import CcxFactory
from lms.djangoapps.ccx.tests.utils import ( from lms.djangoapps.ccx.tests.utils import (
CcxTestCase, CcxTestCase,
...@@ -136,6 +143,16 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase): ...@@ -136,6 +143,16 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
# Login with the instructor account # Login with the instructor account
self.client.login(username=self.coach.username, password="test") self.client.login(username=self.coach.username, password="test")
# adding staff to master course.
staff = UserFactory()
allow_access(self.course, staff, 'staff')
self.assertTrue(CourseStaffRole(self.course.id).has_user(staff))
# adding instructor to master course.
instructor = UserFactory()
allow_access(self.course, instructor, 'instructor')
self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor))
def assert_elements_in_schedule(self, url, n_chapters=2, n_sequentials=4, n_verticals=8): def assert_elements_in_schedule(self, url, n_chapters=2, n_sequentials=4, n_verticals=8):
""" """
Helper function to count visible elements in the schedule Helper function to count visible elements in the schedule
...@@ -222,6 +239,19 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase): ...@@ -222,6 +239,19 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
role = CourseCcxCoachRole(course_key) role = CourseCcxCoachRole(course_key)
self.assertTrue(role.has_user(self.coach)) self.assertTrue(role.has_user(self.coach))
# assert that staff and instructors of master course has staff and instructor roles on ccx
list_staff_master_course = list_with_level(self.course, 'staff')
list_instructor_master_course = list_with_level(self.course, 'instructor')
with ccx_course(course_key) as course_ccx:
list_staff_ccx_course = list_with_level(course_ccx, 'staff')
self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course))
self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email)
list_instructor_ccx_course = list_with_level(course_ccx, 'instructor')
self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course))
self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def test_get_date(self): def test_get_date(self):
""" """
Assert that get_date returns valid date. Assert that get_date returns valid date.
......
...@@ -18,17 +18,18 @@ from courseware.model_data import FieldDataCache ...@@ -18,17 +18,18 @@ from courseware.model_data import FieldDataCache
from courseware.module_render import get_module_for_descriptor from courseware.module_render import get_module_for_descriptor
from instructor.enrollment import ( from instructor.enrollment import (
enroll_email, enroll_email,
get_email_params,
unenroll_email, unenroll_email,
) )
from instructor.access import allow_access from instructor.access import allow_access, list_with_level, revoke_access
from instructor.views.tools import get_student_from_identifier from instructor.views.tools import get_student_from_identifier
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.models import CourseEnrollment
from student.roles import CourseCcxCoachRole from student.roles import CourseCcxCoachRole
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 from lms.djangoapps.ccx.custom_exception import CCXUserValidationException
from lms.djangoapps.ccx.models import CustomCourseForEdX
log = logging.getLogger("edx.ccx") log = logging.getLogger("edx.ccx")
...@@ -260,3 +261,83 @@ def is_email(identifier): ...@@ -260,3 +261,83 @@ def is_email(identifier):
except ValidationError: except ValidationError:
return False return False
return True return True
def add_master_course_staff_to_ccx(master_course, ccx_key, display_name):
"""
Added staff role on ccx to all the staff members of master course.
Arguments:
master_course (CourseDescriptorWithMixins): Master course instance
ccx_key (CCXLocator): CCX course key
display_name (str): ccx display name for email
"""
list_staff = list_with_level(master_course, 'staff')
list_instructor = list_with_level(master_course, 'instructor')
with ccx_course(ccx_key) as course_ccx:
email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name)
for staff in list_staff:
# allow 'staff' access on ccx to staff of master course
allow_access(course_ccx, staff, 'staff')
# Enroll the staff in the ccx
enroll_email(
course_id=ccx_key,
student_email=staff.email,
auto_enroll=True,
email_students=True,
email_params=email_params,
)
for instructor in list_instructor:
# allow 'instructor' access on ccx to instructor of master course
allow_access(course_ccx, instructor, 'instructor')
# Enroll the instructor in the ccx
enroll_email(
course_id=ccx_key,
student_email=instructor.email,
auto_enroll=True,
email_students=True,
email_params=email_params,
)
def reverse_add_master_course_staff_to_ccx(master_course, ccx_key, display_name):
"""
Remove staff of ccx.
Arguments:
master_course (CourseDescriptorWithMixins): Master course instance
ccx_key (CCXLocator): CCX course key
display_name (str): ccx display name for email
"""
list_staff = list_with_level(master_course, 'staff')
list_instructor = list_with_level(master_course, 'instructor')
with ccx_course(ccx_key) as course_ccx:
email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name)
for staff in list_staff:
# allow 'staff' access on ccx to staff of master course
revoke_access(course_ccx, staff, 'staff')
# Enroll the staff in the ccx
unenroll_email(
course_id=ccx_key,
student_email=staff.email,
email_students=True,
email_params=email_params,
)
for instructor in list_instructor:
# allow 'instructor' access on ccx to instructor of master course
revoke_access(course_ccx, instructor, 'instructor')
# Enroll the instructor in the ccx
unenroll_email(
course_id=ccx_key,
student_email=instructor.email,
email_students=True,
email_params=email_params,
)
...@@ -52,6 +52,7 @@ from lms.djangoapps.ccx.overrides import ( ...@@ -52,6 +52,7 @@ from lms.djangoapps.ccx.overrides import (
bulk_delete_ccx_override_fields, bulk_delete_ccx_override_fields,
) )
from lms.djangoapps.ccx.utils import ( from lms.djangoapps.ccx.utils import (
add_master_course_staff_to_ccx,
assign_coach_role_to_ccx, assign_coach_role_to_ccx,
ccx_course, ccx_course,
ccx_students_enrolling_center, ccx_students_enrolling_center,
...@@ -146,6 +147,9 @@ def dashboard(request, course, ccx=None): ...@@ -146,6 +147,9 @@ def dashboard(request, course, ccx=None):
context['grading_policy_url'] = reverse( context['grading_policy_url'] = reverse(
'ccx_set_grading_policy', kwargs={'course_id': ccx_locator}) 'ccx_set_grading_policy', kwargs={'course_id': ccx_locator})
with ccx_course(ccx_locator) as course:
context['course'] = course
else: else:
context['create_ccx_url'] = reverse( context['create_ccx_url'] = reverse(
'create_ccx', kwargs={'course_id': course.id}) 'create_ccx', kwargs={'course_id': course.id})
...@@ -208,7 +212,7 @@ def create_ccx(request, course, ccx=None): ...@@ -208,7 +212,7 @@ def create_ccx(request, course, ccx=None):
) )
assign_coach_role_to_ccx(ccx_id, request.user, course.id) assign_coach_role_to_ccx(ccx_id, request.user, course.id)
add_master_course_staff_to_ccx(course, ccx_id, ccx.display_name)
return redirect(url) return redirect(url)
......
...@@ -132,9 +132,6 @@ def has_access(user, action, obj, course_key=None): ...@@ -132,9 +132,6 @@ def has_access(user, action, obj, course_key=None):
if not user: if not user:
user = AnonymousUser() user = AnonymousUser()
if isinstance(course_key, CCXLocator):
course_key = course_key.to_course_locator()
# delegate the work to type-specific functions. # delegate the work to type-specific functions.
# (start with more specific types, then get more general) # (start with more specific types, then get more general)
if isinstance(obj, CourseDescriptor): if isinstance(obj, CourseDescriptor):
......
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