Example #1
0
 def test_remove_user_from_course_group_permission_denied(self):
     """
     Verifies PermissionDenied if caller of remove_user_from_course_group is not instructor role.
     """
     create_all_course_groups(self.creator, self.location)
     with self.assertRaises(PermissionDenied):
         remove_user_from_course_group(self.staff, self.staff, self.location, STAFF_ROLE_NAME)
Example #2
0
def create_new_course(request):

    if settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff:
        raise PermissionDenied()

    # This logic is repeated in xmodule/modulestore/tests/factories.py
    # so if you change anything here, you need to also change it there.
    # TODO: write a test that creates two courses, one with the factory and
    # the other with this method, then compare them to make sure they are
    # equivalent.
    template = Location(request.POST['template'])
    org = request.POST.get('org')
    number = request.POST.get('number')
    display_name = request.POST.get('display_name')

    try:
        dest_location = Location('i4x', org, number, 'course', Location.clean(display_name))
    except InvalidLocationError as error:
        return HttpResponse(json.dumps({'ErrMsg': "Unable to create course '" +
                                        display_name + "'.\n\n" + error.message}))

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore('direct').get_item(dest_location)
    except ItemNotFoundError:
        pass

    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)

    # clone a default 'about' module as well

    about_template_location = Location(['i4x', 'edx', 'templates', 'about', 'overview'])
    dest_about_location = dest_location._replace(category='about', name='overview')
    modulestore('direct').clone_item(about_template_location, dest_about_location)

    if display_name is not None:
        new_course.display_name = display_name

    # set a default start date to now
    new_course.start = datetime.datetime.now(UTC())

    initialize_course_tabs(new_course)

    create_all_course_groups(request.user, new_course.location)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    return HttpResponse(json.dumps({'id': new_course.location.url()}))
Example #3
0
def create_new_course(request):

    if settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff:
        raise PermissionDenied()

    # This logic is repeated in xmodule/modulestore/tests/factories.py
    # so if you change anything here, you need to also change it there.
    # TODO: write a test that creates two courses, one with the factory and
    # the other with this method, then compare them to make sure they are
    # equivalent.
    template = Location(request.POST['template'])
    org = request.POST.get('org')
    number = request.POST.get('number')
    display_name = request.POST.get('display_name')

    try:
        dest_location = Location('i4x', org, number, 'course', Location.clean(display_name))
    except InvalidLocationError as error:
        return HttpResponse(json.dumps({'ErrMsg': "Unable to create course '" +
                                        display_name + "'.\n\n" + error.message}))

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore('direct').get_item(dest_location)
    except ItemNotFoundError:
        pass

    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)

    # clone a default 'about' module as well

    about_template_location = Location(['i4x', 'edx', 'templates', 'about', 'overview'])
    dest_about_location = dest_location._replace(category='about', name='overview')
    modulestore('direct').clone_item(about_template_location, dest_about_location)

    if display_name is not None:
        new_course.display_name = display_name

    # set a default start date to now
    new_course.start = datetime.datetime.now(UTC())

    initialize_course_tabs(new_course)

    create_all_course_groups(request.user, new_course.location)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    return HttpResponse(json.dumps({'id': new_course.location.url()}))
Example #4
0
 def test_remove_user_from_course_group_permission_denied(self):
     """
     Verifies PermissionDenied if caller of remove_user_from_course_group is not instructor role.
     """
     create_all_course_groups(self.creator, self.location)
     with self.assertRaises(PermissionDenied):
         remove_user_from_course_group(self.staff, self.staff,
                                       self.location, STAFF_ROLE_NAME)
Example #5
0
def create_new_course(request):
    """
    Create a new course
    """
    if not is_user_in_creator_group(request.user):
        raise PermissionDenied()

    org = request.POST.get("org")
    number = request.POST.get("number")
    display_name = request.POST.get("display_name")

    try:
        dest_location = Location("i4x", org, number, "course", Location.clean(display_name))
    except InvalidLocationError as error:
        return JsonResponse(
            {"ErrMsg": "Unable to create course '{name}'.\n\n{err}".format(name=display_name, err=error.message)}
        )

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore("direct").get_item(dest_location)
    except ItemNotFoundError:
        pass
    if existing_course is not None:
        return JsonResponse({"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 JsonResponse(
            {"ErrMsg": "There is already a course defined with the same organization and course number."}
        )

    # instantiate the CourseDescriptor and then persist it
    # note: no system to pass
    if display_name is None:
        metadata = {}
    else:
        metadata = {"display_name": display_name}
    modulestore("direct").create_and_save_xmodule(dest_location, metadata=metadata)
    new_course = modulestore("direct").get_item(dest_location)

    # clone a default 'about' overview module as well
    dest_about_location = dest_location.replace(category="about", name="overview")
    overview_template = AboutDescriptor.get_template("overview.yaml")
    modulestore("direct").create_and_save_xmodule(
        dest_about_location, system=new_course.system, definition_data=overview_template.get("data")
    )

    initialize_course_tabs(new_course)

    create_all_course_groups(request.user, new_course.location)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    return JsonResponse({"id": new_course.location.url()})
Example #6
0
 def create_course(self, index):
     """
     Creates a course with one instructor and one staff member.
     """
     creator = User.objects.create_user('testcreator' + str(index), '*****@*****.**', 'foo')
     staff = User.objects.create_user('teststaff' + str(index), '*****@*****.**', 'foo')
     location = 'i4x', 'mitX', str(index), 'course', 'test'
     create_all_course_groups(creator, location)
     add_user_to_course_group(creator, staff, location, STAFF_ROLE_NAME)
     return [creator, staff]
Example #7
0
    def test_get_staff(self):
        # Do this test with staff in 2 different classes.
        create_all_course_groups(self.creator, self.location)
        add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)

        location2 = 'i4x', 'mitX', '103', 'course', 'test2'
        staff2 = User.objects.create_user('teststaff2', '*****@*****.**', 'foo')
        create_all_course_groups(self.creator, location2)
        add_user_to_course_group(self.creator, staff2, location2, STAFF_ROLE_NAME)

        self.assertSetEqual({self.staff, staff2, self.creator}, get_users_with_staff_role())
Example #8
0
    def test_get_instructor(self):
        # Do this test with creators in 2 different classes.
        create_all_course_groups(self.creator, self.location)
        add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)

        location2 = Location('i4x', 'mitX', '103', 'course', 'test2')
        creator2 = User.objects.create_user('testcreator2', '*****@*****.**', 'foo')
        staff2 = User.objects.create_user('teststaff2', '*****@*****.**', 'foo')
        create_all_course_groups(creator2, location2)
        add_user_to_course_group(creator2, staff2, location2, STAFF_ROLE_NAME)

        self.assertSetEqual({self.creator, creator2}, get_users_with_instructor_role())
Example #9
0
    def test_add_user_to_course_group(self):
        """
        Tests adding user to course group (happy path).
        """
        # Create groups for a new course (and assign instructor role to the creator).
        self.assertFalse(is_user_in_course_group_role(self.creator, self.location, INSTRUCTOR_ROLE_NAME))
        create_all_course_groups(self.creator, self.location)
        self.assertTrue(is_user_in_course_group_role(self.creator, self.location, INSTRUCTOR_ROLE_NAME))

        # Add another user to the staff role.
        self.assertFalse(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))
        self.assertTrue(add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME))
        self.assertTrue(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))
 def create_course(self, index):
     """
     Creates a course with one instructor and one staff member.
     """
     creator = User.objects.create_user('testcreator' + str(index),
                                        '*****@*****.**',
                                        'foo')
     staff = User.objects.create_user('teststaff' + str(index),
                                      '*****@*****.**', 'foo')
     location = 'i4x', 'mitX', str(index), 'course', 'test'
     create_all_course_groups(creator, location)
     add_user_to_course_group(creator, staff, location, STAFF_ROLE_NAME)
     return [creator, staff]
Example #11
0
    def test_remove_user_from_course_group(self):
        """
        Tests removing user from course group (happy path).
        """
        create_all_course_groups(self.creator, self.location)

        self.assertTrue(add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME))
        self.assertTrue(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))

        remove_user_from_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
        self.assertFalse(is_user_in_course_group_role(self.staff, self.location, STAFF_ROLE_NAME))

        remove_user_from_course_group(self.creator, self.creator, self.location, INSTRUCTOR_ROLE_NAME)
        self.assertFalse(is_user_in_course_group_role(self.creator, self.location, INSTRUCTOR_ROLE_NAME))
Example #12
0
    def test_get_staff(self):
        # Do this test with staff in 2 different classes.
        create_all_course_groups(self.creator, self.location)
        add_user_to_course_group(self.creator, self.staff, self.location,
                                 STAFF_ROLE_NAME)

        location2 = Location('i4x', 'mitX', '103', 'course', 'test2')
        staff2 = User.objects.create_user('teststaff2',
                                          '*****@*****.**', 'foo')
        create_all_course_groups(self.creator, location2)
        add_user_to_course_group(self.creator, staff2, location2,
                                 STAFF_ROLE_NAME)

        self.assertSetEqual({self.staff, staff2, self.creator},
                            get_users_with_staff_role())
Example #13
0
    def test_get_instructor(self):
        # Do this test with creators in 2 different classes.
        create_all_course_groups(self.creator, self.location)
        add_user_to_course_group(self.creator, self.staff, self.location,
                                 STAFF_ROLE_NAME)

        location2 = 'i4x', 'mitX', '103', 'course2', 'test2'
        creator2 = User.objects.create_user('testcreator2',
                                            '*****@*****.**',
                                            'foo')
        staff2 = User.objects.create_user('teststaff2',
                                          '*****@*****.**', 'foo')
        create_all_course_groups(creator2, location2)
        add_user_to_course_group(creator2, staff2, location2, STAFF_ROLE_NAME)

        self.assertSetEqual({self.creator, creator2},
                            get_users_with_instructor_role())
Example #14
0
    def test_add_user_to_course_group(self):
        """
        Tests adding user to course group (happy path).
        """
        # Create groups for a new course (and assign instructor role to the creator).
        self.assertFalse(
            is_user_in_course_group_role(self.creator, self.location,
                                         INSTRUCTOR_ROLE_NAME))
        create_all_course_groups(self.creator, self.location)
        self.assertTrue(
            is_user_in_course_group_role(self.creator, self.location,
                                         INSTRUCTOR_ROLE_NAME))

        # Add another user to the staff role.
        self.assertFalse(
            is_user_in_course_group_role(self.staff, self.location,
                                         STAFF_ROLE_NAME))
        self.assertTrue(
            add_user_to_course_group(self.creator, self.staff, self.location,
                                     STAFF_ROLE_NAME))
        self.assertTrue(
            is_user_in_course_group_role(self.staff, self.location,
                                         STAFF_ROLE_NAME))
Example #15
0
    def test_remove_user_from_course_group(self):
        """
        Tests removing user from course group (happy path).
        """
        create_all_course_groups(self.creator, self.location)

        self.assertTrue(
            add_user_to_course_group(self.creator, self.staff, self.location,
                                     STAFF_ROLE_NAME))
        self.assertTrue(
            is_user_in_course_group_role(self.staff, self.location,
                                         STAFF_ROLE_NAME))

        remove_user_from_course_group(self.creator, self.staff, self.location,
                                      STAFF_ROLE_NAME)
        self.assertFalse(
            is_user_in_course_group_role(self.staff, self.location,
                                         STAFF_ROLE_NAME))

        remove_user_from_course_group(self.creator, self.creator,
                                      self.location, INSTRUCTOR_ROLE_NAME)
        self.assertFalse(
            is_user_in_course_group_role(self.creator, self.location,
                                         INSTRUCTOR_ROLE_NAME))
Example #16
0
def import_course(request, org, course, name):
    """
    This method will handle a POST request to upload and import a .tar.gz file into a specified course
    """
    location = get_location_and_verify_access(request, org, course, name)

    if request.method == 'POST':
        filename = request.FILES['course-data'].name

        if not filename.endswith('.tar.gz'):
            return HttpResponse(json.dumps({'ErrMsg': 'We only support uploading a .tar.gz file.'}))

        data_root = path(settings.GITHUB_REPO_ROOT)

        course_subdir = "{0}-{1}-{2}".format(org, course, name)
        course_dir = data_root / course_subdir
        if not course_dir.isdir():
            os.mkdir(course_dir)

        temp_filepath = course_dir / filename

        logging.debug('importing course to {0}'.format(temp_filepath))

        # stream out the uploaded files in chunks to disk
        temp_file = open(temp_filepath, 'wb+')
        for chunk in request.FILES['course-data'].chunks():
            temp_file.write(chunk)
        temp_file.close()

        tar_file = tarfile.open(temp_filepath)
        tar_file.extractall(course_dir + '/')

        # find the 'course.xml' file

        for dirpath, _dirnames, filenames in os.walk(course_dir):
            for filename in filenames:
                if filename == 'course.xml':
                    break
            if filename == 'course.xml':
                break

        if filename != '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(dirpath))

        if dirpath != course_dir:
            for fname in os.listdir(dirpath):
                shutil.move(dirpath / fname, course_dir)

        _module_store, course_items = import_from_xml(modulestore('direct'), settings.GITHUB_REPO_ROOT,
                                                      [course_subdir], load_error_modules=False,
                                                      static_content_store=contentstore(),
                                                      target_location_namespace=location,
                                                      draft_store=modulestore())

        # 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))

        create_all_course_groups(request.user, course_items[0].location)

        return HttpResponse(json.dumps({'Status': 'OK'}))
    else:
        course_module = modulestore().get_item(location)

        return render_to_response('import.html', {
            'context_course': course_module,
            'successful_import_redirect_url': get_url_reverse('CourseOutline', course_module)
        })
Example #17
0
def create_new_course(request):
    """
    Create a new course.

    Returns the URL for the course overview page.
    """
    if not is_user_in_creator_group(request.user):
        raise PermissionDenied()

    org = request.json.get('org')
    number = request.json.get('number')
    display_name = request.json.get('display_name')
    run = request.json.get('run')

    try:
        dest_location = Location('i4x', org, number, 'course', run)
    except InvalidLocationError as error:
        return JsonResponse({
            "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)})

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore('direct').get_item(dest_location)
    except ItemNotFoundError:
        pass
    if existing_course is not None:
        return JsonResponse({
            'ErrMsg': _(
                'There is already a course defined with the same '
                'organization, course number, and course run. Please '
                'change either organization or course number to be '
                'unique.'
            ),
            'OrgErrMsg': _(
                'Please change either the organization or '
                'course number so that it is unique.'
            ),
            'CourseErrMsg': _(
                'Please change either the organization or '
                'course number so that it is unique.'
            ),
        })

    # dhm: this query breaks the abstraction, but I'll fix it when I do my suspended refactoring of this
    # file for new locators. get_items should accept a query rather than requiring it be a legal location
    course_search_location = bson.son.SON({
        '_id.tag': 'i4x',
        # cannot pass regex to Location constructor; thus this hack
        '_id.org': re.compile('^{}$'.format(dest_location.org), re.IGNORECASE),
        '_id.course': re.compile('^{}$'.format(dest_location.course), re.IGNORECASE),
        '_id.category': 'course',
    })
    courses = modulestore().collection.find(course_search_location, fields=('_id'))
    if courses.count() > 0:
        return JsonResponse({
            'ErrMsg': _(
                'There is already a course defined with the same '
                'organization and course number. Please '
                'change at least one field to be unique.'),
            'OrgErrMsg': _(
                'Please change either the organization or '
                'course number so that it is unique.'),
            'CourseErrMsg': _(
                'Please change either the organization or '
                'course number so that it is unique.'),
        })

    # instantiate the CourseDescriptor and then persist it
    # note: no system to pass
    if display_name is None:
        metadata = {}
    else:
        metadata = {'display_name': display_name}
    modulestore('direct').create_and_save_xmodule(
        dest_location,
        metadata=metadata
    )
    new_course = modulestore('direct').get_item(dest_location)

    # clone a default 'about' overview module as well
    dest_about_location = dest_location.replace(
        category='about',
        name='overview'
    )
    overview_template = AboutDescriptor.get_template('overview.yaml')
    modulestore('direct').create_and_save_xmodule(
        dest_about_location,
        system=new_course.system,
        definition_data=overview_template.get('data')
    )

    initialize_course_tabs(new_course)

    new_location = loc_mapper().translate_location(new_course.location.course_id, new_course.location, False, True)
    create_all_course_groups(request.user, new_location)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    # auto-enroll the course creator in the course so that "View Live" will
    # work.
    CourseEnrollment.enroll(request.user, new_course.location.course_id)

    return JsonResponse({'url': new_location.url_reverse("course/", "")})
Example #18
0
def import_course(request, org, course, name):
    """
    This method will handle a POST request to upload and import a .tar.gz file
    into a specified course
    """
    location = get_location_and_verify_access(request, org, course, name)

    @contextmanager
    def wfile(filename, dirname):
        """
        A with-context that creates `filename` on entry and removes it on exit.
        `filename` is truncted on creation. Additionally removes dirname on
        exit.
        """
        open(filename, "w").close()
        try:
            yield filename
        finally:
            os.remove(filename)
            shutil.rmtree(dirname)

    if request.method == 'POST':

        data_root = path(settings.GITHUB_REPO_ROOT)
        course_subdir = "{0}-{1}-{2}".format(org, course, name)
        course_dir = data_root / course_subdir

        filename = request.FILES['course-data'].name
        if not filename.endswith('.tar.gz'):
            return JsonResponse(
                {'ErrMsg': 'We only support uploading a .tar.gz file.'},
                status=415
            )
        temp_filepath = course_dir / filename

        if not course_dir.isdir():
            os.mkdir(course_dir)

        logging.debug('importing course to {0}'.format(temp_filepath))

        # Get upload chunks byte ranges
        try:
            matches = CONTENT_RE.search(request.META["HTTP_CONTENT_RANGE"])
            content_range = matches.groupdict()
        except KeyError:    # Single chunk
            # no Content-Range header, so make one that will work
            content_range = {'start': 0, 'stop': 1, 'end': 2}

        # stream out the uploaded files in chunks to disk
        if int(content_range['start']) == 0:
            mode = "wb+"
        else:
            mode = "ab+"
            size = os.path.getsize(temp_filepath)
            # Check to make sure we haven't missed a chunk
            # This shouldn't happen, even if different instances are handling
            # the same session, but it's always better to catch errors earlier.
            if size < int(content_range['start']):
                log.warning(
                    "Reported range %s does not match size downloaded so far %s",
                    content_range['start'],
                    size
                )
                return JsonResponse(
                    {'ErrMsg': 'File upload corrupted. Please try again'},
                    status=409
                )
            # The last request sometimes comes twice. This happens because
            # nginx sends a 499 error code when the response takes too long.
            elif size > int(content_range['stop']) and size == int(content_range['end']):
                return JsonResponse({'ImportStatus': 1})

        with open(temp_filepath, mode) as temp_file:
            for chunk in request.FILES['course-data'].chunks():
                temp_file.write(chunk)

        size = os.path.getsize(temp_filepath)

        if int(content_range['stop']) != int(content_range['end']) - 1:
            # More chunks coming
            return JsonResponse({
                "files": [{
                    "name": filename,
                    "size": size,
                    "deleteUrl": "",
                    "deleteType": "",
                    "url": reverse('import_course', kwargs={
                        'org': location.org,
                        'course': location.course,
                        'name': location.name
                    }),
                    "thumbnailUrl": ""
                }]
            })

        else:   # This was the last chunk.

            # 'Lock' with status info.
            status_file = data_root / (course + filename + ".lock")

            # Do everything from now on in a with-context, to be sure we've
            # properly cleaned up.
            with wfile(status_file, course_dir):

                with open(status_file, 'w+') as sf:
                    sf.write("Extracting")

                tar_file = tarfile.open(temp_filepath)
                tar_file.extractall((course_dir + '/').encode('utf-8'))

                with open(status_file, 'w+') as sf:
                    sf.write("Verifying")

                # find the 'course.xml' file
                dirpath = None

                def get_all_files(directory):
                    """
                    For each file in the directory, yield a 2-tuple of (file-name,
                    directory-path)
                    """
                    for dirpath, _dirnames, filenames in os.walk(directory):
                        for filename in filenames:
                            yield (filename, dirpath)

                def get_dir_for_fname(directory, filename):
                    """
                    Returns the dirpath for the first file found in the directory
                    with the given name.  If there is no file in the directory with
                    the specified name, return None.
                    """
                    for fname, dirpath in get_all_files(directory):
                        if fname == filename:
                            return dirpath
                    return None

                fname = "course.xml"

                dirpath = get_dir_for_fname(course_dir, fname)

                if not dirpath:
                    return JsonResponse(
                        {'ErrMsg': 'Could not find the course.xml file in the package.'},
                        status=415
                    )

                logging.debug('found course.xml at {0}'.format(dirpath))

                if dirpath != course_dir:
                    for fname in os.listdir(dirpath):
                        shutil.move(dirpath / fname, course_dir)

                _module_store, course_items = import_from_xml(
                    modulestore('direct'),
                    settings.GITHUB_REPO_ROOT,
                    [course_subdir],
                    load_error_modules=False,
                    static_content_store=contentstore(),
                    target_location_namespace=location,
                    draft_store=modulestore()
                )

                logging.debug('new course at {0}'.format(course_items[0].location))

                with open(status_file, 'w') as sf:
                    sf.write("Updating course")

                create_all_course_groups(request.user, course_items[0].location)
                logging.debug('created all course groups at {0}'.format(course_items[0].location))

            return JsonResponse({'Status': 'OK'})
    else:
        course_module = modulestore().get_item(location)

        return render_to_response('import.html', {
            'context_course': course_module,
            'successful_import_redirect_url': reverse('course_index', kwargs={
                'org': location.org,
                'course': location.course,
                'name': location.name,
            })
        })
Example #19
0
def create_new_course(request):
    """
    Create a new course
    """
    if not is_user_in_creator_group(request.user):
        raise PermissionDenied()

    org = request.POST.get('org')
    number = request.POST.get('number')
    display_name = request.POST.get('display_name')
    run = request.POST.get('run')

    try:
        dest_location = Location('i4x', org, number, 'course', run)
    except InvalidLocationError as error:
        return JsonResponse({
            "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)})

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore('direct').get_item(dest_location)
    except ItemNotFoundError:
        pass
    if existing_course is not None:
        return JsonResponse({
                'ErrMsg': _('There is already a course defined with the same '
                    'organization, course number, and course run. Please '
                    'change either organization or course number to be '
                    'unique.'),
                'OrgErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
                'CourseErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
        })

    course_search_location = ['i4x', dest_location.org, dest_location.course,
            'course', None
    ]
    courses = modulestore().get_items(course_search_location)
    if len(courses) > 0:
        return JsonResponse({
                'ErrMsg': _('There is already a course defined with the same '
                    'organization and course number. Please '
                    'change at least one field to be unique.'),
                'OrgErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
                'CourseErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
        })

    # instantiate the CourseDescriptor and then persist it
    # note: no system to pass
    if display_name is None:
        metadata = {}
    else:
        metadata = {'display_name': display_name}
    modulestore('direct').create_and_save_xmodule(
            dest_location,
            metadata=metadata
    )
    new_course = modulestore('direct').get_item(dest_location)

    # clone a default 'about' overview module as well
    dest_about_location = dest_location.replace(
            category='about',
            name='overview'
    )
    overview_template = AboutDescriptor.get_template('overview.yaml')
    modulestore('direct').create_and_save_xmodule(
        dest_about_location,
        system=new_course.system,
        definition_data=overview_template.get('data')
    )

    initialize_course_tabs(new_course)

    create_all_course_groups(request.user, new_course.location)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    # auto-enroll the course creator in the course so that "View Live" will
    # work.
    CourseEnrollment.enroll(request.user, new_course.location.course_id)

    return JsonResponse({'id': new_course.location.url()})
Example #20
0
def import_course(request, org, course, name):

    location = get_location_and_verify_access(request, org, course, name)

    if request.method == "POST":
        filename = request.FILES["course-data"].name

        if not filename.endswith(".tar.gz"):
            return HttpResponse(json.dumps({"ErrMsg": "We only support uploading a .tar.gz file."}))

        data_root = path(settings.GITHUB_REPO_ROOT)

        course_subdir = "{0}-{1}-{2}".format(org, course, name)
        course_dir = data_root / course_subdir
        if not course_dir.isdir():
            os.mkdir(course_dir)

        temp_filepath = course_dir / filename

        logging.debug("importing course to {0}".format(temp_filepath))

        # stream out the uploaded files in chunks to disk
        temp_file = open(temp_filepath, "wb+")
        for chunk in request.FILES["course-data"].chunks():
            temp_file.write(chunk)
        temp_file.close()

        tar_file = tarfile.open(temp_filepath)
        tar_file.extractall(course_dir + "/")

        # find the 'course.xml' file

        for dirpath, _dirnames, filenames in os.walk(course_dir):
            for files in filenames:
                if files == "course.xml":
                    break
            if files == "course.xml":
                break

        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(dirpath))

        if dirpath != course_dir:
            for fname in os.listdir(dirpath):
                shutil.move(dirpath / fname, course_dir)

        _module_store, course_items = import_from_xml(
            modulestore("direct"),
            settings.GITHUB_REPO_ROOT,
            [course_subdir],
            load_error_modules=False,
            static_content_store=contentstore(),
            target_location_namespace=Location(location),
            draft_store=modulestore(),
        )

        # 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))

        create_all_course_groups(request.user, course_items[0].location)

        return HttpResponse(json.dumps({"Status": "OK"}))
    else:
        course_module = modulestore().get_item(location)

        return render_to_response(
            "import.html",
            {
                "context_course": course_module,
                "active_tab": "import",
                "successful_import_redirect_url": get_url_reverse("CourseOutline", course_module),
            },
        )
Example #21
0
def import_handler(request,
                   tag=None,
                   package_id=None,
                   branch=None,
                   version_guid=None,
                   block=None):
    """
    The restful handler for importing a course.

    GET
        html: return html page for import page
        json: not supported
    POST or PUT
        json: import a course via the .tar.gz file specified in request.FILES
    """
    location = BlockUsageLocator(package_id=package_id,
                                 branch=branch,
                                 version_guid=version_guid,
                                 block_id=block)
    if not has_access(request.user, location):
        raise PermissionDenied()

    old_location = loc_mapper().translate_locator_to_location(location)

    if 'application/json' in request.META.get('HTTP_ACCEPT',
                                              'application/json'):
        if request.method == 'GET':
            raise NotImplementedError('coming soon')
        else:
            data_root = path(settings.GITHUB_REPO_ROOT)
            course_subdir = "{0}-{1}-{2}".format(old_location.org,
                                                 old_location.course,
                                                 old_location.name)
            course_dir = data_root / course_subdir

            filename = request.FILES['course-data'].name
            if not filename.endswith('.tar.gz'):
                return JsonResponse(
                    {
                        'ErrMsg':
                        _('We only support uploading a .tar.gz file.'),
                        'Stage': 1
                    },
                    status=415)
            temp_filepath = course_dir / filename

            if not course_dir.isdir():
                os.mkdir(course_dir)

            logging.debug('importing course to {0}'.format(temp_filepath))

            # Get upload chunks byte ranges
            try:
                matches = CONTENT_RE.search(request.META["HTTP_CONTENT_RANGE"])
                content_range = matches.groupdict()
            except KeyError:  # Single chunk
                # no Content-Range header, so make one that will work
                content_range = {'start': 0, 'stop': 1, 'end': 2}

            # stream out the uploaded files in chunks to disk
            if int(content_range['start']) == 0:
                mode = "wb+"
            else:
                mode = "ab+"
                size = os.path.getsize(temp_filepath)
                # Check to make sure we haven't missed a chunk
                # This shouldn't happen, even if different instances are handling
                # the same session, but it's always better to catch errors earlier.
                if size < int(content_range['start']):
                    log.warning(
                        "Reported range %s does not match size downloaded so far %s",
                        content_range['start'], size)
                    return JsonResponse(
                        {
                            'ErrMsg':
                            _('File upload corrupted. Please try again'),
                            'Stage': 1
                        },
                        status=409)
                # The last request sometimes comes twice. This happens because
                # nginx sends a 499 error code when the response takes too long.
                elif size > int(content_range['stop']) and size == int(
                        content_range['end']):
                    return JsonResponse({'ImportStatus': 1})

            with open(temp_filepath, mode) as temp_file:
                for chunk in request.FILES['course-data'].chunks():
                    temp_file.write(chunk)

            size = os.path.getsize(temp_filepath)

            if int(content_range['stop']) != int(content_range['end']) - 1:
                # More chunks coming
                return JsonResponse({
                    "files": [{
                        "name": filename,
                        "size": size,
                        "deleteUrl": "",
                        "deleteType": "",
                        "url": location.url_reverse('import'),
                        "thumbnailUrl": ""
                    }]
                })

            else:  # This was the last chunk.

                # Use sessions to keep info about import progress
                session_status = request.session.setdefault(
                    "import_status", {})
                key = location.package_id + filename
                session_status[key] = 1
                request.session.modified = True

                # Do everything from now on in a try-finally block to make sure
                # everything is properly cleaned up.
                try:

                    tar_file = tarfile.open(temp_filepath)
                    try:
                        safetar_extractall(tar_file,
                                           (course_dir + '/').encode('utf-8'))
                    except SuspiciousOperation as exc:
                        return JsonResponse(
                            {
                                'ErrMsg': 'Unsafe tar file. Aborting import.',
                                'SuspiciousFileOperationMsg': exc.args[0],
                                'Stage': 1
                            },
                            status=400)
                    finally:
                        tar_file.close()

                    session_status[key] = 2
                    request.session.modified = True

                    # find the 'course.xml' file
                    def get_all_files(directory):
                        """
                        For each file in the directory, yield a 2-tuple of (file-name,
                        directory-path)
                        """
                        for dirpath, _dirnames, filenames in os.walk(
                                directory):
                            for filename in filenames:
                                yield (filename, dirpath)

                    def get_dir_for_fname(directory, filename):
                        """
                        Returns the dirpath for the first file found in the directory
                        with the given name.  If there is no file in the directory with
                        the specified name, return None.
                        """
                        for fname, dirpath in get_all_files(directory):
                            if fname == filename:
                                return dirpath
                        return None

                    fname = "course.xml"

                    dirpath = get_dir_for_fname(course_dir, fname)

                    if not dirpath:
                        return JsonResponse(
                            {
                                'ErrMsg':
                                _('Could not find the course.xml file in the package.'
                                  ),
                                'Stage':
                                2
                            },
                            status=415)

                    logging.debug('found course.xml at {0}'.format(dirpath))

                    if dirpath != course_dir:
                        for fname in os.listdir(dirpath):
                            shutil.move(dirpath / fname, course_dir)

                    _module_store, course_items = import_from_xml(
                        modulestore('direct'),
                        settings.GITHUB_REPO_ROOT, [course_subdir],
                        load_error_modules=False,
                        static_content_store=contentstore(),
                        target_location_namespace=old_location,
                        draft_store=modulestore())

                    logging.debug('new course at {0}'.format(
                        course_items[0].location))

                    session_status[key] = 3
                    request.session.modified = True

                    create_all_course_groups(request.user,
                                             course_items[0].location)
                    logging.debug('created all course groups at {0}'.format(
                        course_items[0].location))

                # Send errors to client with stage at which error occured.
                except Exception as exception:  # pylint: disable=W0703
                    return JsonResponse(
                        {
                            'ErrMsg': str(exception),
                            'Stage': session_status[key]
                        },
                        status=400)

                finally:
                    shutil.rmtree(course_dir)

                return JsonResponse({'Status': 'OK'})
    elif request.method == 'GET':  # assume html
        course_module = modulestore().get_item(old_location)
        return render_to_response(
            'import.html', {
                'context_course':
                course_module,
                'successful_import_redirect_url':
                location.url_reverse("course"),
                'import_status_url':
                location.url_reverse("import_status", "fillerName"),
            })
    else:
        return HttpResponseNotFound()
Example #22
0
def create_new_course(request):
    """
    Create a new course
    """
    if not is_user_in_creator_group(request.user):
        raise PermissionDenied()

    org = request.POST.get('org')
    number = request.POST.get('number')
    display_name = request.POST.get('display_name')
    run = request.POST.get('run')

    try:
        dest_location = Location('i4x', org, number, 'course', run)
    except InvalidLocationError as error:
        return JsonResponse({
            "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)})

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore('direct').get_item(dest_location)
    except ItemNotFoundError:
        pass
    if existing_course is not None:
        return JsonResponse({
                'ErrMsg': _('There is already a course defined with the same '
                    'organization, course number, and course run. Please '
                    'change either organization or course number to be '
                    'unique.'),
                'OrgErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
                'CourseErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
        })

    course_search_location = ['i4x', dest_location.org, dest_location.course,
            'course', None
    ]
    courses = modulestore().get_items(course_search_location)
    if len(courses) > 0:
        return JsonResponse({
                'ErrMsg': _('There is already a course defined with the same '
                    'organization and course number. Please '
                    'change at least one field to be unique.'),
                'OrgErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
                'CourseErrMsg': _('Please change either the organization or '
                    'course number so that it is unique.'),
        })

    # instantiate the CourseDescriptor and then persist it
    # note: no system to pass
    if display_name is None:
        metadata = {}
    else:
        metadata = {'display_name': display_name}
    modulestore('direct').create_and_save_xmodule(
            dest_location,
            metadata=metadata
    )
    new_course = modulestore('direct').get_item(dest_location)

    # clone a default 'about' overview module as well
    dest_about_location = dest_location.replace(
            category='about',
            name='overview'
    )
    overview_template = AboutDescriptor.get_template('overview.yaml')
    modulestore('direct').create_and_save_xmodule(
        dest_about_location,
        system=new_course.system,
        definition_data=overview_template.get('data')
    )

    initialize_course_tabs(new_course)

    create_all_course_groups(request.user, new_course.location)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    # auto-enroll the course creator in the course so that "View Live" will
    # work.
    CourseEnrollment.enroll(request.user, new_course.location.course_id)

    return JsonResponse({'id': new_course.location.url()})
Example #23
0
def create_new_course(request):
    """
    Create a new course.

    Returns the URL for the course overview page.
    """
    if not is_user_in_creator_group(request.user):
        raise PermissionDenied()

    org = request.json.get('org')
    number = request.json.get('number')
    display_name = request.json.get('display_name')
    run = request.json.get('run')

    try:
        dest_location = Location('i4x', org, number, 'course', run)
    except InvalidLocationError as error:
        return JsonResponse({
            "ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format(
                name=display_name, err=error.message)})

    # see if the course already exists
    existing_course = None
    try:
        existing_course = modulestore('direct').get_item(dest_location)
    except ItemNotFoundError:
        pass
    if existing_course is not None:
        return JsonResponse({
            'ErrMsg': _('There is already a course defined with the same '
                'organization, course number, and course run. Please '
                'change either organization or course number to be '
                'unique.'),
            'OrgErrMsg': _('Please change either the organization or '
                'course number so that it is unique.'),
            'CourseErrMsg': _('Please change either the organization or '
                'course number so that it is unique.'),
        })

    # dhm: this query breaks the abstraction, but I'll fix it when I do my suspended refactoring of this
    # file for new locators. get_items should accept a query rather than requiring it be a legal location
    course_search_location = bson.son.SON({
        '_id.tag': 'i4x',
        # cannot pass regex to Location constructor; thus this hack
        '_id.org': re.compile('^{}$'.format(dest_location.org), re.IGNORECASE),
        '_id.course': re.compile('^{}$'.format(dest_location.course), re.IGNORECASE),
        '_id.category': 'course',
    })
    courses = modulestore().collection.find(course_search_location, fields=('_id'))
    if courses.count() > 0:
        return JsonResponse({
            'ErrMsg': _('There is already a course defined with the same '
                'organization and course number. Please '
                'change at least one field to be unique.'),
            'OrgErrMsg': _('Please change either the organization or '
                'course number so that it is unique.'),
            'CourseErrMsg': _('Please change either the organization or '
                'course number so that it is unique.'),
        })

    # instantiate the CourseDescriptor and then persist it
    # note: no system to pass
    if display_name is None:
        metadata = {}
    else:
        metadata = {'display_name': display_name}
    modulestore('direct').create_and_save_xmodule(
        dest_location,
        metadata=metadata
    )
    new_course = modulestore('direct').get_item(dest_location)

    # clone a default 'about' overview module as well
    dest_about_location = dest_location.replace(
        category='about',
        name='overview'
    )
    overview_template = AboutDescriptor.get_template('overview.yaml')
    modulestore('direct').create_and_save_xmodule(
        dest_about_location,
        system=new_course.system,
        definition_data=overview_template.get('data')
    )

    initialize_course_tabs(new_course)

    create_all_course_groups(request.user, new_course.location)

    # seed the forums
    seed_permissions_roles(new_course.location.course_id)

    # auto-enroll the course creator in the course so that "View Live" will
    # work.
    CourseEnrollment.enroll(request.user, new_course.location.course_id)

    new_location = loc_mapper().translate_location(new_course.location.course_id, new_course.location, False, True)
    return JsonResponse({'url': new_location.url_reverse("course/", "")})
Example #24
0
def import_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None):
    """
    The restful handler for importing a course.

    GET
        html: return html page for import page
        json: not supported
    POST or PUT
        json: import a course via the .tar.gz file specified in request.FILES
    """
    location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
    if not has_access(request.user, location):
        raise PermissionDenied()

    old_location = loc_mapper().translate_locator_to_location(location)

    if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        if request.method == 'GET':
            raise NotImplementedError('coming soon')
        else:
            data_root = path(settings.GITHUB_REPO_ROOT)
            course_subdir = "{0}-{1}-{2}".format(old_location.org, old_location.course, old_location.name)
            course_dir = data_root / course_subdir

            filename = request.FILES['course-data'].name
            if not filename.endswith('.tar.gz'):
                return JsonResponse(
                    {
                        'ErrMsg': _('We only support uploading a .tar.gz file.'),
                        'Stage': 1
                    },
                    status=415
                )
            temp_filepath = course_dir / filename

            if not course_dir.isdir():
                os.mkdir(course_dir)

            logging.debug('importing course to {0}'.format(temp_filepath))

            # Get upload chunks byte ranges
            try:
                matches = CONTENT_RE.search(request.META["HTTP_CONTENT_RANGE"])
                content_range = matches.groupdict()
            except KeyError:    # Single chunk
                # no Content-Range header, so make one that will work
                content_range = {'start': 0, 'stop': 1, 'end': 2}

            # stream out the uploaded files in chunks to disk
            if int(content_range['start']) == 0:
                mode = "wb+"
            else:
                mode = "ab+"
                size = os.path.getsize(temp_filepath)
                # Check to make sure we haven't missed a chunk
                # This shouldn't happen, even if different instances are handling
                # the same session, but it's always better to catch errors earlier.
                if size < int(content_range['start']):
                    log.warning(
                        "Reported range %s does not match size downloaded so far %s",
                        content_range['start'],
                        size
                    )
                    return JsonResponse(
                        {
                            'ErrMsg': _('File upload corrupted. Please try again'),
                            'Stage': 1
                        },
                        status=409
                    )
                # The last request sometimes comes twice. This happens because
                # nginx sends a 499 error code when the response takes too long.
                elif size > int(content_range['stop']) and size == int(content_range['end']):
                    return JsonResponse({'ImportStatus': 1})

            with open(temp_filepath, mode) as temp_file:
                for chunk in request.FILES['course-data'].chunks():
                    temp_file.write(chunk)

            size = os.path.getsize(temp_filepath)

            if int(content_range['stop']) != int(content_range['end']) - 1:
                # More chunks coming
                return JsonResponse({
                    "files": [{
                                  "name": filename,
                                  "size": size,
                                  "deleteUrl": "",
                                  "deleteType": "",
                                  "url": location.url_reverse('import'),
                                  "thumbnailUrl": ""
                              }]
                })

            else:   # This was the last chunk.

                # Use sessions to keep info about import progress
                session_status = request.session.setdefault("import_status", {})
                key = location.package_id + filename
                session_status[key] = 1
                request.session.modified = True

                # Do everything from now on in a try-finally block to make sure
                # everything is properly cleaned up.
                try:

                    tar_file = tarfile.open(temp_filepath)
                    try:
                        safetar_extractall(tar_file, (course_dir + '/').encode('utf-8'))
                    except SuspiciousOperation as exc:
                        return JsonResponse(
                            {
                                'ErrMsg': 'Unsafe tar file. Aborting import.',
                                'SuspiciousFileOperationMsg': exc.args[0],
                                'Stage': 1
                            },
                            status=400
                        )
                    finally:
                        tar_file.close()

                    session_status[key] = 2
                    request.session.modified = True

                    # find the 'course.xml' file
                    def get_all_files(directory):
                        """
                        For each file in the directory, yield a 2-tuple of (file-name,
                        directory-path)
                        """
                        for dirpath, _dirnames, filenames in os.walk(directory):
                            for filename in filenames:
                                yield (filename, dirpath)

                    def get_dir_for_fname(directory, filename):
                        """
                        Returns the dirpath for the first file found in the directory
                        with the given name.  If there is no file in the directory with
                        the specified name, return None.
                        """
                        for fname, dirpath in get_all_files(directory):
                            if fname == filename:
                                return dirpath
                        return None

                    fname = "course.xml"

                    dirpath = get_dir_for_fname(course_dir, fname)

                    if not dirpath:
                        return JsonResponse(
                            {

                                'ErrMsg': _('Could not find the course.xml file in the package.'),
                                'Stage': 2
                            },
                            status=415
                        )

                    logging.debug('found course.xml at {0}'.format(dirpath))

                    if dirpath != course_dir:
                        for fname in os.listdir(dirpath):
                            shutil.move(dirpath / fname, course_dir)

                    _module_store, course_items = import_from_xml(
                        modulestore('direct'),
                        settings.GITHUB_REPO_ROOT,
                        [course_subdir],
                        load_error_modules=False,
                        static_content_store=contentstore(),
                        target_location_namespace=old_location,
                        draft_store=modulestore()
                    )

                    logging.debug('new course at {0}'.format(course_items[0].location))

                    session_status[key] = 3
                    request.session.modified = True

                    create_all_course_groups(request.user, course_items[0].location)
                    logging.debug('created all course groups at {0}'.format(course_items[0].location))

                # Send errors to client with stage at which error occured.
                except Exception as exception:   # pylint: disable=W0703
                    return JsonResponse(
                        {
                            'ErrMsg': str(exception),
                            'Stage': session_status[key]
                        },
                        status=400
                    )

                finally:
                    shutil.rmtree(course_dir)

                return JsonResponse({'Status': 'OK'})
    elif request.method == 'GET':  # assume html
        course_module = modulestore().get_item(old_location)
        return render_to_response('import.html', {
            'context_course': course_module,
            'successful_import_redirect_url': location.url_reverse("course"),
            'import_status_url': location.url_reverse("import_status", "fillerName"),
        })
    else:
        return HttpResponseNotFound()
Example #25
0
def import_course(request, org, course, name):
    """
    This method will handle a POST request to upload and import a .tar.gz file
    into a specified course
    """
    location = get_location_and_verify_access(request, org, course, name)

    @contextmanager
    def wfile(filename, dirname):
        """
        A with-context that creates `filename` on entry and removes it on exit.
        `filename` is truncted on creation. Additionally removes dirname on
        exit.
        """
        open(filename, "w").close()
        try:
            yield filename
        finally:
            os.remove(filename)
            shutil.rmtree(dirname)

    if request.method == 'POST':

        data_root = path(settings.GITHUB_REPO_ROOT)
        course_subdir = "{0}-{1}-{2}".format(org, course, name)
        course_dir = data_root / course_subdir

        filename = request.FILES['course-data'].name
        if not filename.endswith('.tar.gz'):
            return JsonResponse(
                {'ErrMsg': 'We only support uploading a .tar.gz file.'},
                status=415)
        temp_filepath = course_dir / filename

        if not course_dir.isdir():
            os.mkdir(course_dir)

        logging.debug('importing course to {0}'.format(temp_filepath))

        # Get upload chunks byte ranges
        try:
            matches = CONTENT_RE.search(request.META["HTTP_CONTENT_RANGE"])
            content_range = matches.groupdict()
        except KeyError:  # Single chunk
            # no Content-Range header, so make one that will work
            content_range = {'start': 0, 'stop': 1, 'end': 2}

        # stream out the uploaded files in chunks to disk
        if int(content_range['start']) == 0:
            mode = "wb+"
        else:
            mode = "ab+"
            size = os.path.getsize(temp_filepath)
            # Check to make sure we haven't missed a chunk
            # This shouldn't happen, even if different instances are handling
            # the same session, but it's always better to catch errors earlier.
            if size < int(content_range['start']):
                log.warning(
                    "Reported range %s does not match size downloaded so far %s",
                    content_range['start'], size)
                return JsonResponse(
                    {'ErrMsg': 'File upload corrupted. Please try again'},
                    status=409)
            # The last request sometimes comes twice. This happens because
            # nginx sends a 499 error code when the response takes too long.
            elif size > int(content_range['stop']) and size == int(
                    content_range['end']):
                return JsonResponse({'ImportStatus': 1})

        with open(temp_filepath, mode) as temp_file:
            for chunk in request.FILES['course-data'].chunks():
                temp_file.write(chunk)

        size = os.path.getsize(temp_filepath)

        if int(content_range['stop']) != int(content_range['end']) - 1:
            # More chunks coming
            return JsonResponse({
                "files": [{
                    "name":
                    filename,
                    "size":
                    size,
                    "deleteUrl":
                    "",
                    "deleteType":
                    "",
                    "url":
                    reverse('import_course',
                            kwargs={
                                'org': location.org,
                                'course': location.course,
                                'name': location.name
                            }),
                    "thumbnailUrl":
                    ""
                }]
            })

        else:  # This was the last chunk.

            # 'Lock' with status info.
            status_file = data_root / (course + filename + ".lock")

            # Do everything from now on in a with-context, to be sure we've
            # properly cleaned up.
            with wfile(status_file, course_dir):

                with open(status_file, 'w+') as sf:
                    sf.write("Extracting")

                tar_file = tarfile.open(temp_filepath)
                tar_file.extractall((course_dir + '/').encode('utf-8'))

                with open(status_file, 'w+') as sf:
                    sf.write("Verifying")

                # find the 'course.xml' file
                dirpath = None

                def get_all_files(directory):
                    """
                    For each file in the directory, yield a 2-tuple of (file-name,
                    directory-path)
                    """
                    for dirpath, _dirnames, filenames in os.walk(directory):
                        for filename in filenames:
                            yield (filename, dirpath)

                def get_dir_for_fname(directory, filename):
                    """
                    Returns the dirpath for the first file found in the directory
                    with the given name.  If there is no file in the directory with
                    the specified name, return None.
                    """
                    for fname, dirpath in get_all_files(directory):
                        if fname == filename:
                            return dirpath
                    return None

                fname = "course.xml"

                dirpath = get_dir_for_fname(course_dir, fname)

                if not dirpath:
                    return JsonResponse(
                        {
                            'ErrMsg':
                            'Could not find the course.xml file in the package.'
                        },
                        status=415)

                logging.debug('found course.xml at {0}'.format(dirpath))

                if dirpath != course_dir:
                    for fname in os.listdir(dirpath):
                        shutil.move(dirpath / fname, course_dir)

                _module_store, course_items = import_from_xml(
                    modulestore('direct'),
                    settings.GITHUB_REPO_ROOT, [course_subdir],
                    load_error_modules=False,
                    static_content_store=contentstore(),
                    target_location_namespace=location,
                    draft_store=modulestore())

                logging.debug('new course at {0}'.format(
                    course_items[0].location))

                with open(status_file, 'w') as sf:
                    sf.write("Updating course")

                create_all_course_groups(request.user,
                                         course_items[0].location)
                logging.debug('created all course groups at {0}'.format(
                    course_items[0].location))

            return JsonResponse({'Status': 'OK'})
    else:
        course_module = modulestore().get_item(location)

        return render_to_response(
            'import.html', {
                'context_course':
                course_module,
                'successful_import_redirect_url':
                reverse('course_index',
                        kwargs={
                            'org': location.org,
                            'course': location.course,
                            'name': location.name,
                        })
            })