示例#1
0
def orphan_handler(request, course_key_string):
    """
    View for handling orphan related requests. GET gets all of the current orphans.
    DELETE removes all orphans (requires is_staff access)

    An orphan is a block whose category is not in the DETACHED_CATEGORY list, is not the root, and is not reachable
    from the root via children
    """
    course_usage_key = CourseKey.from_string(course_key_string)
    if request.method == 'GET':
        if has_studio_read_access(request.user, course_usage_key):
            return JsonResponse([
                unicode(item)
                for item in modulestore().get_orphans(course_usage_key)
            ])
        else:
            raise PermissionDenied()
    if request.method == 'DELETE':
        if request.user.is_staff:
            deleted_items = _delete_orphans(course_usage_key,
                                            request.user.id,
                                            commit=True)
            return JsonResponse({'deleted': deleted_items})
        else:
            raise PermissionDenied()
示例#2
0
def _list_libraries(request):
    """
    List all accessible libraries, after applying filters in the request
    Query params:
        org - The organization used to filter libraries
        text_search - The string used to filter libraries by searching in title, id or org
    """
    org = request.GET.get('org', '')
    text_search = request.GET.get('text_search', '').lower()

    if org:
        libraries = modulestore().get_libraries(org=org)
    else:
        libraries = modulestore().get_libraries()

    lib_info = [
        {
            "display_name": lib.display_name,
            "library_key": text_type(lib.location.library_key),
        } for lib in libraries
        if ((text_search in lib.display_name.lower()
             or text_search in lib.location.library_key.org.lower()
             or text_search in lib.location.library_key.library.lower())
            and has_studio_read_access(request.user, lib.location.library_key))
    ]
    return JsonResponse(lib_info)
示例#3
0
def xblock_outline_handler(request, usage_key_string):
    """
    The restful handler for requests for XBlock information about the block and its children.
    This is used by the course outline in particular to construct the tree representation of
    a course.
    """
    usage_key = usage_key_with_run(usage_key_string)
    if not has_studio_read_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    response_format = request.REQUEST.get("format", "html")
    if response_format == "json" or "application/json" in request.META.get("HTTP_ACCEPT", "application/json"):
        store = modulestore()
        with store.bulk_operations(usage_key.course_key):
            root_xblock = store.get_item(usage_key, depth=None)
            return JsonResponse(
                create_xblock_info(
                    root_xblock,
                    include_child_info=True,
                    course_outline=True,
                    include_children_predicate=lambda xblock: not xblock.category == "vertical",
                )
            )
    else:
        return Http404
示例#4
0
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.REQUEST.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)
示例#5
0
    def test_creation(self):
        """
        The user that creates a library should have instructor (admin) and staff permissions
        """
        # self.library has been auto-created by the staff user.
        self.assertTrue(has_studio_write_access(self.user, self.lib_key))
        self.assertTrue(has_studio_read_access(self.user, self.lib_key))
        # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers:
        self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user))

        # Now log out and ensure we are forbidden from creating a library:
        self.client.logout()
        self._assert_cannot_create_library(expected_code=302)  # 302 redirect to login expected

        # Now check that logged-in users without CourseCreator role cannot create libraries
        self._login_as_non_staff_user(logout_first=False)
        with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
            self._assert_cannot_create_library(expected_code=403)  # 403 user is not CourseCreator

        # Now check that logged-in users with CourseCreator role can create libraries
        add_user_with_status_granted(self.user, self.non_staff_user)
        with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
            lib_key2 = self._create_library(library="lib2", display_name="Test Library 2")
            library2 = modulestore().get_library(lib_key2)
            self.assertIsNotNone(library2)
    def test_creation(self):
        """
        The user that creates a library should have instructor (admin) and staff permissions
        """
        # self.library has been auto-created by the staff user.
        self.assertTrue(has_studio_write_access(self.user, self.lib_key))
        self.assertTrue(has_studio_read_access(self.user, self.lib_key))
        # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers:
        self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user))

        # Now log out and ensure we are forbidden from creating a library:
        self.client.logout()
        self._assert_cannot_create_library(expected_code=302)  # 302 redirect to login expected

        # Now check that logged-in users without CourseCreator role cannot create libraries
        self._login_as_non_staff_user(logout_first=False)
        with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
            self._assert_cannot_create_library(expected_code=403)  # 403 user is not CourseCreator

        # Now check that logged-in users with CourseCreator role can create libraries
        add_user_with_status_granted(self.user, self.non_staff_user)
        with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
            lib_key2 = self._create_library(library="lib2", display_name="Test Library 2")
            library2 = modulestore().get_library(lib_key2)
            self.assertIsNotNone(library2)
示例#7
0
def _get_course_and_check_access(course_key, user, depth=0):
    """
    Internal method used to calculate and return the locator and
    course module for the view functions in this file.
    """
    if not has_studio_read_access(user, course_key):
        raise PermissionDenied()
    course_module = modulestore().get_course(course_key, depth=depth)
    return course_module
示例#8
0
def _get_course_and_check_access(course_key, user, depth=0):
    """
    Internal method used to calculate and return the locator and
    course module for the view functions in this file.
    """
    if not has_studio_read_access(user, course_key):
        raise PermissionDenied()
    course_module = modulestore().get_course(course_key, depth=depth)
    return course_module
示例#9
0
    def course_filter(course_summary):
        """
        Filter out unusable and inaccessible courses
        """
        # pylint: disable=fixme
        # TODO remove this condition when templates purged from db
        if course_summary.location.course == 'templates':
            return False

        return has_studio_read_access(user, course_summary.id)
示例#10
0
    def course_filter(course_summary):
        """
        Filter out unusable and inaccessible courses
        """
        # pylint: disable=fixme
        # TODO remove this condition when templates purged from db
        if course_summary.location.course == 'templates':
            return False

        return has_studio_read_access(request.user, course_summary.id)
示例#11
0
def get_in_process_course_actions(request):
    """
     Get all in-process course actions
    """
    return [
        course for course in CourseRerunState.objects.find_all(
            exclude_args={'state': CourseRerunUIStateManager.State.SUCCEEDED},
            should_display=True)
        if has_studio_read_access(request.user, course.course_key)
    ]
示例#12
0
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)
示例#13
0
    def course_filter(course):
        """
        Filter out unusable and inaccessible courses
        """
        if isinstance(course, ErrorDescriptor):
            return False

        # pylint: disable=fixme
        # TODO remove this condition when templates purged from db
        if course.location.course == 'templates':
            return False

        return has_studio_read_access(user, course.id)
示例#14
0
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)
示例#15
0
def get_in_process_course_actions(request, this_user=None):
    """
    Get all in-process course actions
    """
    if this_user:
        user = this_user
    else:
        user = request.user

    return [
        course for course in
        CourseRerunState.objects.find_all(
            exclude_args={'state': CourseRerunUIStateManager.State.SUCCEEDED}, should_display=True
        )
        if has_studio_read_access(user, course.course_key)
    ]
示例#16
0
def orphan_handler(request, course_key_string):
    """
    View for handling orphan related requests. GET gets all of the current orphans.
    DELETE removes all orphans (requires is_staff access)

    An orphan is a block whose category is not in the DETACHED_CATEGORY list, is not the root, and is not reachable
    from the root via children
    """
    course_usage_key = CourseKey.from_string(course_key_string)
    if request.method == 'GET':
        if has_studio_read_access(request.user, course_usage_key):
            return JsonResponse([unicode(item) for item in modulestore().get_orphans(course_usage_key)])
        else:
            raise PermissionDenied()
    if request.method == 'DELETE':
        if request.user.is_staff:
            deleted_items = _delete_orphans(course_usage_key, request.user.id, commit=True)
            return JsonResponse({'deleted': deleted_items})
        else:
            raise PermissionDenied()
示例#17
0
def xblock_container_handler(request, usage_key_string):
    """
    The restful handler for requests for XBlock information about the block and its children.
    This is used by the container page in particular to get additional information about publish state
    and ancestor state.
    """
    usage_key = usage_key_with_run(usage_key_string)

    if not has_studio_read_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    response_format = request.REQUEST.get('format', 'html')
    if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        with modulestore().bulk_operations(usage_key.course_key):
            response = _get_module_info(
                _get_xblock(usage_key, request.user), include_ancestor_info=True, include_publishing_info=True
            )
        return JsonResponse(response)
    else:
        return Http404
示例#18
0
def xblock_container_handler(request, usage_key_string):
    """
    The restful handler for requests for XBlock information about the block and its children.
    This is used by the container page in particular to get additional information about publish state
    and ancestor state.
    """
    usage_key = usage_key_with_run(usage_key_string)

    if not has_studio_read_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    response_format = request.REQUEST.get('format', 'html')
    if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        with modulestore().bulk_operations(usage_key.course_key):
            response = _get_module_info(
                _get_xblock(usage_key, request.user), include_ancestor_info=True, include_publishing_info=True
            )
        return JsonResponse(response)
    else:
        return Http404
示例#19
0
    def test_creation(self):
        """
        The user that creates a library should have instructor (admin) and staff permissions
        """
        # self.library has been auto-created by the staff user.
        self.assertTrue(has_studio_write_access(self.user, self.lib_key))
        self.assertTrue(has_studio_read_access(self.user, self.lib_key))
        # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers:
        self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user))

        # Now log out and ensure we are forbidden from creating a library:
        self.client.logout()
        self._assert_cannot_create_library(expected_code=302)  # 302 redirect to login expected

        # Now create a non-staff user with no permissions:
        self._login_as_non_staff_user(logout_first=False)
        self.assertFalse(CourseCreatorRole().has_user(self.non_staff_user))

        # Now check that logged-in users without any permissions cannot create libraries
        with patch.dict("django.conf.settings.FEATURES", {"ENABLE_CREATOR_GROUP": True}):
            self._assert_cannot_create_library()
示例#20
0
    def test_creation(self):
        """
        The user that creates a library should have instructor (admin) and staff permissions
        """
        # self.library has been auto-created by the staff user.
        self.assertTrue(has_studio_write_access(self.user, self.lib_key))
        self.assertTrue(has_studio_read_access(self.user, self.lib_key))
        # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers:
        self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user))

        # Now log out and ensure we are forbidden from creating a library:
        self.client.logout()
        self._assert_cannot_create_library(expected_code=302)  # 302 redirect to login expected

        # Now create a non-staff user with no permissions:
        self._login_as_non_staff_user(logout_first=False)
        self.assertFalse(CourseCreatorRole().has_user(self.non_staff_user))

        # Now check that logged-in users without any permissions cannot create libraries
        with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
            self._assert_cannot_create_library()
示例#21
0
def xblock_outline_handler(request, usage_key_string):
    """
    The restful handler for requests for XBlock information about the block and its children.
    This is used by the course outline in particular to construct the tree representation of
    a course.
    """
    usage_key = usage_key_with_run(usage_key_string)
    if not has_studio_read_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    response_format = request.REQUEST.get('format', 'html')
    if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        store = modulestore()
        root_xblock = store.get_item(usage_key)
        return JsonResponse(create_xblock_info(
            root_xblock,
            include_child_info=True,
            course_outline=True,
            include_children_predicate=lambda xblock: not xblock.category == 'vertical'
        ))
    else:
        return Http404
示例#22
0
def get_block_exportfs_file(request, usage_key_str, path):
    """
    Serve a static file that got added to the XBlock's export_fs during XBlock
    serialization. Typically these would be video transcript files.
    """
    # Parse the usage key:
    try:
        usage_key = UsageKey.from_string(usage_key_str)
    except (ValueError, InvalidKeyError):
        raise ValidationError('Invalid usage key')
    if usage_key.block_type in ('course', 'chapter', 'sequential'):
        raise ValidationError(
            'Requested XBlock tree is too large - export verticals or their children only'
        )
    course_key = usage_key.context_key
    if not isinstance(course_key, CourseLocator):
        raise ValidationError('Invalid usage key: not a modulestore course')
    # Make sure the user has permission on that course
    if not has_studio_read_access(request.user, course_key):
        raise PermissionDenied(
            "You must be a member of the course team in Studio to export OLX using this API."
        )

    block = compat.get_block(usage_key)
    serialized = XBlockSerializer(block)
    static_file = None
    for f in serialized.static_files:
        if f.name == path:
            static_file = f
            break
    if static_file is None:
        raise NotFound

    response = HttpResponse(static_file.data,
                            content_type='application/octet-stream')
    response['Content-Disposition'] = 'attachment; filename="{}"'.format(path)
    return response
示例#23
0
def xblock_handler(request, usage_key_string):
    """
    The restful handler for xblock requests.

    DELETE
        json: delete this xblock instance from the course.
    GET
        json: returns representation of the xblock (locator id, data, and metadata).
              if ?fields=graderType, it returns the graderType for the unit instead of the above.
        html: returns HTML for rendering the xblock (which includes both the "preview" view and the "editor" view)
    PUT or POST or PATCH
        json: if xblock locator is specified, update the xblock instance. The json payload can contain
              these fields, all optional:
                :data: the new value for the data.
                :children: the unicode representation of the UsageKeys of children for this xblock.
                :metadata: new values for the metadata fields. Any whose values are None will be deleted not set
                       to None! Absent ones will be left alone.
                :nullout: which metadata fields to set to None
                :graderType: change how this unit is graded
                :publish: can be:
                  'make_public': publish the content
                  'republish': publish this item *only* if it was previously published
                  'discard_changes' - reverts to the last published version
                Note: If 'discard_changes', the other fields will not be used; that is, it is not possible
                to update and discard changes in a single operation.
              The JSON representation on the updated xblock (minus children) is returned.

              if usage_key_string is not specified, create a new xblock instance, either by duplicating
              an existing xblock, or creating an entirely new one. The json playload can contain
              these fields:
                :parent_locator: parent for new xblock, required for both duplicate and create new instance
                :duplicate_source_locator: if present, use this as the source for creating a duplicate copy
                :category: type of xblock, required if duplicate_source_locator is not present.
                :display_name: name for new xblock, optional
                :boilerplate: template name for populating fields, optional and only used
                     if duplicate_source_locator is not present
              The locator (unicode representation of a UsageKey) for the created xblock (minus children) is returned.
    """
    if usage_key_string:
        usage_key = usage_key_with_run(usage_key_string)

        access_check = has_studio_read_access if request.method == 'GET' else has_studio_write_access
        if not access_check(request.user, usage_key.course_key):
            raise PermissionDenied()

        if request.method == 'GET':
            accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

            if 'application/json' in accept_header:
                fields = request.REQUEST.get('fields', '').split(',')
                if 'graderType' in fields:
                    # right now can't combine output of this w/ output of _get_module_info, but worthy goal
                    return JsonResponse(
                        CourseGradingModel.get_section_grader_type(usage_key))
                # TODO: pass fields to _get_module_info and only return those
                with modulestore().bulk_operations(usage_key.course_key):
                    response = _get_module_info(
                        _get_xblock(usage_key, request.user))
                return JsonResponse(response)
            else:
                return HttpResponse(status=406)

        elif request.method == 'DELETE':
            _delete_item(usage_key, request.user)
            return JsonResponse()
        else:  # Since we have a usage_key, we are updating an existing xblock.
            return _save_xblock(
                request.user,
                _get_xblock(usage_key, request.user),
                data=request.json.get('data'),
                children_strings=request.json.get('children'),
                metadata=request.json.get('metadata'),
                nullout=request.json.get('nullout'),
                grader_type=request.json.get('graderType'),
                publish=request.json.get('publish'),
            )
    elif request.method in ('PUT', 'POST'):
        if 'duplicate_source_locator' in request.json:
            parent_usage_key = usage_key_with_run(
                request.json['parent_locator'])
            duplicate_source_usage_key = usage_key_with_run(
                request.json['duplicate_source_locator'])

            source_course = duplicate_source_usage_key.course_key
            dest_course = parent_usage_key.course_key
            if (not has_studio_write_access(request.user, dest_course) or
                    not has_studio_read_access(request.user, source_course)):
                raise PermissionDenied()

            dest_usage_key = _duplicate_item(
                parent_usage_key,
                duplicate_source_usage_key,
                request.user,
                request.json.get('display_name'),
            )

            return JsonResponse({
                "locator": unicode(dest_usage_key),
                "courseKey": unicode(dest_usage_key.course_key)
            })
        else:
            return _create_item(request)
    else:
        return HttpResponseBadRequest(
            "Only instance creation is supported without a usage key.",
            content_type="text/plain")
示例#24
0
def xblock_handler(request, usage_key_string):
    """
    The restful handler for xblock requests.

    DELETE
        json: delete this xblock instance from the course.
    GET
        json: returns representation of the xblock (locator id, data, and metadata).
              if ?fields=graderType, it returns the graderType for the unit instead of the above.
        html: returns HTML for rendering the xblock (which includes both the "preview" view and the "editor" view)
    PUT or POST or PATCH
        json: if xblock locator is specified, update the xblock instance. The json payload can contain
              these fields, all optional:
                :data: the new value for the data.
                :children: the unicode representation of the UsageKeys of children for this xblock.
                :metadata: new values for the metadata fields. Any whose values are None will be deleted not set
                       to None! Absent ones will be left alone.
                :nullout: which metadata fields to set to None
                :graderType: change how this unit is graded
                :publish: can be:
                  'make_public': publish the content
                  'republish': publish this item *only* if it was previously published
                  'discard_changes' - reverts to the last published version
                Note: If 'discard_changes', the other fields will not be used; that is, it is not possible
                to update and discard changes in a single operation.
              The JSON representation on the updated xblock (minus children) is returned.

              if usage_key_string is not specified, create a new xblock instance, either by duplicating
              an existing xblock, or creating an entirely new one. The json playload can contain
              these fields:
                :parent_locator: parent for new xblock, required for both duplicate and create new instance
                :duplicate_source_locator: if present, use this as the source for creating a duplicate copy
                :category: type of xblock, required if duplicate_source_locator is not present.
                :display_name: name for new xblock, optional
                :boilerplate: template name for populating fields, optional and only used
                     if duplicate_source_locator is not present
              The locator (unicode representation of a UsageKey) for the created xblock (minus children) is returned.
    """
    if usage_key_string:
        usage_key = usage_key_with_run(usage_key_string)

        access_check = has_studio_read_access if request.method == 'GET' else has_studio_write_access
        if not access_check(request.user, usage_key.course_key):
            raise PermissionDenied()

        if request.method == 'GET':
            accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

            if 'application/json' in accept_header:
                fields = request.REQUEST.get('fields', '').split(',')
                if 'graderType' in fields:
                    # right now can't combine output of this w/ output of _get_module_info, but worthy goal
                    return JsonResponse(CourseGradingModel.get_section_grader_type(usage_key))
                # TODO: pass fields to _get_module_info and only return those
                with modulestore().bulk_operations(usage_key.course_key):
                    response = _get_module_info(_get_xblock(usage_key, request.user))
                return JsonResponse(response)
            else:
                return HttpResponse(status=406)

        elif request.method == 'DELETE':
            _delete_item(usage_key, request.user)
            return JsonResponse()
        else:  # Since we have a usage_key, we are updating an existing xblock.
            return _save_xblock(
                request.user,
                _get_xblock(usage_key, request.user),
                data=request.json.get('data'),
                children_strings=request.json.get('children'),
                metadata=request.json.get('metadata'),
                nullout=request.json.get('nullout'),
                grader_type=request.json.get('graderType'),
                publish=request.json.get('publish'),
            )
    elif request.method in ('PUT', 'POST'):
        if 'duplicate_source_locator' in request.json:
            parent_usage_key = usage_key_with_run(request.json['parent_locator'])
            duplicate_source_usage_key = usage_key_with_run(request.json['duplicate_source_locator'])

            source_course = duplicate_source_usage_key.course_key
            dest_course = parent_usage_key.course_key
            if (
                    not has_studio_write_access(request.user, dest_course) or
                    not has_studio_read_access(request.user, source_course)
            ):
                raise PermissionDenied()

            dest_usage_key = _duplicate_item(
                parent_usage_key,
                duplicate_source_usage_key,
                request.user,
                request.json.get('display_name'),
            )

            return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)})
        else:
            return _create_item(request)
    else:
        return HttpResponseBadRequest(
            "Only instance creation is supported without a usage key.",
            content_type="text/plain"
        )
示例#25
0
def xblock_view_handler(request, usage_key_string, view_name):
    """
    The restful handler for requests for rendered xblock views.

    Returns a json object containing two keys:
        html: The rendered html of the view
        resources: A list of tuples where the first element is the resource hash, and
            the second is the resource description
    """
    usage_key = usage_key_with_run(usage_key_string)
    if not has_studio_read_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

    if 'application/json' in accept_header:
        store = modulestore()
        xblock = store.get_item(usage_key)
        container_views = ['container_preview', 'reorderable_container_child_preview', 'container_child_preview']

        # wrap the generated fragment in the xmodule_editor div so that the javascript
        # can bind to it correctly
        xblock.runtime.wrappers.append(partial(
            wrap_xblock,
            'StudioRuntime',
            usage_id_serializer=unicode,
            request_token=request_token(request),
        ))

        if view_name in (STUDIO_VIEW, VISIBILITY_VIEW):
            try:
                fragment = xblock.render(view_name)
            # catch exceptions indiscriminately, since after this point they escape the
            # dungeon and surface as uneditable, unsaveable, and undeletable
            # component-goblins.
            except Exception as exc:                          # pylint: disable=broad-except
                log.debug("Unable to render %s for %r", view_name, xblock, exc_info=True)
                fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)}))

        elif view_name in PREVIEW_VIEWS + container_views:
            is_pages_view = view_name == STUDENT_VIEW   # Only the "Pages" view uses student view in Studio
            can_edit = has_studio_write_access(request.user, usage_key.course_key)

            # Determine the items to be shown as reorderable. Note that the view
            # 'reorderable_container_child_preview' is only rendered for xblocks that
            # are being shown in a reorderable container, so the xblock is automatically
            # added to the list.
            reorderable_items = set()
            if view_name == 'reorderable_container_child_preview':
                reorderable_items.add(xblock.location)

            paging = None
            try:
                if request.REQUEST.get('enable_paging', 'false') == 'true':
                    paging = {
                        'page_number': int(request.REQUEST.get('page_number', 0)),
                        'page_size': int(request.REQUEST.get('page_size', 0)),
                    }
            except ValueError:
                # pylint: disable=too-many-format-args
                return HttpResponse(
                    content="Couldn't parse paging parameters: enable_paging: "
                            "{0}, page_number: {1}, page_size: {2}".format(
                                request.REQUEST.get('enable_paging', 'false'),
                                request.REQUEST.get('page_number', 0),
                                request.REQUEST.get('page_size', 0)
                            ),
                    status=400,
                    content_type="text/plain",
                )

            force_render = request.REQUEST.get('force_render', None)

            # Set up the context to be passed to each XBlock's render method.
            context = {
                'is_pages_view': is_pages_view,     # This setting disables the recursive wrapping of xblocks
                'is_unit_page': is_unit(xblock),
                'can_edit': can_edit,
                'root_xblock': xblock if (view_name == 'container_preview') else None,
                'reorderable_items': reorderable_items,
                'paging': paging,
                'force_render': force_render,
            }

            fragment = get_preview_fragment(request, xblock, context)

            # Note that the container view recursively adds headers into the preview fragment,
            # so only the "Pages" view requires that this extra wrapper be included.
            if is_pages_view:
                fragment.content = render_to_string('component.html', {
                    'xblock_context': context,
                    'xblock': xblock,
                    'locator': usage_key,
                    'preview': fragment.content,
                    'label': xblock.display_name or xblock.scope_ids.block_type,
                })
        else:
            raise Http404

        hashed_resources = OrderedDict()
        for resource in fragment.resources:
            hashed_resources[hash_resource(resource)] = resource

        return JsonResponse({
            'html': fragment.content,
            'resources': hashed_resources.items()
        })

    else:
        return HttpResponse(status=406)
示例#26
0
 def test_no_global_admin_read_access(self):
     """
     Test that global admins have no read access
     """
     self.assertFalse(has_studio_read_access(self.global_admin, self.ccx_course_key))
示例#27
0
文件: test_authz.py 项目: saadow123/1
 def test_no_staff_read_access(self):
     """
     Test that course staff have no read access
     """
     self.assertFalse(
         has_studio_read_access(self.staff, self.ccx_course_key))
示例#28
0
文件: test_authz.py 项目: saadow123/1
 def test_no_global_admin_read_access(self):
     """
     Test that global admins have no read access
     """
     self.assertFalse(
         has_studio_read_access(self.global_admin, self.ccx_course_key))
示例#29
0
 def can_read(self, course_key):
     """ Does the user have read access to the given course/library? """
     return has_studio_read_access(self._request.user, course_key)
示例#30
0
def xblock_view_handler(request, usage_key_string, view_name):
    """
    The restful handler for requests for rendered xblock views.

    Returns a json object containing two keys:
        html: The rendered html of the view
        resources: A list of tuples where the first element is the resource hash, and
            the second is the resource description
    """
    usage_key = usage_key_with_run(usage_key_string)
    if not has_studio_read_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

    if 'application/json' in accept_header:
        store = modulestore()
        xblock = store.get_item(usage_key)
        container_views = [
            'container_preview', 'reorderable_container_child_preview',
            'container_child_preview'
        ]

        # wrap the generated fragment in the xmodule_editor div so that the javascript
        # can bind to it correctly
        xblock.runtime.wrappers.append(
            partial(
                wrap_xblock,
                'StudioRuntime',
                usage_id_serializer=unicode,
                request_token=request_token(request),
            ))

        if view_name in (STUDIO_VIEW, VISIBILITY_VIEW):
            try:
                fragment = xblock.render(view_name)
            # catch exceptions indiscriminately, since after this point they escape the
            # dungeon and surface as uneditable, unsaveable, and undeletable
            # component-goblins.
            except Exception as exc:  # pylint: disable=broad-except
                log.debug("Unable to render %s for %r",
                          view_name,
                          xblock,
                          exc_info=True)
                fragment = Fragment(
                    render_to_string('html_error.html', {'message': str(exc)}))

        elif view_name in (PREVIEW_VIEWS + container_views):
            is_pages_view = view_name == STUDENT_VIEW  # Only the "Pages" view uses student view in Studio
            can_edit = has_studio_write_access(request.user,
                                               usage_key.course_key)

            # Determine the items to be shown as reorderable. Note that the view
            # 'reorderable_container_child_preview' is only rendered for xblocks that
            # are being shown in a reorderable container, so the xblock is automatically
            # added to the list.
            reorderable_items = set()
            if view_name == 'reorderable_container_child_preview':
                reorderable_items.add(xblock.location)

            paging = None
            try:
                if request.REQUEST.get('enable_paging', 'false') == 'true':
                    paging = {
                        'page_number':
                        int(request.REQUEST.get('page_number', 0)),
                        'page_size': int(request.REQUEST.get('page_size', 0)),
                    }
            except ValueError:
                # pylint: disable=too-many-format-args
                return HttpResponse(
                    content="Couldn't parse paging parameters: enable_paging: "
                    "{0}, page_number: {1}, page_size: {2}".format(
                        request.REQUEST.get('enable_paging', 'false'),
                        request.REQUEST.get('page_number', 0),
                        request.REQUEST.get('page_size', 0)),
                    status=400,
                    content_type="text/plain",
                )

            force_render = request.REQUEST.get('force_render', None)

            # Set up the context to be passed to each XBlock's render method.
            context = {
                'is_pages_view':
                is_pages_view,  # This setting disables the recursive wrapping of xblocks
                'is_unit_page': is_unit(xblock),
                'can_edit': can_edit,
                'root_xblock': xblock if
                (view_name == 'container_preview') else None,
                'reorderable_items': reorderable_items,
                'paging': paging,
                'force_render': force_render,
            }

            fragment = get_preview_fragment(request, xblock, context)

            # Note that the container view recursively adds headers into the preview fragment,
            # so only the "Pages" view requires that this extra wrapper be included.
            if is_pages_view:
                fragment.content = render_to_string(
                    'component.html', {
                        'xblock_context':
                        context,
                        'xblock':
                        xblock,
                        'locator':
                        usage_key,
                        'preview':
                        fragment.content,
                        'label':
                        xblock.display_name or xblock.scope_ids.block_type,
                    })
        else:
            raise Http404

        hashed_resources = OrderedDict()
        for resource in fragment.resources:
            hashed_resources[hash_resource(resource)] = resource

        return JsonResponse({
            'html': fragment.content,
            'resources': hashed_resources.items()
        })

    else:
        return HttpResponse(status=406)
示例#31
0
def accessible_libraries_list(user):
    """
    List all libraries available to the logged in user by iterating through all libraries
    """
    # No need to worry about ErrorDescriptors - split's get_libraries() never returns them.
    return [lib for lib in modulestore().get_libraries() if has_studio_read_access(user, lib.location.library_key)]
示例#32
0
 def can_read(self, course_key):
     """ Does the user have read access to the given course/library? """
     return has_studio_read_access(self._request.user, course_key)
示例#33
0
def get_block_olx(request, usage_key_str):
    """
    Given a modulestore XBlock usage ID (block-v1:...), get its OLX and a list
    of any static asset files it uses.
    """
    # Parse the usage key:
    try:
        usage_key = UsageKey.from_string(usage_key_str)
    except (ValueError, InvalidKeyError):
        raise ValidationError('Invalid usage key')
    if usage_key.block_type in ('course', 'chapter', 'sequential'):
        raise ValidationError(
            'Requested XBlock tree is too large - export verticals or their children only'
        )
    course_key = usage_key.context_key
    if not isinstance(course_key, CourseLocator):
        raise ValidationError('Invalid usage key: not a modulestore course')
    # Make sure the user has permission on that course
    if not has_studio_read_access(request.user, course_key):
        raise PermissionDenied(
            "You must be a member of the course team in Studio to export OLX using this API."
        )

    # Step 1: Serialize the XBlocks to OLX files + static asset files

    serialized_blocks = {}  # Key is each XBlock's original usage key

    def serialize_block(block_key):
        """ Inner method to recursively serialize an XBlock to OLX """
        if block_key in serialized_blocks:
            return

        block = compat.get_block(block_key)
        serialized_blocks[block_key] = XBlockSerializer(block)

        if block.has_children:
            for child_id in block.children:
                serialize_block(child_id)

    serialize_block(usage_key)

    result = {
        "root_block_id": six.text_type(usage_key),
        "blocks": {},
    }

    # For each XBlock that we're exporting:
    for this_usage_key, data in serialized_blocks.items():
        olx_str = data.olx_str

        block_data_out = {"olx": data.olx_str}

        for asset_file in data.static_files:
            if asset_file.url:
                url = request.build_absolute_uri(asset_file.url)
            else:
                # The file is not in GridFS so we don't have a URL for it; serve it
                # via our own get_block_exportfs_file API endpoint.
                url = request.build_absolute_uri(
                    '../' + six.text_type(this_usage_key) + '/export-file/' +
                    asset_file.name, )
            block_data_out.setdefault("static_files", {})[asset_file.name] = {
                "url": url
            }

        result["blocks"][six.text_type(data.orig_block_key)] = block_data_out

    return Response(result)
示例#34
0
 def test_no_staff_read_access(self):
     """
     Test that course staff have no read access
     """
     self.assertFalse(has_studio_read_access(self.staff, self.ccx_course_key))