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)
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)
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)
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)
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 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)
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)
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) ])
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)
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
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
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 == []
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
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
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))
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))
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())
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)
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
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..."
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')
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)
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)
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())
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)
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
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
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_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)
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..."
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
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())
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
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())
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
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
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)
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)
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
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
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)
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)
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)
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))