def test_clone_course(self):

        course_data = {
            "template": "i4x://edx/templates/course/Empty",
            "org": "MITx",
            "number": "999",
            "display_name": "Robot Super Course",
        }

        module_store = modulestore("direct")
        import_from_xml(module_store, "common/test/data/", ["full"])

        resp = self.client.post(reverse("create_new_course"), course_data)
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertEqual(data["id"], "i4x://MITx/999/course/Robot_Super_Course")

        content_store = contentstore()

        source_location = CourseDescriptor.id_to_location("edX/full/6.002_Spring_2012")
        dest_location = CourseDescriptor.id_to_location("MITx/999/Robot_Super_Course")

        clone_course(module_store, content_store, source_location, dest_location)

        # now loop through all the units in the course and verify that the clone can render them, which
        # means the objects are at least present
        items = module_store.get_items(Location(["i4x", "edX", "full", "vertical", None]))
        self.assertGreater(len(items), 0)
        clone_items = module_store.get_items(Location(["i4x", "MITx", "999", "vertical", None]))
        self.assertGreater(len(clone_items), 0)
        for descriptor in items:
            new_loc = descriptor.location.replace(org="MITx", course="999")
            print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
            resp = self.client.get(reverse("edit_unit", kwargs={"location": new_loc.url()}))
            self.assertEqual(resp.status_code, 200)
示例#2
0
    def handle(self, *args, **options):
        "Execute the command"
        if len(args) != 2:
            raise CommandError("clone requires 2 arguments: <source-course_id> <dest-course_id>")

        source_course_id = args[0]
        dest_course_id = args[1]

        mstore = modulestore('direct')
        cstore = contentstore()

        course_id_dict = Location.parse_course_id(dest_course_id)
        mstore.ignore_write_events_on_courses.append('{org}/{course}'.format(**course_id_dict))

        print("Cloning course {0} to {1}".format(source_course_id, dest_course_id))

        source_location = CourseDescriptor.id_to_location(source_course_id)
        dest_location = CourseDescriptor.id_to_location(dest_course_id)

        if clone_course(mstore, cstore, source_location, dest_location):
            # be sure to recompute metadata inheritance after all those updates
            mstore.refresh_cached_metadata_inheritance_tree(dest_location)

            print("copying User permissions...")
            # purposely avoids auth.add_user b/c it doesn't have a caller to authorize
            CourseInstructorRole(dest_location).add_users(
                *CourseInstructorRole(source_location).users_with_role()
            )
            CourseStaffRole(dest_location).add_users(
                *CourseStaffRole(source_location).users_with_role()
            )
    def handle(self, *args, **options):
        "Execute the command"
        if len(args) != 2:
            raise CommandError(
                "clone requires two arguments: <source-course_id> <dest-course_id>"
            )

        source_course_id = args[0]
        dest_course_id = args[1]

        mstore = modulestore('direct')
        cstore = contentstore()

        org, course_num, run = dest_course_id.split("/")
        mstore.ignore_write_events_on_courses.append('{0}/{1}'.format(
            org, course_num))

        print("Cloning course {0} to {1}".format(source_course_id,
                                                 dest_course_id))

        source_location = CourseDescriptor.id_to_location(source_course_id)
        dest_location = CourseDescriptor.id_to_location(dest_course_id)

        if clone_course(mstore, cstore, source_location, dest_location):
            # be sure to recompute metadata inheritance after all those updates
            mstore.refresh_cached_metadata_inheritance_tree(dest_location)

            print("copying User permissions...")
            _copy_course_group(source_location, dest_location)
示例#4
0
    def handle(self, *args, **options):
        "Execute the command"
        if len(args) != 2:
            raise CommandError("clone requires two arguments: <source-course_id> <dest-course_id>")

        source_course_id = args[0]
        dest_course_id = args[1]

        mstore = modulestore('direct')
        cstore = contentstore()

        org, course_num, run = dest_course_id.split("/")
        mstore.ignore_write_events_on_courses.append('{0}/{1}'.format(org, course_num))

        print("Cloning course {0} to {1}".format(source_course_id, dest_course_id))

        source_location = CourseDescriptor.id_to_location(source_course_id)
        dest_location = CourseDescriptor.id_to_location(dest_course_id)

        if clone_course(mstore, cstore, source_location, dest_location):
            # be sure to recompute metadata inheritance after all those updates
            mstore.refresh_cached_metadata_inheritance_tree(dest_location)

            print("copying User permissions...")
            _copy_course_group(source_location, dest_location)
示例#5
0
    def test_clone_course(self):

        course_data = {
            'template': 'i4x://edx/templates/course/Empty',
            'org': 'MITx',
            'number': '999',
            'display_name': 'Robot Super Course',
        }

        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['full'])

        resp = self.client.post(reverse('create_new_course'), course_data)
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')

        content_store = contentstore()

        source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
        dest_location = CourseDescriptor.id_to_location('MITx/999/Robot_Super_Course')

        clone_course(module_store, content_store, source_location, dest_location)

        # now loop through all the units in the course and verify that the clone can render them, which
        # means the objects are at least present
        items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
        self.assertGreater(len(items), 0)
        clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'vertical', None]))
        self.assertGreater(len(clone_items), 0)
        for descriptor in items:
            new_loc = descriptor.location.replace(org='MITx', course='999')
            print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
            resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
            self.assertEqual(resp.status_code, 200)
示例#6
0
    def handle(self, *args, **options):
        "Execute the command"
        if len(args) != 2:
            raise CommandError(
                "clone requires 2 arguments: <source-course_id> <dest-course_id>"
            )

        source_course_id = args[0]
        dest_course_id = args[1]

        mstore = modulestore('direct')
        cstore = contentstore()

        course_id_dict = Location.parse_course_id(dest_course_id)
        mstore.ignore_write_events_on_courses.append(
            '{org}/{course}'.format(**course_id_dict))

        print("Cloning course {0} to {1}".format(source_course_id,
                                                 dest_course_id))

        source_location = CourseDescriptor.id_to_location(source_course_id)
        dest_location = CourseDescriptor.id_to_location(dest_course_id)

        if clone_course(mstore, cstore, source_location, dest_location):
            # be sure to recompute metadata inheritance after all those updates
            mstore.refresh_cached_metadata_inheritance_tree(dest_location)

            print("copying User permissions...")
            # purposely avoids auth.add_user b/c it doesn't have a caller to authorize
            CourseInstructorRole(dest_location).add_users(
                *CourseInstructorRole(source_location).users_with_role())
            CourseStaffRole(dest_location).add_users(
                *CourseStaffRole(source_location).users_with_role())
示例#7
0
def delete_course_and_groups(course_id, commit=False):
    """
    This deletes the courseware associated with a course_id as well as cleaning update_item
    the various user table stuff (groups, permissions, etc.)
    """
    module_store = modulestore('direct')
    content_store = contentstore()

    course_id_dict = Location.parse_course_id(course_id)
    module_store.ignore_write_events_on_courses.append('{org}/{course}'.format(**course_id_dict))

    loc = CourseDescriptor.id_to_location(course_id)
    if delete_course(module_store, content_store, loc, commit):

        print 'removing User permissions from course....'
        # in the django layer, we need to remove all the user permissions groups associated with this course
        if commit:
            try:
                staff_role = CourseStaffRole(loc)
                staff_role.remove_users(*staff_role.users_with_role())
                instructor_role = CourseInstructorRole(loc)
                instructor_role.remove_users(*instructor_role.users_with_role())
            except Exception as err:
                log.error("Error in deleting course groups for {0}: {1}".format(loc, err))

            # remove location of this course from loc_mapper and cache
            loc_mapper().delete_course_mapping(loc)
示例#8
0
    def handle(self, *args, **options):
        if len(args) != 1 and len(args) != 2:
            raise CommandError(
                "delete_course requires one or more arguments: <location> |commit|")

        loc_str = args[0]

        commit = False
        if len(args) == 2:
            commit = args[1] == 'commit'

        if commit:
            print 'Actually going to delete the course from DB....'

        ms = modulestore('direct')
        cs = contentstore()

        if query_yes_no("Deleting course {0}. Confirm?".format(loc_str), default="no"):
            if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
                loc = CourseDescriptor.id_to_location(loc_str)
                if delete_course(ms, cs, loc, commit):
                    print 'removing User permissions from course....'
                    # in the django layer, we need to remove all the user
                    # permissions groups associated with this course
                    if commit:
                        _delete_course_group(loc)
示例#9
0
    def purchased_callback(self):
        """
        When purchased, this should enroll the user in the course.  We are assuming that
        course settings for enrollment date are configured such that only if the (user.email, course_id) pair is found
        in CourseEnrollmentAllowed will the user be allowed to enroll.  Otherwise requiring payment
        would in fact be quite silly since there's a clear back door.
        """
        try:
            course_loc = CourseDescriptor.id_to_location(self.course_id)
            course_exists = modulestore().has_item(self.course_id, course_loc)
        except ValueError:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!"
                .format(self.course_id))

        if not course_exists:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!"
                .format(self.course_id))

        CourseEnrollment.enroll(user=self.user,
                                course_id=self.course_id,
                                mode=self.mode)

        log.info("Enrolled {0} in paid course {1}, paid ${2}".format(
            self.user.email, self.course_id, self.line_cost))
        org, course_num, run = self.course_id.split("/")
        statsd.increment(
            "shoppingcart.PaidCourseRegistration.purchased_callback.enrollment",
            tags=[
                "org:{0}".format(org), "course:{0}".format(course_num),
                "run:{0}".format(run)
            ])
示例#10
0
def delete_course_and_groups(course_id, commit=False):
    """
    This deletes the courseware associated with a course_id as well as cleaning update_item
    the various user table stuff (groups, permissions, etc.)
    """
    module_store = modulestore('direct')
    content_store = contentstore()

    course_id_dict = Location.parse_course_id(course_id)
    module_store.ignore_write_events_on_courses.append(
        '{org}/{course}'.format(**course_id_dict))

    loc = CourseDescriptor.id_to_location(course_id)
    if delete_course(module_store, content_store, loc, commit):

        print 'removing User permissions from course....'
        # in the django layer, we need to remove all the user permissions groups associated with this course
        if commit:
            try:
                staff_role = CourseStaffRole(loc)
                staff_role.remove_users(*staff_role.users_with_role())
                instructor_role = CourseInstructorRole(loc)
                instructor_role.remove_users(
                    *instructor_role.users_with_role())
            except Exception as err:
                log.error(
                    "Error in deleting course groups for {0}: {1}".format(
                        loc, err))

            # remove location of this course from loc_mapper and cache
            loc_mapper().delete_course_mapping(loc)
示例#11
0
    def purchased_callback(self):
        """
        When purchased, this should enroll the user in the course.  We are assuming that
        course settings for enrollment date are configured such that only if the (user.email, course_id) pair is found
        in CourseEnrollmentAllowed will the user be allowed to enroll.  Otherwise requiring payment
        would in fact be quite silly since there's a clear back door.
        """
        try:
            course_loc = CourseDescriptor.id_to_location(self.course_id)
            course_exists = modulestore().has_item(self.course_id, course_loc)
        except ValueError:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!"
                .format(self.course_id))

        if not course_exists:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!"
                .format(self.course_id))

        CourseEnrollment.enroll(user=self.user,
                                course_id=self.course_id,
                                mode=self.mode)

        log.info("Enrolled {0} in paid course {1}, paid ${2}".format(
            self.user.email, self.course_id, self.line_cost))  # pylint: disable=E1101
示例#12
0
    def purchased_callback(self):
        """
        When purchased, this should enroll the user in the course.  We are assuming that
        course settings for enrollment date are configured such that only if the (user.email, course_id) pair is found
        in CourseEnrollmentAllowed will the user be allowed to enroll.  Otherwise requiring payment
        would in fact be quite silly since there's a clear back door.
        """
        try:
            course_loc = CourseDescriptor.id_to_location(self.course_id)
            course_exists = modulestore().has_item(self.course_id, course_loc)
        except ValueError:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!".format(self.course_id)
            )

        if not course_exists:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!".format(self.course_id)
            )

        CourseEnrollment.enroll(user=self.user, course_id=self.course_id, mode=self.mode)

        log.info(
            "Enrolled {0} in paid course {1}, paid ${2}".format(self.user.email, self.course_id, self.line_cost)
        )  # pylint: disable=E1101
示例#13
0
    def get_static_transcript(self, request):
        """
        Courses that are imported with the --nostatic flag do not show
        transcripts/captions properly even if those captions are stored inside
        their static folder. This adds a last resort method of redirecting to
        the static asset path of the course if the transcript can't be found
        inside the contentstore and the course has the static_asset_path field
        set.
        """
        response = Response(status=404)
        # Only do redirect for English
        if not self.transcript_language == 'en':
            return response

        video_id = request.GET.get('videoId', None)
        if video_id:
            transcript_name = video_id
        else:
            transcript_name = self.sub

        if transcript_name:
            course_location = CourseDescriptor.id_to_location(self.course_id)
            course = self.descriptor.runtime.modulestore.get_item(course_location)
            if course.static_asset_path:
                response = Response(
                    status=307,
                    location='/static/{0}/{1}'.format(
                        course.static_asset_path,
                        subs_filename(transcript_name, self.transcript_language)
                    )
                )
        return response
    def test_unicode_chars_in_xml_content(self):
        # edX/full/6.002_Spring_2012 has non-ASCII chars, and during
        # uniquification of names, would raise a UnicodeError. It no longer does.

        # Ensure that there really is a non-ASCII character in the course.
        with open(
                os.path.join(
                    DATA_DIR,
                    "full/sequential/Administrivia_and_Circuit_Elements.xml")
        ) as xmlf:
            xml = xmlf.read()
            with assert_raises(UnicodeDecodeError):
                xml.decode('ascii')

        # Load the course, but don't make error modules.  This will succeed,
        # but will record the errors.
        modulestore = XMLModuleStore(DATA_DIR,
                                     course_dirs=['full'],
                                     load_error_modules=False)

        # Look up the errors during load. There should be none.
        location = CourseDescriptor.id_to_location(
            "edX/full/6.002_Spring_2012")
        errors = modulestore.get_item_errors(location)
        assert errors == []
示例#15
0
def get_array_section_has_problem(course_id):
    """
    Returns an array of true/false whether each section has problems.

    `course_id` the course ID for the course interested in

    The ith value in the array is true if the ith section in the course contains problems and false otherwise.
    """

    course = modulestore().get_instance(
        course_id, CourseDescriptor.id_to_location(course_id), depth=4)

    b_section_has_problem = [False] * len(course.get_children())
    i = 0
    for section in course.get_children():
        for subsection in section.get_children():
            for unit in subsection.get_children():
                for child in unit.get_children():
                    if child.location.category == 'problem':
                        b_section_has_problem[i] = True
                        break  # out of child loop
                if b_section_has_problem[i]:
                    break  # out of unit loop
            if b_section_has_problem[i]:
                break  # out of subsection loop

        i += 1

    return b_section_has_problem
示例#16
0
def get_d3_sequential_open_distrib(course_id):
    """
    Returns how many students opened a sequential/subsection for each section, data already in format for d3 function.

    `course_id` the course ID for the course interested in

    Returns an array in the order of the sections and each dict has:
      'display_name' - display name for the section
      'data' - data for the d3_stacked_bar_graph function of how many students opened each sequential/subsection
    """
    sequential_open_distrib = get_sequential_open_distrib(course_id)

    d3_data = []

    # Retrieve course object down to subsection
    course = modulestore().get_instance(
        course_id, CourseDescriptor.id_to_location(course_id), depth=2)

    # Iterate through sections, subsections
    for section in course.get_children():
        curr_section = {}
        curr_section['display_name'] = own_metadata(section).get(
            'display_name', '')
        data = []
        c_subsection = 0

        # Construct data for each subsection to be sent to d3
        for subsection in section.get_children():
            c_subsection += 1
            subsection_name = own_metadata(subsection).get('display_name', '')

            num_students = 0
            if subsection.location.url() in sequential_open_distrib:
                num_students = sequential_open_distrib[
                    subsection.location.url()]

            stack_data = []
            tooltip = _(
                "{num_students} student(s) opened Subsection {subsection_num}: {subsection_name}"
            ).format(
                num_students=num_students,
                subsection_num=c_subsection,
                subsection_name=subsection_name,
            )

            stack_data.append({
                'color': 0,
                'value': num_students,
                'tooltip': tooltip,
            })
            subsection = {
                'xValue': "SS {0}".format(c_subsection),
                'stackData': stack_data,
            }
            data.append(subsection)

        curr_section['data'] = data
        d3_data.append(curr_section)

    return d3_data
示例#17
0
def delete_course_and_groups(course_id, commit=False):
    """
    This deletes the courseware associated with a course_id as well as cleaning update_item
    the various user table stuff (groups, permissions, etc.)
    """
    module_store = modulestore('direct')
    content_store = contentstore()

    org, course_num, _ = course_id.split("/")
    module_store.ignore_write_events_on_courses.append('{0}/{1}'.format(
        org, course_num))

    loc = CourseDescriptor.id_to_location(course_id)
    if delete_course(module_store, content_store, loc, commit):
        print 'removing forums permissions and roles...'
        unseed_permissions_roles(course_id)

        print 'removing User permissions from course....'
        # in the django layer, we need to remove all the user permissions groups associated with this course
        if commit:
            try:
                staff_role = CourseStaffRole(loc)
                staff_role.remove_users(*staff_role.users_with_role())
                instructor_role = CourseInstructorRole(loc)
                instructor_role.remove_users(
                    *instructor_role.users_with_role())
            except Exception as err:
                log.error(
                    "Error in deleting course groups for {0}: {1}".format(
                        loc, err))
示例#18
0
    def handle(self, *args, **options):
        if len(args) != 1 and len(args) != 2:
            raise CommandError("delete_course requires one or more arguments: <location> |commit|")

        course_id = args[0]

        commit = False
        if len(args) == 2:
            commit = args[1] == 'commit'

        if commit:
            print 'Actually going to delete the course from DB....'

        ms = modulestore('direct')
        cs = contentstore()

        org, course_num, run = course_id.split("/")
        ms.ignore_write_events_on_courses.append('{0}/{1}'.format(org, course_num))

        if query_yes_no("Deleting course {0}. Confirm?".format(course_id), default="no"):
            if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
                loc = CourseDescriptor.id_to_location(course_id)
                if delete_course(ms, cs, loc, commit):
                    print 'removing User permissions from course....'
                    # in the django layer, we need to remove all the user permissions groups associated with this course
                    if commit:
                        try:
                            _delete_course_group(loc)
                        except Exception as err:
                            print("Error in deleting course groups for {0}: {1}".format(loc, err))
示例#19
0
def jump_to_id(request, course_id, module_id):
    """
    This entry point allows for a shorter version of a jump to where just the id of the element is
    passed in. This assumes that id is unique within the course_id namespace
    """

    course_location = CourseDescriptor.id_to_location(course_id)

    items = modulestore().get_items(
        Location('i4x', course_location.org, course_location.course, None, module_id),
        course_id=course_id
    )

    if len(items) == 0:
        raise Http404(
            u"Could not find id: {0} in course_id: {1}. Referer: {2}".format(
                module_id, course_id, request.META.get("HTTP_REFERER", "")
            ))
    if len(items) > 1:
        log.warning(
            u"Multiple items found with id: {0} in course_id: {1}. Referer: {2}. Using first: {3}".format(
                module_id, course_id, request.META.get("HTTP_REFERER", ""), items[0].location.url()
            ))

    return jump_to(request, course_id, items[0].location.url())
示例#20
0
    def handle(self, *args, **options):
        "Execute the command"
        if len(args) != 1:
            raise CommandError("export requires one argument: <output path>")

        output_path = args[0]

        cs = contentstore()
        ms = modulestore('direct')
        root_dir = output_path
        courses = ms.get_courses()

        print("%d courses to export:" % len(courses))
        cids = [x.id for x in courses]
        print(cids)

        for course_id in cids:

            print("-"*77)
            print("Exporting course id = {0} to {1}".format(course_id, output_path))

            if 1:
                try:
                    location = CourseDescriptor.id_to_location(course_id)
                    course_dir = course_id.replace('/', '...')
                    export_to_xml(ms, cs, location, root_dir, course_dir, modulestore())
                except Exception as err:
                    print("="*30 + "> Oops, failed to export %s" % course_id)
                    print("Error:")
                    print(err)
    def test_prefetch_children(self):
        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['full'])

        location = CourseDescriptor.id_to_location(
            'edX/full/6.002_Spring_2012')

        wrapper = MongoCollectionFindWrapper(module_store.collection.find)
        module_store.collection.find = wrapper.find
        course = module_store.get_item(location, depth=2)

        # make sure we haven't done too many round trips to DB
        # note we say 4 round trips here for 1) the course, 2 & 3) for the chapters and sequentials, and
        # 4) because of the RT due to calculating the inherited metadata
        self.assertEqual(wrapper.counter, 4)

        # make sure we pre-fetched a known sequential which should be at
        # depth=2
        self.assertTrue(Location(['i4x', 'edX', 'full', 'sequential',
                                  'Administrivia_and_Circuit_Elements', None]) in course.system.module_data)

        # make sure we don't have a specific vertical which should be at
        # depth=3
        self.assertFalse(Location(['i4x', 'edX', 'full', 'vertical', 'vertical_58', None])
                         in course.system.module_data)
示例#22
0
def course_context_from_course_id(course_id):
    """
    Creates a course context from a `course_id`.

    Example Returned Context::

        {
            'course_id': 'org/course/run',
            'org_id': 'org'
        }

    """

    course_id = course_id or ''
    context = {
        'course_id': course_id,
        'org_id': ''
    }

    if course_id:
        try:
            location = CourseDescriptor.id_to_location(course_id)
            context['org_id'] = location.org
        except ValueError:
            log.warning(
                'Unable to parse course_id "{course_id}"'.format(
                    course_id=course_id
                ),
                exc_info=True
            )

    return context
示例#23
0
    def handle(self, *args, **options):

        user = options['username']
        course_id = options['course']
        if not (course_id and user):
            raise CommandError('both course id and student username are required')

        student = None
        print "Fetching enrollment for student {0} in {1}".format(user, course_id)
        if '@' in user:
            student = User.objects.get(email=user, courseenrollment__course_id=course_id)
        else:
            student = User.objects.get(username=user, courseenrollment__course_id=course_id)

        print "Fetching course data for {0}".format(course_id)
        course = modulestore().get_instance(course_id, CourseDescriptor.id_to_location(course_id), depth=2)

        if not options['noop']:
            # Add the certificate request to the queue
            xq = XQueueCertInterface()
            if options['insecure']:
                xq.use_https = False
            ret = xq.regen_cert(student, course_id, course=course)
            print '{0} - {1}'.format(student, ret)
        else:
            print "noop option given, skipping work queueing..."
示例#24
0
def request_certificate(request):
    """Request the on-demand creation of a certificate for some user, course.

    A request doesn't imply a guarantee that such a creation will take place.
    We intentionally use the same machinery as is used for doing certification
    at the end of a course run, so that we can be sure users get graded and
    then if and only if they pass, do they get a certificate issued.
    """
    if request.method == "POST":
        if request.user.is_authenticated():
            xqci = XQueueCertInterface()
            username = request.user.username
            student = User.objects.get(username=username)
            course_id = request.POST.get('course_id')
            course = modulestore().get_instance(
                course_id, CourseDescriptor.id_to_location(course_id), depth=2)

            status = certificate_status_for_student(student,
                                                    course_id)['status']
            if status in [
                    CertificateStatuses.unavailable,
                    CertificateStatuses.notpassing, CertificateStatuses.error
            ]:
                logger.info(
                    'Grading and certification requested for user {} in course {} via /request_certificate call'
                    .format(username, course_id))
                status = xqci.add_cert(student, course_id, course=course)
            return HttpResponse(json.dumps({'add_status': status}),
                                mimetype='application/json')
        return HttpResponse(json.dumps({'add_status': 'ERRORANONYMOUSUSER'}),
                            mimetype='application/json')
示例#25
0
    def test_prefetch_children(self):
        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['full'])

        location = CourseDescriptor.id_to_location(
            'edX/full/6.002_Spring_2012')

        wrapper = MongoCollectionFindWrapper(module_store.collection.find)
        module_store.collection.find = wrapper.find
        course = module_store.get_item(location, depth=2)

        # make sure we haven't done too many round trips to DB
        # note we say 4 round trips here for 1) the course, 2 & 3) for the chapters and sequentials, and
        # 4) because of the RT due to calculating the inherited metadata
        self.assertEqual(wrapper.counter, 4)

        # make sure we pre-fetched a known sequential which should be at depth=2
        self.assertTrue(
            Location([
                'i4x', 'edX', 'full', 'sequential',
                'Administrivia_and_Circuit_Elements', None
            ]) in course.system.module_data)

        # make sure we don't have a specific vertical which should be at depth=3
        self.assertFalse(
            Location(['i4x', 'edX', 'full', 'vertical', 'vertical_58', None])
            in course.system.module_data)
示例#26
0
    def handle(self, *args, **options):
        if len(args) != 1 and len(args) != 2:
            raise CommandError(
                "delete_course requires one or more arguments: <location> |commit|"
            )

        loc_str = args[0]

        commit = False
        if len(args) == 2:
            commit = args[1] == 'commit'

        if commit:
            print 'Actually going to delete the course from DB....'

        ms = modulestore('direct')
        cs = contentstore()

        if query_yes_no("Deleting course {0}. Confirm?".format(loc_str),
                        default="no"):
            if query_yes_no("Are you sure. This action cannot be undone!",
                            default="no"):
                loc = CourseDescriptor.id_to_location(loc_str)
                if delete_course(ms, cs, loc, commit):
                    print 'removing User permissions from course....'
                    # in the django layer, we need to remove all the user permissions groups associated with this course
                    if commit:
                        _delete_course_group(loc)
示例#27
0
def delete_course_and_groups(course_id, commit=False):
    """
    This deletes the courseware associated with a course_id as well as cleaning update_item
    the various user table stuff (groups, permissions, etc.)
    """
    module_store = modulestore('direct')
    content_store = contentstore()

    org, course_num, _ = course_id.split("/")
    module_store.ignore_write_events_on_courses.append('{0}/{1}'.format(
        org, course_num))

    loc = CourseDescriptor.id_to_location(course_id)
    if delete_course(module_store, content_store, loc, commit):
        print 'removing forums permissions and roles...'
        unseed_permissions_roles(course_id)

        print 'removing User permissions from course....'
        # in the django layer, we need to remove all the user permissions groups associated with this course
        if commit:
            try:
                staff_role = CourseStaffRole(loc)
                staff_role.remove_users(*staff_role.users_with_role())
                instructor_role = CourseInstructorRole(loc)
                instructor_role.remove_users(
                    *instructor_role.users_with_role())
            except Exception as err:
                log.error(
                    "Error in deleting course groups for {0}: {1}".format(
                        loc, err))
示例#28
0
def jump_to_id(request, course_id, module_id):
    """
    This entry point allows for a shorter version of a jump to where just the id of the element is
    passed in. This assumes that id is unique within the course_id namespace
    """

    course_location = CourseDescriptor.id_to_location(course_id)

    items = modulestore().get_items(
        Location("i4x", course_location.org, course_location.course, None, module_id), course_id=course_id
    )

    if len(items) == 0:
        raise Http404(
            "Could not find id = {0} in course_id = {1}. Referer = {2}".format(
                module_id, course_id, request.META.get("HTTP_REFERER", "")
            )
        )
    if len(items) > 1:
        log.warning(
            "Multiple items found with id = {0} in course_id = {1}. Referer = {2}. Using first found {3}...".format(
                module_id, course_id, request.META.get("HTTP_REFERER", ""), items[0].location.url()
            )
        )

    return jump_to(request, course_id, items[0].location.url())
示例#29
0
 def get_course(self):
     """
     Return course by course id.
     """
     course_location = CourseDescriptor.id_to_location(self.course_id)
     course = self.descriptor.runtime.modulestore.get_item(course_location)
     return course
示例#30
0
 def get_course(self):
     """
     Return course by course id.
     """
     course_location = CourseDescriptor.id_to_location(self.course_id)
     course = self.descriptor.runtime.modulestore.get_item(course_location)
     return course
    def test_remove_hide_progress_tab(self):
        module_store = modulestore("direct")
        import_from_xml(module_store, "common/test/data/", ["full"])

        source_location = CourseDescriptor.id_to_location("edX/full/6.002_Spring_2012")
        course = module_store.get_item(source_location)
        self.assertFalse(course.hide_progress_tab)
示例#32
0
def get_array_section_has_problem(course_id):
    """
    Returns an array of true/false whether each section has problems.

    `course_id` the course ID for the course interested in

    The ith value in the array is true if the ith section in the course contains problems and false otherwise.
    """

    course = modulestore().get_instance(course_id, CourseDescriptor.id_to_location(course_id), depth=4)

    b_section_has_problem = [False] * len(course.get_children())
    i = 0
    for section in course.get_children():
        for subsection in section.get_children():
            for unit in subsection.get_children():
                for child in unit.get_children():
                    if child.location.category == 'problem':
                        b_section_has_problem[i] = True
                        break  # out of child loop
                if b_section_has_problem[i]:
                    break  # out of unit loop
            if b_section_has_problem[i]:
                break  # out of subsection loop

        i += 1

    return b_section_has_problem
示例#33
0
def course_context_from_course_id(course_id):
    """
    Creates a course context from a `course_id`.

    Example Returned Context::

        {
            'course_id': 'org/course/run',
            'org_id': 'org'
        }

    """

    course_id = course_id or ''
    context = {'course_id': course_id, 'org_id': ''}

    if course_id:
        try:
            location = CourseDescriptor.id_to_location(course_id)
            context['org_id'] = location.org
        except ValueError:
            log.warning('Unable to parse course_id "{course_id}"'.format(
                course_id=course_id),
                        exc_info=True)

    return context
示例#34
0
    def get_static_transcript(self, request):
        """
        Courses that are imported with the --nostatic flag do not show
        transcripts/captions properly even if those captions are stored inside
        their static folder. This adds a last resort method of redirecting to
        the static asset path of the course if the transcript can't be found
        inside the contentstore and the course has the static_asset_path field
        set.
        """
        response = Response(status=404)
        # Only do redirect for English
        if not self.transcript_language == "en":
            return response

        video_id = request.GET.get("videoId", None)
        if video_id:
            transcript_name = video_id
        else:
            transcript_name = self.sub

        if transcript_name:
            course_location = CourseDescriptor.id_to_location(self.course_id)
            course = self.descriptor.runtime.modulestore.get_item(course_location)
            if course.static_asset_path:
                response = Response(
                    status=307,
                    location="/static/{0}/{1}".format(
                        course.static_asset_path, subs_filename(transcript_name, self.transcript_language)
                    ),
                )
        return response
示例#35
0
    def test_remove_hide_progress_tab(self):
        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['full'])

        source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
        course = module_store.get_item(source_location)
        self.assertFalse(course.hide_progress_tab)
示例#36
0
    def handle(self, *args, **options):

        user = options['username']
        course_id = options['course']
        if not (course_id and user):
            raise CommandError(
                'both course id and student username are required')

        student = None
        print "Fetching enrollment for student {0} in {1}".format(
            user, course_id)
        if '@' in user:
            student = User.objects.get(email=user,
                                       courseenrollment__course_id=course_id)
        else:
            student = User.objects.get(username=user,
                                       courseenrollment__course_id=course_id)

        print "Fetching course data for {0}".format(course_id)
        course = modulestore().get_instance(
            course_id, CourseDescriptor.id_to_location(course_id), depth=2)

        if not options['noop']:
            # Add the certificate request to the queue
            xq = XQueueCertInterface()
            if options['insecure']:
                xq.use_https = False
            ret = xq.regen_cert(student,
                                course_id,
                                course=course,
                                forced_grade=options['grade_value'],
                                template_file=options['template_file'])
            print '{0} - {1}'.format(student, ret)
        else:
            print "noop option given, skipping work queueing..."
示例#37
0
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError("export requires one argument: <output path>")

        output_path = args[0]

        cs = contentstore()
        ms = modulestore('direct')
        root_dir = output_path
        courses = ms.get_courses()

        print "%d courses to export:" % len(courses)
        cids = [x.id for x in courses]
        print cids

        for course_id in cids:

            print "-" * 77
            print "Exporting course id = {0} to {1}".format(
                course_id, output_path)

            if 1:
                try:
                    location = CourseDescriptor.id_to_location(course_id)
                    course_dir = course_id.replace('/', '...')
                    export_to_xml(ms, cs, location, root_dir, course_dir,
                                  modulestore())
                except Exception as err:
                    print "=" * 30 + "> Oops, failed to export %s" % course_id
                    print "Error:"
                    print err
示例#38
0
    def purchased_callback(self):
        """
        When purchased, this should enroll the user in the course.  We are assuming that
        course settings for enrollment date are configured such that only if the (user.email, course_id) pair is found
        in CourseEnrollmentAllowed will the user be allowed to enroll.  Otherwise requiring payment
        would in fact be quite silly since there's a clear back door.
        """
        try:
            course_loc = CourseDescriptor.id_to_location(self.course_id)
            course_exists = modulestore().has_item(self.course_id, course_loc)
        except ValueError:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!".format(self.course_id))

        if not course_exists:
            raise PurchasedCallbackException(
                "The customer purchased Course {0}, but that course doesn't exist!".format(self.course_id))

        CourseEnrollment.enroll(user=self.user, course_id=self.course_id, mode=self.mode)

        log.info("Enrolled {0} in paid course {1}, paid ${2}".format(self.user.email, self.course_id, self.line_cost))
        org, course_num, run = self.course_id.split("/")
        dog_stats_api.increment(
            "shoppingcart.PaidCourseRegistration.purchased_callback.enrollment",
            tags=["org:{0}".format(org),
                  "course:{0}".format(course_num),
                  "run:{0}".format(run)]
        )
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError(
                "check_course requires one argument: <location>")

        loc_str = args[0]

        loc = CourseDescriptor.id_to_location(loc_str)
        store = modulestore()

        course = store.get_item(loc, depth=3)

        err_cnt = 0

        def _xlint_metadata(module):
            err_cnt = check_module_metadata_editability(module)
            for child in module.get_children():
                err_cnt = err_cnt + _xlint_metadata(child)
            return err_cnt

        err_cnt = err_cnt + _xlint_metadata(course)

        # we've had a bug where the xml_attributes field can we rewritten as a string rather than a dict
        def _check_xml_attributes_field(module):
            err_cnt = 0
            if hasattr(module, 'xml_attributes') and isinstance(
                    module.xml_attributes, basestring):
                print 'module = {0} has xml_attributes as a string. It should be a dict'.format(
                    module.location.url())
                err_cnt = err_cnt + 1
            for child in module.get_children():
                err_cnt = err_cnt + _check_xml_attributes_field(child)
            return err_cnt

        err_cnt = err_cnt + _check_xml_attributes_field(course)

        # check for dangling discussion items, this can cause errors in the forums
        def _get_discussion_items(module):
            discussion_items = []
            if module.location.category == 'discussion':
                discussion_items = discussion_items + [module.location.url()]

            for child in module.get_children():
                discussion_items = discussion_items + _get_discussion_items(
                    child)

            return discussion_items

        discussion_items = _get_discussion_items(course)

        # now query all discussion items via get_items() and compare with the tree-traversal
        queried_discussion_items = store.get_items([
            'i4x', course.location.org, course.location.course, 'discussion',
            None, None
        ])

        for item in queried_discussion_items:
            if item.location.url() not in discussion_items:
                print 'Found dangling discussion module = {0}'.format(
                    item.location.url())
示例#40
0
def get_d3_sequential_open_distrib(course_id):
    """
    Returns how many students opened a sequential/subsection for each section, data already in format for d3 function.

    `course_id` the course ID for the course interested in

    Returns an array in the order of the sections and each dict has:
      'display_name' - display name for the section
      'data' - data for the d3_stacked_bar_graph function of how many students opened each sequential/subsection
    """
    sequential_open_distrib = get_sequential_open_distrib(course_id)

    d3_data = []

    # Retrieve course object down to subsection
    course = modulestore().get_instance(course_id, CourseDescriptor.id_to_location(course_id), depth=2)

    # Iterate through sections, subsections
    for section in course.get_children():
        curr_section = {}
        curr_section['display_name'] = own_metadata(section).get('display_name', '')
        data = []
        c_subsection = 0

        # Construct data for each subsection to be sent to d3
        for subsection in section.get_children():
            c_subsection += 1
            subsection_name = own_metadata(subsection).get('display_name', '')

            num_students = 0
            if subsection.location.url() in sequential_open_distrib:
                num_students = sequential_open_distrib[subsection.location.url()]

            stack_data = []

            # Tooltip parameters for subsection in open_distribution view
            tooltip = {
                'type': 'subsection',
                'num_students': num_students,
                'subsection_num': c_subsection,
                'subsection_name': subsection_name
            }

            stack_data.append({
                'color': 0,
                'value': num_students,
                'tooltip': tooltip,
                'module_url': subsection.location.url(),
            })
            subsection = {
                'xValue': "SS {0}".format(c_subsection),
                'stackData': stack_data,
            }
            data.append(subsection)

        curr_section['data'] = data
        d3_data.append(curr_section)

    return d3_data
示例#41
0
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError("check_course requires one argument: <location>")

        loc_str = args[0]

        loc = CourseDescriptor.id_to_location(loc_str)
        store = modulestore()

        # setup a request cache so we don't throttle the DB with all the metadata inheritance requests
        store.set_modulestore_configuration({
            'metadata_inheritance_cache_subsystem': CACHE,
            'request_cache': RequestCache.get_request_cache()
        })

        course = store.get_item(loc, depth=3)

        err_cnt = 0

        def _xlint_metadata(module):
            err_cnt = check_module_metadata_editability(module)
            for child in module.get_children():
                err_cnt = err_cnt + _xlint_metadata(child)
            return err_cnt

        err_cnt = err_cnt + _xlint_metadata(course)

        # we've had a bug where the xml_attributes field can we rewritten as a string rather than a dict
        def _check_xml_attributes_field(module):
            err_cnt = 0
            if hasattr(module, 'xml_attributes') and isinstance(module.xml_attributes, basestring):
                print 'module = {0} has xml_attributes as a string. It should be a dict'.format(module.location.url())
                err_cnt = err_cnt + 1
            for child in module.get_children():
                err_cnt = err_cnt + _check_xml_attributes_field(child)
            return err_cnt

        err_cnt = err_cnt + _check_xml_attributes_field(course)

        # check for dangling discussion items, this can cause errors in the forums
        def _get_discussion_items(module):
            discussion_items = []
            if module.location.category == 'discussion':
                discussion_items = discussion_items + [module.location.url()]

            for child in module.get_children():
                discussion_items = discussion_items + _get_discussion_items(child)

            return discussion_items

        discussion_items = _get_discussion_items(course)

        # now query all discussion items via get_items() and compare with the tree-traversal
        queried_discussion_items = store.get_items(['i4x', course.location.org, course.location.course,
                                                    'discussion', None, None])

        for item in queried_discussion_items:
            if item.location.url() not in discussion_items:
                print 'Found dangling discussion module = {0}'.format(item.location.url())
示例#42
0
 def _ended_courses(self):
     for course_id in [course  # all courses in COURSE_LISTINGS
             for sub in settings.COURSE_LISTINGS
                 for course in settings.COURSE_LISTINGS[sub]]:
         course_loc = CourseDescriptor.id_to_location(course_id)
         course = modulestore().get_instance(course_id, course_loc)
         if course.has_ended():
             yield course_id
示例#43
0
    def has_permission(self, permission):
        course_loc = CourseDescriptor.id_to_location(self.course_id)
        course = modulestore().get_instance(self.course_id, course_loc)
        if self.name == FORUM_ROLE_STUDENT and \
           (permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
           (not course.forum_posts_allowed):
            return False

        return self.permissions.filter(name=permission).exists()
def _check_access(user, course_id):
    """
    Raise 404 if user doesn't have staff access to course_id
    """
    course_location = CourseDescriptor.id_to_location(course_id)
    if not has_access(user, course_location, 'staff'):
        raise Http404

    return
示例#45
0
def _check_access(user, course_id):
    """
    Raise 404 if user doesn't have staff access to course_id
    """
    course_location = CourseDescriptor.id_to_location(course_id)
    if not has_access(user, course_location, 'staff'):
        raise Http404

    return
示例#46
0
    def has_permission(self, permission):
        course_loc = CourseDescriptor.id_to_location(self.course_id)
        course = modulestore().get_instance(self.course_id, course_loc)
        if self.name == FORUM_ROLE_STUDENT and \
           (permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
           (not course.forum_posts_allowed):
            return False

        return self.permissions.filter(name=permission).exists()
示例#47
0
文件: clone.py 项目: 2bj/edx-platform
    def handle(self, *args, **options):
        if len(args) != 2:
            raise CommandError("clone requires two arguments: <source-location> <dest-location>")

        source_location_str = args[0]
        dest_location_str = args[1]

        ms = modulestore('direct')
        cs = contentstore()

        print "Cloning course {0} to {1}".format(source_location_str, dest_location_str)

        source_location = CourseDescriptor.id_to_location(source_location_str)
        dest_location = CourseDescriptor.id_to_location(dest_location_str)

        if clone_course(ms, cs, source_location, dest_location):
            print "copying User permissions..."
            _copy_course_group(source_location, dest_location)
示例#48
0
    def handle(self, *args, **options):
        if len(args) != 2:
            raise CommandError(
                "clone requires two arguments: <source-location> <dest-location>")

        source_location_str = args[0]
        dest_location_str = args[1]

        ms = modulestore('direct')
        cs = contentstore()

        print "Cloning course {0} to {1}".format(source_location_str, dest_location_str)

        source_location = CourseDescriptor.id_to_location(source_location_str)
        dest_location = CourseDescriptor.id_to_location(dest_location_str)

        if clone_course(ms, cs, source_location, dest_location):
            print "copying User permissions..."
            _copy_course_group(source_location, dest_location)
示例#49
0
 def _ended_courses(self):
     for course_id in [
             course  # all courses in COURSE_LISTINGS
             for sub in settings.COURSE_LISTINGS
             for course in settings.COURSE_LISTINGS[sub]
     ]:
         course_loc = CourseDescriptor.id_to_location(course_id)
         course = modulestore().get_instance(course_id, course_loc)
         if course.has_ended():
             yield course_id
示例#50
0
    def load_test_import_course(self):
        '''
        Load the standard course used to test imports (for do_import_static=False behavior).
        '''
        content_store = contentstore()
        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True)
        course_location = CourseDescriptor.id_to_location('edX/test_import_course/2012_Fall')
        course = module_store.get_item(course_location)
        self.assertIsNotNone(course)

        return module_store, content_store, course, course_location
示例#51
0
    def test_delete_course(self):
        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['full'])

        content_store = contentstore()

        location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')

        delete_course(module_store, content_store, location, commit=True)

        items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
        self.assertEqual(len(items), 0)
示例#52
0
def get_course_by_id(course_id, depth=0):
    """
    Given a course id, return the corresponding course descriptor.

    If course_id is not valid, raises a 404.
    depth: The number of levels of children for the modulestore to cache. None means infinite depth
    """
    try:
        course_loc = CourseDescriptor.id_to_location(course_id)
        return modulestore().get_instance(course_id, course_loc, depth=depth)
    except (KeyError, ItemNotFoundError):
        raise Http404("Course not found.")
示例#53
0
def get_course_by_id(course_id, depth=0):
    """
    Given a course id, return the corresponding course descriptor.

    If course_id is not valid, raises a 404.
    depth: The number of levels of children for the modulestore to cache. None means infinite depth
    """
    try:
        course_loc = CourseDescriptor.id_to_location(course_id)
        return modulestore().get_instance(course_id, course_loc, depth=depth)
    except (KeyError, ItemNotFoundError):
        raise Http404("Course not found.")
    def test_delete_course(self):
        module_store = modulestore("direct")
        import_from_xml(module_store, "common/test/data/", ["full"])

        content_store = contentstore()

        location = CourseDescriptor.id_to_location("edX/full/6.002_Spring_2012")

        delete_course(module_store, content_store, location, commit=True)

        items = module_store.get_items(Location(["i4x", "edX", "full", "vertical", None]))
        self.assertEqual(len(items), 0)
示例#55
0
    def load_test_import_course(self):
        '''
        Load the standard course used to test imports (for do_import_static=False behavior).
        '''
        content_store = contentstore()
        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['test_import_course'], static_content_store=content_store, do_import_static=False, verbose=True)
        course_location = CourseDescriptor.id_to_location('edX/test_import_course/2012_Fall')
        course = module_store.get_item(course_location)
        self.assertIsNotNone(course)

        return module_store, content_store, course, course_location
示例#56
0
    def test_asset_import_nostatic(self):
        '''
        This test validates that an image asset is NOT imported when do_import_static=False
        '''
        content_store = contentstore()

        module_store = modulestore('direct')
        import_from_xml(module_store, 'common/test/data/', ['toy'], static_content_store=content_store, do_import_static=False, verbose=True)

        course_location = CourseDescriptor.id_to_location('edX/toy/2012_Fall')
        module_store.get_item(course_location)

        # make sure we have NO assets in our contentstore
        all_assets, count = content_store.get_all_content_for_course(course_location)
        self.assertEqual(len(all_assets), 0)
        self.assertEqual(count, 0)
示例#57
0
    def handle(self, *args, **options):
        if len(args) < 2 or len(args) > 3:
            raise CommandError(
                "dump_course_structure requires two or more arguments: <location> <outfile> |<db>|"
            )

        course_id = args[0]
        outfile = args[1]

        # use a user-specified database name, if present
        # this is useful for doing dumps from databases restored from prod backups
        if len(args) == 3:
            settings.MODULESTORE['direct']['OPTIONS']['db'] = args[2]

        loc = CourseDescriptor.id_to_location(course_id)

        store = modulestore()

        course = None
        try:
            course = store.get_item(loc, depth=4)
        except:
            print 'Could not find course at {0}'.format(course_id)
            return

        info = {}

        def dump_into_dict(module, info):
            filtered_metadata = dict(
                (key, value)
                for key, value in own_metadata(module).iteritems()
                if key not in filter_list)
            info[module.location.url()] = {
                'category': module.location.category,
                'children':
                module.children if hasattr(module, 'children') else [],
                'metadata': filtered_metadata
            }

            for child in module.get_children():
                dump_into_dict(child, info)

        dump_into_dict(course, info)

        with open(outfile, 'w') as f:
            f.write(dumps(info))