diff --git a/cms/djangoapps/contentstore/__init__.py b/cms/djangoapps/contentstore/__init__.py index 433278c47b..ef4e31614e 100644 --- a/cms/djangoapps/contentstore/__init__.py +++ b/cms/djangoapps/contentstore/__init__.py @@ -5,12 +5,17 @@ import logging log = logging.getLogger(__name__) -def import_from_xml(org, course, data_dir): +def import_from_xml(data_dir, course_dirs=None): """ Import the specified xml data_dir into the django defined modulestore, using org and course as the location org and course. """ - module_store = XMLModuleStore(org, course, data_dir, 'xmodule.raw_module.RawDescriptor', eager=True) + module_store = XMLModuleStore( + data_dir, + default_class='xmodule.raw_module.RawDescriptor', + eager=True, + course_dirs=course_dirs + ) for module in module_store.modules.itervalues(): # TODO (cpennington): This forces import to overrite the same items. @@ -26,4 +31,4 @@ def import_from_xml(org, course, data_dir): modulestore().update_children(module.location, module.definition['children']) modulestore().update_metadata(module.location, dict(module.metadata)) - return module_store.course + return module_store diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 89323c3d9b..75d4e2618c 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -13,8 +13,12 @@ class Command(BaseCommand): '''Import the specified data directory into the default ModuleStore''' def handle(self, *args, **options): - if len(args) != 3: - raise CommandError("import requires 3 arguments: ") + if len(args) == 0: + raise CommandError("import requires at least one argument: [...]") - org, course, data_dir = args - import_from_xml(org, course, data_dir) + data_dir = args[0] + if len(args) > 1: + course_dirs = args[1:] + else: + course_dirs = None + import_from_xml(data_dir, course_dirs) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 8ac6aa610e..185ea3868d 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -6,7 +6,7 @@ from django_future.csrf import ensure_csrf_cookie from fs.osfs import OSFS from django.core.urlresolvers import reverse from xmodule.modulestore import Location -from github_sync import repo_path_from_location, export_to_github +from github_sync import export_to_github from mitxmako.shortcuts import render_to_response from xmodule.modulestore.django import modulestore @@ -51,11 +51,12 @@ def save_item(request): modulestore().update_item(item_id, data) # Export the course back to github + # This uses wildcarding to find the course, which requires handling + # multiple courses returned, but there should only ever be one course_location = Location(item_id)._replace(category='course', name=None) courses = modulestore().get_items(course_location) for course in courses: - repo_path = repo_path_from_location(course.location) - export_to_github(course, repo_path, "CMS Edit") + export_to_github(course, "CMS Edit") return HttpResponse(json.dumps({})) diff --git a/cms/djangoapps/github_sync/__init__.py b/cms/djangoapps/github_sync/__init__.py index c901ad37e7..b8d9dbd683 100644 --- a/cms/djangoapps/github_sync/__init__.py +++ b/cms/djangoapps/github_sync/__init__.py @@ -12,6 +12,7 @@ from .exceptions import GithubSyncError log = logging.getLogger(__name__) + def import_from_github(repo_settings): """ Imports data into the modulestore based on the XML stored on github @@ -19,10 +20,9 @@ def import_from_github(repo_settings): repo_settings is a dictionary with the following keys: path: file system path to the local git repo branch: name of the branch to track on github - org: name of the organization to use in the imported course - course: name of the coures to use in the imported course """ repo_path = repo_settings['path'] + data_dir, course_dir = os.path.split(repo_path) if not os.path.isdir(repo_path): Repo.clone_from(repo_settings['origin'], repo_path) @@ -34,18 +34,12 @@ def import_from_github(repo_settings): # Do a hard reset to the remote branch so that we have a clean import git_repo.git.checkout(repo_settings['branch']) git_repo.head.reset('origin/%s' % repo_settings['branch'], index=True, working_tree=True) - - return git_repo.head.commit.hexsha, import_from_xml(repo_settings['org'], repo_settings['course'], repo_path) + module_store = import_from_xml(data_dir, course_dirs=[course_dir]) + return git_repo.head.commit.hexsha, module_store.courses[course_dir] -def repo_path_from_location(location): - location = Location(location) - for name, repo in settings.REPOS.items(): - if repo['org'] == location.org and repo['course'] == location.course: - return repo['path'] - - -def export_to_github(course, repo_path, commit_message): +def export_to_github(course, commit_message): + repo_path = settings.DATA_DIR / course.metadata.get('course_dir', course.location.course) fs = OSFS(repo_path) xml = course.export_to_xml(fs) diff --git a/cms/djangoapps/github_sync/tests/__init__.py b/cms/djangoapps/github_sync/tests/__init__.py index 825fc68313..b644328dd2 100644 --- a/cms/djangoapps/github_sync/tests/__init__.py +++ b/cms/djangoapps/github_sync/tests/__init__.py @@ -1,7 +1,7 @@ from django.test import TestCase from path import path import shutil -from github_sync import import_from_github, export_to_github, repo_path_from_location +from github_sync import import_from_github, export_to_github from git import Repo from django.conf import settings from xmodule.modulestore.django import modulestore @@ -10,6 +10,7 @@ from override_settings import override_settings from github_sync.exceptions import GithubSyncError +@override_settings(DATA_DIR=path('test_root')) class GithubSyncTestCase(TestCase): def setUp(self): @@ -29,8 +30,6 @@ class GithubSyncTestCase(TestCase): 'path': self.repo_dir, 'origin': self.remote_dir, 'branch': 'master', - 'org': 'org', - 'course': 'course' }) def tearDown(self): @@ -49,7 +48,7 @@ class GithubSyncTestCase(TestCase): """ self.assertEquals('Toy Course', self.import_course.metadata['display_name']) self.assertIn( - Location('i4x://org/course/chapter/Overview'), + Location('i4x://edx/local_repo/chapter/Overview'), [child.location for child in self.import_course.get_children()]) self.assertEquals(1, len(self.import_course.get_children())) @@ -58,7 +57,7 @@ class GithubSyncTestCase(TestCase): """ Test that with the GITHUB_PUSH feature disabled, no content is pushed to the remote """ - export_to_github(self.import_course, self.repo_dir, 'Test no-push') + export_to_github(self.import_course, 'Test no-push') self.assertEquals(1, Repo(self.remote_dir).head.commit.count()) @override_settings(MITX_FEATURES={'GITHUB_PUSH': True}) @@ -67,7 +66,7 @@ class GithubSyncTestCase(TestCase): Test that with GITHUB_PUSH enabled, content is pushed to the remote """ self.import_course.metadata['display_name'] = 'Changed display name' - export_to_github(self.import_course, self.repo_dir, 'Test push') + export_to_github(self.import_course, 'Test push') self.assertEquals(2, Repo(self.remote_dir).head.commit.count()) @override_settings(MITX_FEATURES={'GITHUB_PUSH': True}) @@ -80,17 +79,6 @@ class GithubSyncTestCase(TestCase): remote = Repo(self.remote_dir) remote.git.commit(allow_empty=True, m="Testing conflict commit") - self.assertRaises(GithubSyncError, export_to_github, self.import_course, self.repo_dir, 'Test push') + self.assertRaises(GithubSyncError, export_to_github, self.import_course, 'Test push') self.assertEquals(2, remote.head.reference.commit.count()) self.assertEquals("Testing conflict commit\n", remote.head.reference.commit.message) - - -@override_settings(REPOS={'namea': {'path': 'patha', 'org': 'orga', 'course': 'coursea'}, - 'nameb': {'path': 'pathb', 'org': 'orgb', 'course': 'courseb'}}) -class RepoPathLookupTestCase(TestCase): - def test_successful_lookup(self): - self.assertEquals('patha', repo_path_from_location('i4x://orga/coursea/course/foo')) - self.assertEquals('pathb', repo_path_from_location('i4x://orgb/courseb/course/foo')) - - def test_failed_lookup(self): - self.assertEquals(None, repo_path_from_location('i4x://c/c/course/foo')) diff --git a/common/lib/xmodule/tests/__init__.py b/common/lib/xmodule/tests/__init__.py index 0f2425aacc..5483015a82 100644 --- a/common/lib/xmodule/tests/__init__.py +++ b/common/lib/xmodule/tests/__init__.py @@ -412,210 +412,208 @@ class GraderTest(unittest.TestCase): self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) self.assertEqual( len(graded['grade_breakdown']), 3 ) - graded = zeroWeightsGrader.grade(self.test_gradesheet) - self.assertAlmostEqual( graded['percent'], 0.2525 ) - self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) - self.assertEqual( len(graded['grade_breakdown']), 3 ) + graded = zeroWeightsGrader.grade(self.test_gradesheet) + self.assertAlmostEqual( graded['percent'], 0.2525 ) + self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) + self.assertEqual( len(graded['grade_breakdown']), 3 ) - graded = allZeroWeightsGrader.grade(self.test_gradesheet) - self.assertAlmostEqual( graded['percent'], 0.0 ) - self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) - self.assertEqual( len(graded['grade_breakdown']), 3 ) - - for graded in [ weightedGrader.grade(self.empty_gradesheet), - weightedGrader.grade(self.incomplete_gradesheet), - zeroWeightsGrader.grade(self.empty_gradesheet), - allZeroWeightsGrader.grade(self.empty_gradesheet)]: + graded = allZeroWeightsGrader.grade(self.test_gradesheet) self.assertAlmostEqual( graded['percent'], 0.0 ) self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) self.assertEqual( len(graded['grade_breakdown']), 3 ) + for graded in [ weightedGrader.grade(self.empty_gradesheet), + weightedGrader.grade(self.incomplete_gradesheet), + zeroWeightsGrader.grade(self.empty_gradesheet), + allZeroWeightsGrader.grade(self.empty_gradesheet)]: + self.assertAlmostEqual( graded['percent'], 0.0 ) + self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) + self.assertEqual( len(graded['grade_breakdown']), 3 ) + + + graded = emptyGrader.grade(self.test_gradesheet) + self.assertAlmostEqual( graded['percent'], 0.0 ) + self.assertEqual( len(graded['section_breakdown']), 0 ) + self.assertEqual( len(graded['grade_breakdown']), 0 ) + + def test_graderFromConf(self): - graded = emptyGrader.grade(self.test_gradesheet) - self.assertAlmostEqual( graded['percent'], 0.0 ) - self.assertEqual( len(graded['section_breakdown']), 0 ) - self.assertEqual( len(graded['grade_breakdown']), 0 ) + #Confs always produce a graders.WeightedSubsectionsGrader, so we test this by repeating the test + #in test_graders.WeightedSubsectionsGrader, but generate the graders with confs. + + weightedGrader = graders.grader_from_conf([ + { + 'type' : "Homework", + 'min_count' : 12, + 'drop_count' : 2, + 'short_label' : "HW", + 'weight' : 0.25, + }, + { + 'type' : "Lab", + 'min_count' : 7, + 'drop_count' : 3, + 'category' : "Labs", + 'weight' : 0.25 + }, + { + 'type' : "Midterm", + 'name' : "Midterm Exam", + 'short_label' : "Midterm", + 'weight' : 0.5, + }, + ]) + + emptyGrader = graders.grader_from_conf([]) + + graded = weightedGrader.grade(self.test_gradesheet) + self.assertAlmostEqual( graded['percent'], 0.5106547619047619 ) + self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) + self.assertEqual( len(graded['grade_breakdown']), 3 ) + + graded = emptyGrader.grade(self.test_gradesheet) + self.assertAlmostEqual( graded['percent'], 0.0 ) + self.assertEqual( len(graded['section_breakdown']), 0 ) + self.assertEqual( len(graded['grade_breakdown']), 0 ) + + #Test that graders can also be used instead of lists of dictionaries + homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2) + homeworkGrader2 = graders.grader_from_conf(homeworkGrader) + + graded = homeworkGrader2.grade(self.test_gradesheet) + self.assertAlmostEqual( graded['percent'], 0.11 ) + self.assertEqual( len(graded['section_breakdown']), 12 + 1 ) - - -def test_graderFromConf(self): - - #Confs always produce a graders.WeightedSubsectionsGrader, so we test this by repeating the test - #in test_graders.WeightedSubsectionsGrader, but generate the graders with confs. - - weightedGrader = graders.grader_from_conf([ - { - 'type' : "Homework", - 'min_count' : 12, - 'drop_count' : 2, - 'short_label' : "HW", - 'weight' : 0.25, - }, - { - 'type' : "Lab", - 'min_count' : 7, - 'drop_count' : 3, - 'category' : "Labs", - 'weight' : 0.25 - }, - { - 'type' : "Midterm", - 'name' : "Midterm Exam", - 'short_label' : "Midterm", - 'weight' : 0.5, - }, - ]) - - emptyGrader = graders.grader_from_conf([]) - - graded = weightedGrader.grade(self.test_gradesheet) - self.assertAlmostEqual( graded['percent'], 0.5106547619047619 ) - self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 ) - self.assertEqual( len(graded['grade_breakdown']), 3 ) - - graded = emptyGrader.grade(self.test_gradesheet) - self.assertAlmostEqual( graded['percent'], 0.0 ) - self.assertEqual( len(graded['section_breakdown']), 0 ) - self.assertEqual( len(graded['grade_breakdown']), 0 ) - - #Test that graders can also be used instead of lists of dictionaries - homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2) - homeworkGrader2 = graders.grader_from_conf(homeworkGrader) - - graded = homeworkGrader2.grade(self.test_gradesheet) - self.assertAlmostEqual( graded['percent'], 0.11 ) - self.assertEqual( len(graded['section_breakdown']), 12 + 1 ) - - #TODO: How do we test failure cases? The parser only logs an error when it can't parse something. Maybe it should throw exceptions? + #TODO: How do we test failure cases? The parser only logs an error when it can't parse something. Maybe it should throw exceptions? # -------------------------------------------------------------------------- # Module progress tests class ProgressTest(unittest.TestCase): -''' Test that basic Progress objects work. A Progress represents a -fraction between 0 and 1. -''' -not_started = Progress(0, 17) -part_done = Progress(2, 6) -half_done = Progress(3, 6) -also_half_done = Progress(1, 2) -done = Progress(7, 7) + ''' Test that basic Progress objects work. A Progress represents a + fraction between 0 and 1. + ''' + not_started = Progress(0, 17) + part_done = Progress(2, 6) + half_done = Progress(3, 6) + also_half_done = Progress(1, 2) + done = Progress(7, 7) -def test_create_object(self): - # These should work: - p = Progress(0, 2) - p = Progress(1, 2) - p = Progress(2, 2) + def test_create_object(self): + # These should work: + p = Progress(0, 2) + p = Progress(1, 2) + p = Progress(2, 2) - p = Progress(2.5, 5.0) - p = Progress(3.7, 12.3333) - - # These shouldn't - self.assertRaises(ValueError, Progress, 0, 0) - self.assertRaises(ValueError, Progress, 2, 0) - self.assertRaises(ValueError, Progress, 1, -2) - self.assertRaises(ValueError, Progress, 3, 2) - self.assertRaises(ValueError, Progress, -2, 5) + p = Progress(2.5, 5.0) + p = Progress(3.7, 12.3333) + + # These shouldn't + self.assertRaises(ValueError, Progress, 0, 0) + self.assertRaises(ValueError, Progress, 2, 0) + self.assertRaises(ValueError, Progress, 1, -2) + self.assertRaises(ValueError, Progress, 3, 2) + self.assertRaises(ValueError, Progress, -2, 5) - self.assertRaises(TypeError, Progress, 0, "all") - # check complex numbers just for the heck of it :) - self.assertRaises(TypeError, Progress, 2j, 3) + self.assertRaises(TypeError, Progress, 0, "all") + # check complex numbers just for the heck of it :) + self.assertRaises(TypeError, Progress, 2j, 3) -def test_frac(self): - p = Progress(1, 2) - (a, b) = p.frac() - self.assertEqual(a, 1) - self.assertEqual(b, 2) + def test_frac(self): + p = Progress(1, 2) + (a, b) = p.frac() + self.assertEqual(a, 1) + self.assertEqual(b, 2) -def test_percent(self): - self.assertEqual(self.not_started.percent(), 0) - self.assertAlmostEqual(self.part_done.percent(), 33.33333333333333) - self.assertEqual(self.half_done.percent(), 50) - self.assertEqual(self.done.percent(), 100) + def test_percent(self): + self.assertEqual(self.not_started.percent(), 0) + self.assertAlmostEqual(self.part_done.percent(), 33.33333333333333) + self.assertEqual(self.half_done.percent(), 50) + self.assertEqual(self.done.percent(), 100) - self.assertEqual(self.half_done.percent(), self.also_half_done.percent()) + self.assertEqual(self.half_done.percent(), self.also_half_done.percent()) -def test_started(self): - self.assertFalse(self.not_started.started()) + def test_started(self): + self.assertFalse(self.not_started.started()) - self.assertTrue(self.part_done.started()) - self.assertTrue(self.half_done.started()) - self.assertTrue(self.done.started()) + self.assertTrue(self.part_done.started()) + self.assertTrue(self.half_done.started()) + self.assertTrue(self.done.started()) -def test_inprogress(self): - # only true if working on it - self.assertFalse(self.done.inprogress()) - self.assertFalse(self.not_started.inprogress()) + def test_inprogress(self): + # only true if working on it + self.assertFalse(self.done.inprogress()) + self.assertFalse(self.not_started.inprogress()) - self.assertTrue(self.part_done.inprogress()) - self.assertTrue(self.half_done.inprogress()) + self.assertTrue(self.part_done.inprogress()) + self.assertTrue(self.half_done.inprogress()) -def test_done(self): - self.assertTrue(self.done.done()) - self.assertFalse(self.half_done.done()) - self.assertFalse(self.not_started.done()) - -def test_str(self): - self.assertEqual(str(self.not_started), "0/17") - self.assertEqual(str(self.part_done), "2/6") - self.assertEqual(str(self.done), "7/7") + def test_done(self): + self.assertTrue(self.done.done()) + self.assertFalse(self.half_done.done()) + self.assertFalse(self.not_started.done()) + + def test_str(self): + self.assertEqual(str(self.not_started), "0/17") + self.assertEqual(str(self.part_done), "2/6") + self.assertEqual(str(self.done), "7/7") -def test_ternary_str(self): - self.assertEqual(self.not_started.ternary_str(), "none") - self.assertEqual(self.half_done.ternary_str(), "in_progress") - self.assertEqual(self.done.ternary_str(), "done") + def test_ternary_str(self): + self.assertEqual(self.not_started.ternary_str(), "none") + self.assertEqual(self.half_done.ternary_str(), "in_progress") + self.assertEqual(self.done.ternary_str(), "done") -def test_to_js_status(self): - '''Test the Progress.to_js_status_str() method''' - - self.assertEqual(Progress.to_js_status_str(self.not_started), "none") - self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress") - self.assertEqual(Progress.to_js_status_str(self.done), "done") - self.assertEqual(Progress.to_js_status_str(None), "NA") + def test_to_js_status(self): + '''Test the Progress.to_js_status_str() method''' + + self.assertEqual(Progress.to_js_status_str(self.not_started), "none") + self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress") + self.assertEqual(Progress.to_js_status_str(self.done), "done") + self.assertEqual(Progress.to_js_status_str(None), "NA") -def test_to_js_detail_str(self): - '''Test the Progress.to_js_detail_str() method''' - f = Progress.to_js_detail_str - for p in (self.not_started, self.half_done, self.done): - self.assertEqual(f(p), str(p)) - # But None should be encoded as NA - self.assertEqual(f(None), "NA") + def test_to_js_detail_str(self): + '''Test the Progress.to_js_detail_str() method''' + f = Progress.to_js_detail_str + for p in (self.not_started, self.half_done, self.done): + self.assertEqual(f(p), str(p)) + # But None should be encoded as NA + self.assertEqual(f(None), "NA") -def test_add(self): - '''Test the Progress.add_counts() method''' - p = Progress(0, 2) - p2 = Progress(1, 3) - p3 = Progress(2, 5) - pNone = None - add = lambda a, b: Progress.add_counts(a, b).frac() + def test_add(self): + '''Test the Progress.add_counts() method''' + p = Progress(0, 2) + p2 = Progress(1, 3) + p3 = Progress(2, 5) + pNone = None + add = lambda a, b: Progress.add_counts(a, b).frac() - self.assertEqual(add(p, p), (0, 4)) - self.assertEqual(add(p, p2), (1, 5)) - self.assertEqual(add(p2, p3), (3, 8)) - - self.assertEqual(add(p2, pNone), p2.frac()) - self.assertEqual(add(pNone, p2), p2.frac()) + self.assertEqual(add(p, p), (0, 4)) + self.assertEqual(add(p, p2), (1, 5)) + self.assertEqual(add(p2, p3), (3, 8)) + + self.assertEqual(add(p2, pNone), p2.frac()) + self.assertEqual(add(pNone, p2), p2.frac()) -def test_equality(self): - '''Test that comparing Progress objects for equality - works correctly.''' - p = Progress(1, 2) - p2 = Progress(2, 4) - p3 = Progress(1, 2) - self.assertTrue(p == p3) - self.assertFalse(p == p2) + def test_equality(self): + '''Test that comparing Progress objects for equality + works correctly.''' + p = Progress(1, 2) + p2 = Progress(2, 4) + p3 = Progress(1, 2) + self.assertTrue(p == p3) + self.assertFalse(p == p2) - # Check != while we're at it - self.assertTrue(p != p2) - self.assertFalse(p != p3) + # Check != while we're at it + self.assertTrue(p != p2) + self.assertFalse(p != p3) class ModuleProgressTest(unittest.TestCase): -''' Test that get_progress() does the right thing for the different modules -''' -def test_xmodule_default(self): - '''Make sure default get_progress exists, returns None''' + ''' Test that get_progress() does the right thing for the different modules + ''' + def test_xmodule_default(self): + '''Make sure default get_progress exists, returns None''' xm = x_module.XModule(i4xs, 'a://b/c/d/e', {}) p = xm.get_progress() self.assertEqual(p, None) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index d8450998fd..37e1b00099 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -15,7 +15,6 @@ from xmodule.raw_module import RawDescriptor from progress import Progress from capa.capa_problem import LoncapaProblem from capa.responsetypes import StudentInputError -from static_replace import replace_urls log = logging.getLogger("mitx.courseware") @@ -275,7 +274,7 @@ class CapaModule(XModule): html = '
'.format( id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
" - return replace_urls(html, self.metadata['data_dir']) + return self.system.replace_urls(html, self.metadata['data_dir']) def handle_ajax(self, dispatch, get): ''' diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py index 818dd67de8..7474666e21 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml.py +++ b/common/lib/xmodule/xmodule/modulestore/xml.py @@ -20,19 +20,21 @@ class XMLModuleStore(ModuleStore): """ An XML backed ModuleStore """ - def __init__(self, data_dir, default_class=None, eager=False): + def __init__(self, data_dir, default_class=None, eager=False, course_dirs=None): """ Initialize an XMLModuleStore from data_dir data_dir: path to data directory containing the course directories default_class: dot-separated string defining the default descriptor class to use if non is specified in entry_points eager: If true, load the modules children immediately to force the entire course tree to be parsed + course_dirs: If specified, the list of course_dirs to load. Otherwise, load + all course dirs """ self.eager = eager self.data_dir = path(data_dir) self.modules = {} - self.courses = [] + self.courses = {} if default_class is None: self.default_class = None @@ -46,10 +48,14 @@ class XMLModuleStore(ModuleStore): log.debug('default_class = %s' % self.default_class) for course_dir in os.listdir(self.data_dir): - if not os.path.exists(self.data_dir + "/" + course_dir + "/course.xml"): + if course_dirs is not None and course_dir not in course_dirs: continue - self.courses.append(self.load_course(course_dir)) + if not os.path.exists(self.data_dir / course_dir / "course.xml"): + continue + + course_descriptor = self.load_course(course_dir) + self.courses[course_dir] = course_descriptor def load_course(self, course_dir): """ @@ -148,7 +154,7 @@ class XMLModuleStore(ModuleStore): """ Returns a list of course descriptors """ - return self.courses + return self.courses.values() def create_item(self, location): raise NotImplementedError("XMLModuleStores are read-only") diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 8128e2d194..99c2a586c4 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -27,8 +27,8 @@ class I4xSystem(object): and user, or other environment-specific info. ''' def __init__(self, ajax_url, track_function, - get_module, render_template, user=None, - filestore=None): + get_module, render_template, replace_urls, + user=None, filestore=None): ''' Create a closure around the system environment. @@ -44,6 +44,8 @@ class I4xSystem(object): user - The user to base the seed off of for this request filestore - A filestore ojbect. Defaults to an instance of OSFS based at settings.DATA_DIR. + replace_urls - TEMPORARY - A function like static_replace.replace_urls + that capa_module can use to fix up the static urls in ajax results. ''' self.ajax_url = ajax_url self.track_function = track_function @@ -53,6 +55,7 @@ class I4xSystem(object): self.exception404 = Http404 self.DEBUG = settings.DEBUG self.seed = user.id if user is not None else 0 + self.replace_urls = replace_urls def get(self, attr): ''' provide uniform access to attributes (like etree).''' @@ -209,6 +212,9 @@ def get_module(user, request, location, student_module_cache, position=None): (module, _, _, _) = get_module(user, request, location, student_module_cache, position) return module + # TODO (cpennington): When modules are shared between courses, the static + # prefix is going to have to be specific to the module, not the directory + # that the xml was loaded from system = I4xSystem(track_function=make_track_function(request), render_template=render_to_string, ajax_url=ajax_url, @@ -216,17 +222,18 @@ def get_module(user, request, location, student_module_cache, position=None): filestore=descriptor.system.resources_fs, get_module=_get_module, user=user, + # TODO (cpennington): This should be removed when all html from + # a module is coming through get_html and is therefore covered + # by the replace_static_urls code below + replace_urls=replace_urls, ) # pass position specified in URL to module through I4xSystem system.set('position', position) module = descriptor.xmodule_constructor(system)(instance_state, shared_state) - # TODO (cpennington): When modules are shared between courses, the static - # prefix is going to have to be specific to the module, not the directory - # that the xml was loaded from - prefix = module.metadata['data_dir'] - module = replace_static_urls(module, prefix) + replace_prefix = module.metadata['data_dir'] + module = replace_static_urls(module, replace_prefix) if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF') and user.is_staff: module = add_histogram(module) diff --git a/rakefile b/rakefile index fe21c04212..6733bebaf5 100644 --- a/rakefile +++ b/rakefile @@ -27,7 +27,7 @@ NORMALIZED_DEPLOY_NAME = DEPLOY_NAME.downcase().gsub(/[_\/]/, '-') INSTALL_DIR_PATH = File.join(DEPLOY_DIR, NORMALIZED_DEPLOY_NAME) # Set up the clean and clobber tasks -CLOBBER.include(BUILD_DIR, REPORT_DIR, 'cover*', '.coverage') +CLOBBER.include(BUILD_DIR, REPORT_DIR, 'cover*', '.coverage', 'test_root/*_repo') CLEAN.include("#{BUILD_DIR}/*.deb", "#{BUILD_DIR}/util") def select_executable(*cmds) @@ -159,7 +159,6 @@ task :package do FileUtils.chmod(0755, postinstall.path) args = ["fakeroot", "fpm", "-s", "dir", "-t", "deb", - "--verbose", "--after-install=#{postinstall.path}", "--after-remove=#{afterremove.path}", "--prefix=#{INSTALL_DIR_PATH}", diff --git a/requirements.txt b/requirements.txt index d9086defd5..29d166fcee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,20 +5,15 @@ scipy matplotlib markdown pygments -django-mako -django-ses lxml boto mako python-memcached -django-celery path.py django_debug_toolbar -e git+git://github.com/MITx/django-pipeline.git#egg=django-pipeline django-staticfiles>=1.2.1 -django-masquerade fs -django-jasmine beautifulsoup requests sympy @@ -36,10 +31,14 @@ django-override-settings mock>=0.8, <0.9 PyYAML South +django-celery django-countries django-kombu django-followit +django-jasmine django-keyedcache +django-mako +django-masquerade django-robots django-ses django-storages