def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked): ''' Helper method for formatting the asset information to send to client. ''' asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, # needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.get_url_path_from_location(location) external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'date_added': strftime_localized(date, "%Y年%m月%d日 %H:%M"), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None, 'locked': locked, # Needed for Backbone delete/update. 'id': asset_url }
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = location.to_deprecated_string() external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': thumbnail_location.to_deprecated_string() if thumbnail_location is not None else None, 'locked': locked, # Needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked): ''' Helper method for formatting the asset information to send to client. ''' # Colaraz Custom: overriding this to get site base web link of the asset(s) asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = 'https://' + configuration_helpers.get_value( 'LMS_BASE') + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, # needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.serialize_asset_key_with_slash(location) # eduNEXT 14.07.2016 lms_base = microsite.get_value_for_org(location.org, 'SITE_NAME', settings.LMS_BASE) external_url = lms_base + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, # Needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json_for_editorjs(display_name, content_type, date, location, thumbnail_location, locked): ''' Helper method for formatting the asset information to send to client. ''' asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = settings.LMS_BASE + asset_url return { "success": 1, "file": { 'url': asset_url, 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'asset_url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, 'id': six.text_type(location) } }
def asset_index(request, org, course, name): """ Display an editable asset library org, course, name: Attributes of the Location for the item to edit """ location = get_location_and_verify_access(request, org, course, name) upload_asset_callback_url = reverse('upload_asset', kwargs={ 'org': org, 'course': course, 'coursename': name }) course_module = modulestore().get_item(location) course_reference = StaticContent.compute_location(org, course, name) assets = contentstore().get_all_content_for_course(course_reference) # sort in reverse upload date order assets = sorted(assets, key=lambda asset: asset['uploadDate'], reverse=True) if request.META.get('HTTP_ACCEPT', "").startswith("application/json"): return JsonResponse(assets_to_json_dict(assets)) asset_display = [] for asset in assets: asset_id = asset['_id'] display_info = {} display_info['displayname'] = asset['displayname'] display_info['uploadDate'] = get_default_time_display(asset['uploadDate']) asset_location = StaticContent.compute_location(asset_id['org'], asset_id['course'], asset_id['name']) display_info['url'] = StaticContent.get_url_path_from_location(asset_location) display_info['portable_url'] = StaticContent.get_static_path_from_location(asset_location) # note, due to the schema change we may not have a 'thumbnail_location' in the result set _thumbnail_location = asset.get('thumbnail_location', None) thumbnail_location = Location(_thumbnail_location) if _thumbnail_location is not None else None display_info['thumb_url'] = StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None asset_display.append(display_info) return render_to_response('asset_index.html', { 'context_course': course_module, 'assets': asset_display, 'upload_asset_callback_url': upload_asset_callback_url, 'remove_asset_callback_url': reverse('remove_asset', kwargs={ 'org': org, 'course': course, 'name': name }) })
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.get_url_path_from_location(location) return { 'display_name': display_name, 'date_added': get_default_time_display(date), 'url': asset_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None, 'locked': locked, # Needed for Backbone delete/update. 'id': asset_url }
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.get_url_path_from_location(location) external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'date_added': strftime_localized(date, "%m %d, %Y %H:%M"), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None, 'locked': locked, # Needed for Backbone delete/update. 'id': asset_url }
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = _add_slash(location.to_deprecated_string()) external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': _add_slash(unicode(thumbnail_location)) if thumbnail_location else None, 'locked': locked, # Needed for Backbone delete/update. 'id': unicode(location) }
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked,name_creater): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.serialize_asset_key_with_slash(location) external_url = 'localhost:8000' + asset_url return { 'display_name': display_name, 'content_type': content_type, 'date_added': get_default_time_display(date), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None, 'locked': locked, 'name_creater':name_creater }
def static_updater(self, course, source_courselike, courselike_key, dest_id, runtime): """ Update special static assets, such as PDF textbooks and wiki resources. """ for entry in course.pdf_textbooks: for chapter in entry.get('chapters', []): if StaticContent.is_c4x_path(chapter.get('url', '')): asset_key = StaticContent.get_location_from_path(chapter['url']) chapter['url'] = StaticContent.get_static_path_from_location(asset_key) # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default # values then remap it so that the wiki does not point to the old wiki. if courselike_key != course.id: if self.xml_module_store.course_run is None: original_course_run = courselike_key.run else: original_course_run = self.xml_module_store.course_run original_unique_wiki_slug = u'{0}.{1}.{2}'.format( courselike_key.org, courselike_key.course, original_course_run, ) if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == courselike_key.course: course.wiki_slug = u'{0}.{1}.{2}'.format( course.id.org, course.id.course, course.id.run, ) # cdodge: more hacks (what else). Seems like we have a # problem when importing a course (like 6.002) which # does not have any tabs defined in the policy file. # The import goes fine and then displays fine in LMS, # but if someone tries to add a new tab in the CMS, then # the LMS barfs because it expects that -- if there are # *any* tabs -- then there at least needs to be # some predefined ones if course.tabs is None or len(course.tabs) == 0: CourseTabList.initialize_default(course)
def static_updater(self, course, source_courselike, courselike_key, dest_id, runtime): """ Update special static assets, such as PDF textbooks and wiki resources. """ for entry in course.pdf_textbooks: for chapter in entry.get('chapters', []): if StaticContent.is_c4x_path(chapter.get('url', '')): asset_key = StaticContent.get_location_from_path( chapter['url']) chapter[ 'url'] = StaticContent.get_static_path_from_location( asset_key) # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default # values then remap it so that the wiki does not point to the old wiki. if courselike_key != course.id: original_unique_wiki_slug = u'{0}.{1}.{2}'.format( courselike_key.org, courselike_key.course, courselike_key.run) if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == courselike_key.course: course.wiki_slug = u'{0}.{1}.{2}'.format( course.id.org, course.id.course, course.id.run, ) # cdodge: more hacks (what else). Seems like we have a # problem when importing a course (like 6.002) which # does not have any tabs defined in the policy file. # The import goes fine and then displays fine in LMS, # but if someone tries to add a new tab in the CMS, then # the LMS barfs because it expects that -- if there are # *any* tabs -- then there at least needs to be # some predefined ones if course.tabs is None or len(course.tabs) == 0: CourseTabList.initialize_default(course)
def test_static_url_generation(self): location = Location(["i4x", "foo", "bar", "asset", "my_file_name.jpg"]) path = StaticContent.get_static_path_from_location(location) self.assertEquals(path, "/static/my_file_name.jpg")
def remap_namespace(module, target_location_namespace): if target_location_namespace is None: return module original_location = module.location # This looks a bit wonky as we need to also change the 'name' of the # imported course to be what the caller passed in if module.location.category != 'course': _update_module_location( module, module.location.replace( tag=target_location_namespace.tag, org=target_location_namespace.org, course=target_location_namespace.course ) ) else: # # module is a course module # module.location = module.location.replace( tag=target_location_namespace.tag, org=target_location_namespace.org, course=target_location_namespace.course, name=target_location_namespace.name ) # There is more re-namespacing work we have to do when # importing course modules # remap pdf_textbook urls to portable static URLs for entry in module.pdf_textbooks: for chapter in entry.get('chapters', []): if StaticContent.is_c4x_path(chapter.get('url', '')): chapter_loc = StaticContent.get_location_from_path(chapter['url']) chapter['url'] = StaticContent.get_static_path_from_location( chapter_loc ) # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default # values then remap it so that the wiki does not point to the old wiki. if original_location.course_id != target_location_namespace.course_id: original_unique_wiki_slug = u'{0}.{1}.{2}'.format( original_location.org, original_location.course, original_location.name ) if module.wiki_slug == original_unique_wiki_slug or module.wiki_slug == original_location.course: module.wiki_slug = u'{0}.{1}.{2}'.format( target_location_namespace.org, target_location_namespace.course, target_location_namespace.name, ) module.save() all_fields = module.get_explicitly_set_fields_by_scope(Scope.content) all_fields.update(module.get_explicitly_set_fields_by_scope(Scope.settings)) if hasattr(module, 'children'): all_fields['children'] = module.children def convert_ref(reference): """ Convert a reference to the new namespace, but only if the original namespace matched the original course. Otherwise, returns the input value. """ new_ref = reference ref = Location(reference) in_original_namespace = (original_location.tag == ref.tag and original_location.org == ref.org and original_location.course == ref.course) if in_original_namespace: new_ref = ref.replace( tag=target_location_namespace.tag, org=target_location_namespace.org, course=target_location_namespace.course ).url() return new_ref for field_name in all_fields: field_object = module.fields.get(field_name) if isinstance(field_object, Reference): new_ref = convert_ref(getattr(module, field_name)) setattr(module, field_name, new_ref) module.save() elif isinstance(field_object, ReferenceList): references = getattr(module, field_name) new_references = [convert_ref(reference) for reference in references] setattr(module, field_name, new_references) module.save() elif isinstance(field_object, ReferenceValueDict): reference_dict = getattr(module, field_name) new_reference_dict = { key: convert_ref(reference) for key, reference in reference_dict.items() } setattr(module, field_name, new_reference_dict) module.save() return module
def import_from_xml(store, data_dir, course_dirs=None, default_class='xmodule.raw_module.RawDescriptor', load_error_modules=True, static_content_store=None, target_course_id=None, verbose=False, draft_store=None, do_import_static=True, create_new_course=False): """ Import the specified xml data_dir into the "store" modulestore, using org and course as the location org and course. course_dirs: If specified, the list of course_dirs to load. Otherwise, load all course dirs target_course_id is the CourseKey that all modules should be remapped to after import off disk. We do this remapping as a post-processing step because there's logic in the importing which expects a 'url_name' as an identifier to where things are on disk e.g. ../policies/<url_name>/policy.json as well as metadata keys in the policy.json. so we need to keep the original url_name during import :param do_import_static: if False, then static files are not imported into the static content store. This can be employed for courses which have substantial unchanging static content, which is to inefficient to import every time the course is loaded. Static content for some courses may also be served directly by nginx, instead of going through django. : create_new_course: If True, then courses whose ids already exist in the store are not imported. The check for existing courses is case-insensitive. """ xml_module_store = XMLModuleStore( data_dir, default_class=default_class, course_dirs=course_dirs, load_error_modules=load_error_modules, xblock_mixins=store.xblock_mixins, xblock_select=store.xblock_select, ) # If we're going to remap the course_id, then we can only do that with # a single course if target_course_id: assert (len(xml_module_store.modules) == 1) # NOTE: the XmlModuleStore does not implement get_items() # which would be a preferable means to enumerate the entire collection # of course modules. It will be left as a TBD to implement that # method on XmlModuleStore. course_items = [] for course_key in xml_module_store.modules.keys(): if target_course_id is not None: dest_course_id = target_course_id else: dest_course_id = course_key if create_new_course: # this tests if exactly this course (ignoring case) exists; so, it checks the run if store.has_course(dest_course_id, ignore_case=True): log.debug("Skipping import of course with id, {0}," "since it collides with an existing one".format( dest_course_id)) continue else: try: store.create_course(dest_course_id.org, dest_course_id.offering) except InvalidLocationError: # course w/ same org and course exists and store is old mongo log.debug("Skipping import of course with id, {0}," "since it collides with an existing one".format( dest_course_id)) continue try: # turn off all write signalling while importing as this # is a high volume operation on stores that need it if hasattr(store, 'ignore_write_events_on_courses'): store.ignore_write_events_on_courses.add(dest_course_id) course_data_path = None if verbose: log.debug( "Scanning {0} for course module...".format(course_key)) # Quick scan to get course module as we need some info from there. # Also we need to make sure that the course module is committed # first into the store for module in xml_module_store.modules[course_key].itervalues(): if module.scope_ids.block_type == 'course': course_data_path = path(data_dir) / module.data_dir log.debug(u'======> IMPORTING course {course_key}'.format( course_key=course_key, )) if not do_import_static: # for old-style xblock where this was actually linked to kvs module.static_asset_path = module.data_dir module.save() log.debug('course static_asset_path={path}'.format( path=module.static_asset_path)) log.debug('course data_dir={0}'.format(module.data_dir)) course = import_module(module, store, course_key, dest_course_id, do_import_static=do_import_static) for entry in course.pdf_textbooks: for chapter in entry.get('chapters', []): if StaticContent.is_c4x_path(chapter.get( 'url', '')): asset_key = StaticContent.get_location_from_path( chapter['url']) chapter[ 'url'] = StaticContent.get_static_path_from_location( asset_key) # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default # values then remap it so that the wiki does not point to the old wiki. if course_key != course.id: original_unique_wiki_slug = u'{0}.{1}.{2}'.format( course_key.org, course_key.course, course_key.run) if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course: course.wiki_slug = u'{0}.{1}.{2}'.format( course.id.org, course.id.course, course.id.run, ) # cdodge: more hacks (what else). Seems like we have a # problem when importing a course (like 6.002) which # does not have any tabs defined in the policy file. # The import goes fine and then displays fine in LMS, # but if someone tries to add a new tab in the CMS, then # the LMS barfs because it expects that -- if there are # *any* tabs -- then there at least needs to be # some predefined ones if course.tabs is None or len(course.tabs) == 0: CourseTabList.initialize_default(course) store.update_item(course) course_items.append(course) # then import all the static content if static_content_store is not None and do_import_static: # first pass to find everything in /static/ import_static_content(course_data_path, static_content_store, dest_course_id, subpath='static', verbose=verbose) elif verbose and not do_import_static: log.debug( "Skipping import of static content, " "since do_import_static={0}".format(do_import_static)) # no matter what do_import_static is, import "static_import" directory # This is needed because the "about" pages (eg "overview") are # loaded via load_extra_content, and do not inherit the lms # metadata from the course module, and thus do not get # "static_content_store" properly defined. Static content # referenced in those extra pages thus need to come through the # c4x:// contentstore, unfortunately. Tell users to copy that # content into the "static_import" subdir. simport = 'static_import' if os.path.exists(course_data_path / simport): import_static_content(course_data_path, static_content_store, dest_course_id, subpath=simport, verbose=verbose) # finally loop through all the modules for module in xml_module_store.modules[course_key].itervalues(): if module.scope_ids.block_type == 'course': # we've already saved the course module up at the top # of the loop so just skip over it in the inner loop continue if verbose: log.debug('importing module location {loc}'.format( loc=module.location)) import_module(module, store, course_key, dest_course_id, do_import_static=do_import_static, system=course.runtime) # now import any 'draft' items if draft_store is not None: import_course_draft(xml_module_store, store, draft_store, course_data_path, static_content_store, course_key, dest_course_id, course.runtime) finally: # turn back on all write signalling on stores that need it if (hasattr(store, 'ignore_write_events_on_courses') and dest_course_id in store.ignore_write_events_on_courses): store.ignore_write_events_on_courses.remove(dest_course_id) store.refresh_cached_metadata_inheritance_tree(dest_course_id) return xml_module_store, course_items
def _import_course_module( store, runtime, user_id, data_dir, course_key, dest_course_id, source_course, do_import_static, verbose, ): if verbose: log.debug("Scanning {0} for course module...".format(course_key)) # Quick scan to get course module as we need some info from there. # Also we need to make sure that the course module is committed # first into the store course_data_path = path(data_dir) / source_course.data_dir log.debug(u'======> IMPORTING course {course_key}'.format( course_key=course_key, )) if not do_import_static: # for old-style xblock where this was actually linked to kvs source_course.static_asset_path = source_course.data_dir source_course.save() log.debug('course static_asset_path={path}'.format( path=source_course.static_asset_path )) log.debug('course data_dir={0}'.format(source_course.data_dir)) with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, dest_course_id): course = _import_module_and_update_references( source_course, store, user_id, course_key, dest_course_id, do_import_static=do_import_static, runtime=runtime, ) for entry in course.pdf_textbooks: for chapter in entry.get('chapters', []): if StaticContent.is_c4x_path(chapter.get('url', '')): asset_key = StaticContent.get_location_from_path(chapter['url']) chapter['url'] = StaticContent.get_static_path_from_location(asset_key) # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default # values then remap it so that the wiki does not point to the old wiki. if course_key != course.id: original_unique_wiki_slug = u'{0}.{1}.{2}'.format( course_key.org, course_key.course, course_key.run ) if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course: course.wiki_slug = u'{0}.{1}.{2}'.format( course.id.org, course.id.course, course.id.run, ) # cdodge: more hacks (what else). Seems like we have a # problem when importing a course (like 6.002) which # does not have any tabs defined in the policy file. # The import goes fine and then displays fine in LMS, # but if someone tries to add a new tab in the CMS, then # the LMS barfs because it expects that -- if there are # *any* tabs -- then there at least needs to be # some predefined ones if course.tabs is None or len(course.tabs) == 0: CourseTabList.initialize_default(course) store.update_item(course, user_id) return course, course_data_path
def test_static_url_generation(self): course_key = SlashSeparatedCourseKey('org', 'class', 'run') location = course_key.make_asset_key('asset', 'my_file_name.jpg') path = StaticContent.get_static_path_from_location(location) self.assertEquals(path, '/static/my_file_name.jpg')
def test_static_url_generation(self): course_key = SlashSeparatedCourseKey("org", "class", "run") location = course_key.make_asset_key("asset", "my_file_name.jpg") path = StaticContent.get_static_path_from_location(location) self.assertEquals(path, "/static/my_file_name.jpg")
def import_from_xml( store, user_id, data_dir, course_dirs=None, default_class='xmodule.raw_module.RawDescriptor', load_error_modules=True, static_content_store=None, target_course_id=None, verbose=False, do_import_static=True, create_new_course_if_not_present=False): """ Import the specified xml data_dir into the "store" modulestore, using org and course as the location org and course. course_dirs: If specified, the list of course_dirs to load. Otherwise, load all course dirs target_course_id is the CourseKey that all modules should be remapped to after import off disk. We do this remapping as a post-processing step because there's logic in the importing which expects a 'url_name' as an identifier to where things are on disk e.g. ../policies/<url_name>/policy.json as well as metadata keys in the policy.json. so we need to keep the original url_name during import :param do_import_static: if False, then static files are not imported into the static content store. This can be employed for courses which have substantial unchanging static content, which is to inefficient to import every time the course is loaded. Static content for some courses may also be served directly by nginx, instead of going through django. : create_new_course_if_not_present: If True, then a new course is created if it doesn't already exist. The check for existing courses is case-insensitive. """ xml_module_store = XMLModuleStore( data_dir, default_class=default_class, course_dirs=course_dirs, load_error_modules=load_error_modules, xblock_mixins=store.xblock_mixins, xblock_select=store.xblock_select, ) # If we're going to remap the course_id, then we can only do that with # a single course if target_course_id: assert(len(xml_module_store.modules) == 1) # NOTE: the XmlModuleStore does not implement get_items() # which would be a preferable means to enumerate the entire collection # of course modules. It will be left as a TBD to implement that # method on XmlModuleStore. course_items = [] with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): for course_key in xml_module_store.modules.keys(): if target_course_id is not None: dest_course_id = target_course_id else: dest_course_id = course_key # Creates a new course if it doesn't already exist if create_new_course_if_not_present and not store.has_course(dest_course_id, ignore_case=True): try: store.create_course(dest_course_id.org, dest_course_id.course, dest_course_id.run, user_id) except InvalidLocationError: # course w/ same org and course exists log.debug( "Skipping import of course with id, {0}," "since it collides with an existing one".format(dest_course_id) ) continue with store.bulk_write_operations(dest_course_id): course_data_path = None if verbose: log.debug("Scanning {0} for course module...".format(course_key)) # Quick scan to get course module as we need some info from there. # Also we need to make sure that the course module is committed # first into the store for module in xml_module_store.modules[course_key].itervalues(): if module.scope_ids.block_type == 'course': course_data_path = path(data_dir) / module.data_dir log.debug(u'======> IMPORTING course {course_key}'.format( course_key=course_key, )) if not do_import_static: # for old-style xblock where this was actually linked to kvs module.static_asset_path = module.data_dir module.save() log.debug('course static_asset_path={path}'.format( path=module.static_asset_path )) log.debug('course data_dir={0}'.format(module.data_dir)) course = _import_module_and_update_references( module, store, user_id, course_key, dest_course_id, do_import_static=do_import_static ) for entry in course.pdf_textbooks: for chapter in entry.get('chapters', []): if StaticContent.is_c4x_path(chapter.get('url', '')): asset_key = StaticContent.get_location_from_path(chapter['url']) chapter['url'] = StaticContent.get_static_path_from_location(asset_key) # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'. # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default # values then remap it so that the wiki does not point to the old wiki. if course_key != course.id: original_unique_wiki_slug = u'{0}.{1}.{2}'.format( course_key.org, course_key.course, course_key.run ) if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course: course.wiki_slug = u'{0}.{1}.{2}'.format( course.id.org, course.id.course, course.id.run, ) # cdodge: more hacks (what else). Seems like we have a # problem when importing a course (like 6.002) which # does not have any tabs defined in the policy file. # The import goes fine and then displays fine in LMS, # but if someone tries to add a new tab in the CMS, then # the LMS barfs because it expects that -- if there are # *any* tabs -- then there at least needs to be # some predefined ones if course.tabs is None or len(course.tabs) == 0: CourseTabList.initialize_default(course) store.update_item(course, user_id) course_items.append(course) break # TODO: shouldn't this raise an exception if course wasn't found? # then import all the static content if static_content_store is not None and do_import_static: # first pass to find everything in /static/ import_static_content( course_data_path, static_content_store, dest_course_id, subpath='static', verbose=verbose ) elif verbose and not do_import_static: log.debug( "Skipping import of static content, " "since do_import_static={0}".format(do_import_static) ) # no matter what do_import_static is, import "static_import" directory # This is needed because the "about" pages (eg "overview") are # loaded via load_extra_content, and do not inherit the lms # metadata from the course module, and thus do not get # "static_content_store" properly defined. Static content # referenced in those extra pages thus need to come through the # c4x:// contentstore, unfortunately. Tell users to copy that # content into the "static_import" subdir. simport = 'static_import' if os.path.exists(course_data_path / simport): import_static_content( course_data_path, static_content_store, dest_course_id, subpath=simport, verbose=verbose ) # now loop through all the modules for module in xml_module_store.modules[course_key].itervalues(): if module.scope_ids.block_type == 'course': # we've already saved the course module up at the top # of the loop so just skip over it in the inner loop continue if verbose: log.debug('importing module location {loc}'.format( loc=module.location )) _import_module_and_update_references( module, store, user_id, course_key, dest_course_id, do_import_static=do_import_static, runtime=course.runtime ) # finally, publish the course store.publish(course.location, user_id) # now import any DRAFT items _import_course_draft( xml_module_store, store, user_id, course_data_path, course_key, dest_course_id, course.runtime ) return xml_module_store, course_items
def test_static_url_generation(self): location = Location(['i4x', 'foo', 'bar', 'asset', 'my_file_name.jpg']) path = StaticContent.get_static_path_from_location(location) self.assertEquals(path, '/static/my_file_name.jpg')
def upload_asset(request, org, course, coursename): ''' This method allows for POST uploading of files into the course asset library, which will be supported by GridFS in MongoDB. ''' # construct a location from the passed in path location = get_location_and_verify_access(request, org, course, coursename) # Does the course actually exist?!? Get anything from it to prove its # existence try: modulestore().get_item(location) except: # no return it as a Bad Request response logging.error('Could not find course' + location) return HttpResponseBadRequest() if 'files[]' not in request.FILES: return HttpResponseBadRequest() # compute a 'filename' which is similar to the location formatting, we're # using the 'filename' nomenclature since we're using a FileSystem paradigm # here. We're just imposing the Location string formatting expectations to # keep things a bit more consistent upload_file = request.FILES['files[]'] filename = upload_file.name mime_type = upload_file.content_type content_loc = StaticContent.compute_location(org, course, filename) chunked = upload_file.multiple_chunks() sc_partial = partial(StaticContent, content_loc, filename, mime_type) if chunked: content = sc_partial(upload_file.chunks()) tempfile_path = upload_file.temporary_file_path() else: content = sc_partial(upload_file.read()) tempfile_path = None thumbnail_content = None thumbnail_location = None # first let's see if a thumbnail can be created (thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail( content, tempfile_path=tempfile_path ) # delete cached thumbnail even if one couldn't be created this time (else # the old thumbnail will continue to show) del_cached_content(thumbnail_location) # now store thumbnail location only if we could create it if thumbnail_content is not None: content.thumbnail_location = thumbnail_location # then commit the content contentstore().save(content) del_cached_content(content.location) # readback the saved content - we need the database timestamp readback = contentstore().find(content.location) response_payload = { 'displayname': content.name, 'uploadDate': get_default_time_display(readback.last_modified_at), 'url': StaticContent.get_url_path_from_location(content.location), 'portable_url': StaticContent.get_static_path_from_location(content.location), 'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None, 'msg': 'Upload completed' } response = JsonResponse(response_payload) return response
def test_static_url_generation(self): course_key = CourseLocator('org', 'class', 'run') location = course_key.make_asset_key('asset', 'my_file_name.jpg') path = StaticContent.get_static_path_from_location(location) self.assertEquals(path, '/static/my_file_name.jpg')
def upload_asset(request, org, course, coursename): ''' This method allows for POST uploading of files into the course asset library, which will be supported by GridFS in MongoDB. ''' # construct a location from the passed in path location = get_location_and_verify_access(request, org, course, coursename) # Does the course actually exist?!? Get anything from it to prove its # existence try: modulestore().get_item(location) except: # no return it as a Bad Request response logging.error('Could not find course' + location) return HttpResponseBadRequest() if 'file' not in request.FILES: return HttpResponseBadRequest() # compute a 'filename' which is similar to the location formatting, we're # using the 'filename' nomenclature since we're using a FileSystem paradigm # here. We're just imposing the Location string formatting expectations to # keep things a bit more consistent upload_file = request.FILES['file'] filename = upload_file.name mime_type = upload_file.content_type content_loc = StaticContent.compute_location(org, course, filename) chunked = upload_file.multiple_chunks() sc_partial = partial(StaticContent, content_loc, filename, mime_type) if chunked: content = sc_partial(upload_file.chunks()) tempfile_path = upload_file.temporary_file_path() else: content = sc_partial(upload_file.read()) tempfile_path = None thumbnail_content = None thumbnail_location = None # first let's see if a thumbnail can be created (thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail( content, tempfile_path=tempfile_path ) # delete cached thumbnail even if one couldn't be created this time (else # the old thumbnail will continue to show) del_cached_content(thumbnail_location) # now store thumbnail location only if we could create it if thumbnail_content is not None: content.thumbnail_location = thumbnail_location # then commit the content contentstore().save(content) del_cached_content(content.location) # readback the saved content - we need the database timestamp readback = contentstore().find(content.location) response_payload = { 'displayname': content.name, 'uploadDate': get_default_time_display(readback.last_modified_at), 'url': StaticContent.get_url_path_from_location(content.location), 'portable_url': StaticContent.get_static_path_from_location(content.location), 'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None, 'msg': 'Upload completed' } response = JsonResponse(response_payload) return response