Ejemplo n.º 1
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()
            )
Ejemplo n.º 2
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)
Ejemplo n.º 3
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)
Ejemplo n.º 4
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)
Ejemplo n.º 5
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
Ejemplo n.º 6
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..."
Ejemplo n.º 7
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)
Ejemplo n.º 8
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)
Ejemplo n.º 9
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))
Ejemplo n.º 10
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)
Ejemplo n.º 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
Ejemplo n.º 12
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)
Ejemplo n.º 13
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())
Ejemplo n.º 14
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
Ejemplo n.º 15
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
Ejemplo n.º 16
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)
Ejemplo n.º 17
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
Ejemplo n.º 18
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)]
        )
Ejemplo n.º 19
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))
Ejemplo n.º 20
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)
Ejemplo n.º 21
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())
Ejemplo n.º 22
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
Ejemplo n.º 23
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
Ejemplo n.º 24
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)
Ejemplo n.º 25
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
Ejemplo n.º 27
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.")
Ejemplo n.º 28
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)
Ejemplo n.º 29
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
Ejemplo n.º 30
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)
Ejemplo n.º 31
0
    def handle(self, *args, **options):
        username = args[0]
        course_id = args[1]
        print username, course_id

        our_options = dict((k, v) for k, v in options.items()
                           if Command.is_valid_option(k) and v is not None)
        try:
            student = User.objects.get(username=username)
        except User.DoesNotExist:
            raise CommandError("User \"{}\" does not exist".format(username))

        try:
            testcenter_user = TestCenterUser.objects.get(user=student)
        except TestCenterUser.DoesNotExist:
            raise CommandError(
                "User \"{}\" does not have an existing demographics record".
                format(username))

        # get an "exam" object.  Check to see if a course_id was specified, and use information from that:
        exam = None
        create_dummy_exam = 'create_dummy_exam' in our_options and our_options[
            'create_dummy_exam']
        if not create_dummy_exam:
            try:
                course = course_from_id(course_id)
                if 'ignore_registration_dates' in our_options:
                    examlist = [
                        exam for exam in course.test_center_exams
                        if exam.exam_series_code == our_options.get(
                            'exam_series_code')
                    ]
                    exam = examlist[0] if len(examlist) > 0 else None
                else:
                    exam = course.current_test_center_exam
            except ItemNotFoundError:
                pass
        else:
            # otherwise use explicit values (so we don't have to define a course):
            exam_name = "Dummy Placeholder Name"
            exam_info = {
                'Exam_Series_Code':
                our_options['exam_series_code'],
                'First_Eligible_Appointment_Date':
                our_options['eligibility_appointment_date_first'],
                'Last_Eligible_Appointment_Date':
                our_options['eligibility_appointment_date_last'],
            }
            exam = CourseDescriptor.TestCenterExam(course_id, exam_name,
                                                   exam_info)
            # update option values for date_first and date_last to use YYYY-MM-DD format
            # instead of YYYY-MM-DDTHH:MM
            our_options[
                'eligibility_appointment_date_first'] = exam.first_eligible_appointment_date.strftime(
                    "%Y-%m-%d")
            our_options[
                'eligibility_appointment_date_last'] = exam.last_eligible_appointment_date.strftime(
                    "%Y-%m-%d")

        if exam is None:
            raise CommandError(
                "Exam for course_id {} does not exist".format(course_id))

        exam_code = exam.exam_series_code

        UPDATE_FIELDS = (
            'accommodation_request',
            'accommodation_code',
            'client_authorization_id',
            'exam_series_code',
            'eligibility_appointment_date_first',
            'eligibility_appointment_date_last',
        )

        # create and save the registration:
        needs_updating = False
        registrations = get_testcenter_registration(student, course_id,
                                                    exam_code)
        if len(registrations) > 0:
            registration = registrations[0]
            for fieldname in UPDATE_FIELDS:
                if fieldname in our_options and registration.__getattribute__(
                        fieldname) != our_options[fieldname]:
                    needs_updating = True
        else:
            accommodation_request = our_options.get('accommodation_request',
                                                    '')
            registration = TestCenterRegistration.create(
                testcenter_user, exam, accommodation_request)
            needs_updating = True

        if needs_updating:
            # first update the record with the new values, if any:
            for fieldname in UPDATE_FIELDS:
                if fieldname in our_options and fieldname not in TestCenterRegistrationForm.Meta.fields:
                    registration.__setattr__(fieldname, our_options[fieldname])

            # the registration form normally populates the data dict with
            # the accommodation request (if any).  But here we want to
            # specify only those values that might change, so update the dict with existing
            # values.
            form_options = dict(our_options)
            for propname in TestCenterRegistrationForm.Meta.fields:
                if propname not in form_options:
                    form_options[propname] = registration.__getattribute__(
                        propname)
            form = TestCenterRegistrationForm(instance=registration,
                                              data=form_options)
            if form.is_valid():
                form.update_and_save()
                print "Updated registration information for user's registration: username \"{}\" course \"{}\", examcode \"{}\"".format(
                    student.username, course_id, exam_code)
            else:
                if (len(form.errors) > 0):
                    print "Field Form errors encountered:"
                for fielderror in form.errors:
                    for msg in form.errors[fielderror]:
                        print "Field Form Error:  {} -- {}".format(
                            fielderror, msg)
                    if (len(form.non_field_errors()) > 0):
                        print "Non-field Form errors encountered:"
                        for nonfielderror in form.non_field_errors:
                            print "Non-field Form Error:  %s" % nonfielderror

        else:
            print "No changes necessary to make to existing user's registration."

        # override internal values:
        change_internal = False
        if 'exam_series_code' in our_options:
            exam_code = our_options['exam_series_code']
        registration = get_testcenter_registration(student, course_id,
                                                   exam_code)[0]
        for internal_field in [
                'upload_error_message', 'upload_status', 'authorization_id'
        ]:
            if internal_field in our_options:
                registration.__setattr__(internal_field,
                                         our_options[internal_field])
                change_internal = True

        if change_internal:
            print "Updated  confirmation information in existing user's registration."
            registration.save()
        else:
            print "No changes necessary to make to confirmation information in existing user's registration."
Ejemplo n.º 32
0
def path_to_location(modulestore, course_id, location):
    '''
    Try to find a course_id/chapter/section[/position] path to location in
    modulestore.  The courseware insists that the first level in the course is
    chapter, but any kind of module can be a "section".

    location: something that can be passed to Location
    course_id: Search for paths in this course.

    raise ItemNotFoundError if the location doesn't exist.

    raise NoPathToItem if the location exists, but isn't accessible via
    a chapter/section path in the course(s) being searched.

    Return a tuple (course_id, chapter, section, position) suitable for the
    courseware index view.

    A location may be accessible via many paths. This method may
    return any valid path.

    If the section is a sequential or vertical, position will be the position
    of this location in that sequence.  Otherwise, position will
    be None. TODO (vshnayder): Not true yet.
    '''

    def flatten(xs):
        '''Convert lisp-style (a, (b, (c, ()))) list into a python list.
        Not a general flatten function. '''
        p = []
        while xs != ():
            p.append(xs[0])
            xs = xs[1]
        return p

    def find_path_to_course():
        '''Find a path up the location graph to a node with the
        specified category.

        If no path exists, return None.

        If a path exists, return it as a list with target location first, and
        the starting location last.
        '''
        # Standard DFS

        # To keep track of where we came from, the work queue has
        # tuples (location, path-so-far).  To avoid lots of
        # copying, the path-so-far is stored as a lisp-style
        # list--nested hd::tl tuples, and flattened at the end.
        queue = [(location, ())]
        while len(queue) > 0:
            (loc, path) = queue.pop()  # Takes from the end
            loc = Location(loc)

            # get_parent_locations should raise ItemNotFoundError if location
            # isn't found so we don't have to do it explicitly.  Call this
            # first to make sure the location is there (even if it's a course, and
            # we would otherwise immediately exit).
            parents = modulestore.get_parent_locations(loc, course_id)

            # print 'Processing loc={0}, path={1}'.format(loc, path)
            if loc.category == "course":
                # confirm that this is the right course
                if course_id == CourseDescriptor.location_to_id(loc):
                    # Found it!
                    path = (loc, path)
                    return flatten(path)

            # otherwise, add parent locations at the end
            newpath = (loc, path)
            queue.extend(zip(parents, repeat(newpath)))

        # If we're here, there is no path
        return None

    if not modulestore.has_item(location):
        raise ItemNotFoundError

    path = find_path_to_course()
    if path is None:
        raise NoPathToItem(location)

    n = len(path)
    course_id = CourseDescriptor.location_to_id(path[0])
    # pull out the location names
    chapter = path[1].name if n > 1 else None
    section = path[2].name if n > 2 else None
    # Figure out the position
    position = None

    # This block of code will find the position of a module within a nested tree
    # of modules. If a problem is on tab 2 of a sequence that's on tab 3 of a
    # sequence, the resulting position is 3_2. However, no positional modules
    # (e.g. sequential and videosequence) currently deal with this form of
    # representing nested positions. This needs to happen before jumping to a
    # module nested in more than one positional module will work.
    if n > 3:
        position_list = []
        for path_index in range(2, n - 1):
            category = path[path_index].category
            if category == 'sequential' or category == 'videosequence':
                section_desc = modulestore.get_instance(course_id, path[path_index])
                child_locs = [c.location for c in section_desc.get_children()]
                # positions are 1-indexed, and should be strings to be consistent with
                # url parsing.
                position_list.append(str(child_locs.index(path[path_index + 1]) + 1))
        position = "_".join(position_list)

    return (course_id, chapter, section, position)
Ejemplo n.º 33
0
def get_d3_problem_grade_distrib(course_id):
    """
    Returns problem grade distribution information for each section, data already in format for d3 function.

    `course_id` the course ID for the course interested in

    Returns an array of dicts in the order of the sections. Each dict has:
      'display_name' - display name for the section
      'data' - data for the d3_stacked_bar_graph function of the grade distribution for that problem
    """

    prob_grade_distrib = get_problem_grade_distribution(course_id)
    d3_data = []

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

    # Iterate through sections, subsections, units, problems
    for section in course.get_children():
        curr_section = {}
        curr_section['display_name'] = own_metadata(section).get(
            'display_name', '')
        data = []
        c_subsection = 0
        for subsection in section.get_children():
            c_subsection += 1
            c_unit = 0
            for unit in subsection.get_children():
                c_unit += 1
                c_problem = 0
                for child in unit.get_children():

                    # Student data is at the problem level
                    if child.location.category == 'problem':
                        c_problem += 1
                        stack_data = []

                        # Construct label to display for this problem
                        label = "P{0}.{1}.{2}".format(c_subsection, c_unit,
                                                      c_problem)

                        # Only problems in prob_grade_distrib have had a student submission.
                        if child.location.url() in prob_grade_distrib:

                            # Get max_grade, grade_distribution for this problem
                            problem_info = prob_grade_distrib[
                                child.location.url()]

                            # Get problem_name for tooltip
                            problem_name = own_metadata(child).get(
                                'display_name', '')

                            # Compute percent of this grade over max_grade
                            max_grade = float(problem_info['max_grade'])
                            for (grade,
                                 count_grade) in problem_info['grade_distrib']:
                                percent = 0.0
                                if max_grade > 0:
                                    percent = (grade * 100.0) / max_grade

                                # Construct tooltip for problem in grade distibution view
                                tooltip = _(
                                    "{label} {problem_name} - {count_grade} {students} ({percent:.0f}%: {grade:.0f}/{max_grade:.0f} {questions})"
                                ).format(
                                    label=label,
                                    problem_name=problem_name,
                                    count_grade=count_grade,
                                    students=_("students"),
                                    percent=percent,
                                    grade=grade,
                                    max_grade=max_grade,
                                    questions=_("questions"),
                                )

                                # Construct data to be sent to d3
                                stack_data.append({
                                    'color': percent,
                                    'value': count_grade,
                                    'tooltip': tooltip,
                                })

                        problem = {
                            'xValue': label,
                            'stackData': stack_data,
                        }
                        data.append(problem)
        curr_section['data'] = data

        d3_data.append(curr_section)

    return d3_data
Ejemplo n.º 34
0
def get_d3_section_grade_distrib(course_id, section):
    """
    Returns the grade distribution for the problems in the `section` section in a format for the d3 code.

    `course_id` a string that is the course's ID.

    `section` an int that is a zero-based index into the course's list of sections.

    Navigates to the section specified to find all the problems associated with that section and then finds the grade
    distribution for those problems. Finally returns an object formated the way the d3_stacked_bar_graph.js expects its
    data object to be in.

    If this is requested multiple times quickly for the same course, it is better to call
    get_d3_problem_grade_distrib and pick out the sections of interest.

    Returns an array of dicts with the following keys (taken from d3_stacked_bar_graph.js's documentation)
      'xValue' - Corresponding value for the x-axis
      'stackData' - Array of objects with key, value pairs that represent a bar:
        'color' - Defines what "color" the bar will map to
        'value' - Maps to the height of the bar, along the y-axis
        'tooltip' - (Optional) Text to display on mouse hover
    """

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

    problem_set = []
    problem_info = {}
    c_subsection = 0
    for subsection in course.get_children()[section].get_children():
        c_subsection += 1
        c_unit = 0
        for unit in subsection.get_children():
            c_unit += 1
            c_problem = 0
            for child in unit.get_children():
                if (child.location.category == 'problem'):
                    c_problem += 1
                    problem_set.append(child.location.url())
                    problem_info[child.location.url()] = {
                        'id':
                        child.location.url(),
                        'x_value':
                        "P{0}.{1}.{2}".format(c_subsection, c_unit, c_problem),
                        'display_name':
                        own_metadata(child).get('display_name', ''),
                    }

    # Retrieve grade distribution for these problems
    grade_distrib = get_problem_set_grade_distrib(course_id, problem_set)

    d3_data = []

    # Construct data for each problem to be sent to d3
    for problem in problem_set:
        stack_data = []

        if problem in grade_distrib:  # Some problems have no data because students have not tried them yet.
            max_grade = float(grade_distrib[problem]['max_grade'])
            for (grade,
                 count_grade) in grade_distrib[problem]['grade_distrib']:
                percent = 0.0
                if max_grade > 0:
                    percent = (grade * 100.0) / max_grade

                # Construct tooltip for problem in grade distibution view
                tooltip = _(
                    "{problem_info_x} {problem_info_n} - {count_grade} {students} ({percent:.0f}%: {grade:.0f}/{max_grade:.0f} {questions})"
                ).format(
                    problem_info_x=problem_info[problem]['x_value'],
                    count_grade=count_grade,
                    students=_("students"),
                    percent=percent,
                    problem_info_n=problem_info[problem]['display_name'],
                    grade=grade,
                    max_grade=max_grade,
                    questions=_("questions"),
                )

                stack_data.append({
                    'color': percent,
                    'value': count_grade,
                    'tooltip': tooltip,
                })

        d3_data.append({
            'xValue': problem_info[problem]['x_value'],
            'stackData': stack_data,
        })

    return d3_data
Ejemplo n.º 35
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())
Ejemplo n.º 36
0
    def test_export_course(self):
        module_store = modulestore('direct')
        draft_store = modulestore('draft')
        content_store = contentstore()

        import_from_xml(module_store, 'common/test/data/', ['full'])
        location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')

        # get a vertical (and components in it) to put into 'draft'
        vertical = module_store.get_item(Location(['i4x', 'edX', 'full',
                                         'vertical', 'vertical_66', None]), depth=1)

        draft_store.clone_item(vertical.location, vertical.location)

        # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case.
        draft_store.clone_item(vertical.location, Location(['i4x', 'edX', 'full',
                                                            'vertical', 'no_references', 'draft']))

        for child in vertical.get_children():
            draft_store.clone_item(child.location, child.location)

        root_dir = path(mkdtemp_clean())

        # now create a private vertical
        private_vertical = draft_store.clone_item(vertical.location,
                                                  Location(['i4x', 'edX', 'full', 'vertical', 'a_private_vertical', None]))

        # add private to list of children
        sequential = module_store.get_item(Location(['i4x', 'edX', 'full',
                                           'sequential', 'Administrivia_and_Circuit_Elements', None]))
        private_location_no_draft = private_vertical.location.replace(revision=None)
        module_store.update_children(sequential.location, sequential.children +
                                     [private_location_no_draft.url()])

        # read back the sequential, to make sure we have a pointer to
        sequential = module_store.get_item(Location(['i4x', 'edX', 'full',
                                                     'sequential', 'Administrivia_and_Circuit_Elements', None]))

        self.assertIn(private_location_no_draft.url(), sequential.children)

        print 'Exporting to tempdir = {0}'.format(root_dir)

        # export out to a tempdir
        export_to_xml(module_store, content_store, location, root_dir, 'test_export', draft_modulestore=draft_store)

        # check for static tabs
        self.verify_content_existence(module_store, root_dir, location, 'tabs', 'static_tab', '.html')

        # check for custom_tags
        self.verify_content_existence(module_store, root_dir, location, 'info', 'course_info', '.html')

        # check for custom_tags
        self.verify_content_existence(module_store, root_dir, location, 'custom_tags', 'custom_tag_template')

        # check for graiding_policy.json
        filesystem = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012')
        self.assertTrue(filesystem.exists('grading_policy.json'))

        course = module_store.get_item(location)
        # compare what's on disk compared to what we have in our course
        with filesystem.open('grading_policy.json', 'r') as grading_policy:
            on_disk = loads(grading_policy.read())
            self.assertEqual(on_disk, course.grading_policy)

        #check for policy.json
        self.assertTrue(filesystem.exists('policy.json'))

        # compare what's on disk to what we have in the course module
        with filesystem.open('policy.json', 'r') as course_policy:
            on_disk = loads(course_policy.read())
            self.assertIn('course/6.002_Spring_2012', on_disk)
            self.assertEqual(on_disk['course/6.002_Spring_2012'], own_metadata(course))

        # remove old course
        delete_course(module_store, content_store, location)

        # reimport
        import_from_xml(module_store, root_dir, ['test_export'], draft_store=draft_store)

        items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
        self.assertGreater(len(items), 0)
        for descriptor in items:
            # don't try to look at private verticals. Right now we're running
            # the service in non-draft aware
            if getattr(descriptor, 'is_draft', False):
                print "Checking {0}....".format(descriptor.location.url())
                resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
                self.assertEqual(resp.status_code, 200)

        # verify that we have the content in the draft store as well
        vertical = draft_store.get_item(Location(['i4x', 'edX', 'full',
                                                  'vertical', 'vertical_66', None]), depth=1)

        self.assertTrue(getattr(vertical, 'is_draft', False))
        for child in vertical.get_children():
            self.assertTrue(getattr(child, 'is_draft', False))

        # make sure that we don't have a sequential that is in draft mode
        sequential = draft_store.get_item(Location(['i4x', 'edX', 'full',
                                                    'sequential', 'Administrivia_and_Circuit_Elements', None]))

        self.assertFalse(getattr(sequential, 'is_draft', False))

        # verify that we have the private vertical
        test_private_vertical = draft_store.get_item(Location(['i4x', 'edX', 'full',
                                                               'vertical', 'vertical_66', None]))

        self.assertTrue(getattr(test_private_vertical, 'is_draft', False))

        # make sure the textbook survived the export/import
        course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))

        self.assertGreater(len(course.textbooks), 0)

        shutil.rmtree(root_dir)
Ejemplo n.º 37
0
def _has_staff_access_to_course_id(user, course_id):
    """Helper method that takes a course_id instead of a course name"""
    loc = CourseDescriptor.id_to_location(course_id)
    return _has_staff_access_to_location(user, loc, course_id)
Ejemplo n.º 38
0
    def handle(self, *args, **options):

        # Will only generate a certificate if the current
        # status is in the unavailable state, can be set
        # to something else with the force flag

        if options['force']:
            valid_statuses = getattr(CertificateStatuses, options['force'])
        else:
            valid_statuses = [CertificateStatuses.unavailable]

        # Print update after this many students

        STATUS_INTERVAL = 500

        if options['course']:
            ended_courses = [options['course']]
        else:
            # Find all courses that have ended
            ended_courses = []
            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():
                    ended_courses.append(course_id)

        for course_id in ended_courses:
            # prefetch all chapters/sequentials by saying depth=2
            course = modulestore().get_instance(
                course_id, CourseDescriptor.id_to_location(course_id), depth=2)

            print "Fetching enrolled students for {0}".format(course_id)
            enrolled_students = User.objects.filter(
                courseenrollment__course_id=course_id).prefetch_related(
                    "groups").order_by('username')
            xq = XQueueCertInterface()
            total = enrolled_students.count()
            count = 0
            start = datetime.datetime.now(UTC)
            for student in enrolled_students:
                count += 1
                if count % STATUS_INTERVAL == 0:
                    # Print a status update with an approximation of
                    # how much time is left based on how long the last
                    # interval took
                    diff = datetime.datetime.now(UTC) - start
                    timeleft = diff * (total - count) / STATUS_INTERVAL
                    hours, remainder = divmod(timeleft.seconds, 3600)
                    minutes, seconds = divmod(remainder, 60)
                    print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format(
                        count, total, hours, minutes)
                    start = datetime.datetime.now(UTC)

                if certificate_status_for_student(
                        student, course_id)['status'] in valid_statuses:
                    if not options['noop']:
                        # Add the certificate request to the queue
                        ret = xq.add_cert(student, course_id, course=course)
                        if ret == 'generating':
                            print '{0} - {1}'.format(student, ret)
Ejemplo n.º 39
0
    def load_course(self, course_dir, course_ids, tracker):
        """
        Load a course into this module store
        course_path: Course directory name

        returns a CourseDescriptor for the course
        """
        log.debug(
            '========> Starting course import from {0}'.format(course_dir))

        with open(self.data_dir / course_dir / "course.xml") as course_file:

            # VS[compat]
            # TODO (cpennington): Remove this once all fall 2012 courses have
            # been imported into the cms from xml
            course_file = StringIO(
                clean_out_mako_templating(course_file.read()))

            course_data = etree.parse(course_file,
                                      parser=edx_xml_parser).getroot()

            org = course_data.get('org')

            if org is None:
                msg = ("No 'org' attribute set for course in {dir}. "
                       "Using default 'edx'".format(dir=course_dir))
                log.warning(msg)
                tracker(msg)
                org = 'edx'

            course = course_data.get('course')

            if course is None:
                msg = ("No 'course' attribute set for course in {dir}."
                       " Using default '{default}'".format(dir=course_dir,
                                                           default=course_dir))
                log.warning(msg)
                tracker(msg)
                course = course_dir

            url_name = course_data.get('url_name', course_data.get('slug'))
            policy_dir = None
            if url_name:
                policy_dir = self.data_dir / course_dir / 'policies' / url_name
                policy_path = policy_dir / 'policy.json'

                policy = self.load_policy(policy_path, tracker)

                # VS[compat]: remove once courses use the policy dirs.
                if policy == {}:
                    old_policy_path = self.data_dir / course_dir / 'policies' / '{0}.json'.format(
                        url_name)
                    policy = self.load_policy(old_policy_path, tracker)
            else:
                policy = {}
                # VS[compat] : 'name' is deprecated, but support it for now...
                if course_data.get('name'):
                    url_name = Location.clean(course_data.get('name'))
                    tracker("'name' is deprecated for module xml.  Please use "
                            "display_name and url_name.")
                else:
                    raise ValueError(
                        "Can't load a course without a 'url_name' "
                        "(or 'name') set.  Set url_name.")

            course_id = CourseDescriptor.make_id(org, course, url_name)
            if course_ids is not None and course_id not in course_ids:
                return None

            def get_policy(usage_id):
                """
                Return the policy dictionary to be applied to the specified XBlock usage
                """
                return policy.get(policy_key(usage_id), {})

            system = ImportSystem(
                xmlstore=self,
                course_id=course_id,
                course_dir=course_dir,
                error_tracker=tracker,
                parent_tracker=self.parent_trackers[course_id],
                load_error_modules=self.load_error_modules,
                get_policy=get_policy,
                mixins=self.xblock_mixins,
                default_class=self.default_class,
                select=self.xblock_select,
                field_data=self.field_data,
            )

            course_descriptor = system.process_xml(
                etree.tostring(course_data, encoding='unicode'))

            # If we fail to load the course, then skip the rest of the loading steps
            if isinstance(course_descriptor, ErrorDescriptor):
                return course_descriptor

            # NOTE: The descriptors end up loading somewhat bottom up, which
            # breaks metadata inheritance via get_children().  Instead
            # (actually, in addition to, for now), we do a final inheritance pass
            # after we have the course descriptor.
            compute_inherited_metadata(course_descriptor)

            # now import all pieces of course_info which is expected to be stored
            # in <content_dir>/info or <content_dir>/info/<url_name>
            self.load_extra_content(system, course_descriptor, 'course_info',
                                    self.data_dir / course_dir / 'info',
                                    course_dir, url_name)

            # now import all static tabs which are expected to be stored in
            # in <content_dir>/tabs or <content_dir>/tabs/<url_name>
            self.load_extra_content(system, course_descriptor, 'static_tab',
                                    self.data_dir / course_dir / 'tabs',
                                    course_dir, url_name)

            self.load_extra_content(system, course_descriptor,
                                    'custom_tag_template',
                                    self.data_dir / course_dir / 'custom_tags',
                                    course_dir, url_name)

            self.load_extra_content(system, course_descriptor, 'about',
                                    self.data_dir / course_dir / 'about',
                                    course_dir, url_name)

            log.debug('========> Done with course import from {0}'.format(
                course_dir))
            return course_descriptor
Ejemplo n.º 40
0
def course_from_id(course_id):
    """Return the CourseDescriptor corresponding to this course_id"""
    course_loc = CourseDescriptor.id_to_location(course_id)
    return modulestore().get_instance(course_id, course_loc)
Ejemplo n.º 41
0
    def get_html(self):
        """
        Renders parameters to template.
        """

        # LTI provides a list of default parameters that might be passed as
        # part of the POST data. These parameters should not be prefixed.
        # Likewise, The creator of an LTI link can add custom key/value parameters
        # to a launch which are to be included with the launch of the LTI link.
        # In this case, we will automatically add `custom_` prefix before this parameters.
        # See http://www.imsglobal.org/LTI/v1p1p1/ltiIMGv1p1p1.html#_Toc316828520
        PARAMETERS = [
            "lti_message_type",
            "lti_version",
            "resource_link_id",
            "resource_link_title",
            "resource_link_description",
            "user_id",
            "user_image",
            "roles",
            "lis_person_name_given",
            "lis_person_name_family",
            "lis_person_name_full",
            "lis_person_contact_email_primary",
            "lis_person_sourcedid",
            "role_scope_mentor",
            "context_id",
            "context_type",
            "context_title",
            "context_label",
            "launch_presentation_locale",
            "launch_presentation_document_target",
            "launch_presentation_css_url",
            "launch_presentation_width",
            "launch_presentation_height",
            "launch_presentation_return_url",
            "tool_consumer_info_product_family_code",
            "tool_consumer_info_version",
            "tool_consumer_instance_guid",
            "tool_consumer_instance_name",
            "tool_consumer_instance_description",
            "tool_consumer_instance_url",
            "tool_consumer_instance_contact_email",
        ]

        # Obtains client_key and client_secret credentials from current course:
        course_id = self.course_id
        course_location = CourseDescriptor.id_to_location(course_id)
        course = self.descriptor.runtime.modulestore.get_item(course_location)
        client_key = client_secret = ''

        for lti_passport in course.lti_passports:
            try:
                lti_id, key, secret = [
                    i.strip() for i in lti_passport.split(':')
                ]
            except ValueError:
                raise LTIError('Could not parse LTI passport: {0!r}. \
                    Should be "id:key:secret" string.'.format(lti_passport))
            if lti_id == self.lti_id.strip():
                client_key, client_secret = key, secret
                break

        # parsing custom parameters to dict
        custom_parameters = {}
        for custom_parameter in self.custom_parameters:
            try:
                param_name, param_value = [
                    p.strip() for p in custom_parameter.split('=', 1)
                ]
            except ValueError:
                raise LTIError('Could not parse custom parameter: {0!r}. \
                    Should be "x=y" string.'.format(custom_parameter))

            # LTI specs: 'custom_' should be prepended before each custom parameter, as pointed in link above.
            if param_name not in PARAMETERS:
                param_name = 'custom_' + param_name

            custom_parameters[unicode(param_name)] = unicode(param_value)

        input_fields = self.oauth_params(custom_parameters, client_key,
                                         client_secret)
        context = {
            'input_fields': input_fields,

            # these params do not participate in oauth signing
            'launch_url': self.launch_url.strip(),
            'element_id': self.location.html_id(),
            'element_class': self.category,
            'open_in_a_new_page': self.open_in_a_new_page,
            'display_name': self.display_name,
        }

        return self.system.render_template('lti.html', context)
Ejemplo n.º 42
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