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)
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). Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema i.e create, delete field (column) """ CustomCourseForEdX = apps.get_model("ccx", "CustomCourseForEdX") list_ccx = CustomCourseForEdX.objects.all() for ccx in list_ccx: if not ccx.course_id or ccx.course_id.deprecated: # prevent migration for deprecated course ids or invalid ids. continue ccx_locator = CCXLocator.from_course_locator(ccx.course_id, six.text_type(ccx.id)) try: course = get_course_by_id(ccx.course_id) add_master_course_staff_to_ccx( course, ccx_locator, ccx.display_name, send_email=False ) except Http404: log.warning( "Unable to add instructors and staff of master course %s to ccx %s.", ccx.course_id, ccx_locator )
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). Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema i.e create, delete field (column) """ CustomCourseForEdX = apps.get_model("ccx", "CustomCourseForEdX") list_ccx = CustomCourseForEdX.objects.all() for ccx in list_ccx: if not ccx.course_id or ccx.course_id.deprecated: # prevent migration for deprecated course ids or invalid ids. continue ccx_locator = CCXLocator.from_course_locator(ccx.course_id, six.text_type(ccx.id)) try: course = get_course_by_id(ccx.course_id) add_master_course_staff_to_ccx( course, ccx_locator, ccx.display_name, send_email=False ) except Http404: log.warning( "Unable to add instructors and staff of master course %s to ccx %s.", ccx.course_id, ccx_locator )
def test_add_master_course_staff_to_ccx_with_exception(self): """ When exception raise from ``enroll_email`` assert that enrollment skipped for that staff or instructor. """ staff = self.make_staff() assert CourseStaffRole(self.course.id).has_user(staff) # adding instructor to master course. instructor = self.make_instructor() assert CourseInstructorRole(self.course.id).has_user(instructor) with mock.patch.object(CourseEnrollment, 'enroll_by_email', side_effect=CourseEnrollmentException()): add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) assert not CourseEnrollment.objects.filter( course_id=self.ccx_locator, user=staff).exists() assert not CourseEnrollment.objects.filter( course_id=self.ccx_locator, user=instructor).exists() with mock.patch.object(CourseEnrollment, 'enroll_by_email', side_effect=SMTPException()): add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) assert not CourseEnrollment.objects.filter( course_id=self.ccx_locator, user=staff).exists() assert not CourseEnrollment.objects.filter( course_id=self.ccx_locator, user=instructor).exists()
def test_remove_master_course_staff_from_ccx_display_name(self): """ Test remove role of staff of master course on ccx course. Specific test to check that a passed display name is in the subject of the email sent to the unenrolled users. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) # create a unique display name display_name = 'custom_display_{}'.format(uuid.uuid4()) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') self.assertEqual(len(outbox), 0) # give access to the course staff/instructor remove_master_course_staff_from_ccx(self.course, self.ccx_locator, display_name) self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) for email in outbox: self.assertIn(display_name, email.subject)
def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ # adding staff to master course. staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.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(self.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)
def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ # adding staff to master course. staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.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(self.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)
def test_add_master_course_staff_to_ccx_with_exception(self): """ When exception raise from ``enroll_email`` assert that enrollment skipped for that staff or instructor. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) with mock.patch.object(CourseEnrollment, 'enroll_by_email', side_effect=CourseEnrollmentException()): add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertFalse( CourseEnrollment.objects.filter(course_id=self.ccx_locator, user=staff).exists() ) self.assertFalse( CourseEnrollment.objects.filter(course_id=self.ccx_locator, user=instructor).exists() ) with mock.patch.object(CourseEnrollment, 'enroll_by_email', side_effect=SMTPException()): add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertFalse( CourseEnrollment.objects.filter(course_id=self.ccx_locator, user=staff).exists() ) self.assertFalse( CourseEnrollment.objects.filter(course_id=self.ccx_locator, user=instructor).exists() )
def test_add_master_course_staff_to_ccx_display_name(self): """ Test add staff of master course to ccx course. Specific test to check that a passed display name is in the subject of the email sent to the enrolled users. """ staff = self.make_staff() assert CourseStaffRole(self.course.id).has_user(staff) # adding instructor to master course. instructor = self.make_instructor() assert CourseInstructorRole(self.course.id).has_user(instructor) outbox = self.get_outbox() # create a unique display name display_name = f'custom_display_{uuid.uuid4()}' list_staff_master_course = list_with_level(self.course.id, 'staff') list_instructor_master_course = list_with_level( self.course.id, 'instructor') assert len(outbox) == 0 # give access to the course staff/instructor add_master_course_staff_to_ccx(self.course, self.ccx_locator, display_name) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) for email in outbox: assert display_name in email.subject
def create_ccx(request, course, ccx=None): """ Create a new CCX """ name = request.POST.get('name') # prevent CCX objects from being created for deprecated course ids. if course.id.deprecated: messages.error( request, _("You cannot create a CCX from a course using a deprecated id. " "Please create a rerun of this course in the studio to allow " "this action.")) url = reverse('ccx_coach_dashboard', kwargs={'course_id': course.id}) return redirect(url) ccx = CustomCourseForEdX(course_id=course.id, coach=request.user, display_name=name) ccx.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx, course, 'start', start) override_field_for_ccx(ccx, course, 'due', None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx(ccx, course, 'max_student_enrollments_allowed', settings.CCX_MAX_STUDENTS_ALLOWED) # Hide anything that can show up in the schedule hidden = 'visible_to_staff_only' for chapter in course.get_children(): override_field_for_ccx(ccx, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx, vertical, hidden, True) ccx_id = CCXLocator.from_course_locator(course.id, ccx.id) url = reverse('ccx_coach_dashboard', kwargs={'course_id': ccx_id}) # Enroll the coach in the course email_params = get_email_params(course, auto_enroll=True, course_key=ccx_id, display_name=ccx.display_name) enroll_email( course_id=ccx_id, student_email=request.user.email, auto_enroll=True, email_students=True, email_params=email_params, ) 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)
def test_remove_master_course_staff_from_ccx_idempotent(self): """ Test remove staff of master course from ccx course """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() self.assertEqual(len(outbox), 0) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(self.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) # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx( self.course, self.ccx_locator, self.ccx.display_name, send_email=True ) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course) # Run again remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course)
def test_remove_master_course_staff_from_ccx_idempotent(self): """ Test remove staff of master course from ccx course """ staff = self.make_staff() assert CourseStaffRole(self.course.id).has_user(staff) # adding instructor to master course. instructor = self.make_instructor() assert CourseInstructorRole(self.course.id).has_user(instructor) outbox = self.get_outbox() assert len(outbox) == 0 add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course.id, 'staff') list_instructor_master_course = list_with_level(self.course.id, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) == len(list_staff_ccx_course) assert list_staff_master_course[0].email == list_staff_ccx_course[0].email list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) == len(list_instructor_master_course) assert list_instructor_ccx_course[0].email == list_instructor_master_course[0].email # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx( self.course, self.ccx_locator, self.ccx.display_name, send_email=True ) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) != len(list_staff_ccx_course) list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) != len(list_instructor_master_course) for user in list_staff_master_course: assert user not in list_staff_ccx_course for user in list_instructor_master_course: assert user not in list_instructor_ccx_course # Run again remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) != len(list_staff_ccx_course) list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) != len(list_instructor_master_course) for user in list_staff_master_course: assert user not in list_staff_ccx_course for user in list_instructor_master_course: assert user not in list_instructor_ccx_course
def test_remove_master_course_staff_from_ccx_idempotent(self): """ Test remove staff of master course from ccx course """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() self.assertEqual(len(outbox), 0) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(self.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) # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx( self.course, self.ccx_locator, self.ccx.display_name, send_email=True ) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course) # Run again remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course)
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 test_add_master_course_staff_to_ccx_idempotent(self): """ Test add staff of master course to ccx course multiple time will not result in multiple enrollments. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') self.assertEqual(len(outbox), 0) # run the assignment the first time add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course) # run the assignment again add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # there are no new duplicated email self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) # there are no duplicated staffs with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course)
def create_ccx(request, course, ccx=None): """ Create a new CCX """ name = request.POST.get("name") # prevent CCX objects from being created for deprecated course ids. if course.id.deprecated: messages.error( request, _( "You cannot create a CCX from a course using a deprecated id. " "Please create a rerun of this course in the studio to allow " "this action." ), ) url = reverse("ccx_coach_dashboard", kwargs={"course_id": course.id}) return redirect(url) ccx = CustomCourseForEdX(course_id=course.id, coach=request.user, display_name=name) ccx.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx, course, "start", start) override_field_for_ccx(ccx, course, "due", None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx(ccx, course, "max_student_enrollments_allowed", settings.CCX_MAX_STUDENTS_ALLOWED) # Hide anything that can show up in the schedule hidden = "visible_to_staff_only" for chapter in course.get_children(): override_field_for_ccx(ccx, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx, vertical, hidden, True) ccx_id = CCXLocator.from_course_locator(course.id, ccx.id) url = reverse("ccx_coach_dashboard", kwargs={"course_id": ccx_id}) # Enroll the coach in the course email_params = get_email_params(course, auto_enroll=True, course_key=ccx_id, display_name=ccx.display_name) enroll_email( course_id=ccx_id, student_email=request.user.email, auto_enroll=True, email_students=True, email_params=email_params, ) 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)
def test_add_master_course_staff_to_ccx_no_email(self): """ Test add staff of master course to ccx course without sending enrollment email. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() self.assertEqual(len(outbox), 0) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) self.assertEqual(len(outbox), 0)
def test_add_master_course_staff_to_ccx_no_email(self): """ Test add staff of master course to ccx course without sending enrollment email. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() self.assertEqual(len(outbox), 0) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) self.assertEqual(len(outbox), 0)
def test_add_master_course_staff_to_ccx_idempotent(self): """ Test add staff of master course to ccx course multiple time will not result in multiple enrollments. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') self.assertEqual(len(outbox), 0) # run the assignment the first time add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course) # run the assignment again add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # there are no new duplicated email self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) # there are no duplicated staffs with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course)
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)
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). Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema i.e create, delete field (column) """ CustomCourseForEdX = apps.get_model("ccx", "CustomCourseForEdX") list_ccx = CustomCourseForEdX.objects.all() for ccx in list_ccx: if ccx.course_id.deprecated: # prevent migration for deprecated course ids. continue ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id)) add_master_course_staff_to_ccx(get_course_by_id(ccx.course_id), ccx_locator, ccx.display_name, send_email=False)
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). Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema i.e create, delete field (column) """ CustomCourseForEdX = apps.get_model("ccx", "CustomCourseForEdX") list_ccx = CustomCourseForEdX.objects.all() for ccx in list_ccx: if ccx.course_id.deprecated: # prevent migration for deprecated course ids. continue ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id)) add_master_course_staff_to_ccx( get_course_by_id(ccx.course_id), ccx_locator, ccx.display_name, send_email=False )
def test_add_master_course_staff_to_ccx_display_name(self): """ Test add staff of master course to ccx course. Specific test to check that a passed display name is in the subject of the email sent to the enrolled users. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() # create a unique display name display_name = 'custom_display_{}'.format(uuid.uuid4()) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') self.assertEqual(len(outbox), 0) # give access to the course staff/instructor add_master_course_staff_to_ccx(self.course, self.ccx_locator, display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) for email in outbox: self.assertIn(display_name, email.subject)
def test_remove_master_course_staff_from_ccx(self): """ Test remove staff of master course to ccx course """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course, "staff") list_instructor_master_course = list_with_level(self.course, "instructor") with ccx_course(self.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) # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_ccx_course = list_with_level(course_ccx, "staff") self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, "instructor") self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course)
def post(self, request): """ Creates a new CCX course for a given Master Course. Args: request (Request): Django request object. Return: A JSON serialized representation a newly created CCX course. """ master_course_id = request.data.get('master_course_id') master_course_object, master_course_key, error_code, http_status = get_valid_course( master_course_id, advanced_course_check=True) if master_course_object is None: return Response(status=http_status, data={'error_code': error_code}) # validating the rest of the input valid_input, field_errors = get_valid_input(request.data) if field_errors: return Response(status=status.HTTP_400_BAD_REQUEST, data={'field_errors': field_errors}) try: # Retired users should effectively appear to not exist when # attempts are made to modify them, so a direct User model email # lookup is sufficient here. This corner case relies on the fact # that we scramble emails immediately during user lock-out. Of # course, the normal cases are that the email just never existed, # or it is currently associated with an active account. coach = User.objects.get(email=valid_input['coach_email']) except User.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND, data={'error_code': 'coach_user_does_not_exist'}) if valid_input.get('course_modules'): if not valid_course_modules(valid_input['course_modules'], master_course_key): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'error_code': 'course_module_list_not_belonging_to_master_course' }) # prepare the course_modules to be stored in a json stringified field course_modules_json = json.dumps(valid_input.get('course_modules')) with transaction.atomic(): ccx_course_object = CustomCourseForEdX( course_id=master_course_object.id, coach=coach, display_name=valid_input['display_name'], structure_json=course_modules_json) ccx_course_object.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx_course_object, master_course_object, 'start', start) override_field_for_ccx(ccx_course_object, master_course_object, 'due', None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx(ccx_course_object, master_course_object, 'max_student_enrollments_allowed', valid_input['max_students_allowed']) # Hide anything that can show up in the schedule hidden = 'visible_to_staff_only' for chapter in master_course_object.get_children(): override_field_for_ccx(ccx_course_object, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx_course_object, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx_course_object, vertical, hidden, True) # make the coach user a coach on the master course make_user_coach(coach, master_course_key) # pull the ccx course key ccx_course_key = CCXLocator.from_course_locator( master_course_object.id, str(ccx_course_object.id)) # enroll the coach in the newly created ccx email_params = get_email_params( master_course_object, auto_enroll=True, course_key=ccx_course_key, display_name=ccx_course_object.display_name) enroll_email( course_id=ccx_course_key, student_email=coach.email, auto_enroll=True, email_students=True, email_params=email_params, ) # assign staff role for the coach to the newly created ccx assign_staff_role_to_ccx(ccx_course_key, coach, master_course_object.id) # assign staff role for all the staff and instructor of the master course to the newly created ccx add_master_course_staff_to_ccx(master_course_object, ccx_course_key, ccx_course_object.display_name, send_email=False) serializer = self.get_serializer(ccx_course_object) # using CCX object as sender here. responses = SignalHandler.course_published.send( sender=ccx_course_object, course_key=ccx_course_key) for rec, response in responses: log.info( 'Signal fired when course is published. Receiver: %s. Response: %s', rec, response) return Response(status=status.HTTP_201_CREATED, data=serializer.data)
def post(self, request): """ Creates a new CCX course for a given Master Course. Args: request (Request): Django request object. Return: A JSON serialized representation a newly created CCX course. """ master_course_id = request.data.get('master_course_id') master_course_object, master_course_key, error_code, http_status = get_valid_course( master_course_id, advanced_course_check=True ) if master_course_object is None: return Response( status=http_status, data={ 'error_code': error_code } ) # validating the rest of the input valid_input, field_errors = get_valid_input(request.data) if field_errors: return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'field_errors': field_errors } ) try: # Retired users should effectively appear to not exist when # attempts are made to modify them, so a direct User model email # lookup is sufficient here. This corner case relies on the fact # that we scramble emails immediately during user lock-out. Of # course, the normal cases are that the email just never existed, # or it is currently associated with an active account. coach = User.objects.get(email=valid_input['coach_email']) except User.DoesNotExist: return Response( status=status.HTTP_404_NOT_FOUND, data={ 'error_code': 'coach_user_does_not_exist' } ) if valid_input.get('course_modules'): if not valid_course_modules(valid_input['course_modules'], master_course_key): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'error_code': 'course_module_list_not_belonging_to_master_course' } ) # prepare the course_modules to be stored in a json stringified field course_modules_json = json.dumps(valid_input.get('course_modules')) with transaction.atomic(): ccx_course_object = CustomCourseForEdX( course_id=master_course_object.id, coach=coach, display_name=valid_input['display_name'], structure_json=course_modules_json ) ccx_course_object.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx_course_object, master_course_object, 'start', start) override_field_for_ccx(ccx_course_object, master_course_object, 'due', None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx( ccx_course_object, master_course_object, 'max_student_enrollments_allowed', valid_input['max_students_allowed'] ) # Hide anything that can show up in the schedule hidden = 'visible_to_staff_only' for chapter in master_course_object.get_children(): override_field_for_ccx(ccx_course_object, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx_course_object, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx_course_object, vertical, hidden, True) # make the coach user a coach on the master course make_user_coach(coach, master_course_key) # pull the ccx course key ccx_course_key = CCXLocator.from_course_locator(master_course_object.id, unicode(ccx_course_object.id)) # enroll the coach in the newly created ccx email_params = get_email_params( master_course_object, auto_enroll=True, course_key=ccx_course_key, display_name=ccx_course_object.display_name ) enroll_email( course_id=ccx_course_key, student_email=coach.email, auto_enroll=True, email_students=True, email_params=email_params, ) # assign staff role for the coach to the newly created ccx assign_staff_role_to_ccx(ccx_course_key, coach, master_course_object.id) # assign staff role for all the staff and instructor of the master course to the newly created ccx add_master_course_staff_to_ccx( master_course_object, ccx_course_key, ccx_course_object.display_name, send_email=False ) serializer = self.get_serializer(ccx_course_object) # using CCX object as sender here. responses = SignalHandler.course_published.send( sender=ccx_course_object, course_key=ccx_course_key ) for rec, response in responses: log.info('Signal fired when course is published. Receiver: %s. Response: %s', rec, response) return Response( status=status.HTTP_201_CREATED, data=serializer.data )
def create_ccx(request, course, ccx=None): """ Create a new CCX """ name = request.POST.get('name') if hasattr(course, 'ccx_connector') and course.ccx_connector: # if ccx connector url is set in course settings then inform user that he can # only create ccx by using ccx connector url. context = get_ccx_creation_dict(course) messages.error(request, context['use_ccx_con_error_message']) return render_to_response('ccx/coach_dashboard.html', context) # prevent CCX objects from being created for deprecated course ids. if course.id.deprecated: messages.error(request, _( "You cannot create a CCX from a course using a deprecated id. " "Please create a rerun of this course in the studio to allow " "this action.")) url = reverse('ccx_coach_dashboard', kwargs={'course_id': course.id}) return redirect(url) ccx = CustomCourseForEdX( course_id=course.id, coach=request.user, display_name=name) ccx.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx, course, 'start', start) override_field_for_ccx(ccx, course, 'due', None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx(ccx, course, 'max_student_enrollments_allowed', settings.CCX_MAX_STUDENTS_ALLOWED) # Hide anything that can show up in the schedule hidden = 'visible_to_staff_only' for chapter in course.get_children(): override_field_for_ccx(ccx, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx, vertical, hidden, True) ccx_id = CCXLocator.from_course_locator(course.id, unicode(ccx.id)) # Create forum roles seed_permissions_roles(ccx_id) # Assign administrator forum role to CCX coach assign_role(ccx_id, request.user, FORUM_ROLE_ADMINISTRATOR) url = reverse('ccx_coach_dashboard', kwargs={'course_id': ccx_id}) # Enroll the coach in the course email_params = get_email_params(course, auto_enroll=True, course_key=ccx_id, display_name=ccx.display_name) enroll_email( course_id=ccx_id, student_email=request.user.email, auto_enroll=True, email_students=True, email_params=email_params, ) assign_staff_role_to_ccx(ccx_id, request.user, course.id) add_master_course_staff_to_ccx(course, ccx_id, ccx.display_name) # using CCX object as sender here. responses = SignalHandler.course_published.send( sender=ccx, course_key=CCXLocator.from_course_locator(course.id, unicode(ccx.id)) ) for rec, response in responses: log.info('Signal fired when course is published. Receiver: %s. Response: %s', rec, response) return redirect(url)
def post(self, request): """ Creates a new CCX course for a given Master Course. Args: request (Request): Django request object. Return: A JSON serialized representation a newly created CCX course. """ master_course_id = request.data.get('master_course_id') master_course_object, master_course_key, error_code, http_status = get_valid_course( master_course_id, advanced_course_check=True ) if master_course_object is None: return Response( status=http_status, data={ 'error_code': error_code } ) # validating the rest of the input valid_input, field_errors = get_valid_input(request.data) if field_errors: return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'field_errors': field_errors } ) try: coach = User.objects.get(email=valid_input['coach_email']) except User.DoesNotExist: return Response( status=status.HTTP_404_NOT_FOUND, data={ 'error_code': 'coach_user_does_not_exist' } ) if valid_input.get('course_modules'): if not valid_course_modules(valid_input['course_modules'], master_course_key): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'error_code': 'course_module_list_not_belonging_to_master_course' } ) # prepare the course_modules to be stored in a json stringified field course_modules_json = json.dumps(valid_input.get('course_modules')) with transaction.atomic(): ccx_course_object = CustomCourseForEdX( course_id=master_course_object.id, coach=coach, display_name=valid_input['display_name'], structure_json=course_modules_json ) ccx_course_object.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx_course_object, master_course_object, 'start', start) override_field_for_ccx(ccx_course_object, master_course_object, 'due', None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx( ccx_course_object, master_course_object, 'max_student_enrollments_allowed', valid_input['max_students_allowed'] ) # Hide anything that can show up in the schedule hidden = 'visible_to_staff_only' for chapter in master_course_object.get_children(): override_field_for_ccx(ccx_course_object, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx_course_object, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx_course_object, vertical, hidden, True) # make the coach user a coach on the master course make_user_coach(coach, master_course_key) # pull the ccx course key ccx_course_key = CCXLocator.from_course_locator(master_course_object.id, ccx_course_object.id) # enroll the coach in the newly created ccx email_params = get_email_params( master_course_object, auto_enroll=True, course_key=ccx_course_key, display_name=ccx_course_object.display_name ) enroll_email( course_id=ccx_course_key, student_email=coach.email, auto_enroll=True, email_students=True, email_params=email_params, ) # assign coach role for the coach to the newly created ccx assign_coach_role_to_ccx(ccx_course_key, coach, master_course_object.id) # assign staff role for all the staff and instructor of the master course to the newly created ccx add_master_course_staff_to_ccx( master_course_object, ccx_course_key, ccx_course_object.display_name, send_email=False ) serializer = self.get_serializer(ccx_course_object) return Response( status=status.HTTP_201_CREATED, data=serializer.data )
def create_ccx(request, course, ccx=None): """ Create a new CCX """ name = request.POST.get('name') if hasattr(course, 'ccx_connector') and course.ccx_connector: # if ccx connector url is set in course settings then inform user that he can # only create ccx by using ccx connector url. context = get_ccx_creation_dict(course) messages.error(request, context['use_ccx_con_error_message']) return render_to_response('ccx/coach_dashboard.html', context) # prevent CCX objects from being created for deprecated course ids. if course.id.deprecated: messages.error( request, _("You cannot create a CCX from a course using a deprecated id. " "Please create a rerun of this course in the studio to allow " "this action.")) url = reverse('ccx_coach_dashboard', kwargs={'course_id': course.id}) return redirect(url) ccx = CustomCourseForEdX(course_id=course.id, coach=request.user, display_name=name) ccx.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx, course, 'start', start) override_field_for_ccx(ccx, course, 'due', None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx(ccx, course, 'max_student_enrollments_allowed', settings.CCX_MAX_STUDENTS_ALLOWED) # Save display name explicitly override_field_for_ccx(ccx, course, 'display_name', name) # Hide anything that can show up in the schedule hidden = 'visible_to_staff_only' for chapter in course.get_children(): override_field_for_ccx(ccx, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx, vertical, hidden, True) ccx_id = CCXLocator.from_course_locator(course.id, str(ccx.id)) # Create forum roles seed_permissions_roles(ccx_id) # Assign administrator forum role to CCX coach assign_role(ccx_id, request.user, FORUM_ROLE_ADMINISTRATOR) url = reverse('ccx_coach_dashboard', kwargs={'course_id': ccx_id}) # Enroll the coach in the course email_params = get_email_params(course, auto_enroll=True, course_key=ccx_id, display_name=ccx.display_name) enroll_email( course_id=ccx_id, student_email=request.user.email, auto_enroll=True, email_students=True, email_params=email_params, ) assign_staff_role_to_ccx(ccx_id, request.user, course.id) add_master_course_staff_to_ccx(course, ccx_id, ccx.display_name) # using CCX object as sender here. responses = SignalHandler.course_published.send( sender=ccx, course_key=CCXLocator.from_course_locator(course.id, str(ccx.id))) for rec, response in responses: log.info( 'Signal fired when course is published. Receiver: %s. Response: %s', rec, response) return redirect(url)
def post(self, request): """ Creates a new CCX course for a given Master Course. Args: request (Request): Django request object. Return: A JSON serialized representation a newly created CCX course. """ master_course_id = request.data.get('master_course_id') master_course_object, master_course_key, error_code, http_status = get_valid_course( master_course_id, advanced_course_check=True) if master_course_object is None: return Response(status=http_status, data={'error_code': error_code}) # validating the rest of the input valid_input, field_errors = get_valid_input(request.data) if field_errors: return Response(status=status.HTTP_400_BAD_REQUEST, data={'field_errors': field_errors}) try: coach = User.objects.get(email=valid_input['coach_email']) except User.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND, data={'error_code': 'coach_user_does_not_exist'}) if valid_input.get('course_modules'): if not valid_course_modules(valid_input['course_modules'], master_course_key): return Response( status=status.HTTP_400_BAD_REQUEST, data={ 'error_code': 'course_module_list_not_belonging_to_master_course' }) # prepare the course_modules to be stored in a json stringified field course_modules_json = json.dumps(valid_input.get('course_modules')) with transaction.atomic(): ccx_course_object = CustomCourseForEdX( course_id=master_course_object.id, coach=coach, display_name=valid_input['display_name'], structure_json=course_modules_json) ccx_course_object.save() # Make sure start/due are overridden for entire course start = TODAY().replace(tzinfo=pytz.UTC) override_field_for_ccx(ccx_course_object, master_course_object, 'start', start) override_field_for_ccx(ccx_course_object, master_course_object, 'due', None) # Enforce a static limit for the maximum amount of students that can be enrolled override_field_for_ccx(ccx_course_object, master_course_object, 'max_student_enrollments_allowed', valid_input['max_students_allowed']) # Hide anything that can show up in the schedule hidden = 'visible_to_staff_only' for chapter in master_course_object.get_children(): override_field_for_ccx(ccx_course_object, chapter, hidden, True) for sequential in chapter.get_children(): override_field_for_ccx(ccx_course_object, sequential, hidden, True) for vertical in sequential.get_children(): override_field_for_ccx(ccx_course_object, vertical, hidden, True) # make the coach user a coach on the master course make_user_coach(coach, master_course_key) # pull the ccx course key ccx_course_key = CCXLocator.from_course_locator( master_course_object.id, ccx_course_object.id) # enroll the coach in the newly created ccx email_params = get_email_params( master_course_object, auto_enroll=True, course_key=ccx_course_key, display_name=ccx_course_object.display_name) enroll_email( course_id=ccx_course_key, student_email=coach.email, auto_enroll=True, email_students=True, email_params=email_params, ) # assign coach role for the coach to the newly created ccx assign_coach_role_to_ccx(ccx_course_key, coach, master_course_object.id) # assign staff role for all the staff and instructor of the master course to the newly created ccx add_master_course_staff_to_ccx(master_course_object, ccx_course_key, ccx_course_object.display_name, send_email=False) serializer = self.get_serializer(ccx_course_object) return Response(status=status.HTTP_201_CREATED, data=serializer.data)