diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 582123b78f..18afd331d0 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -27,7 +27,7 @@ def get_course_location_for_item(location): raise BaseException('Could not find course at {0}'.format(course_search_location)) if found_cnt > 1: - raise BaseException('Found more than one course at {0}. There should only be one!!!'.format(course_search_location)) + raise BaseException('Found more than one course at {0}. There should only be one!!! Dump = {1}'.format(course_search_location, courses)) location = courses[0].location diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index c53b764a03..5f68faed15 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -10,6 +10,7 @@ import sys import time import tarfile import shutil +import tempfile from datetime import datetime from collections import defaultdict from uuid import uuid4 @@ -947,6 +948,12 @@ def create_new_course(request): if existing_course is not None: return HttpResponse(json.dumps({'ErrMsg': 'There is already a course defined with this name.'})) + course_search_location = ['i4x', dest_location.org, dest_location.course, 'course', None] + courses = modulestore().get_items(course_search_location) + + if len(courses) > 0: + return HttpResponse(json.dumps({'ErrMsg': 'There is already a course defined with the same organization and course number.'})) + new_course = modulestore('direct').clone_item(template, dest_location) if display_name is not None: @@ -982,7 +989,11 @@ def import_course(request, org, course, name): data_root = path(settings.GITHUB_REPO_ROOT) - temp_filepath = data_root / filename + course_dir = data_root / "{0}-{1}-{2}".format(org, course, name) + if not course_dir.isdir(): + os.mkdir(course_dir) + + temp_filepath = course_dir / filename logging.debug('importing course to {0}'.format(temp_filepath)) @@ -992,32 +1003,42 @@ def import_course(request, org, course, name): temp_file.write(chunk) temp_file.close() - # @todo: don't assume the top-level directory that was unziped was the same name (but without .tar.gz) - course_dir = filename.replace('.tar.gz', '') - tf = tarfile.open(temp_filepath) - if (data_root / course_dir).isdir(): - shutil.rmtree(data_root / course_dir) - tf.extractall(data_root + '/') + tf.extractall(course_dir + '/') - os.remove(temp_filepath) # remove the .tar.gz file + # find the 'course.xml' file + for r,d,f in os.walk(course_dir): + for files in f: + if files == 'course.xml': + break + if files == 'course.xml': + break - with open(data_root / course_dir / 'course.xml', 'r') as course_file: + if files != 'course.xml': + return HttpResponse(json.dumps({'ErrMsg': 'Could not find the course.xml file in the package.'})) + + logging.debug('found course.xml at {0}'.format(r)) + + if r != course_dir: + for fname in os.listdir(r): + shutil.move(r/fname, course_dir) + + with open(course_dir / 'course.xml', 'r') as course_file: course_data = etree.parse(course_file, parser=edx_xml_parser) course_data_root = course_data.getroot() course_data_root.set('org', org) course_data_root.set('course', course) course_data_root.set('url_name', name) - with open(data_root / course_dir / 'course.xml', 'w') as course_file: + with open(course_dir / 'course.xml', 'w') as course_file: course_data.write(course_file) module_store, course_items = import_from_xml(modulestore('direct'), settings.GITHUB_REPO_ROOT, [course_dir], load_error_modules=False, static_content_store=contentstore()) - # remove content directory - we *shouldn't* need this any longer :-) - shutil.rmtree(data_root / course_dir) + # we can blow this away when we're done importing. + shutil.rmtree(course_dir) logging.debug('new course at {0}'.format(course_items[0].location)) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 5e91eca875..84c520a6ac 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -10,7 +10,8 @@ var $spinner; $(document).ready(function() { $body = $('body'); $modal = $('.history-modal'); - $modalCover = $('.modal-cover'); + $modalCover = $(' +% if source: +
+

Download video here.

+
+% endif diff --git a/lms/urls.py b/lms/urls.py index a5c06ad979..8cc81a438b 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -19,9 +19,9 @@ urlpatterns = ('', # (specifically missing get parameters in certain cases) url(r'^debug_request$', 'util.views.debug_request'), - url(r'^change_email$', 'student.views.change_email_request'), + url(r'^change_email$', 'student.views.change_email_request', name="change_email"), url(r'^email_confirm/(?P[^/]*)$', 'student.views.confirm_email_change'), - url(r'^change_name$', 'student.views.change_name_request'), + url(r'^change_name$', 'student.views.change_name_request', name="change_name"), url(r'^accept_name_change$', 'student.views.accept_name_change'), url(r'^reject_name_change$', 'student.views.reject_name_change'), url(r'^pending_name_changes$', 'student.views.pending_name_changes'), @@ -52,6 +52,7 @@ urlpatterns = ('', url(r'^heartbeat$', include('heartbeat.urls')), + url(r'^university_profile/UTx$', 'courseware.views.static_university_profile', name="static_university_profile", kwargs={'org_id':'UTx'}), url(r'^university_profile/(?P[^/]+)$', 'courseware.views.university_profile', name="university_profile"), #Semi-static views (these need to be rendered and have the login bar, but don't change) @@ -88,7 +89,10 @@ urlpatterns = ('', {'template': 'press_releases/edX_announces_proctored_exam_testing.html'}, name="press/edX-announces-proctored-exam-testing"), url(r'^press/elsevier-collaborates-with-edx$', 'static_template_view.views.render', {'template': 'press_releases/Elsevier_collaborates_with_edX.html'}, name="press/elsevier-collaborates-with-edx"), - + url(r'^press/ut-joins-edx$', 'static_template_view.views.render', + {'template': 'press_releases/UT_joins_edX.html'}, name="press/ut-joins-edx"), + url(r'^press/cengage-to-provide-book-content$', 'static_template_view.views.render', + {'template': 'press_releases/Cengage_to_provide_book_content.html'}, name="press/cengage-to-provide-book-content"), # Should this always update to point to the latest press release? (r'^pressrelease$', 'django.views.generic.simple.redirect_to', {'url': '/press/uc-berkeley-joins-edx'}), @@ -141,6 +145,24 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/modx/(?P.*?)/(?P[^/]*)$', 'courseware.module_render.modx_dispatch', name='modx_dispatch'), + + # TODO (vshnayder): This is a hack. It creates a direct connection from + # the LMS to capa functionality, and really wants to go through the + # input types system so that previews can be context-specific. + # Unfortunately, we don't have time to think through the right way to do + # that (and implement it), and it's not a terrible thing to provide a + # generic chemican-equation rendering service. + url(r'^preview/chemcalc', 'courseware.module_render.preview_chemcalc', + name='preview_chemcalc'), + + # Software Licenses + + # TODO: for now, this is the endpoint of an ajax replay + # service that retrieve and assigns license numbers for + # software assigned to a course. The numbers have to be loaded + # into the database. + url(r'^software-licenses$', 'licenses.views.user_software_license', name="user_software_license"), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xqueue/(?P[^/]*)/(?P.*?)/(?P[^/]*)$', 'courseware.module_render.xqueue_callback', name='xqueue_callback'), @@ -236,7 +258,7 @@ if settings.MITX_FEATURES.get('AUTH_USE_OPENID'): if settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'): urlpatterns += ( url(r'^openid/provider/login/$', 'external_auth.views.provider_login', name='openid-provider-login'), - url(r'^openid/provider/login/(?:[\w%\. ]+)$', 'external_auth.views.provider_identity', name='openid-provider-login-identity'), + url(r'^openid/provider/login/(?:.+)$', 'external_auth.views.provider_identity', name='openid-provider-login-identity'), url(r'^openid/provider/identity/$', 'external_auth.views.provider_identity', name='openid-provider-identity'), url(r'^openid/provider/xrds/$', 'external_auth.views.provider_xrds', name='openid-provider-xrds') ) diff --git a/rakefile b/rakefile index f5e26a208f..90cd5fe87f 100644 --- a/rakefile +++ b/rakefile @@ -151,6 +151,13 @@ Dir["common/lib/*"].each do |lib| sh("nosetests #{lib} --cover-erase --with-xunit --with-xcoverage --cover-html --cover-inclusive --cover-package #{File.basename(lib)} --cover-html-dir #{File.join(report_dir, "cover")}") end TEST_TASKS << task_name + + desc "Run tests for common lib #{lib} (without coverage)" + task "fasttest_#{lib}" do + sh("nosetests #{lib}") + end + + end task :test do @@ -171,6 +178,12 @@ task "django-admin", [:action, :system, :env, :options] => [:predjango] do |t, a sh(django_admin(args.system, args.env, args.action, args.options)) end +desc "Set the staff bit for a user" +task :set_staff, [:user, :system, :env] do |t, args| + args.with_defaults(:env => 'dev', :system => 'lms', :options => '') + sh(django_admin(args.system, args.env, 'set_staff', args.user)) +end + task :package do FileUtils.mkdir_p(BUILD_DIR) diff --git a/requirements.txt b/requirements.txt index c3322c5b7c..2ebca50bc5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ beautifulsoup beautifulsoup4 feedparser requests -sympy +http://sympy.googlecode.com/files/sympy-0.7.1.tar.gz newrelic glob2 pymongo @@ -49,3 +49,4 @@ networkx pygraphviz -r repo-requirements.txt pil +nltk