예제 #1
0
 def delete(self, request, lib_key_str):  # pylint: disable=unused-argument
     """
     Revert the draft changes made to the specified block and its
     descendants. Restore it to the last published version
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(
         key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
     api.revert_changes(key)
     return Response({})
예제 #2
0
 def delete(self, request, lib_key_str, user_id):
     """
     Remove the specified user's permission to access or edit this content
     library.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY_TEAM)
     group = get_object_or_404(Group, pk=int(user_id))
     api.set_library_group_permissions(key, group, access_level=None)
     return Response({})
예제 #3
0
 def post(self, request, lib_key_str):
     """
     Create a new link in this library.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(
         key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
     serializer = LibraryBundleLinkSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     target_key = LibraryLocatorV2.from_string(
         serializer.validated_data['opaque_key'])
     api.create_bundle_link(
         library_key=key,
         link_id=serializer.validated_data['id'],
         target_opaque_key=target_key,
         version=serializer.validated_data[
             'version'],  # a number, or None for "use latest version"
     )
     return Response({})
예제 #4
0
 def post(self, request, lib_key_str):
     """
     Commit the draft changes made to the specified block and its
     descendants.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(
         key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
     api.publish_changes(key)
     return Response({})
예제 #5
0
 def patch(self, request, lib_key_str):
     """
     Update a content library
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
     serializer = ContentLibraryUpdateSerializer(data=request.data, partial=True)
     serializer.is_valid(raise_exception=True)
     api.update_library(key, **serializer.validated_data)
     result = api.get_library(key)
     return Response(ContentLibraryMetadataSerializer(result).data)
예제 #6
0
 def get(self, request, lib_key_str, username):
     """
     Gets the current permissions settings for a particular user.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY_TEAM)
     user = get_object_or_404(User, username=username)
     grant = api.get_library_user_permissions(key, user)
     if not grant:
         raise NotFound
     return Response(ContentLibraryPermissionSerializer(grant).data)
예제 #7
0
 def get(self, request, lib_key_str):
     """
     Get the list of users and groups who have permissions to view and edit
     this library.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(
         key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY_TEAM)
     team = api.get_library_team(key)
     return Response(
         ContentLibraryPermissionSerializer(team, many=True).data)
예제 #8
0
 def patch(self, request, lib_key_str):
     """
     Update a content library
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     serializer = ContentLibraryUpdateSerializer(data=request.data,
                                                 partial=True)
     serializer.is_valid(raise_exception=True)
     api.update_library(key, **serializer.validated_data)
     result = api.get_library(key)
     return Response(ContentLibraryMetadataSerializer(result).data)
예제 #9
0
 def put(self, request, lib_key_str, group_name):
     """
     Add a group to this content library, with permissions specified in the
     request body.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY_TEAM)
     serializer = ContentLibraryPermissionLevelSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     group = get_object_or_404(Group, name=group_name)
     api.set_library_group_permissions(key, group, access_level=serializer.validated_data["access_level"])
     return Response({})
예제 #10
0
 def test_authorize_lti_launch_when_no_library(self):
     """
     Given no library
     When authorize_lti_launch is called
     Then return False
     """
     self.assertFalse(ContentLibrary.objects.exists())
     authorized = ContentLibrary.authorize_lti_launch(
         LibraryLocatorV2(org='foo', slug='foobar'),
         issuer='http://a-fake-issuer',
         client_id='a-fake-client-id')
     self.assertFalse(authorized)
예제 #11
0
 def delete(self, request, lib_key_str, username):
     """
     Remove the specified user's permission to access or edit this content
     library.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY_TEAM)
     user = get_object_or_404(User, username=username)
     try:
         api.set_library_user_permissions(key, user, access_level=None)
     except api.LibraryPermissionIntegrityError as err:
         raise ValidationError(detail=str(err))
     return Response({})
예제 #12
0
 def create_mock_library(self, *, course_id=None, course_key_str=None):
     """
     Create a library mock.
     """
     mock_library = mock.MagicMock()
     mock_library.library_key = LibraryLocatorV2.from_string(
         self.library_key_str)
     if course_key_str is None:
         course_key_str = self.course_key_str
     if course_id is None:
         course_id = CourseKey.from_string(course_key_str)
     type(mock_library).course_id = mock.PropertyMock(
         return_value=course_id)
     return mock_library
예제 #13
0
    def patch(self, request, lib_key_str, link_id):
        """
        Update the specified link to point to a different version of its
        target bundle.

        Pass e.g. {"version": 40} or pass {"version": None} to update to the
        latest published version.
        """
        key = LibraryLocatorV2.from_string(lib_key_str)
        api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
        serializer = LibraryBundleLinkUpdateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        api.update_bundle_link(key, link_id, version=serializer.validated_data['version'])
        return Response({})
예제 #14
0
    def retrieve(self, request, lib_key_str, pk=None):
        """
        Retrieve a import task for inspection.
        """

        library_key = LibraryLocatorV2.from_string(lib_key_str)
        api.require_permission_for_library_key(
            library_key,
            request.user,
            permissions.CAN_VIEW_THIS_CONTENT_LIBRARY,
        )

        import_task = api.ContentLibraryBlockImportTask.objects.get(pk=pk)
        return Response(ContentLibraryBlockImportTaskSerializer(import_task).data)
예제 #15
0
 def list(self, request, lib_key_str):
     """
     List all import tasks for this library.
     """
     library_key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(
         library_key, request.user,
         permissions.CAN_VIEW_THIS_CONTENT_LIBRARY)
     queryset = api.ContentLibrary.objects.get_by_key(
         library_key).import_tasks
     result = ContentLibraryBlockImportTaskSerializer(queryset,
                                                      many=True).data
     paginator = LibraryApiPagination()
     return paginator.get_paginated_response(
         paginator.paginate_queryset(result, request))
예제 #16
0
 def put(self, request, lib_key_str, username):
     """
     Add a user to this content library, with permissions specified in the
     request body.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY_TEAM)
     serializer = ContentLibraryPermissionLevelSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     user = get_object_or_404(User, username=username)
     try:
         api.set_library_user_permissions(key, user, access_level=serializer.validated_data["access_level"])
     except api.LibraryPermissionIntegrityError as err:
         raise ValidationError(detail=str(err))
     grant = api.get_library_user_permissions(key, user)
     return Response(ContentLibraryPermissionSerializer(grant).data)
    def test_schema_updates(self):
        """
        Test that outdated indexes aren't retrieved
        """
        with patch("openedx.core.djangoapps.content_libraries.libraries_index.ContentLibraryIndexer.SCHEMA_VERSION",
                   new=0):
            result = self._create_library(slug="test-lib-schemaupdates-1", title="Title 1", description="Description")
            library_key = LibraryLocatorV2.from_string(result['id'])
            self.assertEqual(len(ContentLibraryIndexer.get_items([library_key])), 1)

        with patch("openedx.core.djangoapps.content_libraries.libraries_index.ContentLibraryIndexer.SCHEMA_VERSION",
                   new=1):
            self.assertEqual(len(ContentLibraryIndexer.get_items([library_key])), 0)

            call_command("reindex_content_library", all=True, force=True)

            self.assertEqual(len(ContentLibraryIndexer.get_items([library_key])), 1)
예제 #18
0
 def patch(self, request, lib_key_str):
     """
     Update a content library
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
     serializer = ContentLibraryUpdateSerializer(data=request.data, partial=True)
     serializer.is_valid(raise_exception=True)
     data = dict(serializer.validated_data)
     if 'type' in data:
         data['library_type'] = data.pop('type')
     try:
         api.update_library(key, **data)
     except api.IncompatibleTypesError as err:
         raise ValidationError({'type': str(err)})
     result = api.get_library(key)
     return Response(ContentLibraryMetadataSerializer(result).data)
예제 #19
0
    def test_schema_updates(self):
        """
        Test that outdated indexes aren't retrieved
        """
        result = self._create_library(slug="test-lib-schemaupdates-1", title="Title 1", description="Description")
        library_key = LibraryLocatorV2.from_string(result['id'])

        ContentLibraryIndexer.get_libraries([library_key])

        with patch("openedx.core.djangoapps.content_libraries.libraries_index.ContentLibraryIndexer.SCHEMA_VERSION",
                   new=1):
            with self.assertRaises(LibraryNotIndexedException):
                ContentLibraryIndexer.get_libraries([library_key])

            call_command("reindex_content_library", all=True, quiet=True)

            ContentLibraryIndexer.get_libraries([library_key])
예제 #20
0
 def post(self, request, lib_key_str):
     """
     Add a new XBlock to this content library
     """
     library_key = LibraryLocatorV2.from_string(lib_key_str)
     serializer = LibraryXBlockCreationSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     parent_block_usage_str = serializer.validated_data.pop("parent_block", None)
     if parent_block_usage_str:
         # Add this as a child of an existing block:
         parent_block_usage = LibraryUsageLocatorV2.from_string(parent_block_usage_str)
         if parent_block_usage.context_key != library_key:
             raise ValidationError(detail={"parent_block": "Usage ID doesn't match library ID in the URL."})
         result = api.create_library_block_child(parent_block_usage, **serializer.validated_data)
     else:
         # Create a new regular top-level block:
         result = api.create_library_block(library_key, **serializer.validated_data)
     return Response(LibraryXBlockMetadataSerializer(result).data)
예제 #21
0
    def create(self, request, lib_key_str):
        """
        Create and queue an import tasks for this library.
        """

        library_key = LibraryLocatorV2.from_string(lib_key_str)
        api.require_permission_for_library_key(
            library_key,
            request.user,
            permissions.CAN_EDIT_THIS_CONTENT_LIBRARY,
        )

        serializer = ContentLibraryBlockImportTaskCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        course_key = serializer.validated_data['course_key']

        import_task = api.import_blocks_create_task(library_key, course_key)
        return Response(ContentLibraryBlockImportTaskSerializer(import_task).data)
예제 #22
0
    def get(self, request, lib_key_str):
        """
        Get the list of all top-level blocks in this content library
        """
        key = LibraryLocatorV2.from_string(lib_key_str)
        text_search = request.query_params.get('text_search', None)

        api.require_permission_for_library_key(key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY)
        result = api.get_library_blocks(key, text_search=text_search)

        # Verify `pagination` param to maintain compatibility with older
        # non pagination-aware clients
        if request.GET.get('pagination', 'false').lower() == 'true':
            paginator = LibraryApiPagination()
            result = paginator.paginate_queryset(result, request)
            serializer = LibraryXBlockMetadataSerializer(result, many=True)
            return paginator.get_paginated_response(serializer.data)

        return Response(LibraryXBlockMetadataSerializer(result, many=True).data)
    def test_index_libraries(self):
        """
        Test if libraries are being indexed correctly
        """
        result1 = self._create_library(slug="test-lib-index-1", title="Title 1", description="Description")
        result2 = self._create_library(slug="test-lib-index-2", title="Title 2", description="Description")

        for result in [result1, result2]:
            library_key = LibraryLocatorV2.from_string(result['id'])
            response = ContentLibraryIndexer.get_items([library_key])[0]

            self.assertEqual(response['id'], result['id'])
            self.assertEqual(response['title'], result['title'])
            self.assertEqual(response['description'], result['description'])
            self.assertEqual(response['uuid'], result['bundle_uuid'])
            self.assertEqual(response['num_blocks'], 0)
            self.assertEqual(response['version'], result['version'])
            self.assertEqual(response['last_published'], None)
            self.assertEqual(response['has_unpublished_changes'], False)
            self.assertEqual(response['has_unpublished_deletes'], False)
예제 #24
0
 def post(self, request, lib_key_str):
     """
     Add a user to this content library via email, with permissions specified in the
     request body.
     """
     key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(
         key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY_TEAM)
     serializer = ContentLibraryAddPermissionByEmailSerializer(
         data=request.data)
     serializer.is_valid(raise_exception=True)
     try:
         user = User.objects.get(
             email=serializer.validated_data.get('email'))
     except User.DoesNotExist:
         raise ValidationError({
             'email':
             _('We could not find a user with that email address.')
         })
     grant = api.get_library_user_permissions(key, user)
     if grant:
         return Response(
             {
                 'email':
                 [_('This user already has access to this library.')]
             },
             status=status.HTTP_400_BAD_REQUEST,
         )
     try:
         api.set_library_user_permissions(
             key,
             user,
             access_level=serializer.validated_data["access_level"])
     except api.LibraryPermissionIntegrityError as err:
         raise ValidationError(detail=str(err))
     grant = api.get_library_user_permissions(key, user)
     return Response(ContentLibraryPermissionSerializer(grant).data)
예제 #25
0
    def test_update_libraries(self):
        """
        Test if indexes are updated when libraries are updated
        """
        lib = self._create_library(slug="test-lib-update", title="Title", description="Description")
        library_key = LibraryLocatorV2.from_string(lib['id'])

        self._update_library(lib['id'], title="New Title", description="New Title")

        response = ContentLibraryIndexer.get_libraries([library_key])[0]

        self.assertEqual(response['id'], lib['id'])
        self.assertEqual(response['title'], "New Title")
        self.assertEqual(response['description'], "New Title")
        self.assertEqual(response['uuid'], lib['bundle_uuid'])
        self.assertEqual(response['num_blocks'], 0)
        self.assertEqual(response['version'], lib['version'])
        self.assertEqual(response['last_published'], None)
        self.assertEqual(response['has_unpublished_changes'], False)
        self.assertEqual(response['has_unpublished_deletes'], False)

        self._delete_library(lib['id'])
        with self.assertRaises(LibraryNotIndexedException):
            ContentLibraryIndexer.get_libraries([library_key])
예제 #26
0
 def post(self, request, lib_key_str):
     """
     Add a new XBlock to this content library
     """
     library_key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(library_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
     serializer = LibraryXBlockCreationSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     parent_block_usage_str = serializer.validated_data.pop("parent_block", None)
     if parent_block_usage_str:
         # Add this as a child of an existing block:
         parent_block_usage = LibraryUsageLocatorV2.from_string(parent_block_usage_str)
         if parent_block_usage.context_key != library_key:
             raise ValidationError(detail={"parent_block": "Usage ID doesn't match library ID in the URL."})
         result = api.create_library_block_child(parent_block_usage, **serializer.validated_data)
     else:
         # Create a new regular top-level block:
         try:
             result = api.create_library_block(library_key, **serializer.validated_data)
         except api.IncompatibleTypesError as err:
             raise ValidationError(
                 detail={'block_type': str(err)},
             )
     return Response(LibraryXBlockMetadataSerializer(result).data)
class LibraryUsageLocatorV2Tests(TestCase):
    """
    Tests for :class:`.LibraryUsageLocatorV2`
    """
    VALID_LIB_KEY = LibraryLocatorV2("SchoolX", "lib-slug")

    def test_inheritance(self):
        """
        A LibraryUsageLocatorV2 is a usage key
        """
        usage_key = LibraryUsageLocatorV2(self.VALID_LIB_KEY, "problem", "p1")
        self.assertIsInstance(usage_key, UsageKey)

    @ddt.data(
        'lb:MITx:reallyhardproblems:problem:problem1',
        'lb:edX:demo-lib.2019:html:introduction',
        'lb:UnicodeX:i18n-lib:html:έψιλον',
    )
    def test_roundtrip_from_string(self, key):
        usage_key = UsageKey.from_string(key)
        serialized = text_type(usage_key)
        self.assertEqual(key, serialized)

    @ddt.data(
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "video",
            "usage_id": "vid-a4"
        },
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "problem",
            "usage_id": "p1"
        },
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "problem",
            "usage_id": "1"
        },
    )
    def test_roundtrip_from_key(self, key_args):
        key = LibraryUsageLocatorV2(**key_args)
        serialized = text_type(key)
        deserialized = UsageKey.from_string(serialized)
        self.assertEqual(key, deserialized)

    @ddt.data(
        # Keys with invalid lib_key:
        {
            "lib_key": "lib:SchoolX:this-is-a-string",
            "block_type": "problem",
            "usage_id": "p1"
        },
        {
            "lib_key": "foobar",
            "block_type": "problem",
            "usage_id": "p1"
        },
        {
            "lib_key": None,
            "block_type": "problem",
            "usage_id": "p1"
        },
        # Keys with invalid block_type:
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": None,
            "usage_id": "vid-a4"
        },
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "a b",
            "usage_id": "vid-a4"
        },
        # Keys with invalid usage_id:
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "video",
            "usage_id": None
        },
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "video",
            "usage_id": ""
        },
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "video",
            "usage_id": "a b c"
        },
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "video",
            "usage_id": "$!%^"
        },
        {
            "lib_key": VALID_LIB_KEY,
            "block_type": "video",
            "usage_id": 1
        },
    )
    def test_invalid_args(self, key_args):
        with self.assertRaises((InvalidKeyError, TypeError, ValueError)):
            LibraryUsageLocatorV2(**key_args)

    def test_map_into_course(self):
        """
        Test that key.map_into_course(key.course_key) won't raise an error as
        this pattern is used in several places in the LMS that still support
        old mongo.
        """
        key = LibraryUsageLocatorV2(self.VALID_LIB_KEY,
                                    block_type="problem",
                                    usage_id="p1")
        self.assertEqual(key.map_into_course(key.course_key), key)
예제 #28
0
    def test_update_library_blocks(self):
        """
        Test if indexes are updated when blocks in libraries are updated
        """
        def commit_library_and_verify(library_key):
            """
            Commit library changes, and verify that there are no uncommited changes anymore
            """
            last_published = ContentLibraryIndexer.get_libraries([library_key])[0]['last_published']
            self._commit_library_changes(str(library_key))
            response = ContentLibraryIndexer.get_libraries([library_key])[0]
            self.assertEqual(response['has_unpublished_changes'], False)
            self.assertEqual(response['has_unpublished_deletes'], False)
            self.assertGreaterEqual(response['last_published'], last_published)
            return response

        def verify_uncommitted_libraries(library_key, has_unpublished_changes, has_unpublished_deletes):
            """
            Verify uncommitted changes and deletes in the index
            """
            response = ContentLibraryIndexer.get_libraries([library_key])[0]
            self.assertEqual(response['has_unpublished_changes'], has_unpublished_changes)
            self.assertEqual(response['has_unpublished_deletes'], has_unpublished_deletes)
            return response

        lib = self._create_library(slug="test-lib-update-block", title="Title", description="Description")
        library_key = LibraryLocatorV2.from_string(lib['id'])

        # Verify uncommitted new blocks
        block = self._add_block_to_library(lib['id'], "problem", "problem1")
        response = verify_uncommitted_libraries(library_key, True, False)
        self.assertEqual(response['last_published'], None)
        self.assertEqual(response['num_blocks'], 1)
        # Verify committed new blocks
        self._commit_library_changes(lib['id'])
        response = verify_uncommitted_libraries(library_key, False, False)
        self.assertEqual(response['num_blocks'], 1)
        # Verify uncommitted deleted blocks
        self._delete_library_block(block['id'])
        response = verify_uncommitted_libraries(library_key, True, True)
        self.assertEqual(response['num_blocks'], 0)
        # Verify committed deleted blocks
        self._commit_library_changes(lib['id'])
        response = verify_uncommitted_libraries(library_key, False, False)
        self.assertEqual(response['num_blocks'], 0)

        block = self._add_block_to_library(lib['id'], "problem", "problem1")
        self._commit_library_changes(lib['id'])

        # Verify changes to blocks
        # Verify OLX updates on blocks
        self._set_library_block_olx(block["id"], "<problem/>")
        verify_uncommitted_libraries(library_key, True, False)
        commit_library_and_verify(library_key)
        # Verify asset updates on blocks
        self._set_library_block_asset(block["id"], "whatever.png", b"data")
        verify_uncommitted_libraries(library_key, True, False)
        commit_library_and_verify(library_key)
        self._delete_library_block_asset(block["id"], "whatever.png", expect_response=204)
        verify_uncommitted_libraries(library_key, True, False)
        commit_library_and_verify(library_key)

        lib2 = self._create_library(slug="test-lib-update-block-2", title="Title 2", description="Description")
        self._add_block_to_library(lib2["id"], "problem", "problem1")
        self._commit_library_changes(lib2["id"])

        #Verify new links on libraries
        self._link_to_library(lib["id"], "library_2", lib2["id"])
        verify_uncommitted_libraries(library_key, True, False)
        #Verify reverting uncommitted changes
        self._revert_library_changes(lib["id"])
        verify_uncommitted_libraries(library_key, False, False)
예제 #29
0
 def library_key(self):
     """
     Get the LibraryLocatorV2 opaque key for this library
     """
     return LibraryLocatorV2(org=self.org.short_name, slug=self.slug)
예제 #30
0
 def test_invalid_args(self, key_args):
     with self.assertRaises((InvalidKeyError, TypeError, ValueError)):
         LibraryLocatorV2(**key_args)