Beispiel #1
0
 def get(self, request, usage_key_str):
     """
     Get metadata about an existing XBlock in the content library
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     result = api.get_library_block(key)
     return Response(LibraryXBlockMetadataSerializer(result).data)
Beispiel #2
0
def create_library_block(library_key, block_type, definition_id):
    """
    Create a new XBlock in this library of the specified type (e.g. "html").

    The 'definition_id' value (which should be a string like "problem1") will be
    used as both the definition_id and the usage_id.
    """
    assert isinstance(library_key, LibraryLocatorV2)
    ref = ContentLibrary.objects.get_by_key(library_key)
    # Make sure the proposed ID will be valid:
    validate_unicode_slug(definition_id)
    # Ensure the XBlock type is valid and installed:
    XBlock.load_class(block_type)  # Will raise an exception if invalid
    # Make sure the new ID is not taken already:
    new_usage_id = definition_id  # Since this is a top level XBlock, usage_id == definition_id
    usage_key = LibraryUsageLocatorV2(
        lib_key=library_key,
        block_type=block_type,
        usage_id=new_usage_id,
    )
    library_context = get_learning_context_impl(usage_key)
    if library_context.definition_for_usage(usage_key) is not None:
        raise LibraryBlockAlreadyExists("An XBlock with ID '{}' already exists".format(new_usage_id))

    new_definition_xml = '<{}/>'.format(block_type)  # xss-lint: disable=python-wrap-html
    path = "{}/{}/definition.xml".format(block_type, definition_id)
    # Write the new XML/OLX file into the library bundle's draft
    draft = get_or_create_bundle_draft(ref.bundle_uuid, DRAFT_NAME)
    write_draft_file(draft.uuid, path, new_definition_xml.encode('utf-8'))
    # Clear the bundle cache so everyone sees the new block immediately:
    BundleCache(ref.bundle_uuid, draft_name=DRAFT_NAME).clear()
    # Now return the metadata about the new block:
    return get_library_block(usage_key)
Beispiel #3
0
 def get(self, request, usage_key_str):
     """
     Get the block's OLX
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     xml_str = api.get_library_block_olx(key)
     return Response(LibraryXBlockOlxSerializer({"olx": xml_str}).data)
Beispiel #4
0
def get_library_blocks(library_key, text_search=None, block_types=None):
    """
    Get the list of top-level XBlocks in the specified library.

    Returns a list of LibraryXBlockMetadata objects
    """
    metadata = None
    if LibraryBlockIndexer.indexing_is_enabled():
        try:
            filter_terms = {
                'library_key': [str(library_key)],
                'is_child': [False],
            }
            if block_types:
                filter_terms['block_type'] = block_types
            metadata = [
                {
                    **item,
                    "id": LibraryUsageLocatorV2.from_string(item['id']),
                }
                for item in LibraryBlockIndexer.get_items(filter_terms=filter_terms, text_search=text_search)
                if item is not None
            ]
        except ElasticConnectionError as e:
            log.exception(e)

    # If indexing is disabled, or connection to elastic failed
    if metadata is None:
        metadata = []
        ref = ContentLibrary.objects.get_by_key(library_key)
        lib_bundle = LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME)
        usages = lib_bundle.get_top_level_usages()

        for usage_key in usages:
            # For top-level definitions, we can go from definition key to usage key using the following, but this would
            # not work for non-top-level blocks as they may have multiple usages. Top level blocks are guaranteed to
            # have only a single usage in the library, which is part of the definition of top level block.
            def_key = lib_bundle.definition_for_usage(usage_key)
            display_name = get_block_display_name(def_key)
            text_match = (text_search is None or
                          text_search.lower() in display_name.lower() or
                          text_search.lower() in str(usage_key).lower())
            type_match = (block_types is None or usage_key.block_type in block_types)
            if text_match and type_match:
                metadata.append({
                    "id": usage_key,
                    "def_key": def_key,
                    "display_name": display_name,
                    "has_unpublished_changes": lib_bundle.does_definition_have_unpublished_changes(def_key),
                })

    return [
        LibraryXBlockMetadata(
            usage_key=item['id'],
            def_key=item['def_key'],
            display_name=item['display_name'],
            has_unpublished_changes=item['has_unpublished_changes'],
        )
        for item in metadata
    ]
Beispiel #5
0
 def get(self, request, usage_key_str):
     """
     List the static asset files belonging to this block.
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     files = api.get_library_block_static_asset_files(key)
     return Response(LibraryXBlockStaticFilesSerializer({"files": files}).data)
Beispiel #6
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)
Beispiel #7
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)
Beispiel #8
0
 def get(self, request, usage_key_str):
     """
     List the static asset files belonging to this block.
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     api.require_permission_for_library_key(key.lib_key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY)
     files = api.get_library_block_static_asset_files(key)
     return Response(LibraryXBlockStaticFilesSerializer({"files": files}).data)
Beispiel #9
0
 def get(self, request, usage_key_str):
     """
     Get metadata about an existing XBlock in the content library
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     api.require_permission_for_library_key(key.lib_key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY)
     result = api.get_library_block(key)
     return Response(LibraryXBlockMetadataSerializer(result).data)
Beispiel #10
0
 def get(self, request, usage_key_str):
     """
     Get the block's OLX
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     api.require_permission_for_library_key(key.lib_key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY)
     xml_str = api.get_library_block_olx(key)
     return Response(LibraryXBlockOlxSerializer({"olx": xml_str}).data)
Beispiel #11
0
 def get(self, request, usage_key_str, file_path):
     """
     Get a static asset file belonging to this block.
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     files = api.get_library_block_static_asset_files(key)
     for f in files:
         if f.path == file_path:
             return Response(LibraryXBlockStaticFileSerializer(f).data)
     raise NotFound
Beispiel #12
0
 def delete(self, request, usage_key_str, file_path):  # pylint: disable=unused-argument
     """
     Delete a static asset file belonging to this block.
     """
     usage_key = LibraryUsageLocatorV2.from_string(usage_key_str)
     try:
         api.delete_library_block_static_asset_file(usage_key, file_path)
     except ValueError:
         raise ValidationError("Invalid file path")
     return Response(status=status.HTTP_204_NO_CONTENT)
 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)
Beispiel #14
0
def create_library_block(library_key, block_type, definition_id):
    """
    Create a new XBlock in this library of the specified type (e.g. "html").

    The 'definition_id' value (which should be a string like "problem1") will be
    used as both the definition_id and the usage_id.
    """
    assert isinstance(library_key, LibraryLocatorV2)
    ref = ContentLibrary.objects.get_by_key(library_key)
    if ref.type != COMPLEX:
        if block_type != ref.type:
            raise IncompatibleTypesError(
                _('Block type "{block_type}" is not compatible with library type "{library_type}".'
                  ).format(
                      block_type=block_type,
                      library_type=ref.type,
                  ))
    lib_bundle = LibraryBundle(library_key,
                               ref.bundle_uuid,
                               draft_name=DRAFT_NAME)
    # Total number of blocks should not exceed the maximum allowed
    total_blocks = len(lib_bundle.get_top_level_usages())
    if total_blocks + 1 > settings.MAX_BLOCKS_PER_CONTENT_LIBRARY:
        raise BlockLimitReachedError(
            _(u"Library cannot have more than {} XBlocks").format(
                settings.MAX_BLOCKS_PER_CONTENT_LIBRARY))
    # Make sure the proposed ID will be valid:
    validate_unicode_slug(definition_id)
    # Ensure the XBlock type is valid and installed:
    XBlock.load_class(block_type)  # Will raise an exception if invalid
    # Make sure the new ID is not taken already:
    new_usage_id = definition_id  # Since this is a top level XBlock, usage_id == definition_id
    usage_key = LibraryUsageLocatorV2(
        lib_key=library_key,
        block_type=block_type,
        usage_id=new_usage_id,
    )
    library_context = get_learning_context_impl(usage_key)
    if library_context.definition_for_usage(usage_key) is not None:
        raise LibraryBlockAlreadyExists(
            "An XBlock with ID '{}' already exists".format(new_usage_id))

    new_definition_xml = '<{}/>'.format(
        block_type)  # xss-lint: disable=python-wrap-html
    path = "{}/{}/definition.xml".format(block_type, definition_id)
    # Write the new XML/OLX file into the library bundle's draft
    draft = get_or_create_bundle_draft(ref.bundle_uuid, DRAFT_NAME)
    write_draft_file(draft.uuid, path, new_definition_xml.encode('utf-8'))
    # Clear the bundle cache so everyone sees the new block immediately:
    BundleCache(ref.bundle_uuid, draft_name=DRAFT_NAME).clear()
    # Now return the metadata about the new block:
    LIBRARY_BLOCK_CREATED.send(sender=None,
                               library_key=ref.library_key,
                               usage_key=usage_key)
    return get_library_block(usage_key)
Beispiel #15
0
    def get(self, request, usage_key_str):
        """
        Get the LTI launch URL for the XBlock.
        """
        key = LibraryUsageLocatorV2.from_string(usage_key_str)
        api.require_permission_for_library_key(key.lib_key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY)

        # Get the block to validate its existence
        api.get_library_block(key)
        lti_login_url = f"{reverse('content_libraries:lti-launch')}?id={key}"
        return Response({"lti_url": lti_login_url})
Beispiel #16
0
    def get_all_usages(self):
        """
        Get usage keys of all the blocks in this bundle
        """
        usage_keys = []
        for olx_file_path in self.get_olx_files():
            block_type, usage_id, _unused = olx_file_path.split('/')
            usage_key = LibraryUsageLocatorV2(self.library_key, block_type, usage_id)
            usage_keys.append(usage_key)

        return usage_keys
Beispiel #17
0
 def get(self, request, usage_key_str, file_path):
     """
     Get a static asset file belonging to this block.
     """
     key = LibraryUsageLocatorV2.from_string(usage_key_str)
     api.require_permission_for_library_key(key.lib_key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY)
     files = api.get_library_block_static_asset_files(key)
     for f in files:
         if f.path == file_path:
             return Response(LibraryXBlockStaticFileSerializer(f).data)
     raise NotFound
Beispiel #18
0
    def get_top_level_usages(self):
        """
        Get the set of usage keys in this bundle that have no parent.
        """
        own_usage_keys = []
        for olx_file_path in self.get_olx_files():
            block_type, usage_id, _unused = olx_file_path.split('/')
            usage_key = LibraryUsageLocatorV2(self.library_key, block_type, usage_id)
            own_usage_keys.append(usage_key)

        usage_keys_with_parents = self.get_bundle_includes().keys()
        return [usage_key for usage_key in own_usage_keys if usage_key not in usage_keys_with_parents]
Beispiel #19
0
 def delete(self, request, usage_key_str, file_path):
     """
     Delete a static asset file belonging to this block.
     """
     usage_key = LibraryUsageLocatorV2.from_string(usage_key_str)
     api.require_permission_for_library_key(
         usage_key.lib_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY,
     )
     try:
         api.delete_library_block_static_asset_file(usage_key, file_path)
     except ValueError:
         raise ValidationError("Invalid file path")
     return Response(status=status.HTTP_204_NO_CONTENT)
Beispiel #20
0
def usage_for_child_include(parent_usage, parent_definition, parsed_include):
    """
    Get the usage ID for a child XBlock, given the parent's keys and the
    <xblock-include /> element that specifies the child.

    Consider two bundles, one with three definitions:
        main-unit, html1, subunit1
    And a second bundle with two definitions:
        unit1, html1
    Note that both bundles have a definition called "html1". Now, with the
    following tree structure, where "unit/unit1" and the second "html/html1"
    are in a linked bundle:

    <unit> in unit/main-unit/definition.xml
        <xblock-include definition="html/html1" />
        <xblock-include definition="unit/subunit1" />
            <xblock-include source="linked_bundle" definition="unit/unit1" usage="alias1" />
                <xblock-include definition="html/html1" />

    The following usage IDs would result:

    main-unit
        html1
        subunit1
            alias1
                alias1-html1

    Notice that "html1" in the linked bundle is prefixed so its ID stays
    unique from the "html1" in the original library.
    """
    assert isinstance(parent_usage, LibraryUsageLocatorV2)
    usage_id = parsed_include.usage_hint if parsed_include.usage_hint else parsed_include.definition_id
    library_bundle_uuid = bundle_uuid_for_library_key(parent_usage.context_key)
    # Is the parent usage from the same bundle as the library?
    parent_usage_from_library_bundle = parent_definition.bundle_uuid == library_bundle_uuid
    if not parent_usage_from_library_bundle:
        # This XBlock has been linked in to the library via a chain of one
        # or more bundle links. In order to keep usage_id collisions from
        # happening, any descdenants of the first linked block must have
        # their usage_id prefixed with the parent usage's usage_id.
        # (It would be possible to only change the prefix when the block is
        # a child of a block with an explicit usage="" attribute on its
        # <xblock-include> but that requires much more complex logic.)
        usage_id = parent_usage.usage_id + "-" + usage_id
    return LibraryUsageLocatorV2(
        lib_key=parent_usage.lib_key,
        block_type=parsed_include.block_type,
        usage_id=usage_id,
    )
def index_library(sender, library_key, **kwargs):  # pylint: disable=unused-argument
    """
    Index library when created or updated, or when its blocks are modified.
    """
    if ContentLibraryIndexer.indexing_is_enabled():
        try:
            ContentLibraryIndexer.index_items([library_key])
            if kwargs.get('update_blocks', False):
                blocks = LibraryBlockIndexer.get_items(filter_terms={
                    'library_key': str(library_key)
                })
                usage_keys = [LibraryUsageLocatorV2.from_string(block['id']) for block in blocks]
                LibraryBlockIndexer.index_items(usage_keys)
        except ElasticConnectionError as e:
            log.exception(e)
Beispiel #22
0
    def delete(self, request, usage_key_str):  # pylint: disable=unused-argument
        """
        Delete a usage of a block from the library (and any children it has).

        If this is the only usage of the block's definition within this library,
        both the definition and the usage will be deleted. If this is only one
        of several usages, the definition will be kept. Usages by linked bundles
        are ignored and will not prevent deletion of the definition.

        If the usage points to a definition in a linked bundle, the usage will
        be deleted but the link and the linked bundle will be unaffected.
        """
        key = LibraryUsageLocatorV2.from_string(usage_key_str)
        api.delete_library_block(key)
        return Response({})
Beispiel #23
0
    def post(self, request, usage_key_str):
        """
        Replace the block's OLX.

        This API is only meant for use by developers or API client applications.
        Very little validation is done.
        """
        key = LibraryUsageLocatorV2.from_string(usage_key_str)
        serializer = LibraryXBlockOlxSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        new_olx_str = serializer.validated_data["olx"]
        try:
            api.set_library_block_olx(key, new_olx_str)
        except ValueError as err:
            raise ValidationError(detail=str(err))
        return Response(LibraryXBlockOlxSerializer({"olx": new_olx_str}).data)
Beispiel #24
0
 def put(self, request, usage_key_str, file_path):
     """
     Replace a static asset file belonging to this block.
     """
     usage_key = LibraryUsageLocatorV2.from_string(usage_key_str)
     file_wrapper = request.data['content']
     if file_wrapper.size > 20 * 1024 * 1024:  # > 20 MiB
         # In the future, we need a way to use file_wrapper.chunks() to read
         # the file in chunks and stream that to Blockstore, but Blockstore
         # currently lacks an API for streaming file uploads.
         raise ValidationError("File too big")
     file_content = file_wrapper.read()
     try:
         result = api.add_library_block_static_asset_file(usage_key, file_path, file_content)
     except ValueError:
         raise ValidationError("Invalid file path")
     return Response(LibraryXBlockStaticFileSerializer(result).data)
Beispiel #25
0
    def post(self, request, usage_key_str):
        """
        Replace the block's OLX.

        This API is only meant for use by developers or API client applications.
        Very little validation is done.
        """
        key = LibraryUsageLocatorV2.from_string(usage_key_str)
        api.require_permission_for_library_key(key.lib_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
        serializer = LibraryXBlockOlxSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        new_olx_str = serializer.validated_data["olx"]
        try:
            api.set_library_block_olx(key, new_olx_str)
        except ValueError as err:
            raise ValidationError(detail=str(err))  # lint-amnesty, pylint: disable=raise-missing-from
        return Response(LibraryXBlockOlxSerializer({"olx": new_olx_str}).data)
Beispiel #26
0
    def post(self, request):
        """
        Process LTI platform launch requests.
        """

        # Parse LTI launch message.

        try:
            self.launch_message = self.get_launch_message()
        except LtiException as exc:
            log.exception('LTI 1.3: Tool launch failed: %s', exc)
            return self._bad_request_response()

        log.info("LTI 1.3: Launch message body: %s",
                 json.dumps(self.launch_data))

        # Parse content key.

        usage_key_str = request.GET.get('id')
        if not usage_key_str:
            return self._bad_request_response()
        usage_key = LibraryUsageLocatorV2.from_string(usage_key_str)
        log.info('LTI 1.3: Launch block: id=%s', usage_key)

        # Authenticate the launch and setup LTI profiles.

        edx_user = self._authenticate_and_login(usage_key)
        if not edx_user:
            return self._bad_request_response()

        # Get the block.

        self.block = xblock_api.load_block(
            usage_key,
            user=self.request.user)

        # Handle Assignment and Grade Service request.

        self.handle_ags()

        # Render context and response.
        context = self.get_context_data()
        response = self.render_to_response(context)
        mark_user_change_as_expected(edx_user.id)
        return response
    def test_index_block(self):
        """
        Test if libraries are being indexed correctly
        """
        lib = self._create_library(slug="test-lib-index-1", title="Title 1", description="Description")
        block1 = self._add_block_to_library(lib['id'], "problem", "problem1")
        block2 = self._add_block_to_library(lib['id'], "problem", "problem2")

        self.assertEqual(len(LibraryBlockIndexer.get_items()), 2)

        for block in [block1, block2]:
            usage_key = LibraryUsageLocatorV2.from_string(block['id'])
            response = LibraryBlockIndexer.get_items([usage_key])[0]

            self.assertEqual(response['id'], block['id'])
            self.assertEqual(response['def_key'], block['def_key'])
            self.assertEqual(response['block_type'], block['block_type'])
            self.assertEqual(response['display_name'], block['display_name'])
            self.assertEqual(response['has_unpublished_changes'], block['has_unpublished_changes'])
Beispiel #28
0
 def put(self, request, usage_key_str, file_path):
     """
     Replace a static asset file belonging to this block.
     """
     usage_key = LibraryUsageLocatorV2.from_string(usage_key_str)
     api.require_permission_for_library_key(
         usage_key.lib_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY,
     )
     file_wrapper = request.data['content']
     if file_wrapper.size > 20 * 1024 * 1024:  # > 20 MiB
         # In the future, we need a way to use file_wrapper.chunks() to read
         # the file in chunks and stream that to Blockstore, but Blockstore
         # currently lacks an API for streaming file uploads.
         raise ValidationError("File too big")
     file_content = file_wrapper.read()
     try:
         result = api.add_library_block_static_asset_file(usage_key, file_path, file_content)
     except ValueError:
         raise ValidationError("Invalid file path")  # lint-amnesty, pylint: disable=raise-missing-from
     return Response(LibraryXBlockStaticFileSerializer(result).data)
Beispiel #29
0
    def test_upsert_from_ags_launch(self):
        """
        Give no graded resource
        When get_or_create_from_launch twice
        Then created at first, retrieved at second.
        """

        resource_id = 'resource-foobar'
        usage_key = 'lb:foo:bar:fooz:barz'
        lineitem = 'http://canvas.docker/api/lti/courses/1/line_items/7'
        resource_endpoint = {
            "lineitem":
            lineitem,
            "scope": [
                "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
                "https://purl.imsglobal.org/spec/lti-ags/scope/score"
            ],
        }
        resource_link = {
            "id": resource_id,
            "title": "A custom title",
        }

        profile = LtiProfile.objects.get_or_create_from_claims(iss=self.iss,
                                                               aud=self.aud,
                                                               sub=self.sub)
        block_mock = mock.Mock()
        block_mock.scope_ids.usage_id = LibraryUsageLocatorV2.from_string(
            usage_key)
        res = LtiGradedResource.objects.upsert_from_ags_launch(
            profile.user, block_mock, resource_endpoint, resource_link)

        self.assertEqual(resource_id, res.resource_id)
        self.assertEqual(lineitem, res.ags_lineitem)
        self.assertEqual(usage_key, str(res.usage_key))
        self.assertEqual(profile, res.profile)

        res2 = LtiGradedResource.objects.upsert_from_ags_launch(
            profile.user, block_mock, resource_endpoint, resource_link)

        self.assertEqual(res, res2)
Beispiel #30
0
def score_changed_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Match the score event to an LTI resource and update.
    """

    lti_enabled = (settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES') and
                   settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES_LTI_TOOL'))
    if not lti_enabled:
        return

    modified = kwargs.get('modified')
    usage_id = kwargs.get('usage_id')
    user_id = kwargs.get('user_id')
    weighted_earned = kwargs.get('weighted_earned')
    weighted_possible = kwargs.get('weighted_possible')

    if None in (modified, usage_id, user_id, weighted_earned,
                weighted_possible):
        log.debug(
            "LTI 1.3: Score Signal: Missing a required parameters, "
            "ignoring: kwargs=%s", kwargs)
        return
    try:
        usage_key = LibraryUsageLocatorV2.from_string(usage_id)
    except InvalidKeyError:
        log.debug(
            "LTI 1.3: Score Signal: Not a content libraries v2 usage key, "
            "ignoring: usage_id=%s", usage_id)
        return
    try:
        resource = LtiGradedResource.objects.get_from_user_id(
            user_id, usage_key=usage_key)
    except LtiGradedResource.DoesNotExist:
        log.debug(
            "LTI 1.3: Score Signal: Unknown resource, ignoring: kwargs=%s",
            kwargs)
    else:
        resource.update_score(weighted_earned, weighted_possible, modified)
        log.info("LTI 1.3: Score Signal: Grade upgraded: resource; %s",
                 resource)