Commit 9008548c by Sarina Canelake

Merge pull request #8207 from amir-qayyum-khan/add_ccx_enable_to_settings

Added ccx enable/disable flag to advance settings and show hide ccx coach option on lms
parents 46cb847e 6a41cd7b
...@@ -83,6 +83,10 @@ class CourseMetadata(object): ...@@ -83,6 +83,10 @@ class CourseMetadata(object):
if not settings.FEATURES.get('ENABLE_VIDEO_BUMPER'): if not settings.FEATURES.get('ENABLE_VIDEO_BUMPER'):
filtered_list.append('video_bumper') filtered_list.append('video_bumper')
# Do not show enable_ccx if feature is not enabled.
if not settings.FEATURES.get('CUSTOM_COURSES_EDX'):
filtered_list.append('enable_ccx')
return filtered_list return filtered_list
@classmethod @classmethod
......
...@@ -386,6 +386,21 @@ class CourseFields(object): ...@@ -386,6 +386,21 @@ class CourseFields(object):
), ),
scope=Scope.settings scope=Scope.settings
) )
enable_ccx = Boolean(
# Translators: Custom Courses for edX (CCX) is an edX feature for re-using course content. CCX Coach is
# a role created by a course Instructor to enable a person (the "Coach") to manage the custom course for
# his students.
display_name=_("Enable CCX"),
# Translators: Custom Courses for edX (CCX) is an edX feature for re-using course content. CCX Coach is
# a role created by a course Instructor to enable a person (the "Coach") to manage the custom course for
# his students.
help=_(
"Allow course instructors to assign CCX Coach roles, and allow coaches to manage Custom Courses on edX."
" When false, Custom Courses cannot be created, but existing Custom Courses will be preserved."
),
default=False,
scope=Scope.settings
)
allow_anonymous = Boolean( allow_anonymous = Boolean(
display_name=_("Allow Anonymous Discussion Posts"), display_name=_("Allow Anonymous Discussion Posts"),
help=_("Enter true or false. If true, students can create discussion posts that are anonymous to all users."), help=_("Enter true or false. If true, students can create discussion posts that are anonymous to all users."),
......
...@@ -758,7 +758,7 @@ class CcxCoachTab(CourseTab): ...@@ -758,7 +758,7 @@ class CcxCoachTab(CourseTab):
the user is one. the user is one.
""" """
user_is_coach = False user_is_coach = False
if settings.FEATURES.get('CUSTOM_COURSES_EDX', False): if settings.FEATURES.get('CUSTOM_COURSES_EDX', False) and course.enable_ccx:
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from student.roles import CourseCcxCoachRole # pylint: disable=import-error from student.roles import CourseCcxCoachRole # pylint: disable=import-error
from ccx.overrides import get_current_request # pylint: disable=import-error from ccx.overrides import get_current_request # pylint: disable=import-error
......
...@@ -5,7 +5,9 @@ import datetime ...@@ -5,7 +5,9 @@ import datetime
import json import json
import re import re
import pytz import pytz
from mock import patch import ddt
import unittest
from mock import patch, MagicMock
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from capa.tests.response_xml_factory import StringResponseXMLFactory from capa.tests.response_xml_factory import StringResponseXMLFactory
...@@ -14,6 +16,7 @@ from courseware.tests.factories import StudentModuleFactory # pylint: disable=i ...@@ -14,6 +16,7 @@ from courseware.tests.factories import StudentModuleFactory # pylint: disable=i
from courseware.tests.helpers import LoginEnrollmentTestCase # pylint: disable=import-error from courseware.tests.helpers import LoginEnrollmentTestCase # pylint: disable=import-error
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test.utils import override_settings from django.test.utils import override_settings
from django.test import RequestFactory
from edxmako.shortcuts import render_to_response # pylint: disable=import-error from edxmako.shortcuts import render_to_response # pylint: disable=import-error
from student.roles import CourseCcxCoachRole # pylint: disable=import-error from student.roles import CourseCcxCoachRole # pylint: disable=import-error
from student.tests.factories import ( # pylint: disable=import-error from student.tests.factories import ( # pylint: disable=import-error
...@@ -22,12 +25,14 @@ from student.tests.factories import ( # pylint: disable=import-error ...@@ -22,12 +25,14 @@ from student.tests.factories import ( # pylint: disable=import-error
UserFactory, UserFactory,
) )
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import ( from xmodule.modulestore.tests.factories import (
CourseFactory, CourseFactory,
ItemFactory, ItemFactory,
) )
import xmodule.tabs as tabs
from ..models import ( from ..models import (
CustomCourseForEdX, CustomCourseForEdX,
CcxMembership, CcxMembership,
...@@ -56,6 +61,17 @@ def intercept_renderer(path, context): ...@@ -56,6 +61,17 @@ def intercept_renderer(path, context):
return response return response
def ccx_dummy_request():
"""
Returns dummy request object for CCX coach tab test
"""
factory = RequestFactory()
request = factory.get('ccx_coach_dashboard')
request.user = MagicMock()
return request
@attr('shard_1') @attr('shard_1')
class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
...@@ -756,6 +772,48 @@ class TestSwitchActiveCCX(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -756,6 +772,48 @@ class TestSwitchActiveCCX(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.verify_active_ccx(self.client) self.verify_active_ccx(self.client)
@ddt.ddt
class CCXCoachTabTestCase(unittest.TestCase):
"""
Test case for CCX coach tab.
"""
def setUp(self):
super(CCXCoachTabTestCase, self).setUp()
self.course = MagicMock()
self.course.id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.settings = MagicMock()
self.settings.FEATURES = {}
def check_ccx_tab(self):
"""Helper function for verifying the ccx tab."""
tab = tabs.CcxCoachTab({'type': tabs.CcxCoachTab.type, 'name': 'CCX Coach'})
return tab
@ddt.data(
(True, True, True),
(True, False, False),
(False, True, False),
(False, False, False),
(True, None, False)
)
@patch('ccx.overrides.get_current_request', ccx_dummy_request)
@ddt.unpack
def test_coach_tab_for_ccx_advance_settings(self, ccx_feature_flag, enable_ccx, expected_result):
"""
Test ccx coach tab state (visible or hidden) depending on the value of enable_ccx flag, ccx feature flag.
"""
tab = self.check_ccx_tab()
self.settings.FEATURES = {'CUSTOM_COURSES_EDX': ccx_feature_flag}
self.course.enable_ccx = enable_ccx
self.assertEquals(
expected_result,
tab.can_display(
self.course, self.settings, is_user_authenticated=True, is_user_staff=False, is_user_enrolled=True
)
)
def flatten(seq): def flatten(seq):
""" """
For [[1, 2], [3, 4]] returns [1, 2, 3, 4]. Does not recurse. For [[1, 2], [3, 4]] returns [1, 2, 3, 4]. Does not recurse.
......
""" """
Unit tests for instructor_dashboard.py. Unit tests for instructor_dashboard.py.
""" """
import ddt
from mock import patch from mock import patch
from django.conf import settings from django.conf import settings
...@@ -16,6 +17,7 @@ from course_modes.models import CourseMode ...@@ -16,6 +17,7 @@ from course_modes.models import CourseMode
from student.roles import CourseFinanceAdminRole from student.roles import CourseFinanceAdminRole
@ddt.ddt
class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
Tests for the instructor dashboard (not legacy). Tests for the instructor dashboard (not legacy).
...@@ -179,3 +181,29 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -179,3 +181,29 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
total_amount = single_purchase_total + bulk_purchase_total total_amount = single_purchase_total + bulk_purchase_total
response = self.client.get(self.url) response = self.client.get(self.url)
self.assertIn('{currency}{amount}'.format(currency='$', amount=total_amount), response.content) self.assertIn('{currency}{amount}'.format(currency='$', amount=total_amount), response.content)
@ddt.data(
(True, True, True),
(True, False, False),
(True, None, False),
(False, True, False),
(False, False, False),
(False, None, False),
)
@ddt.unpack
def test_ccx_coaches_option_on_admin_list_management_instructor(
self, ccx_feature_flag, enable_ccx, expected_result
):
"""
Test whether the "CCX Coaches" option is visible or hidden depending on the value of course.enable_ccx.
"""
with patch.dict(settings.FEATURES, {'CUSTOM_COURSES_EDX': ccx_feature_flag}):
self.course.enable_ccx = enable_ccx
self.store.update_item(self.course, self.instructor.id)
response = self.client.get(self.url)
self.assertEquals(
expected_result,
'CCX Coaches are able to create their own Custom Courses based on this course' in response.content
)
...@@ -324,10 +324,12 @@ def _section_course_info(course, access): ...@@ -324,10 +324,12 @@ def _section_course_info(course, access):
def _section_membership(course, access): def _section_membership(course, access):
""" Provide data for the corresponding dashboard section """ """ Provide data for the corresponding dashboard section """
course_key = course.id course_key = course.id
ccx_enabled = settings.FEATURES.get('CUSTOM_COURSES_EDX', False) and course.enable_ccx
section_data = { section_data = {
'section_key': 'membership', 'section_key': 'membership',
'section_display_name': _('Membership'), 'section_display_name': _('Membership'),
'access': access, 'access': access,
'ccx_is_enabled': ccx_enabled,
'enroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': unicode(course_key)}), 'enroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': unicode(course_key)}),
'unenroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': unicode(course_key)}), 'unenroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': unicode(course_key)}),
'upload_student_csv_button_url': reverse('register_and_enroll_students', kwargs={'course_id': unicode(course_key)}), 'upload_student_csv_button_url': reverse('register_and_enroll_students', kwargs={'course_id': unicode(course_key)}),
......
...@@ -243,8 +243,8 @@ ...@@ -243,8 +243,8 @@
data-add-button-label="${_("Add Community TA")}" data-add-button-label="${_("Add Community TA")}"
></div> ></div>
%endif %endif
%if section_data['access']['instructor'] and settings.FEATURES.get('CUSTOM_COURSES_EDX', False): %if section_data['access']['instructor'] and section_data['ccx_is_enabled']:
<div class="auth-list-container" <div class="auth-list-container"
data-rolename="ccx_coach" data-rolename="ccx_coach"
data-display-name="${_("CCX Coaches")}" data-display-name="${_("CCX Coaches")}"
......
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