add flag DISABLE_LIBRARY_CREATION add comma use CourseCreatorRole to determine if user can create a library add disable library creation feature flag Conflicts: cms/djangoapps/contentstore/views/course.py ENABLE_CONTENT_LIBRARIES flag check for course creator role for library creation Conflicts: cms/djangoapps/contentstore/views/course.py add unit tests make check of creation of library a true/false for forntend, add security in api call, clean tests update tests fix docstring of tests fixed quality violation fixed broken unit test and quality violations Feedback changes and unit test to assert libraries are visible to non staff users too fixed quality violation and feedback changes
257 lines
9.7 KiB
Python
257 lines
9.7 KiB
Python
"""
|
|
Views related to content libraries.
|
|
A content library is a structure containing XBlocks which can be re-used in the
|
|
multiple courses.
|
|
"""
|
|
from __future__ import absolute_import
|
|
|
|
import logging
|
|
|
|
from contentstore.views.item import create_xblock_info
|
|
from contentstore.utils import reverse_library_url, add_instructor
|
|
from django.http import HttpResponseNotAllowed, Http404, HttpResponseForbidden
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.conf import settings
|
|
from django.utils.translation import ugettext as _
|
|
from django.views.decorators.http import require_http_methods
|
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
|
from edxmako.shortcuts import render_to_response
|
|
from opaque_keys import InvalidKeyError
|
|
from opaque_keys.edx.keys import CourseKey
|
|
from opaque_keys.edx.locator import LibraryLocator, LibraryUsageLocator
|
|
from xmodule.modulestore.exceptions import DuplicateCourseError
|
|
from xmodule.modulestore import ModuleStoreEnum
|
|
from xmodule.modulestore.django import modulestore
|
|
from .user import user_with_role
|
|
|
|
from course_creators.views import get_course_creator_status
|
|
from .component import get_component_templates, CONTAINER_TEMPLATES
|
|
from student.auth import (
|
|
STUDIO_VIEW_USERS, STUDIO_EDIT_ROLES, get_user_permissions, has_studio_read_access, has_studio_write_access
|
|
)
|
|
from student.roles import CourseInstructorRole, CourseStaffRole, LibraryUserRole
|
|
from util.json_request import expect_json, JsonResponse, JsonResponseBadRequest
|
|
|
|
__all__ = ['library_handler', 'manage_library_users']
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
LIBRARIES_ENABLED = settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES', False)
|
|
|
|
|
|
def get_library_creator_status(user):
|
|
"""
|
|
Helper method for returning the library creation status for a particular user,
|
|
taking into account the value LIBRARIES_ENABLED.
|
|
"""
|
|
|
|
if not LIBRARIES_ENABLED:
|
|
return False
|
|
elif user.is_staff:
|
|
return True
|
|
elif settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
|
|
return get_course_creator_status(user) == 'granted'
|
|
else:
|
|
return True
|
|
|
|
|
|
@login_required
|
|
@ensure_csrf_cookie
|
|
@require_http_methods(('GET', 'POST'))
|
|
def library_handler(request, library_key_string=None):
|
|
"""
|
|
RESTful interface to most content library related functionality.
|
|
"""
|
|
if not LIBRARIES_ENABLED:
|
|
log.exception("Attempted to use the content library API when the libraries feature is disabled.")
|
|
raise Http404 # Should never happen because we test the feature in urls.py also
|
|
|
|
if not get_library_creator_status(request.user):
|
|
if not request.user.is_staff:
|
|
return HttpResponseForbidden()
|
|
|
|
if library_key_string is not None and request.method == 'POST':
|
|
return HttpResponseNotAllowed(("POST",))
|
|
|
|
if request.method == 'POST':
|
|
return _create_library(request)
|
|
|
|
# request method is get, since only GET and POST are allowed by @require_http_methods(('GET', 'POST'))
|
|
if library_key_string:
|
|
return _display_library(library_key_string, request)
|
|
|
|
return _list_libraries(request)
|
|
|
|
|
|
def _display_library(library_key_string, request):
|
|
"""
|
|
Displays single library
|
|
"""
|
|
library_key = CourseKey.from_string(library_key_string)
|
|
if not isinstance(library_key, LibraryLocator):
|
|
log.exception("Non-library key passed to content libraries API.") # Should never happen due to url regex
|
|
raise Http404 # This is not a library
|
|
if not has_studio_read_access(request.user, library_key):
|
|
log.exception(
|
|
u"User %s tried to access library %s without permission",
|
|
request.user.username, unicode(library_key)
|
|
)
|
|
raise PermissionDenied()
|
|
|
|
library = modulestore().get_library(library_key)
|
|
if library is None:
|
|
log.exception(u"Library %s not found", unicode(library_key))
|
|
raise Http404
|
|
|
|
response_format = 'html'
|
|
if (
|
|
request.GET.get('format', 'html') == 'json' or
|
|
'application/json' in request.META.get('HTTP_ACCEPT', 'text/html')
|
|
):
|
|
response_format = 'json'
|
|
|
|
return library_blocks_view(library, request.user, response_format)
|
|
|
|
|
|
def _list_libraries(request):
|
|
"""
|
|
List all accessible libraries
|
|
"""
|
|
lib_info = [
|
|
{
|
|
"display_name": lib.display_name,
|
|
"library_key": unicode(lib.location.library_key),
|
|
}
|
|
for lib in modulestore().get_libraries()
|
|
if has_studio_read_access(request.user, lib.location.library_key)
|
|
]
|
|
return JsonResponse(lib_info)
|
|
|
|
|
|
@expect_json
|
|
def _create_library(request):
|
|
"""
|
|
Helper method for creating a new library.
|
|
"""
|
|
display_name = None
|
|
try:
|
|
display_name = request.json['display_name']
|
|
org = request.json['org']
|
|
library = request.json.get('number', None)
|
|
if library is None:
|
|
library = request.json['library']
|
|
store = modulestore()
|
|
with store.default_store(ModuleStoreEnum.Type.split):
|
|
new_lib = store.create_library(
|
|
org=org,
|
|
library=library,
|
|
user_id=request.user.id,
|
|
fields={"display_name": display_name},
|
|
)
|
|
# Give the user admin ("Instructor") role for this library:
|
|
add_instructor(new_lib.location.library_key, request.user, request.user)
|
|
except KeyError as error:
|
|
log.exception("Unable to create library - missing required JSON key.")
|
|
return JsonResponseBadRequest({
|
|
"ErrMsg": _("Unable to create library - missing required field '{field}'").format(field=error.message)
|
|
})
|
|
except InvalidKeyError as error:
|
|
log.exception("Unable to create library - invalid key.")
|
|
return JsonResponseBadRequest({
|
|
"ErrMsg": _("Unable to create library '{name}'.\n\n{err}").format(name=display_name, err=error.message)
|
|
})
|
|
except DuplicateCourseError:
|
|
log.exception("Unable to create library - one already exists with the same key.")
|
|
return JsonResponseBadRequest({
|
|
'ErrMsg': _(
|
|
'There is already a library defined with the same '
|
|
'organization and library code. Please '
|
|
'change your library code so that it is unique within your organization.'
|
|
)
|
|
})
|
|
|
|
lib_key_str = unicode(new_lib.location.library_key)
|
|
return JsonResponse({
|
|
'url': reverse_library_url('library_handler', lib_key_str),
|
|
'library_key': lib_key_str,
|
|
})
|
|
|
|
|
|
def library_blocks_view(library, user, response_format):
|
|
"""
|
|
The main view of a course's content library.
|
|
Shows all the XBlocks in the library, and allows adding/editing/deleting
|
|
them.
|
|
Can be called with response_format="json" to get a JSON-formatted list of
|
|
the XBlocks in the library along with library metadata.
|
|
|
|
Assumes that read permissions have been checked before calling this.
|
|
"""
|
|
assert isinstance(library.location.library_key, LibraryLocator)
|
|
assert isinstance(library.location, LibraryUsageLocator)
|
|
|
|
children = library.children
|
|
if response_format == "json":
|
|
# The JSON response for this request is short and sweet:
|
|
prev_version = library.runtime.course_entry.structure['previous_version']
|
|
return JsonResponse({
|
|
"display_name": library.display_name,
|
|
"library_id": unicode(library.location.library_key),
|
|
"version": unicode(library.runtime.course_entry.course_key.version),
|
|
"previous_version": unicode(prev_version) if prev_version else None,
|
|
"blocks": [unicode(x) for x in children],
|
|
})
|
|
|
|
can_edit = has_studio_write_access(user, library.location.library_key)
|
|
|
|
xblock_info = create_xblock_info(library, include_ancestor_info=False, graders=[])
|
|
component_templates = get_component_templates(library, library=True) if can_edit else []
|
|
|
|
return render_to_response('library.html', {
|
|
'can_edit': can_edit,
|
|
'context_library': library,
|
|
'component_templates': component_templates,
|
|
'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': unicode(library_key),
|
|
'lib_users_url': reverse_library_url('manage_library_users', library_key_string),
|
|
'show_children_previews': library.show_children_previews
|
|
})
|