Example #1
0
    def test_xblock_metadata(self):
        """
        Test the XBlock metadata API
        """
        unit_block_key = library_api.create_library_block(
            self.library.key, "unit", "metadata-u1").usage_key
        problem_key = library_api.create_library_block_child(
            unit_block_key, "problem", "metadata-p1").usage_key
        new_olx = """
        <problem display_name="New Multi Choice Question" max_attempts="5">
            <multiplechoiceresponse>
                <p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p>
                <label>Blockstore is designed to store.</label>
                <choicegroup type="MultipleChoice">
                    <choice correct="false">XBlock metadata only</choice>
                    <choice correct="true">XBlock data/metadata and associated static asset files</choice>
                    <choice correct="false">Static asset files for XBlocks and courseware</choice>
                    <choice correct="false">XModule metadata only</choice>
                </choicegroup>
            </multiplechoiceresponse>
        </problem>
        """.strip()
        library_api.set_library_block_olx(problem_key, new_olx)
        library_api.publish_changes(self.library.key)

        # Now view the problem as Alice:
        client = APIClient()
        client.login(username=self.student_a.username, password='******')

        # Check the metadata API for the unit:
        metadata_view_result = client.get(
            URL_BLOCK_METADATA_URL.format(block_key=unit_block_key),
            {"include": "children,editable_children"},
        )
        self.assertEqual(metadata_view_result.data["children"],
                         [str(problem_key)])
        self.assertEqual(metadata_view_result.data["editable_children"],
                         [str(problem_key)])

        # Check the metadata API for the problem:
        metadata_view_result = client.get(
            URL_BLOCK_METADATA_URL.format(block_key=problem_key),
            {"include": "student_view_data,index_dictionary"},
        )
        self.assertEqual(metadata_view_result.data["block_id"],
                         str(problem_key))
        self.assertEqual(metadata_view_result.data["display_name"],
                         "New Multi Choice Question")
        self.assertNotIn("children", metadata_view_result.data)
        self.assertNotIn("editable_children", metadata_view_result.data)
        self.assertDictContainsSubset(
            {
                "content_type": "CAPA",
                "problem_types": ["multiplechoiceresponse"],
            }, metadata_view_result.data["index_dictionary"])
        self.assertEqual(metadata_view_result.data["student_view_data"],
                         None)  # Capa doesn't provide student_view_data
    def test_complex_xblock_tree(self, _mock1, _mock2):
        """
        Test a Pathway with a deep XBlock tree and inter-bundle-links
        """
        # Create the following XBlock tree
        # Library 2
        #   unit "alpha"
        #     -> link to library 1's bundle ("link1")
        #       -> unit "beta" (in Library 1)
        #         -> html "gamma"
        # Then make sure we can add that whole hierarchy into a pathway.

        beta_key = library_api.create_library_block(self.lib1.key, "unit", "beta").usage_key
        gamma_key = library_api.create_library_block_child(beta_key, "html", "gamma").usage_key
        library_api.set_library_block_olx(gamma_key, '<html>This is gamma.</html>')
        library_api.publish_changes(self.lib1.key)
        library_api.create_bundle_link(self.lib2.key, 'link1', self.lib1.key)
        alpha_key = library_api.create_library_block(self.lib2.key, "unit", "alpha").usage_key
        library_api.set_library_block_olx(alpha_key, '''
            <unit display_name="alpha">
                <xblock-include source="link1" definition="unit/beta" usage_hint="beta" />
            </unit>
        ''')
        library_api.publish_changes(self.lib2.key)

        # Create a new pathway:
        response = self.client.post(URL_CREATE_PATHWAY, {
            "owner_user_id": self.user.id,
            "draft_data": {
                "title": "A Commplex Pathway",
                "description": "Complex pathway for testing",
                "data": {},
                "items": [
                    {"original_usage_id": str(alpha_key)},
                ],
            },
        }, format='json')
        self.assertEqual(response.status_code, 200)
        pathway_id = response.data["id"]
        pathway_url = URL_GET_PATHWAY.format(pathway_id=pathway_id)

        # Now publish the pathway:
        response = self.client.post(pathway_url + 'publish/')
        self.assertEqual(response.status_code, 200)
        # Get the resulting pathway:
        response = self.client.get(pathway_url)
        self.assertEqual(response.status_code, 200)

        # Get the usage ID of the root 'alpha' XBlock:
        alpha_key_pathway = UsageKey.from_string(response.data["published_data"]["items"][0]["usage_id"])
        self.assertNotEqual(alpha_key_pathway, alpha_key)

        block = xblock_api.load_block(alpha_key_pathway, user=self.other_user)
        # Render the block and all its children - make sure even the descendants are rendered:
        fragment = block.render('student_view', context={})
        self.assertIn('This is gamma.', fragment.content)
Example #3
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))
        return Response(LibraryXBlockOlxSerializer({"olx": new_olx_str}).data)
Example #4
0
    def test_mark_complete_via_handler(self):
        """
        Test that a "complete on view" XBlock like the HTML block can be marked
        as complete using the LmsBlockMixin.publish_completion handler.
        """
        block_id = library_api.create_library_block(
            self.library.key, "html", "completable_html").usage_key
        new_olx = """
        <html display_name="Read this HTML">
            <![CDATA[
                <p>This is some <strong>HTML</strong>.</p>
            ]]>
        </html>
        """.strip()
        library_api.set_library_block_olx(block_id, new_olx)
        library_api.publish_changes(self.library.key)

        # We should get a REST API for retrieving completion data; for now use python

        def get_block_completion_status():
            """ Get block completion status (0 to 1) """
            block = xblock_api.load_block(block_id, self.student_a)
            assert hasattr(block, 'publish_completion')
            service = block.runtime.service(block, 'completion')
            return service.get_completions([block_id])[block_id]

        # At first the block is not completed
        assert get_block_completion_status() == 0

        # Now call the 'publish_completion' handler:
        client = APIClient()
        client.login(username=self.student_a.username, password='******')
        result = client.get(
            URL_BLOCK_GET_HANDLER_URL.format(
                block_key=block_id, handler_name='publish_completion'))
        publish_completion_url = result.data["handler_url"]

        # This will test the 'completion' service and the completion event handler:
        result2 = client.post(publish_completion_url, {"completion": 1.0},
                              format='json')
        assert result2.status_code == 200

        # Now the block is completed
        assert get_block_completion_status() == 1
Example #5
0
    def test_i18n(self):
        """
        Test that a block's rendered content respects the Accept-Language header and returns translated content.
        """
        block_id = library_api.create_library_block(self.library.key,
                                                    "problem",
                                                    "i18n_problem").usage_key
        new_olx = """
        <problem display_name="New Multi Choice Question" max_attempts="5">
            <multiplechoiceresponse>
                <p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p>
                <label>Blockstore is designed to store.</label>
                <choicegroup type="MultipleChoice">
                    <choice correct="false">XBlock metadata only</choice>
                    <choice correct="true">XBlock data/metadata and associated static asset files</choice>
                    <choice correct="false">Static asset files for XBlocks and courseware</choice>
                    <choice correct="false">XModule metadata only</choice>
                </choicegroup>
            </multiplechoiceresponse>
        </problem>
        """.strip()
        library_api.set_library_block_olx(block_id, new_olx)
        library_api.publish_changes(self.library.key)

        # Enable the dummy language in darklang
        DarkLangConfig(released_languages='eo',
                       changed_by=self.student_a,
                       enabled=True).save()

        client = APIClient()

        # View the problem without specifying a language
        default_public_view = client.get(
            URL_BLOCK_RENDER_VIEW.format(block_key=block_id,
                                         view_name='public_view'))
        assert 'Submit' in default_public_view.data['content']
        assert 'Süßmït' not in default_public_view.data['content']

        # View the problem and request the dummy language
        dummy_public_view = client.get(URL_BLOCK_RENDER_VIEW.format(
            block_key=block_id, view_name='public_view'),
                                       HTTP_ACCEPT_LANGUAGE='eo')
        assert 'Süßmït' in dummy_public_view.data['content']
        assert 'Submit' not in dummy_public_view.data['content']
Example #6
0
    def test_views_for_anonymous_users(self):
        """
        Test that anonymous users can view XBlock's 'public_view' but not other
        views
        """
        # Create an XBlock
        block_metadata = library_api.create_library_block(self.library.key, "html", "html1")
        block_usage_key = block_metadata.usage_key
        library_api.set_library_block_olx(block_usage_key, "<html>Hello world</html>")
        library_api.publish_changes(self.library.key)

        anon_client = APIClient()
        # View the public_view:
        public_view_result = anon_client.get(
            URL_BLOCK_RENDER_VIEW.format(block_key=block_usage_key, view_name='public_view'),
        )
        assert public_view_result.status_code == 200
        assert 'Hello world' in public_view_result.data['content']

        # Try to view the student_view:
        public_view_result = anon_client.get(
            URL_BLOCK_RENDER_VIEW.format(block_key=block_usage_key, view_name='student_view'),
        )
        assert public_view_result.status_code == 403
Example #7
0
    def test_scores_persisted(self):
        """
        Test that a block's emitted scores are cached in StudentModule

        In the future if there is a REST API to retrieve individual block's
        scores, that should be used instead of checking StudentModule directly.
        """
        block_id = library_api.create_library_block(self.library.key,
                                                    "problem",
                                                    "scored_problem").usage_key
        new_olx = """
        <problem display_name="New Multi Choice Question" max_attempts="5">
            <multiplechoiceresponse>
                <p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p>
                <label>Blockstore is designed to store.</label>
                <choicegroup type="MultipleChoice">
                    <choice correct="false">XBlock metadata only</choice>
                    <choice correct="true">XBlock data/metadata and associated static asset files</choice>
                    <choice correct="false">Static asset files for XBlocks and courseware</choice>
                    <choice correct="false">XModule metadata only</choice>
                </choicegroup>
            </multiplechoiceresponse>
        </problem>
        """.strip()
        library_api.set_library_block_olx(block_id, new_olx)
        library_api.publish_changes(self.library.key)

        # Now view the problem as Alice:
        client = APIClient()
        client.login(username=self.student_a.username, password='******')
        student_view_result = client.get(
            URL_BLOCK_RENDER_VIEW.format(block_key=block_id,
                                         view_name='student_view'))
        problem_key = "input_{}_2_1".format(block_id)
        self.assertIn(problem_key, student_view_result.data["content"])
        # And submit a wrong answer:
        result = client.get(
            URL_BLOCK_GET_HANDLER_URL.format(block_key=block_id,
                                             handler_name='xmodule_handler'))
        problem_check_url = result.data["handler_url"] + 'problem_check'

        submit_result = client.post(problem_check_url,
                                    data={problem_key: "choice_3"})
        self.assertEqual(submit_result.status_code, 200)
        submit_data = json.loads(submit_result.content)
        self.assertDictContainsSubset(
            {
                "current_score": 0,
                "total_possible": 1,
                "attempts_used": 1,
            }, submit_data)

        # Now test that the score is also persisted in StudentModule:
        # If we add a REST API to get an individual block's score, that should be checked instead of StudentModule.
        sm = get_score(self.student_a, block_id)
        self.assertEqual(sm.grade, 0)
        self.assertEqual(sm.max_grade, 1)

        # And submit a correct answer:
        submit_result = client.post(problem_check_url,
                                    data={problem_key: "choice_1"})
        self.assertEqual(submit_result.status_code, 200)
        submit_data = json.loads(submit_result.content)
        self.assertDictContainsSubset(
            {
                "current_score": 1,
                "total_possible": 1,
                "attempts_used": 2,
            }, submit_data)
        # Now test that the score is also updated in StudentModule:
        # If we add a REST API to get an individual block's score, that should be checked instead of StudentModule.
        sm = get_score(self.student_a, block_id)
        self.assertEqual(sm.grade, 1)
        self.assertEqual(sm.max_grade, 1)