Example #1
0
def _change_access(course, user, level, action, send_email=True):
    """
    Change access of user.

    `level` is one of ['instructor', 'staff', 'beta']
    action is one of ['allow', 'revoke']

    NOTE: will create a group if it does not yet exist.
    """

    try:
        role = ROLES[level](course.id)
    except KeyError:
        raise ValueError("unrecognized level '{}'".format(level))

    if action == "allow":
        if level == "ccx_coach":
            email_params = get_email_params(course, True)
            enroll_email(
                course_id=course.id,
                student_email=user.email,
                auto_enroll=True,
                email_students=send_email,
                email_params=email_params,
            )
        role.add_users(user)
    elif action == "revoke":
        role.remove_users(user)
    else:
        raise ValueError("unrecognized action '{}'".format(action))
Example #2
0
def bulk_beta_modify_access(request, course_id):
    """
    Enroll or unenroll users in beta testing program.

    Query parameters:
    - emails is string containing a list of emails separated by anything split_input_list can handle.
    - action is one of ['add', 'remove']
    """
    action = request.GET.get('action')
    emails_raw = request.GET.get('emails')
    emails = _split_input_list(emails_raw)
    email_students = request.GET.get('email_students') in ['true', 'True', True]
    results = []
    rolename = 'beta'
    course = get_course_by_id(course_id)

    email_params = {}
    if email_students:
        email_params = get_email_params(course, auto_enroll=False)

    for email in emails:
        try:
            error = False
            user_does_not_exist = False
            user = User.objects.get(email=email)

            if action == 'add':
                allow_access(course, user, rolename)
            elif action == 'remove':
                revoke_access(course, user, rolename)
            else:
                return HttpResponseBadRequest(strip_tags(
                    "Unrecognized action '{}'".format(action)
                ))
        except User.DoesNotExist:
            error = True
            user_does_not_exist = True
        # catch and log any unexpected exceptions
        # so that one error doesn't cause a 500.
        except Exception as exc:  # pylint: disable=broad-except
            log.exception("Error while #{}ing student")
            log.exception(exc)
            error = True
        else:
            # If no exception thrown, see if we should send an email
            if email_students:
                send_beta_role_email(action, user, email_params)
        finally:
            # Tabulate the action result of this email address
            results.append({
                'email': email,
                'error': error,
                'userDoesNotExist': user_does_not_exist
            })

    response_payload = {
        'action': action,
        'results': results,
    }
    return JsonResponse(response_payload)
Example #3
0
    def make_ccx(self, max_students_allowed=200):
        """
        Overridden method to replicate (part of) the actual
        creation of ccx courses
        """
        ccx = super(CcxDetailTest,
                    self).make_ccx(max_students_allowed=max_students_allowed)

        today = datetime.datetime.today()
        start = today.replace(tzinfo=pytz.UTC)
        override_field_for_ccx(ccx, self.course, 'start', start)
        override_field_for_ccx(ccx, self.course, 'due', None)
        # Hide anything that can show up in the schedule
        hidden = 'visible_to_staff_only'
        for chapter in self.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)
        # enroll the coach in the CCX
        ccx_course_key = CCXLocator.from_course_locator(self.course.id, ccx.id)
        email_params = get_email_params(self.course,
                                        auto_enroll=True,
                                        course_key=ccx_course_key,
                                        display_name=ccx.display_name)
        enroll_email(
            course_id=ccx_course_key,
            student_email=self.coach.email,
            auto_enroll=True,
            email_students=False,
            email_params=email_params,
        )
        return ccx
Example #4
0
def ccx_invite(request, course, ccx=None):
    """
    Invite users to new ccx
    """
    if not ccx:
        raise Http404

    action = request.POST.get("enrollment-button")
    identifiers_raw = request.POST.get("student-ids")
    identifiers = _split_input_list(identifiers_raw)
    auto_enroll = True if "auto-enroll" in request.POST else False
    email_students = True if "email-students" in request.POST else False
    for identifier in identifiers:
        user = None
        email = None
        try:
            user = get_student_from_identifier(identifier)
        except User.DoesNotExist:
            email = identifier
        else:
            email = user.email
        try:
            validate_email(email)
            course_key = CCXLocator.from_course_locator(course.id, ccx.id)
            email_params = get_email_params(course, auto_enroll, course_key=course_key, display_name=ccx.display_name)
            if action == "Enroll":
                enroll_email(
                    course_key, email, auto_enroll=auto_enroll, email_students=email_students, email_params=email_params
                )
            if action == "Unenroll":
                unenroll_email(course_key, email, email_students=email_students, email_params=email_params)
        except ValidationError:
            log.info("Invalid user name or email when trying to invite students: %s", email)
    url = reverse("ccx_coach_dashboard", kwargs={"course_id": CCXLocator.from_course_locator(course.id, ccx.id)})
    return redirect(url)
Example #5
0
def ccx_student_management(request, course, ccx=None):
    """
    Manage the enrollment of individual students in a CCX
    """
    if not ccx:
        raise Http404

    action = request.POST.get('student-action', None)
    student_id = request.POST.get('student-id', '')
    email_students = 'email-students' in request.POST
    identifiers = [student_id]
    course_key = CCXLocator.from_course_locator(course.id, ccx.id)
    email_params = get_email_params(course,
                                    auto_enroll=True,
                                    course_key=course_key,
                                    display_name=ccx.display_name)

    errors = ccx_students_enrolling_center(action, identifiers, email_students,
                                           course_key, email_params)

    for error_message in errors:
        messages.error(request, error_message)

    url = reverse('ccx_coach_dashboard', kwargs={'course_id': course_key})
    return redirect(url)
Example #6
0
def _change_access(course, user, level, action):
    """
    Change access of user.

    `level` is one of ['instructor', 'staff', 'beta']
    action is one of ['allow', 'revoke']

    NOTE: will create a group if it does not yet exist.
    """

    try:
        role = ROLES[level](course.id)
    except KeyError:
        raise ValueError("unrecognized level '{}'".format(level))

    if action == 'allow':
        if level == 'ccx_coach':
            email_params = get_email_params(course, True)
            enroll_email(
                course_id=course.id,
                student_email=user.email,
                auto_enroll=True,
                email_students=True,
                email_params=email_params,
            )
        role.add_users(user)
    elif action == 'revoke':
        role.remove_users(user)
    else:
        raise ValueError("unrecognized action '{}'".format(action))
Example #7
0
    def make_ccx(self, max_students_allowed=200):
        """
        Overridden method to replicate (part of) the actual
        creation of ccx courses
        """
        ccx = super(CcxDetailTest, self).make_ccx(max_students_allowed=max_students_allowed)

        today = datetime.datetime.today()
        start = today.replace(tzinfo=pytz.UTC)
        override_field_for_ccx(ccx, self.course, 'start', start)
        override_field_for_ccx(ccx, self.course, 'due', None)
        # Hide anything that can show up in the schedule
        hidden = 'visible_to_staff_only'
        for chapter in self.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)
        # enroll the coach in the CCX
        ccx_course_key = CCXLocator.from_course_locator(self.course.id, ccx.id)
        email_params = get_email_params(
            self.course,
            auto_enroll=True,
            course_key=ccx_course_key,
            display_name=ccx.display_name
        )
        enroll_email(
            course_id=ccx_course_key,
            student_email=self.coach.email,
            auto_enroll=True,
            email_students=False,
            email_params=email_params,
        )
        return ccx
Example #8
0
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)
Example #9
0
def bulk_beta_modify_access(request, course_id):
    """
    Enroll or unenroll users in beta testing program.

    Query parameters:
    - identifiers is string containing a list of emails and/or usernames separated by
      anything split_input_list can handle.
    - action is one of ['add', 'remove']
    """
    action = request.GET.get("action")
    identifiers_raw = request.GET.get("identifiers")
    identifiers = _split_input_list(identifiers_raw)
    email_students = request.GET.get("email_students") in ["true", "True", True]
    auto_enroll = request.GET.get("auto_enroll") in ["true", "True", True]
    results = []
    rolename = "beta"
    course = get_course_by_id(course_id)

    email_params = {}
    if email_students:
        email_params = get_email_params(course, auto_enroll=auto_enroll)

    for identifier in identifiers:
        try:
            error = False
            user_does_not_exist = False
            user = get_student_from_identifier(identifier)

            if action == "add":
                allow_access(course, user, rolename)
            elif action == "remove":
                revoke_access(course, user, rolename)
            else:
                return HttpResponseBadRequest(strip_tags("Unrecognized action '{}'".format(action)))
        except User.DoesNotExist:
            error = True
            user_does_not_exist = True
        # catch and log any unexpected exceptions
        # so that one error doesn't cause a 500.
        except Exception as exc:  # pylint: disable=broad-except
            log.exception("Error while #{}ing student")
            log.exception(exc)
            error = True
        else:
            # If no exception thrown, see if we should send an email
            if email_students:
                send_beta_role_email(action, user, email_params)
            # See if we should autoenroll the student
            if auto_enroll:
                # Check if student is already enrolled
                if not CourseEnrollment.is_enrolled(user, course_id):
                    CourseEnrollment.enroll(user, course_id)

        finally:
            # Tabulate the action result of this email address
            results.append({"identifier": identifier, "error": error, "userDoesNotExist": user_does_not_exist})

    response_payload = {"action": action, "results": results}
    return JsonResponse(response_payload)
Example #10
0
    def get_email_params(self):
        """
        Returns a dictionary of parameters used to render an email.
        """
        email_params = get_email_params(self.course, True)
        email_params["email_address"] = "*****@*****.**"
        email_params["full_name"] = "Jean Reno"

        return email_params
Example #11
0
    def test_normal_params(self):
        # For a normal site, what do we expect to get for the URLs?
        # Also make sure `auto_enroll` is properly passed through.
        result = get_email_params(self.course, False)

        self.assertEqual(result['auto_enroll'], False)
        self.assertEqual(result['course_about_url'], self.course_about_url)
        self.assertEqual(result['registration_url'], self.registration_url)
        self.assertEqual(result['course_url'], self.course_url)
Example #12
0
    def test_normal_params(self):
        # For a normal site, what do we expect to get for the URLs?
        # Also make sure `auto_enroll` is properly passed through.
        result = get_email_params(self.course, False)

        self.assertEqual(result['auto_enroll'], False)
        self.assertEqual(result['course_about_url'], self.course_about_url)
        self.assertEqual(result['registration_url'], self.registration_url)
        self.assertEqual(result['course_url'], self.course_url)
Example #13
0
    def get_email_params(self):
        """
        Returns a dictionary of parameters used to render an email.
        """
        email_params = get_email_params(self.course, True)
        email_params["email_address"] = "*****@*****.**"
        email_params["full_name"] = "Jean Reno"

        return email_params
Example #14
0
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)
Example #15
0
    def test_ccx_enrollment_email_params(self):
        # For a CCX, what do we expect to get for the URLs?
        # Also make sure `auto_enroll` is properly passed through.
        result = get_email_params(self.course, True, course_key=self.course_key, display_name=self.ccx.display_name)

        self.assertEqual(result["display_name"], self.ccx.display_name)
        self.assertEqual(result["auto_enroll"], True)
        self.assertEqual(result["course_about_url"], self.course_about_url)
        self.assertEqual(result["registration_url"], self.registration_url)
        self.assertEqual(result["course_url"], self.course_url)
Example #16
0
    def get_email_params_ccx(self):
        """
        Returns a dictionary of parameters used to render an email for CCX.
        """
        email_params = get_email_params(
            self.course, True, course_key=self.course_key, display_name=self.ccx.display_name
        )
        email_params["email_address"] = "*****@*****.**"
        email_params["full_name"] = "Jean Reno"

        return email_params
Example #17
0
    def test_marketing_params(self):
        # For a site with a marketing front end, what do we expect to get for the URLs?
        # Also make sure `auto_enroll` is properly passed through.
        with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
            result = get_email_params(self.course, True)

        self.assertEqual(result['auto_enroll'], True)
        # We should *not* get a course about url (LMS doesn't know what the marketing site URLs are)
        self.assertEqual(result['course_about_url'], None)
        self.assertEqual(result['registration_url'], self.registration_url)
        self.assertEqual(result['course_url'], self.course_url)
Example #18
0
    def test_marketing_params(self):
        # For a site with a marketing front end, what do we expect to get for the URLs?
        # Also make sure `auto_enroll` is properly passed through.
        with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
            result = get_email_params(self.course, True)

        self.assertEqual(result['auto_enroll'], True)
        # We should *not* get a course about url (LMS doesn't know what the marketing site URLs are)
        self.assertEqual(result['course_about_url'], None)
        self.assertEqual(result['registration_url'], self.registration_url)
        self.assertEqual(result['course_url'], self.course_url)
Example #19
0
    def get_email_params_ccx(self):
        """
        Returns a dictionary of parameters used to render an email for CCX.
        """
        email_params = get_email_params(self.course,
                                        True,
                                        course_key=self.course_key,
                                        display_name=self.ccx.display_name)
        email_params["email_address"] = "*****@*****.**"
        email_params["full_name"] = "Jean Reno"

        return email_params
Example #20
0
    def test_ccx_enrollment_email_params(self):
        # For a CCX, what do we expect to get for the URLs?
        # Also make sure `auto_enroll` is properly passed through.
        result = get_email_params(self.course,
                                  True,
                                  course_key=self.course_key,
                                  display_name=self.ccx.display_name)

        self.assertEqual(result['display_name'], self.ccx.display_name)
        self.assertEqual(result['auto_enroll'], True)
        self.assertEqual(result['course_about_url'], self.course_about_url)
        self.assertEqual(result['registration_url'], self.registration_url)
        self.assertEqual(result['course_url'], self.course_url)
Example #21
0
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)

    # 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)  # pylint: disable=no-member

    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,
    )

    return redirect(url)
Example #22
0
def ccx_invite(request, course, ccx=None):
    """
    Invite users to new ccx
    """
    if not ccx:
        raise Http404

    action = request.POST.get('enrollment-button')
    identifiers_raw = request.POST.get('student-ids')
    identifiers = _split_input_list(identifiers_raw)
    auto_enroll = True if 'auto-enroll' in request.POST else False
    email_students = True if 'email-students' in request.POST else False
    for identifier in identifiers:
        user = None
        email = None
        try:
            user = get_student_from_identifier(identifier)
        except User.DoesNotExist:
            email = identifier
        else:
            email = user.email
        try:
            validate_email(email)
            course_key = CCXLocator.from_course_locator(course.id, ccx.id)
            email_params = get_email_params(course,
                                            auto_enroll,
                                            course_key=course_key,
                                            display_name=ccx.display_name)
            if action == 'Enroll':
                enroll_email(course_key,
                             email,
                             auto_enroll=auto_enroll,
                             email_students=email_students,
                             email_params=email_params)
            if action == "Unenroll":
                unenroll_email(course_key,
                               email,
                               email_students=email_students,
                               email_params=email_params)
        except ValidationError:
            log.info(
                'Invalid user name or email when trying to invite students: %s',
                email)
    url = reverse('ccx_coach_dashboard',
                  kwargs={
                      'course_id':
                      CCXLocator.from_course_locator(course.id, ccx.id)
                  })
    return redirect(url)
def remove_master_course_staff_from_ccx(master_course,
                                        ccx_key,
                                        display_name,
                                        send_email=True):
    """
    Remove staff and instructor roles on ccx to all the staff and instructors 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.
        send_email (bool): flag to switch on or off email to the users on revoke access.

    """
    list_staff = list_with_level(master_course, 'staff')
    list_instructor = list_with_level(master_course, 'instructor')

    with ccx_course(ccx_key) as course_ccx:
        list_staff_ccx = list_with_level(course_ccx, 'staff')
        list_instructor_ccx = list_with_level(course_ccx, 'instructor')
        email_params = get_email_params(course_ccx,
                                        auto_enroll=True,
                                        course_key=ccx_key,
                                        display_name=display_name)
        for staff in list_staff:
            if staff in list_staff_ccx:
                # revoke 'staff' access on ccx.
                revoke_access(course_ccx, staff, 'staff')

                # Unenroll the staff on ccx.
                unenroll_email(
                    course_id=ccx_key,
                    student_email=staff.email,
                    email_students=send_email,
                    email_params=email_params,
                )

        for instructor in list_instructor:
            if instructor in list_instructor_ccx:
                # revoke 'instructor' access on ccx.
                revoke_access(course_ccx, instructor, 'instructor')

                # Unenroll the instructor on ccx.
                unenroll_email(
                    course_id=ccx_key,
                    student_email=instructor.email,
                    email_students=send_email,
                    email_params=email_params,
                )
Example #24
0
def ccx_invite(request, course, ccx=None):
    """
    Invite users to new ccx
    """
    if not ccx:
        raise Http404

    action = request.POST.get('enrollment-button')
    identifiers_raw = request.POST.get('student-ids')
    identifiers = _split_input_list(identifiers_raw)
    email_students = 'email-students' in request.POST
    course_key = CCXLocator.from_course_locator(course.id, ccx.id)
    email_params = get_email_params(course, auto_enroll=True, course_key=course_key, display_name=ccx.display_name)

    _ccx_students_enrrolling_center(action, identifiers, email_students, course_key, email_params)

    url = reverse('ccx_coach_dashboard', kwargs={'course_id': course_key})
    return redirect(url)
Example #25
0
def ccx_invite(request, course, ccx=None):
    """
    Invite users to new ccx
    """
    if not ccx:
        raise Http404

    action = request.POST.get('enrollment-button')
    identifiers_raw = request.POST.get('student-ids')
    identifiers = _split_input_list(identifiers_raw)
    email_students = 'email-students' in request.POST
    course_key = CCXLocator.from_course_locator(course.id, ccx.id)
    email_params = get_email_params(course, auto_enroll=True, course_key=course_key, display_name=ccx.display_name)

    _ccx_students_enrrolling_center(action, identifiers, email_students, course_key, email_params)

    url = reverse('ccx_coach_dashboard', kwargs={'course_id': course_key})
    return redirect(url)
    def get_email_params_ccx(self):
        """
        Returns a dictionary of parameters used to render an email for CCX.
        """
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(self.course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=self.course.id, coach=coach)
        self.course_key = CCXLocator.from_course_locator(
            self.course.id, self.ccx.id)

        email_params = get_email_params(self.course,
                                        True,
                                        course_key=self.course_key,
                                        display_name=self.ccx.display_name)
        email_params["email_address"] = "*****@*****.**"
        email_params["full_name"] = "Jean Reno"

        return email_params
Example #27
0
def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, send_email=True):
    """
    Remove staff and instructor roles on ccx to all the staff and instructors 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.
        send_email (bool): flag to switch on or off email to the users on revoke access.

    """
    list_staff = list_with_level(master_course, 'staff')
    list_instructor = list_with_level(master_course, 'instructor')

    with ccx_course(ccx_key) as course_ccx:
        list_staff_ccx = list_with_level(course_ccx, 'staff')
        list_instructor_ccx = list_with_level(course_ccx, 'instructor')
        email_params = get_email_params(course_ccx, auto_enroll=True, course_key=ccx_key, display_name=display_name)
        for staff in list_staff:
            if staff in list_staff_ccx:
                # revoke 'staff' access on ccx.
                revoke_access(course_ccx, staff, 'staff')

                # Unenroll the staff on ccx.
                unenroll_email(
                    course_id=ccx_key,
                    student_email=staff.email,
                    email_students=send_email,
                    email_params=email_params,
                )

        for instructor in list_instructor:
            if instructor in list_instructor_ccx:
                # revoke 'instructor' access on ccx.
                revoke_access(course_ccx, instructor, 'instructor')

                # Unenroll the instructor on ccx.
                unenroll_email(
                    course_id=ccx_key,
                    student_email=instructor.email,
                    email_students=send_email,
                    email_params=email_params,
                )
Example #28
0
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,
            )
Example #29
0
    def get_email_params_ccx(self):
        """
        Returns a dictionary of parameters used to render an email for CCX.
        """
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(self.course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=self.course.id, coach=coach)
        self.course_key = CCXLocator.from_course_locator(self.course.id, self.ccx.id)

        email_params = get_email_params(
            self.course,
            True,
            course_key=self.course_key,
            display_name=self.ccx.display_name
        )
        email_params["email_address"] = "*****@*****.**"
        email_params["full_name"] = "Jean Reno"

        return email_params
Example #30
0
def ccx_student_management(request, course, ccx=None):
    """
    Manage the enrollment of individual students in a CCX
    """
    if not ccx:
        raise Http404

    action = request.POST.get('student-action', None)
    student_id = request.POST.get('student-id', '')
    email_students = 'email-students' in request.POST
    identifiers = [student_id]
    course_key = CCXLocator.from_course_locator(course.id, ccx.id)
    email_params = get_email_params(course, auto_enroll=True, course_key=course_key, display_name=ccx.display_name)

    errors = _ccx_students_enrrolling_center(action, identifiers, email_students, course_key, email_params)

    for error_message in errors:
        messages.error(request, error_message)

    url = reverse('ccx_coach_dashboard', kwargs={'course_id': course_key})
    return redirect(url)
def delete_redemption_entry(request, code_redemption, course_key):
    """
    delete the redemption entry from the table and
    unenroll the user who used the registration code
    for the enrollment and send him/her the unenrollment email.
    """
    user = code_redemption.redeemed_by
    email_address = code_redemption.redeemed_by.email
    full_name = code_redemption.redeemed_by.profile.name
    CourseEnrollment.unenroll(user, course_key, skip_refund=True)

    course = get_course_by_id(course_key, depth=0)
    email_params = get_email_params(course, True, secure=request.is_secure())
    email_params['message'] = 'enrolled_unenroll'
    email_params['email_address'] = email_address
    email_params['full_name'] = full_name
    send_mail_to_student(email_address, email_params)

    # remove the redemption entry from the database.
    log.info('deleting redemption entry (%s) from the database.', code_redemption.id)
    code_redemption.delete()
Example #32
0
def delete_redemption_entry(request, code_redemption, course_key):
    """
    delete the redemption entry from the table and
    unenroll the user who used the registration code
    for the enrollment and send him/her the unenrollment email.
    """
    user = code_redemption.redeemed_by
    email_address = code_redemption.redeemed_by.email
    full_name = code_redemption.redeemed_by.profile.name
    CourseEnrollment.unenroll(user, course_key, skip_refund=True)

    course = get_course_by_id(course_key, depth=0)
    email_params = get_email_params(course, True, secure=request.is_secure())
    email_params['message'] = 'enrolled_unenroll'
    email_params['email_address'] = email_address
    email_params['full_name'] = full_name
    send_mail_to_student(email_address, email_params)

    # remove the redemption entry from the database.
    log.info('deleting redemption entry (%s) from the database.',
             code_redemption.id)
    code_redemption.delete()
Example #33
0
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,
            )
Example #34
0
def students_update_enrollment(request, course_id):
    """
    Enroll or unenroll students by email.
    Requires staff access.

    Query Parameters:
    - action in ['enroll', 'unenroll']
    - identifiers is string containing a list of emails and/or usernames separated by anything split_input_list can handle.
    - auto_enroll is a boolean (defaults to false)
        If auto_enroll is false, students will be allowed to enroll.
        If auto_enroll is true, students will be enrolled as soon as they register.
    - email_students is a boolean (defaults to false)
        If email_students is true, students will be sent email notification
        If email_students is false, students will not be sent email notification

    Returns an analog to this JSON structure: {
        "action": "enroll",
        "auto_enroll": false,
        "results": [
            {
                "email": "*****@*****.**",
                "before": {
                    "enrollment": false,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                },
                "after": {
                    "enrollment": true,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                }
            }
        ]
    }
    """
    course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)

    action = request.GET.get('action')
    identifiers_raw = request.GET.get('identifiers')
    identifiers = _split_input_list(identifiers_raw)
    auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True]
    email_students = request.GET.get('email_students') in ['true', 'True', True]

    email_params = {}
    if email_students:
        course = get_course_by_id(course_id)
        email_params = get_email_params(course, auto_enroll)

    results = []
    for identifier in identifiers:
        # First try to get a user object from the identifer
        user = None
        email = None
        try:
            user = get_student_from_identifier(identifier)
        except User.DoesNotExist:
            email = identifier
        else:
            email = user.email

        try:
            # Use django.core.validators.validate_email to check email address
            # validity (obviously, cannot check if email actually /exists/,
            # simply that it is plausibly valid)
            validate_email(email)  # Raises ValidationError if invalid

            if action == 'enroll':
                before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params)
            elif action == 'unenroll':
                before, after = unenroll_email(course_id, email, email_students, email_params)
            else:
                return HttpResponseBadRequest(strip_tags(
                    "Unrecognized action '{}'".format(action)
                ))

        except ValidationError:
            # Flag this email as an error if invalid, but continue checking
            # the remaining in the list
            results.append({
                'identifier': identifier,
                'invalidIdentifier': True,
            })

        except Exception as exc:  # pylint: disable=W0703
            # catch and log any exceptions
            # so that one error doesn't cause a 500.
            log.exception("Error while #{}ing student")
            log.exception(exc)
            results.append({
                'identifier': identifier,
                'error': True,
            })

        else:
            results.append({
                'identifier': identifier,
                'before': before.to_dict(),
                'after': after.to_dict(),
            })

    response_payload = {
        'action': action,
        'results': results,
        'auto_enroll': auto_enroll,
    }
    return JsonResponse(response_payload)
Example #35
0
def create_ccx(request, course, ccx=None, **kwargs):
    """
    Create a new CCX
    """
    if not is_ccx_coach_on_master_course(
            request.user, course) or not request.user.profile.affiliate:
        return HttpResponseForbidden()

    affiliate_slug = request.POST.get('affiliate')
    name = request.POST.get('name')
    delivery_mode = request.POST.get('delivery_mode')
    location_city = request.POST.get('city')
    location_state = request.POST.get('state')
    location_postal_code = request.POST.get('postal_code')
    time = '{} {}Z'.format(request.POST.get('date'), request.POST.get('time'))
    enrollment_end_date = '{} {}Z'.format(
        request.POST.get('enrollment_end_date'),
        request.POST.get('enrollment_end_time'))
    end_date = '{} {}Z'.format(request.POST.get('end_date'),
                               request.POST.get('end_time'))
    fee = request.POST.get('fee')
    course_description = request.POST.get('course_description')
    enrollment_type = request.POST.get('enrollment_type')
    facilitators = dict(request.POST).get('facilitators')

    context = get_ccx_creation_dict(course)

    if not affiliate_slug:
        messages.error(request, 'Affiliate not selected.')
        return render_to_response('ccx/coach_dashboard.html', context)

    if not facilitators:
        messages.error(request, 'No facilitators added.')
        return render_to_response('ccx/coach_dashboard.html', context)

    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.
        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)

    affiliate = AffiliateEntity.objects.get(slug=affiliate_slug)
    if not request.user.is_staff and not AffiliateMembership.objects.filter(
            member=request.user,
            affiliate=affiliate,
            role__in=AffiliateMembership.STAFF_ROLES).exists():
        return HttpResponseForbidden()

    ccx = CustomCourseForEdX(affiliate=affiliate,
                             course_id=course.id,
                             coach=request.user,
                             display_name=name,
                             delivery_mode=delivery_mode,
                             location_city=location_city,
                             location_state=location_state,
                             location_postal_code=location_postal_code,
                             time=time,
                             enrollment_end_date=enrollment_end_date,
                             end_date=end_date,
                             fee=ast.literal_eval(fee),
                             enrollment_type=enrollment_type,
                             course_description=course_description)
    ccx.save()

    # we need this for authorization
    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.pk)

    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,
    )

    # Add facilitators
    if facilitators:
        course_obj = get_course_by_id(ccx.ccx_course_id, depth=None)
        facilitator_ids = [int(i) for i in facilitators]

        for user_id in facilitator_ids:
            user = User.objects.get(id=user_id)
            enroll_email(course_id=ccx_id,
                         student_email=user.email,
                         auto_enroll=True,
                         email_students=True,
                         email_params=email_params)
            allow_access(course_obj, user, AffiliateMembership.CCX_COACH,
                         False)

    return redirect(url)
Example #36
0
def edit_ccx(request, course, ccx=None, **kwargs):
    if not ccx:
        raise Http404

    name = request.POST.get('name')
    delivery_mode = request.POST.get('delivery_mode')
    location_city = request.POST.get('city')
    location_state = request.POST.get('state')
    location_postal_code = request.POST.get('postal_code')
    time = '{} {}Z'.format(request.POST.get('date'), request.POST.get('time'))
    enrollment_end_date = '{} {}Z'.format(
        request.POST.get('enrollment_end_date'),
        request.POST.get('enrollment_end_time'))
    end_date = '{} {}Z'.format(request.POST.get('end_date'),
                               request.POST.get('end_time'))
    fee = request.POST.get('fee')
    course_description = request.POST.get('course_description')
    enrollment_type = request.POST.get('enrollment_type')
    facilitators = dict(request.POST).get('facilitators')

    ccx.display_name = name
    ccx.delivery_mode = delivery_mode
    ccx.location_city = location_city
    ccx.location_state = location_state
    ccx.location_postal_code = location_postal_code
    ccx.enrollment_type = enrollment_type
    ccx.time = time
    ccx.enrollment_end_date = enrollment_end_date
    ccx.end_date = end_date
    ccx.fee = ast.literal_eval(fee)
    ccx.course_description = course_description
    ccx.save()

    current_facilitator_ids = CourseAccessRole.objects.filter(
        course_id=ccx.ccx_course_id,
        role=AffiliateMembership.CCX_COACH).values_list('user_id', flat=True)
    removed_facilitator_ids = set(current_facilitator_ids).difference(
        set(facilitators))
    added_facilitator_ids = set(facilitators).difference(
        set(current_facilitator_ids))

    ccx_id = CCXLocator.from_course_locator(course.id, ccx.pk)
    course_obj = get_course_by_id(ccx.ccx_course_id, depth=None)

    for facilitator_id in removed_facilitator_ids:
        user = User.objects.get(id=facilitator_id)
        revoke_access(course_obj, user, AffiliateMembership.CCX_COACH, False)

    email_params = get_email_params(course,
                                    auto_enroll=True,
                                    course_key=ccx_id,
                                    display_name=ccx.display_name)

    for facilitator_id in added_facilitator_ids:
        user = User.objects.get(id=facilitator_id)
        enroll_email(course_id=ccx_id,
                     student_email=user.email,
                     auto_enroll=True,
                     email_students=True,
                     email_params=email_params)
        allow_access(course_obj, user, AffiliateMembership.CCX_COACH, False)

    url = reverse('ccx_coach_dashboard', kwargs={'course_id': ccx_id})
    return redirect(url)
Example #37
0
def add_master_course_staff_to_ccx(master_course, ccx_key, display_name, send_email=True):
    """
    Add staff and instructor roles on ccx to all the staff and instructors 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.
        send_email (bool): flag to switch on or off email to the users on access grant.

    """
    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)
        list_staff_ccx = list_with_level(course_ccx, 'staff')
        list_instructor_ccx = list_with_level(course_ccx, 'instructor')
        for staff in list_staff:
            # this call should be idempotent
            if staff not in list_staff_ccx:
                try:
                    # Enroll the staff in the ccx
                    enroll_email(
                        course_id=ccx_key,
                        student_email=staff.email,
                        auto_enroll=True,
                        email_students=send_email,
                        email_params=email_params,
                    )

                    # allow 'staff' access on ccx to staff of master course
                    allow_access(course_ccx, staff, 'staff')
                except CourseEnrollmentException:
                    log.warning(
                        "Unable to enroll staff %s to course with id %s",
                        staff.email,
                        ccx_key
                    )
                    continue
                except SMTPException:
                    continue

        for instructor in list_instructor:
            # this call should be idempotent
            if instructor not in list_instructor_ccx:
                try:
                    # Enroll the instructor in the ccx
                    enroll_email(
                        course_id=ccx_key,
                        student_email=instructor.email,
                        auto_enroll=True,
                        email_students=send_email,
                        email_params=email_params,
                    )

                    # allow 'instructor' access on ccx to instructor of master course
                    allow_access(course_ccx, instructor, 'instructor')
                except CourseEnrollmentException:
                    log.warning(
                        "Unable to enroll instructor %s to course with id %s",
                        instructor.email,
                        ccx_key
                    )
                    continue
                except SMTPException:
                    continue
Example #38
0
def students_update_enrollment(request, course_id):
    """
    Enroll or unenroll students by email.
    Requires staff access.

    Query Parameters:
    - action in ['enroll', 'unenroll']
    - emails is string containing a list of emails separated by anything split_input_list can handle.
    - auto_enroll is a boolean (defaults to false)
        If auto_enroll is false, students will be allowed to enroll.
        If auto_enroll is true, students will be enrolled as soon as they register.
    - email_students is a boolean (defaults to false)
        If email_students is true, students will be sent email notification
        If email_students is false, students will not be sent email notification

    Returns an analog to this JSON structure: {
        "action": "enroll",
        "auto_enroll": false,
        "results": [
            {
                "email": "*****@*****.**",
                "before": {
                    "enrollment": false,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                },
                "after": {
                    "enrollment": true,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                }
            }
        ]
    }
    """

    action = request.GET.get("action")
    emails_raw = request.GET.get("emails")
    emails = _split_input_list(emails_raw)
    auto_enroll = request.GET.get("auto_enroll") in ["true", "True", True]
    email_students = request.GET.get("email_students") in ["true", "True", True]

    email_params = {}
    if email_students:
        course = get_course_by_id(course_id)
        email_params = get_email_params(course, auto_enroll)

    results = []
    for email in emails:
        try:
            # Use django.core.validators.validate_email to check email address
            # validity (obviously, cannot check if email actually /exists/,
            # simply that it is plausibly valid)
            validate_email(email)
        except ValidationError:
            # Flag this email as an error if invalid, but continue checking
            # the remaining in the list
            results.append({"email": email, "error": True, "invalidEmail": True})
            continue

        try:
            if action == "enroll":
                before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params)
            elif action == "unenroll":
                before, after = unenroll_email(course_id, email, email_students, email_params)
            else:
                return HttpResponseBadRequest(strip_tags("Unrecognized action '{}'".format(action)))

            results.append({"email": email, "before": before.to_dict(), "after": after.to_dict()})
        # catch and log any exceptions
        # so that one error doesn't cause a 500.
        except Exception as exc:  # pylint: disable=W0703
            log.exception("Error while #{}ing student")
            log.exception(exc)
            results.append({"email": email, "error": True, "invalidEmail": False})

    response_payload = {"action": action, "results": results, "auto_enroll": auto_enroll}
    return JsonResponse(response_payload)
Example #39
0
    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)

        serializer = self.get_serializer(ccx_course_object)
        return Response(
            status=status.HTTP_201_CREATED,
            data=serializer.data
        )
Example #40
0
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_coach_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)
Example #41
0
    def patch(self, request, ccx_course_id=None):
        """
        Modifies a CCX course.

        Args:
            request (Request): Django request object.
            ccx_course_id (string): URI element specifying the CCX course location.
        """
        ccx_course_object, ccx_course_key, error_code, http_status = self.get_object(ccx_course_id, is_ccx=True)
        if ccx_course_object is None:
            return Response(
                status=http_status,
                data={
                    'error_code': error_code
                }
            )

        master_course_id = request.data.get('master_course_id')
        if master_course_id is not None and unicode(ccx_course_object.course_id) != master_course_id:
            return Response(
                status=status.HTTP_403_FORBIDDEN,
                data={
                    'error_code': 'master_course_id_change_not_allowed'
                }
            )

        valid_input, field_errors = get_valid_input(request.data, ignore_missing=True)
        if field_errors:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    'field_errors': field_errors
                }
            )

        # get the master course key and master course object
        master_course_object, master_course_key, _, _ = get_valid_course(unicode(ccx_course_object.course_id))

        with transaction.atomic():
            # update the display name
            if 'display_name' in valid_input:
                ccx_course_object.display_name = valid_input['display_name']
            # check if the coach has changed and in case update it
            old_coach = None
            if 'coach_email' in valid_input:
                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 ccx_course_object.coach.id != coach.id:
                    old_coach = ccx_course_object.coach
                    ccx_course_object.coach = coach
            if 'course_modules' in valid_input:
                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'
                            }
                        )
                # course_modules to be stored in a json stringified field
                ccx_course_object.structure_json = json.dumps(valid_input.get('course_modules'))
            ccx_course_object.save()
            # update the overridden field for the maximum amount of students
            if 'max_students_allowed' in valid_input:
                override_field_for_ccx(
                    ccx_course_object,
                    ccx_course_object.course,
                    'max_student_enrollments_allowed',
                    valid_input['max_students_allowed']
                )
            # if the coach has changed, update the permissions
            if old_coach is not None:
                # make the new ccx coach a coach on the master course
                make_user_coach(coach, master_course_key)
                # enroll the coach in the 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,
                )
                # enroll the coach to the newly created ccx
                assign_coach_role_to_ccx(ccx_course_key, coach, master_course_object.id)

        return Response(
            status=status.HTTP_204_NO_CONTENT,
        )
Example #42
0
def students_update_enrollment(request, course_id):
    """
Enroll or unenroll students by email.
Requires staff access.

Query Parameters:
- action in ['enroll', 'unenroll']
- emails is string containing a list of emails separated by anything split_input_list can handle.
- auto_enroll is a boolean (defaults to false)
If auto_enroll is false, students will be allowed to enroll.
If auto_enroll is true, students will be enrolled as soon as they register.
- email_students is a boolean (defaults to false)
If email_students is true, students will be sent email notification
If email_students is false, students will not be sent email notification

Returns an analog to this JSON structure: {
"action": "enroll",
"auto_enroll": false,
"results": [
{
"email": "*****@*****.**",
"before": {
"enrollment": false,
"auto_enroll": false,
"user": true,
"allowed": false
},
"after": {
"enrollment": true,
"auto_enroll": false,
"user": true,
"allowed": false
}
}
]
}
"""

    action = request.GET.get('action')
    emails_raw = request.GET.get('emails')
    emails = _split_input_list(emails_raw)
    auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True]
    email_students = request.GET.get('email_students') in ['true', 'True', True]

    email_params = {}
    if email_students:
        course = get_course_by_id(course_id)
        email_params = get_email_params(course, auto_enroll)

    results = []
    for email in emails:
        try:
            if action == 'enroll':
                before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params)
            elif action == 'unenroll':
                before, after = unenroll_email(course_id, email, email_students, email_params)
            else:
                return HttpResponseBadRequest("Unrecognized action '{}'".format(action))

            results.append({
                'email': email,
                'before': before.to_dict(),
                'after': after.to_dict(),
            })
        # catch and log any exceptions
        # so that one error doesn't cause a 500.
        except Exception as exc: # pylint: disable=W0703
            log.exception("Error while #{}ing student")
            log.exception(exc)
            results.append({
                'email': email,
                'error': True,
            })

    response_payload = {
        'action': action,
        'results': results,
        'auto_enroll': auto_enroll,
    }
    return JsonResponse(response_payload)
Example #43
0
    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)
Example #44
0
    def patch(self, request, ccx_course_id=None):
        """
        Modifies a CCX course.

        Args:
            request (Request): Django request object.
            ccx_course_id (string): URI element specifying the CCX course location.
        """
        ccx_course_object, ccx_course_key, error_code, http_status = self.get_object(
            ccx_course_id, is_ccx=True)
        if ccx_course_object is None:
            return Response(status=http_status,
                            data={'error_code': error_code})

        master_course_id = request.data.get('master_course_id')
        if master_course_id is not None and unicode(
                ccx_course_object.course_id) != master_course_id:
            return Response(
                status=status.HTTP_403_FORBIDDEN,
                data={'error_code': 'master_course_id_change_not_allowed'})

        valid_input, field_errors = get_valid_input(request.data,
                                                    ignore_missing=True)
        if field_errors:
            return Response(status=status.HTTP_400_BAD_REQUEST,
                            data={'field_errors': field_errors})

        # get the master course key and master course object
        master_course_object, master_course_key, _, _ = get_valid_course(
            unicode(ccx_course_object.course_id))

        with transaction.atomic():
            # update the display name
            if 'display_name' in valid_input:
                ccx_course_object.display_name = valid_input['display_name']
            # check if the coach has changed and in case update it
            old_coach = None
            if 'coach_email' in valid_input:
                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 ccx_course_object.coach.id != coach.id:
                    old_coach = ccx_course_object.coach
                    ccx_course_object.coach = coach
            if 'course_modules' in valid_input:
                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'
                            })
                # course_modules to be stored in a json stringified field
                ccx_course_object.structure_json = json.dumps(
                    valid_input.get('course_modules'))
            ccx_course_object.save()
            # update the overridden field for the maximum amount of students
            if 'max_students_allowed' in valid_input:
                override_field_for_ccx(ccx_course_object,
                                       ccx_course_object.course,
                                       'max_student_enrollments_allowed',
                                       valid_input['max_students_allowed'])
            # if the coach has changed, update the permissions
            if old_coach is not None:
                # make the new ccx coach a coach on the master course
                make_user_coach(coach, master_course_key)
                # enroll the coach in the 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,
                )
                # enroll the coach to the newly created ccx
                assign_coach_role_to_ccx(ccx_course_key, coach,
                                         master_course_object.id)

        return Response(status=status.HTTP_204_NO_CONTENT, )
Example #45
0
def students_update_enrollment(request, course_id):
    """
    Enroll or unenroll students by email.
    Requires staff access.

    Query Parameters:
    - action in ['enroll', 'unenroll']
    - identifiers is string containing a list of emails and/or usernames separated by anything split_input_list can handle.
    - auto_enroll is a boolean (defaults to false)
        If auto_enroll is false, students will be allowed to enroll.
        If auto_enroll is true, students will be enrolled as soon as they register.
    - email_students is a boolean (defaults to false)
        If email_students is true, students will be sent email notification
        If email_students is false, students will not be sent email notification

    Returns an analog to this JSON structure: {
        "action": "enroll",
        "auto_enroll": false,
        "results": [
            {
                "email": "*****@*****.**",
                "before": {
                    "enrollment": false,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                },
                "after": {
                    "enrollment": true,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                }
            }
        ]
    }
    """

    action = request.GET.get('action')
    identifiers_raw = request.GET.get('identifiers')
    identifiers = _split_input_list(identifiers_raw)
    auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True]
    email_students = request.GET.get('email_students') in [
        'true', 'True', True
    ]

    email_params = {}
    if email_students:
        course = get_course_by_id(course_id)
        email_params = get_email_params(course, auto_enroll)

    results = []
    for identifier in identifiers:
        # First try to get a user object from the identifer
        user = None
        email = None
        try:
            user = get_student_from_identifier(identifier)
        except User.DoesNotExist:
            email = identifier
        else:
            email = user.email

        try:
            # Use django.core.validators.validate_email to check email address
            # validity (obviously, cannot check if email actually /exists/,
            # simply that it is plausibly valid)
            validate_email(email)  # Raises ValidationError if invalid

            if action == 'enroll':
                before, after = enroll_email(course_id, email, auto_enroll,
                                             email_students, email_params)
            elif action == 'unenroll':
                before, after = unenroll_email(course_id, email,
                                               email_students, email_params)
            else:
                return HttpResponseBadRequest(
                    strip_tags("Unrecognized action '{}'".format(action)))

        except ValidationError:
            # Flag this email as an error if invalid, but continue checking
            # the remaining in the list
            results.append({
                'identifier': identifier,
                'invalidIdentifier': True,
            })

        except Exception as exc:  # pylint: disable=W0703
            # catch and log any exceptions
            # so that one error doesn't cause a 500.
            log.exception("Error while #{}ing student")
            log.exception(exc)
            results.append({
                'identifier': identifier,
                'error': True,
            })

        else:
            results.append({
                'identifier': identifier,
                'before': before.to_dict(),
                'after': after.to_dict(),
            })

    response_payload = {
        'action': action,
        'results': results,
        'auto_enroll': auto_enroll,
    }
    return JsonResponse(response_payload)
Example #46
0
def students_update_enrollment(request, course_id):
    """
    Enroll or unenroll students by email.
    Requires staff access.

    Query Parameters:
    - action in ['enroll', 'unenroll']
    - emails is string containing a list of emails separated by anything split_input_list can handle.
    - auto_enroll is a boolean (defaults to false)
        If auto_enroll is false, students will be allowed to enroll.
        If auto_enroll is true, students will be enrolled as soon as they register.
    - email_students is a boolean (defaults to false)
        If email_students is true, students will be sent email notification
        If email_students is false, students will not be sent email notification

    Returns an analog to this JSON structure: {
        "action": "enroll",
        "auto_enroll": false,
        "results": [
            {
                "email": "*****@*****.**",
                "before": {
                    "enrollment": false,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                },
                "after": {
                    "enrollment": true,
                    "auto_enroll": false,
                    "user": true,
                    "allowed": false
                }
            }
        ]
    }
    """

    action = request.GET.get('action')
    emails_raw = request.GET.get('emails')
    emails = _split_input_list(emails_raw)
    auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True]
    email_students = request.GET.get('email_students') in ['true', 'True', True]

    email_params = {}
    if email_students:
        course = get_course_by_id(course_id)
        email_params = get_email_params(course, auto_enroll)

    results = []
    for email in emails:
        try:
            if action == 'enroll':
                before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params)
            elif action == 'unenroll':
                before, after = unenroll_email(course_id, email, email_students, email_params)
            else:
                return HttpResponseBadRequest("Unrecognized action '{}'".format(action))

            results.append({
                'email': email,
                'before': before.to_dict(),
                'after': after.to_dict(),
            })
        # catch and log any exceptions
        # so that one error doesn't cause a 500.
        except Exception as exc:  # pylint: disable=W0703
            log.exception("Error while #{}ing student")
            log.exception(exc)
            results.append({
                'email': email,
                'error': True,
            })

    response_payload = {
        'action': action,
        'results': results,
        'auto_enroll': auto_enroll,
    }
    return JsonResponse(response_payload)
Example #47
0
def bulk_beta_modify_access(request, course_id):
    """
    Enroll or unenroll users in beta testing program.

    Query parameters:
    - identifiers is string containing a list of emails and/or usernames separated by
      anything split_input_list can handle.
    - action is one of ['add', 'remove']
    """
    action = request.GET.get('action')
    identifiers_raw = request.GET.get('identifiers')
    identifiers = _split_input_list(identifiers_raw)
    email_students = request.GET.get('email_students') in [
        'true', 'True', True
    ]
    auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True]
    results = []
    rolename = 'beta'
    course = get_course_by_id(course_id)

    email_params = {}
    if email_students:
        email_params = get_email_params(course, auto_enroll=auto_enroll)

    for identifier in identifiers:
        try:
            error = False
            user_does_not_exist = False
            user = get_student_from_identifier(identifier)

            if action == 'add':
                allow_access(course, user, rolename)
            elif action == 'remove':
                revoke_access(course, user, rolename)
            else:
                return HttpResponseBadRequest(
                    strip_tags("Unrecognized action '{}'".format(action)))
        except User.DoesNotExist:
            error = True
            user_does_not_exist = True
        # catch and log any unexpected exceptions
        # so that one error doesn't cause a 500.
        except Exception as exc:  # pylint: disable=broad-except
            log.exception("Error while #{}ing student")
            log.exception(exc)
            error = True
        else:
            # If no exception thrown, see if we should send an email
            if email_students:
                send_beta_role_email(action, user, email_params)
            # See if we should autoenroll the student
            if auto_enroll:
                # Check if student is already enrolled
                if not CourseEnrollment.is_enrolled(user, course_id):
                    CourseEnrollment.enroll(user, course_id)

        finally:
            # Tabulate the action result of this email address
            results.append({
                'identifier': identifier,
                'error': error,
                'userDoesNotExist': user_does_not_exist
            })

    response_payload = {
        'action': action,
        'results': results,
    }
    return JsonResponse(response_payload)
Example #48
0
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_coach_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 add_master_course_staff_to_ccx(master_course,
                                   ccx_key,
                                   display_name,
                                   send_email=True):
    """
    Add staff and instructor roles on ccx to all the staff and instructors 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.
        send_email (bool): flag to switch on or off email to the users on access grant.

    """
    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)
        list_staff_ccx = list_with_level(course_ccx, 'staff')
        list_instructor_ccx = list_with_level(course_ccx, 'instructor')
        for staff in list_staff:
            # this call should be idempotent
            if staff not in list_staff_ccx:
                try:
                    # Enroll the staff in the ccx
                    enroll_email(
                        course_id=ccx_key,
                        student_email=staff.email,
                        auto_enroll=True,
                        email_students=send_email,
                        email_params=email_params,
                    )

                    # allow 'staff' access on ccx to staff of master course
                    allow_access(course_ccx, staff, 'staff')
                except CourseEnrollmentException:
                    log.warning(
                        "Unable to enroll staff %s to course with id %s",
                        staff.email, ccx_key)
                    continue
                except SMTPException:
                    continue

        for instructor in list_instructor:
            # this call should be idempotent
            if instructor not in list_instructor_ccx:
                try:
                    # Enroll the instructor in the ccx
                    enroll_email(
                        course_id=ccx_key,
                        student_email=instructor.email,
                        auto_enroll=True,
                        email_students=send_email,
                        email_params=email_params,
                    )

                    # allow 'instructor' access on ccx to instructor of master course
                    allow_access(course_ccx, instructor, 'instructor')
                except CourseEnrollmentException:
                    log.warning(
                        "Unable to enroll instructor %s to course with id %s",
                        instructor.email, ccx_key)
                    continue
                except SMTPException:
                    continue