Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 9 additions & 43 deletions cms/djangoapps/contentstore/views/import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
from user_tasks.conf import settings as user_tasks_settings
from user_tasks.models import UserTaskArtifact, UserTaskStatus

from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.util.json_request import JsonResponse
from common.djangoapps.util.monitoring import monitor_import_failure
from common.djangoapps.util.views import ensure_valid_course_key
Expand All @@ -43,7 +42,7 @@

from ..storage import course_import_export_storage
from ..tasks import CourseExportTask, CourseImportTask, export_olx, import_olx
from ..utils import IMPORTABLE_FILE_TYPES, get_export_url, get_import_url, reverse_course_url, reverse_library_url
from ..utils import IMPORTABLE_FILE_TYPES, get_export_url, get_import_url, reverse_course_url

__all__ = [
'import_handler', 'import_status_handler',
Expand Down Expand Up @@ -74,15 +73,10 @@ def import_handler(request, course_key_string):
json: import a course via the .tar.gz or .zip file specified in request.FILES
"""
courselike_key = CourseKey.from_string(course_key_string)
library = isinstance(courselike_key, LibraryLocator)
if library:
successful_url = reverse_library_url('library_handler', courselike_key)
context_name = 'context_library'
courselike_block = modulestore().get_library(courselike_key)
else:
successful_url = reverse_course_url('course_handler', courselike_key)
context_name = 'context_course'
courselike_block = modulestore().get_course(courselike_key)
# Legacy (v1) libraries are no longer supported for import. Reject early
# to avoid an invalid redirect to the MFE with a library key.
if isinstance(courselike_key, LibraryLocator):
raise Http404
if not user_has_course_permission(
Comment thread
salman2013 marked this conversation as resolved.
user=request.user,
authz_permission=COURSES_IMPORT_COURSE.identifier,
Expand All @@ -98,17 +92,7 @@ def import_handler(request, course_key_string):
return _write_chunk(request, courselike_key)
elif request.method == 'GET': # assume html

if not library:
return redirect(get_import_url(courselike_key))
status_url = reverse_course_url(
"import_status_handler", courselike_key, kwargs={'filename': "fillerName"}
)
return render_to_response('import.html', {
context_name: courselike_block,
'successful_import_redirect_url': successful_url,
'import_status_url': status_url,
'library': isinstance(courselike_key, LibraryLocator)
})
return redirect(get_import_url(courselike_key))
else:
return HttpResponseNotFound()

Expand Down Expand Up @@ -331,24 +315,8 @@ def export_handler(request, course_key_string):
legacy_permission=LegacyAuthoringPermission.WRITE
):
raise PermissionDenied()
library = isinstance(course_key, LibraryLocator)
if library:
courselike_block = modulestore().get_library(course_key)
context = {
'context_library': courselike_block,
'courselike_home_url': reverse_library_url("library_handler", course_key),
'library': True
}
else:
courselike_block = modulestore().get_course(course_key)
if courselike_block is None:
raise Http404
context = {
'context_course': courselike_block,
'courselike_home_url': reverse_course_url("course_handler", course_key),
'library': False
}
context['status_url'] = reverse_course_url('export_status_handler', course_key)
if modulestore().get_course(course_key) is None:
raise Http404

# an _accept URL parameter will be preferred over HTTP_ACCEPT in the header.
requested_format = request.GET.get('_accept', request.META.get('HTTP_ACCEPT', 'text/html'))
Expand All @@ -357,9 +325,7 @@ def export_handler(request, course_key_string):
export_olx.delay(request.user.id, course_key_string, request.LANGUAGE_CODE)
return JsonResponse({'ExportStatus': 1})
elif 'text/html' in requested_format:
if not library:
return redirect(get_export_url(course_key))
return render_to_response('export.html', context)
return redirect(get_export_url(course_key))
else:
# Only HTML request format is supported (no JSON).
return HttpResponse(status=406)
Expand Down
46 changes: 1 addition & 45 deletions cms/djangoapps/contentstore/views/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,12 @@
from cms.djangoapps.course_creators.views import get_course_creator_status
from common.djangoapps.edxmako.shortcuts import render_to_response
from common.djangoapps.student.auth import (
STUDIO_EDIT_ROLES,
STUDIO_VIEW_USERS,
get_user_permissions,
has_studio_read_access,
has_studio_write_access,
)
from common.djangoapps.student.roles import (
CourseInstructorRole,
CourseStaffRole,
LibraryUserRole,
OrgStaffRole,
UserBasedRole,
)
Expand All @@ -45,9 +41,8 @@
from ..toggles import libraries_v1_enabled
from ..utils import add_instructor, reverse_library_url
Comment thread
salman2013 marked this conversation as resolved.
from .component import CONTAINER_TEMPLATES, get_component_templates
from .user import user_with_role

__all__ = ['library_handler', 'manage_library_users']
__all__ = ['library_handler']

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -311,42 +306,3 @@ def library_blocks_view(library, user, response_format):
'xblock_info': xblock_info,
'templates': CONTAINER_TEMPLATES,
})


def manage_library_users(request, library_key_string):
"""
Studio UI for editing the users within a library.

Uses the /course_team/:library_key/:user_email/ REST API to make changes.
"""
library_key = CourseKey.from_string(library_key_string)
if not isinstance(library_key, LibraryLocator):
raise Http404 # This is not a library
user_perms = get_user_permissions(request.user, library_key)
if not user_perms & STUDIO_VIEW_USERS:
raise PermissionDenied()
library = modulestore().get_library(library_key)
if library is None:
raise Http404

# Segment all the users explicitly associated with this library, ensuring each user only has one role listed:
instructors = set(CourseInstructorRole(library_key).users_with_role())
staff = set(CourseStaffRole(library_key).users_with_role()) - instructors
users = set(LibraryUserRole(library_key).users_with_role()) - instructors - staff

formatted_users = []
for user in instructors:
formatted_users.append(user_with_role(user, 'instructor'))
for user in staff:
formatted_users.append(user_with_role(user, 'staff'))
for user in users:
formatted_users.append(user_with_role(user, 'library_user'))

return render_to_response('manage_users_lib.html', {
'context_library': library,
'users': formatted_users,
'allow_actions': bool(user_perms & STUDIO_EDIT_ROLES),
'library_key': str(library_key),
'lib_users_url': reverse_library_url('manage_library_users', library_key_string),
'show_children_previews': library.show_children_previews
})
21 changes: 21 additions & 0 deletions cms/djangoapps/contentstore/views/tests/test_import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,17 @@ def test_import_failure_for_unknown_failures(self, exception, mocked_import, moc
status_response = self.get_import_status(self.course.id, good_file)
self.assertImportStatusResponse(status_response, self.UpdatingError, import_error.UNKNOWN_ERROR_IN_IMPORT)

def test_import_handler_rejects_legacy_library_key(self):
"""
Passing a legacy (v1) LibraryLocator key to import_handler must return 404.
This guards against the early-reject added in import_handler from being
accidentally removed.
"""
lib_key = LibraryLocator(org='TestOrg', library='TestLib')
url = reverse_course_url('import_handler', lib_key)
resp = self.client.get(url)
self.assertEqual(resp.status_code, 404) # noqa: PT009

@ddt.data('zip', 'tar')
def test_import_status_response_is_not_cached(self, fmt):
"""To test import_status endpoint response is not cached"""
Expand Down Expand Up @@ -945,6 +956,16 @@ def test_export_course_does_not_exist(self, url):
resp = self.client.get_html(url)
self.assertEqual(resp.status_code, 404) # noqa: PT009

def test_export_handler_rejects_legacy_library_key(self):
"""
Passing a legacy (v1) LibraryLocator key to export_handler must return 404
because modulestore().get_course() returns None for a library key.
"""
lib_key = LibraryLocator(org='TestOrg', library='TestLib')
url = reverse_course_url('export_handler', lib_key)
resp = self.client.get(url)
self.assertEqual(resp.status_code, 404) # noqa: PT009

def test_non_course_author(self):
"""
Verify that users who aren't authors of the course are unable to export it
Expand Down
30 changes: 1 addition & 29 deletions cms/djangoapps/contentstore/views/tests/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
from organizations.exceptions import InvalidOrganizationException

from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, parse_json
from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_library_url
from cms.djangoapps.course_creators.models import CourseCreator
from cms.djangoapps.course_creators.views import add_user_with_status_granted as grant_course_creator_status
from common.djangoapps.student import auth
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, LibraryUserRole
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
from xmodule.modulestore.tests.factories import LibraryFactory # pylint: disable=wrong-import-order

from ..component import get_component_templates
Expand Down Expand Up @@ -427,33 +426,6 @@ def test_advanced_problem_types(self):
for advance_problem_type in settings.ADVANCED_PROBLEM_TYPES:
self.assertNotIn(advance_problem_type['component'], problem_type_categories) # noqa: PT009

def test_manage_library_users(self):
"""
Simple test that the Library "User Access" view works.
Also tests that we can use the REST API to assign a user to a library.
"""
library = LibraryFactory.create()
extra_user, _ = self.create_non_staff_user()
manage_users_url = reverse_library_url('manage_library_users', str(library.location.library_key))

response = self.client.get(manage_users_url)
self.assertEqual(response.status_code, 200) # noqa: PT009
# extra_user has not been assigned to the library so should not show up in the list:
self.assertNotContains(response, extra_user.username)

# Now add extra_user to the library:
user_details_url = reverse_course_url(
'course_team_handler',
library.location.library_key, kwargs={'email': extra_user.email}
)
edit_response = self.client.ajax_post(user_details_url, {"role": LibraryUserRole.ROLE})
self.assertIn(edit_response.status_code, (200, 204)) # noqa: PT009

# Now extra_user should apear in the list:
response = self.client.get(manage_users_url)
self.assertEqual(response.status_code, 200) # noqa: PT009
self.assertContains(response, extra_user.username)

def test_component_limits(self):
"""
Test that component limits in libraries are respected.
Expand Down
11 changes: 3 additions & 8 deletions cms/djangoapps/contentstore/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods, require_POST
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import LibraryLocator

from cms.djangoapps.course_creators.views import user_requested_access
from common.djangoapps.student import auth
from common.djangoapps.student.auth import STUDIO_EDIT_ROLES, STUDIO_VIEW_USERS, get_user_permissions
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, LibraryUserRole
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
from common.djangoapps.util.json_request import JsonResponse, expect_json

from ..utils import get_course_team_url
Expand Down Expand Up @@ -95,12 +94,8 @@ def _course_team_user(request, course_key, email):
}
return JsonResponse(msg, 404)

is_library = isinstance(course_key, LibraryLocator)
# Ordered list of roles: can always move self to the right, but need STUDIO_EDIT_ROLES to move any user left
if is_library:
role_hierarchy = (CourseInstructorRole, CourseStaffRole, LibraryUserRole)
else:
role_hierarchy = (CourseInstructorRole, CourseStaffRole)
role_hierarchy = (CourseInstructorRole, CourseStaffRole)

if request.method == "GET":
# just return info about the user
Expand Down Expand Up @@ -164,7 +159,7 @@ def _course_team_user(request, course_key, email):
return JsonResponse(msg, 400)
auth.remove_users(request.user, role, user)

if new_role and not is_library:
if new_role:
# The user may be newly added to this course.
# auto-enroll the user in the course so that "View Live" will work.
CourseEnrollment.enroll(user, course_key)
Expand Down
1 change: 0 additions & 1 deletion cms/static/cms/js/spec/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@
'js/spec/views/paging_spec',
'js/spec/views/pages/course_rerun_spec',
'js/spec/views/pages/index_spec',
'js/spec/views/pages/library_users_spec',
'js/spec/views/modals/base_modal_spec',
'js/spec/views/modals/move_xblock_modal_spec'
];
Expand Down
8 changes: 4 additions & 4 deletions cms/static/js/factories/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ define([
], function(domReady, Export, $, gettext) {
'use strict';

return function(courselikeHomeUrl, library, statusUrl) {
return function(courselikeHomeUrl, statusUrl) {
var $submitBtn = $('.action-export'),
unloading = false,
previousExport = Export.storedExport(courselikeHomeUrl);
Expand All @@ -15,7 +15,7 @@ define([
var startExport = function(e) {
e.preventDefault();
$submitBtn.hide();
Export.reset(library);
Export.reset();
Export.start(statusUrl).then(onComplete);
$.ajax({
type: 'POST',
Expand All @@ -30,7 +30,7 @@ define([
if (!unloading) {
$(window).off('beforeunload.import');

Export.reset(library);
Export.reset();
onComplete();

Export.showError(gettext('Your export has failed.'));
Expand All @@ -47,7 +47,7 @@ define([
if (previousExport.completed !== true) {
$submitBtn.hide();
}
Export.resume(library).then(onComplete);
Export.resume().then(onComplete);
}

domReady(function() {
Expand Down
40 changes: 0 additions & 40 deletions cms/static/js/factories/manage_users_lib.js

This file was deleted.

10 changes: 2 additions & 8 deletions cms/static/js/features/import/factories/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ define([
const IMPORTABLE_FILE_TYPES = /\.tar\.gz$|\.zip$/;

return {
Import: function(feedbackUrl, library) {
var dbError,
Import: function(feedbackUrl) {
var dbError = gettext('There was an error while importing the new course to our database.'),
$bar = $('.progress-bar'),
Comment thread
Copilot marked this conversation as resolved.
$fill = $('.progress-fill'),
$submitBtn = $('.submit-button'),
Expand Down Expand Up @@ -52,12 +52,6 @@ define([
}
};

if (library) {
dbError = gettext('There was an error while importing the new library to our database.');
} else {
dbError = gettext('There was an error while importing the new course to our database.');
}

$(window).on('beforeunload', function() { unloading = true; });

// Display the status of last file upload on page load
Expand Down
Loading
Loading