Exemple #1
0
 def id_to_location(course_id):
     '''Convert the given course_id (org/course/name) to a location object.
     Throws ValueError if course_id is of the wrong format.
     '''
     org, course, name = course_id.split('/')
     return Location('i4x', org, course, 'course', name)
Exemple #2
0
 def test_jumpto_invalid_location(self):
     location = Location('i4x', 'edX', 'toy', 'NoSuchPlace', None)
     jumpto_url = '%s/%s/jump_to/%s' % (
         '/courses', self.course_name, location)
     response = self.client.get(jumpto_url)
     self.assertEqual(response.status_code, 404)
Exemple #3
0
def test_equality():
    assert_equals(Location('tag', 'org', 'course', 'category', 'name'),
                  Location('tag', 'org', 'course', 'category', 'name'))

    assert_not_equals(Location('tag', 'org', 'course', 'category', 'name1'),
                      Location('tag', 'org', 'course', 'category', 'name'))
class CombinedOpenEndedModuleTest(unittest.TestCase):
    """
    Unit tests for the combined open ended xmodule
    """
    location = Location(
        ["i4x", "edX", "open_ended", "combinedopenended", "SampleQuestion"])
    definition_template = """
                    <combinedopenended attempts="10000">
                    {rubric}
                    {prompt}
                    <task>
                    {task1}
                    </task>
                    <task>
                    {task2}
                    </task>
                    </combinedopenended>
                    """
    prompt = "<prompt>This is a question prompt</prompt>"
    rubric = '''<rubric><rubric>
        <category>
        <description>Response Quality</description>
        <option>The response is not a satisfactory answer to the question.  It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option>
        <option>Second option</option>
        </category>
         </rubric></rubric>'''
    max_score = 1

    metadata = {'attempts': '10', 'max_score': max_score}

    static_data = {
        'max_attempts': 20,
        'prompt': prompt,
        'rubric': rubric,
        'max_score': max_score,
        'display_name': 'Name',
        'accept_file_upload': False,
        'close_date': "",
        's3_interface': test_util_open_ended.S3_INTERFACE,
        'open_ended_grading_interface':
        test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
        'skip_basic_checks': False,
        'graded': True,
    }

    oeparam = etree.XML('''
      <openendedparam>
            <initial_display>Enter essay here.</initial_display>
            <answer_display>This is the answer.</answer_display>
            <grader_payload>{"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
        </openendedparam>
    ''')

    task_xml1 = '''
                <selfassessment>
                    <hintprompt>
                        What hint about this problem would you give to someone?
                    </hintprompt>
                    <submitmessage>
                        Save Succcesful.  Thanks for participating!
                    </submitmessage>
                </selfassessment>
            '''
    task_xml2 = '''
    <openended min_score_to_attempt="1" max_score_to_attempt="1">
            <openendedparam>
                    <initial_display>Enter essay here.</initial_display>
                    <answer_display>This is the answer.</answer_display>
                    <grader_payload>{"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
           </openendedparam>
    </openended>'''
    definition = {
        'prompt': etree.XML(prompt),
        'rubric': etree.XML(rubric),
        'task_xml': [task_xml1, task_xml2]
    }
    full_definition = definition_template.format(prompt=prompt,
                                                 rubric=rubric,
                                                 task1=task_xml1,
                                                 task2=task_xml2)
    descriptor = Mock(data=full_definition)
    test_system = get_test_system()
    test_system.open_ended_grading_interface = None
    combinedoe_container = CombinedOpenEndedModule(
        descriptor=descriptor,
        runtime=test_system,
        field_data=DictFieldData({
            'data': full_definition,
            'weight': '1',
        }),
        scope_ids=ScopeIds(None, None, None, None),
    )

    def setUp(self):
        self.combinedoe = CombinedOpenEndedV1Module(
            self.test_system,
            self.location,
            self.definition,
            self.descriptor,
            static_data=self.static_data,
            metadata=self.metadata,
            instance_state=self.static_data)

    def test_get_tag_name(self):
        """
        Test to see if the xml tag name is correct
        """
        name = self.combinedoe.get_tag_name("<t>Tag</t>")
        self.assertEqual(name, "t")

    def test_get_last_response(self):
        """
        See if we can parse the last response
        """
        response_dict = self.combinedoe.get_last_response(0)
        self.assertEqual(response_dict['type'], "selfassessment")
        self.assertEqual(response_dict['max_score'], self.max_score)
        self.assertEqual(response_dict['state'],
                         CombinedOpenEndedV1Module.INITIAL)

    def test_update_task_states(self):
        """
        See if we can update the task states properly
        """
        changed = self.combinedoe.update_task_states()
        self.assertFalse(changed)

        current_task = self.combinedoe.current_task
        current_task.change_state(CombinedOpenEndedV1Module.DONE)
        changed = self.combinedoe.update_task_states()

        self.assertTrue(changed)

    def test_get_max_score(self):
        """
        Try to get the max score of the problem
        """
        self.combinedoe.update_task_states()
        self.combinedoe.state = "done"
        self.combinedoe.is_scored = True
        max_score = self.combinedoe.max_score()
        self.assertEqual(max_score, 1)

    def test_container_get_max_score(self):
        """
        See if we can get the max score from the actual xmodule
        """
        #The progress view requires that this function be exposed
        max_score = self.combinedoe_container.max_score()
        self.assertEqual(max_score, None)

    def test_container_get_progress(self):
        """
        See if we can get the progress from the actual xmodule
        """
        progress = self.combinedoe_container.max_score()
        self.assertEqual(progress, None)

    def test_get_progress(self):
        """
        Test if we can get the correct progress from the combined open ended class
        """
        self.combinedoe.update_task_states()
        self.combinedoe.state = "done"
        self.combinedoe.is_scored = True
        progress = self.combinedoe.get_progress()
        self.assertIsInstance(progress, Progress)

        # progress._a is the score of the xmodule, which is 0 right now.
        self.assertEqual(progress._a, 0)

        # progress._b is the max_score (which is 1), divided by the weight (which is 1).
        self.assertEqual(progress._b, 1)

    def test_container_weight(self):
        """
        Check the problem weight in the container
        """
        weight = self.combinedoe_container.weight
        self.assertEqual(weight, 1)

    def test_container_child_weight(self):
        """
        Test the class to see if it picks up the right weight
        """
        weight = self.combinedoe_container.child_module.weight
        self.assertEqual(weight, 1)

    def test_get_score(self):
        """
        See if scoring works
        """
        score_dict = self.combinedoe.get_score()
        self.assertEqual(score_dict['score'], 0)
        self.assertEqual(score_dict['total'], 1)

    def test_alternate_orderings(self):
        """
        Try multiple ordering of definitions to see if the problem renders different steps correctly.
        """
        t1 = self.task_xml1
        t2 = self.task_xml2
        xml_to_test = [[t1], [t2], [t1, t1], [t1, t2], [t2, t2], [t2, t1],
                       [t1, t2, t1]]
        for xml in xml_to_test:
            definition = {
                'prompt': etree.XML(self.prompt),
                'rubric': etree.XML(self.rubric),
                'task_xml': xml
            }
            descriptor = Mock(data=definition)
            combinedoe = CombinedOpenEndedV1Module(
                self.test_system,
                self.location,
                definition,
                descriptor,
                static_data=self.static_data,
                metadata=self.metadata,
                instance_state=self.static_data)

            changed = combinedoe.update_task_states()
            self.assertFalse(changed)

            combinedoe = CombinedOpenEndedV1Module(
                self.test_system,
                self.location,
                definition,
                descriptor,
                static_data=self.static_data,
                metadata=self.metadata,
                instance_state={'task_states': TEST_STATE_SA})

            combinedoe = CombinedOpenEndedV1Module(
                self.test_system,
                self.location,
                definition,
                descriptor,
                static_data=self.static_data,
                metadata=self.metadata,
                instance_state={'task_states': TEST_STATE_SA_IN})

    def test_get_score_realistic(self):
        """
        Try to parse the correct score from a json instance state
        """
        instance_state = json.loads(MOCK_INSTANCE_STATE)
        rubric = """
        <rubric>
            <rubric>
                <category>
                    <description>Response Quality</description>
                    <option>The response is not a satisfactory answer to the question.  It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option>
                    <option>The response is a marginal answer to the question.  It may contain some elements of a proficient response, but it is inaccurate or incomplete.</option>
                    <option>The response is a proficient answer to the question.  It is generally correct, although it may contain minor inaccuracies.  There is limited evidence of higher-order thinking.</option>
                    <option>The response is correct, complete, and contains evidence of higher-order thinking.</option>
                </category>
            </rubric>
        </rubric>
        """
        definition = {
            'prompt': etree.XML(self.prompt),
            'rubric': etree.XML(rubric),
            'task_xml': [self.task_xml1, self.task_xml2]
        }
        descriptor = Mock(data=definition)
        combinedoe = CombinedOpenEndedV1Module(self.test_system,
                                               self.location,
                                               definition,
                                               descriptor,
                                               static_data=self.static_data,
                                               metadata=self.metadata,
                                               instance_state=instance_state)
        score_dict = combinedoe.get_score()
        self.assertEqual(score_dict['score'], 15.0)
        self.assertEqual(score_dict['total'], 15.0)

    def generate_oe_module(self, task_state, task_number, task_xml):
        """
        Return a combined open ended module with the specified parameters
        """
        definition = {
            'prompt': etree.XML(self.prompt),
            'rubric': etree.XML(self.rubric),
            'task_xml': task_xml
        }
        descriptor = Mock(data=definition)
        instance_state = {'task_states': task_state, 'graded': True}
        if task_number is not None:
            instance_state.update({'current_task_number': task_number})
        combinedoe = CombinedOpenEndedV1Module(self.test_system,
                                               self.location,
                                               definition,
                                               descriptor,
                                               static_data=self.static_data,
                                               metadata=self.metadata,
                                               instance_state=instance_state)
        return combinedoe

    def ai_state_reset(self, task_state, task_number=None):
        """
        See if state is properly reset
        """
        combinedoe = self.generate_oe_module(task_state, task_number,
                                             [self.task_xml2])
        html = combinedoe.get_html()
        self.assertIsInstance(html, basestring)

        score = combinedoe.get_score()
        if combinedoe.is_scored:
            self.assertEqual(score['score'], 0)
        else:
            self.assertEqual(score['score'], None)

    def ai_state_success(self,
                         task_state,
                         task_number=None,
                         iscore=2,
                         tasks=None):
        """
        See if state stays the same
        """
        if tasks is None:
            tasks = [self.task_xml1, self.task_xml2]
        combinedoe = self.generate_oe_module(task_state, task_number, tasks)
        html = combinedoe.get_html()
        self.assertIsInstance(html, basestring)
        score = combinedoe.get_score()
        self.assertEqual(int(score['score']), iscore)

    def test_ai_state_reset(self):
        self.ai_state_reset(TEST_STATE_AI)

    def test_ai_state2_reset(self):
        self.ai_state_reset(TEST_STATE_AI2)

    def test_ai_invalid_state(self):
        self.ai_state_reset(TEST_STATE_AI2_INVALID)

    def test_ai_state_rest_task_number(self):
        self.ai_state_reset(TEST_STATE_AI, task_number=2)
        self.ai_state_reset(TEST_STATE_AI, task_number=5)
        self.ai_state_reset(TEST_STATE_AI, task_number=1)
        self.ai_state_reset(TEST_STATE_AI, task_number=0)

    def test_ai_state_success(self):
        self.ai_state_success(TEST_STATE_AI)

    def test_state_single(self):
        self.ai_state_success(TEST_STATE_SINGLE, iscore=12)

    def test_state_pe_single(self):
        self.ai_state_success(TEST_STATE_PE_SINGLE,
                              iscore=0,
                              tasks=[self.task_xml2])
class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
    """
    Test if student is able to upload images properly.
    """
    problem_location = Location([
        "i4x", "edX", "open_ended", "combinedopenended",
        "SampleQuestionImageUpload"
    ])
    answer_text = "Hello, this is my amazing answer."
    file_text = "Hello, this is my amazing file."
    file_name = "Student file 1"
    answer_link = "http://www.edx.org"
    autolink_tag = "<a href="

    def setUp(self):
        self.test_system = get_test_system()
        self.test_system.open_ended_grading_interface = None
        self.test_system.s3_interface = test_util_open_ended.S3_INTERFACE
        self.test_system.xqueue['interface'] = Mock(send_to_queue=Mock(
            side_effect=[1, "queued"]))
        self.setup_modulestore(COURSE)

    def test_file_upload_fail(self):
        """
        Test to see if a student submission without a file attached fails.
        """
        module = self.get_module_from_location(self.problem_location, COURSE)

        #Simulate a student saving an answer
        response = module.handle_ajax("save_answer",
                                      {"student_answer": self.answer_text})
        response = json.loads(response)
        self.assertFalse(response['success'])
        self.assertIn('error', response)

    @patch('xmodule.open_ended_grading_classes.openendedchild.S3Connection',
           test_util_open_ended.MockS3Connection)
    @patch('xmodule.open_ended_grading_classes.openendedchild.Key',
           test_util_open_ended.MockS3Key)
    def test_file_upload_success(self):
        """
        Test to see if a student submission with a file is handled properly.
        """
        module = self.get_module_from_location(self.problem_location, COURSE)

        #Simulate a student saving an answer with a file
        response = module.handle_ajax(
            "save_answer", {
                "student_answer": self.answer_text,
                "valid_files_attached": True,
                "student_file":
                [MockUploadedFile(self.file_name, self.file_text)],
            })

        response = json.loads(response)
        self.assertTrue(response['success'])
        self.assertIn(self.file_name, response['student_response'])
        self.assertIn(self.autolink_tag, response['student_response'])

    def test_link_submission_success(self):
        """
        Students can submit links instead of files.  Check that the link is properly handled.
        """
        module = self.get_module_from_location(self.problem_location, COURSE)

        # Simulate a student saving an answer with a link.
        response = module.handle_ajax("save_answer", {
            "student_answer":
            "{0} {1}".format(self.answer_text, self.answer_link)
        })

        response = json.loads(response)

        self.assertTrue(response['success'])
        self.assertIn(self.answer_link, response['student_response'])
        self.assertIn(self.autolink_tag, response['student_response'])
 def test_immutable(self, attr):
     loc = Location('t://o/c/c/n@r')
     with self.assertRaises(AttributeError):
         setattr(loc, attr, attr)
class OpenEndedModuleTest(unittest.TestCase):
    """
    Test the open ended module class
    """
    location = Location(
        ["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"])

    metadata = json.dumps({'attempts': '10'})
    prompt = etree.XML("<prompt>This is a question prompt</prompt>")
    rubric = etree.XML('''<rubric>
        <category>
        <description>Response Quality</description>
        <option>The response is not a satisfactory answer to the question.  It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option>
        </category>
         </rubric>''')
    max_score = 4

    static_data = {
        'max_attempts': 20,
        'prompt': prompt,
        'rubric': rubric,
        'max_score': max_score,
        'display_name': 'Name',
        'accept_file_upload': False,
        'close_date': None,
        's3_interface': test_util_open_ended.S3_INTERFACE,
        'open_ended_grading_interface':
        test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
        'skip_basic_checks': False,
        'control': {
            'required_peer_grading': 1,
            'peer_grader_count': 1,
            'min_to_calibrate': 3,
            'max_to_calibrate': 6,
            'peer_grade_finished_submissions_when_none_pending': False,
        }
    }

    oeparam = etree.XML('''
      <openendedparam>
            <initial_display>Enter essay here.</initial_display>
            <answer_display>This is the answer.</answer_display>
            <grader_payload>{"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
        </openendedparam>
    ''')
    definition = {'oeparam': oeparam}
    descriptor = Mock()

    def setUp(self):
        self.test_system = get_test_system()
        self.test_system.open_ended_grading_interface = None
        self.test_system.location = self.location
        self.mock_xqueue = MagicMock()
        self.mock_xqueue.send_to_queue.return_value = (None, "Message")

        def constructed_callback(dispatch="score_update"):
            return dispatch

        self.test_system.xqueue = {
            'interface': self.mock_xqueue,
            'construct_callback': constructed_callback,
            'default_queuename': 'testqueue',
            'waittime': 1
        }
        self.openendedmodule = OpenEndedModule(self.test_system, self.location,
                                               self.definition,
                                               self.descriptor,
                                               self.static_data, self.metadata)

    def test_message_post(self):
        get = {
            'feedback': 'feedback text',
            'submission_id': '1',
            'grader_id': '1',
            'score': 3
        }
        qtime = datetime.strftime(datetime.now(UTC),
                                  xqueue_interface.dateformat)
        student_info = {
            'anonymous_student_id': self.test_system.anonymous_student_id,
            'submission_time': qtime
        }
        contents = {
            'feedback': get['feedback'],
            'submission_id': int(get['submission_id']),
            'grader_id': int(get['grader_id']),
            'score': get['score'],
            'student_info': json.dumps(student_info)
        }

        result = self.openendedmodule.message_post(get, self.test_system)
        self.assertTrue(result['success'])
        # make sure it's actually sending something we want to the queue
        self.mock_xqueue.send_to_queue.assert_called_with(
            body=json.dumps(contents), header=ANY)

        state = json.loads(self.openendedmodule.get_instance_state())
        self.assertIsNotNone(state['child_state'], OpenEndedModule.DONE)

    def test_send_to_grader(self):
        submission = "This is a student submission"
        qtime = datetime.strftime(datetime.now(UTC),
                                  xqueue_interface.dateformat)
        student_info = {
            'anonymous_student_id': self.test_system.anonymous_student_id,
            'submission_time': qtime
        }
        contents = self.openendedmodule.payload.copy()
        contents.update({
            'student_info': json.dumps(student_info),
            'student_response': submission,
            'max_score': self.max_score
        })
        result = self.openendedmodule.send_to_grader(submission,
                                                     self.test_system)
        self.assertTrue(result)
        self.mock_xqueue.send_to_queue.assert_called_with(
            body=json.dumps(contents), header=ANY)

    def update_score_single(self):
        self.openendedmodule.new_history_entry("New Entry")
        score_msg = {
            'correct': True,
            'score': 4,
            'msg': 'Grader Message',
            'feedback': "Grader Feedback"
        }
        get = {'queuekey': "abcd", 'xqueue_body': score_msg}
        self.openendedmodule.update_score(get, self.test_system)

    def update_score_single(self):
        self.openendedmodule.new_history_entry("New Entry")
        feedback = {"success": True, "feedback": "Grader Feedback"}
        score_msg = {
            'correct': True,
            'score': 4,
            'msg': 'Grader Message',
            'feedback': json.dumps(feedback),
            'grader_type': 'IN',
            'grader_id': '1',
            'submission_id': '1',
            'success': True,
            'rubric_scores': [0],
            'rubric_scores_complete': True,
            'rubric_xml': etree.tostring(self.rubric)
        }
        get = {'queuekey': "abcd", 'xqueue_body': json.dumps(score_msg)}
        self.openendedmodule.update_score(get, self.test_system)

    def update_score_multiple(self):
        self.openendedmodule.new_history_entry("New Entry")
        feedback = {"success": True, "feedback": "Grader Feedback"}
        score_msg = {
            'correct':
            True,
            'score': [0, 1],
            'msg':
            'Grader Message',
            'feedback': [json.dumps(feedback),
                         json.dumps(feedback)],
            'grader_type':
            'PE',
            'grader_id': ['1', '2'],
            'submission_id':
            '1',
            'success':
            True,
            'rubric_scores': [[0], [0]],
            'rubric_scores_complete': [True, True],
            'rubric_xml':
            [etree.tostring(self.rubric),
             etree.tostring(self.rubric)]
        }
        get = {'queuekey': "abcd", 'xqueue_body': json.dumps(score_msg)}
        self.openendedmodule.update_score(get, self.test_system)

    def test_latest_post_assessment(self):
        self.update_score_single()
        assessment = self.openendedmodule.latest_post_assessment(
            self.test_system)
        self.assertFalse(assessment == '')
        # check for errors
        self.assertFalse('errors' in assessment)

    def test_update_score_single(self):
        self.update_score_single()
        score = self.openendedmodule.latest_score()
        self.assertEqual(score, 4)

    def test_update_score_multiple(self):
        """
        Tests that a score of [0, 1] gets aggregated to 1.  A change in behavior added by @jbau
        """
        self.update_score_multiple()
        score = self.openendedmodule.latest_score()
        self.assertEquals(score, 1)

    def test_open_ended_display(self):
        """
        Test storing answer with the open ended module.
        """

        # Create a module with no state yet.  Important that this start off as a blank slate.
        test_module = OpenEndedModule(self.test_system, self.location,
                                      self.definition, self.descriptor,
                                      self.static_data, self.metadata)

        saved_response = "Saved response."
        submitted_response = "Submitted response."

        # Initially, there will be no stored answer.
        self.assertEqual(test_module.stored_answer, None)
        # And the initial answer to display will be an empty string.
        self.assertEqual(test_module.get_display_answer(), "")

        # Now, store an answer in the module.
        test_module.handle_ajax("store_answer",
                                {'student_answer': saved_response},
                                get_test_system())
        # The stored answer should now equal our response.
        self.assertEqual(test_module.stored_answer, saved_response)
        self.assertEqual(test_module.get_display_answer(), saved_response)

        # Mock out the send_to_grader function so it doesn't try to connect to the xqueue.
        test_module.send_to_grader = Mock(return_value=True)
        # Submit a student response to the question.
        test_module.handle_ajax("save_answer",
                                {"student_answer": submitted_response},
                                get_test_system())
        # Submitting an answer should clear the stored answer.
        self.assertEqual(test_module.stored_answer, None)
        # Confirm that the answer is stored properly.
        self.assertEqual(test_module.latest_answer(), submitted_response)
Exemple #8
0
def add_repo(repo, rdir_in):
    """This will add a git repo into the mongo modulestore"""
    # pylint: disable=R0915

    # Set defaults even if it isn't defined in settings
    mongo_db = {
        'host': 'localhost',
        'user': '',
        'password': '',
        'db': 'xlog',
    }

    # Allow overrides
    if hasattr(settings, 'MONGODB_LOG'):
        for config_item in [
                'host',
                'user',
                'password',
                'db',
        ]:
            mongo_db[config_item] = settings.MONGODB_LOG.get(
                config_item, mongo_db[config_item])

    if not os.path.isdir(GIT_REPO_DIR):
        raise GitImportError(GitImportError.NO_DIR)
    # pull from git
    if not (repo.endswith('.git') or repo.startswith(
        ('http:', 'https:', 'git:', 'file:'))):
        raise GitImportError(GitImportError.URL_BAD)

    if rdir_in:
        rdir = os.path.basename(rdir_in)
    else:
        rdir = repo.rsplit('/', 1)[-1].rsplit('.git', 1)[0]
    log.debug('rdir = {0}'.format(rdir))

    rdirp = '{0}/{1}'.format(GIT_REPO_DIR, rdir)
    if os.path.exists(rdirp):
        log.info('directory already exists, doing a git pull instead '
                 'of git clone')
        cmd = [
            'git',
            'pull',
        ]
        cwd = rdirp
    else:
        cmd = [
            'git',
            'clone',
            repo,
        ]
        cwd = GIT_REPO_DIR

    cwd = os.path.abspath(cwd)
    try:
        ret_git = cmd_log(cmd, cwd=cwd)
    except subprocess.CalledProcessError as ex:
        log.exception('Error running git pull: %r', ex.output)
        raise GitImportError(GitImportError.CANNOT_PULL)

    # get commit id
    cmd = [
        'git',
        'log',
        '-1',
        '--format=%H',
    ]
    try:
        commit_id = cmd_log(cmd, cwd=rdirp)
    except subprocess.CalledProcessError as ex:
        log.exception('Unable to get git log: %r', ex.output)
        raise GitImportError(GitImportError.BAD_REPO)

    ret_git += '\nCommit ID: {0}'.format(commit_id)

    # get branch
    cmd = [
        'git',
        'symbolic-ref',
        '--short',
        'HEAD',
    ]
    try:
        branch = cmd_log(cmd, cwd=rdirp)
    except subprocess.CalledProcessError as ex:
        # I can't discover a way to excercise this, but git is complex
        # so still logging and raising here in case.
        log.exception('Unable to determine branch: %r', ex.output)
        raise GitImportError(GitImportError.BAD_REPO)

    ret_git += '{0}Branch: {1}'.format('   \n', branch)

    # Get XML logging logger and capture debug to parse results
    output = StringIO.StringIO()
    import_log_handler = logging.StreamHandler(output)
    import_log_handler.setLevel(logging.DEBUG)

    logger_names = [
        'xmodule.modulestore.xml_importer',
        'git_add_course',
        'xmodule.modulestore.xml',
        'xmodule.seq_module',
    ]
    loggers = []

    for logger_name in logger_names:
        logger = logging.getLogger(logger_name)
        logger.setLevel(logging.DEBUG)
        logger.addHandler(import_log_handler)
        loggers.append(logger)

    try:
        management.call_command('import',
                                GIT_REPO_DIR,
                                rdir,
                                nostatic=not GIT_IMPORT_STATIC)
    except CommandError:
        raise GitImportError(GitImportError.XML_IMPORT_FAILED)
    except NotImplementedError:
        raise GitImportError(GitImportError.UNSUPPORTED_STORE)

    ret_import = output.getvalue()

    # Remove handler hijacks
    for logger in loggers:
        logger.setLevel(logging.NOTSET)
        logger.removeHandler(import_log_handler)

    course_id = 'unknown'
    location = 'unknown'

    # extract course ID from output of import-command-run and make symlink
    # this is needed in order for custom course scripts to work
    match = re.search('(?ms)===> IMPORTING course to location (\S+)',
                      ret_import)
    if match:
        location = Location(match.group(1))
        log.debug('location = {0}'.format(location))
        course_id = location.course_id

        cdir = '{0}/{1}'.format(GIT_REPO_DIR, location.course)
        log.debug('Studio course dir = {0}'.format(cdir))

        if os.path.exists(cdir) and not os.path.islink(cdir):
            log.debug('   -> exists, but is not symlink')
            log.debug(
                subprocess.check_output([
                    'ls',
                    '-l',
                ],
                                        cwd=os.path.abspath(cdir)))
            try:
                os.rmdir(os.path.abspath(cdir))
            except OSError:
                log.exception('Failed to remove course directory')

        if not os.path.exists(cdir):
            log.debug('   -> creating symlink between {0} and {1}'.format(
                rdirp, cdir))
            try:
                os.symlink(os.path.abspath(rdirp), os.path.abspath(cdir))
            except OSError:
                log.exception('Unable to create course symlink')
            log.debug(
                subprocess.check_output([
                    'ls',
                    '-l',
                ],
                                        cwd=os.path.abspath(cdir)))

    # store import-command-run output in mongo
    mongouri = 'mongodb://{user}:{password}@{host}/{db}'.format(**mongo_db)

    try:
        if mongo_db['user'] and mongo_db['password']:
            mdb = mongoengine.connect(mongo_db['db'], host=mongouri)
        else:
            mdb = mongoengine.connect(mongo_db['db'], host=mongo_db['host'])
    except mongoengine.connection.ConnectionError:
        log.exception('Unable to connect to mongodb to save log, please '
                      'check MONGODB_LOG settings')
    cil = CourseImportLog(
        course_id=course_id,
        location=unicode(location),
        repo_dir=rdir,
        created=timezone.now(),
        import_log=ret_import,
        git_log=ret_git,
    )
    cil.save()

    log.debug('saved CourseImportLog for {0}'.format(cil.course_id))
    mdb.disconnect()
 def test_static_url_generation(self):
     location = Location(['i4x', 'foo', 'bar', 'asset', 'my_file_name.jpg'])
     path = StaticContent.get_static_path_from_location(location)
     self.assertEquals(path, '/static/my_file_name.jpg')
Exemple #10
0
    def compute_metadata_inheritance_tree(self, location):
        '''
        TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
        '''

        # get all collections in the course, this query should not return any leaf nodes
        # note this is a bit ugly as when we add new categories of containers, we have to add it here
        query = {
            '_id.org': location.org,
            '_id.course': location.course,
            '_id.category': {
                '$in': [
                    'course', 'chapter', 'sequential', 'vertical',
                    'videosequence', 'wrapper', 'problemset', 'conditional',
                    'randomize'
                ]
            }
        }
        # we just want the Location, children, and inheritable metadata
        record_filter = {'_id': 1, 'definition.children': 1}

        # just get the inheritable metadata since that is all we need for the computation
        # this minimizes both data pushed over the wire
        for field_name in InheritanceMixin.fields:
            record_filter['metadata.{0}'.format(field_name)] = 1

        # call out to the DB
        resultset = self.collection.find(query, record_filter)

        results_by_url = {}
        root = None

        # now go through the results and order them by the location url
        for result in resultset:
            location = Location(result['_id'])
            # We need to collate between draft and non-draft
            # i.e. draft verticals can have children which are not in non-draft versions
            location = location.replace(revision=None)
            location_url = location.url()
            if location_url in results_by_url:
                existing_children = results_by_url[location_url].get(
                    'definition', {}).get('children', [])
                additional_children = result.get('definition',
                                                 {}).get('children', [])
                total_children = existing_children + additional_children
                if 'definition' not in results_by_url[location_url]:
                    results_by_url[location_url]['definition'] = {}
                results_by_url[location_url]['definition'][
                    'children'] = total_children
            results_by_url[location.url()] = result
            if location.category == 'course':
                root = location.url()

        # now traverse the tree and compute down the inherited metadata
        metadata_to_inherit = {}

        def _compute_inherited_metadata(url):
            """
            Helper method for computing inherited metadata for a specific location url
            """
            # check for presence of metadata key. Note that a given module may not yet be fully formed.
            # example: update_item -> update_children -> update_metadata sequence on new item create
            # if we get called here without update_metadata called first then 'metadata' hasn't been set
            # as we're not fully transactional at the DB layer. Same comment applies to below key name
            # check
            my_metadata = results_by_url[url].get('metadata', {})

            # go through all the children and recurse, but only if we have
            # in the result set. Remember results will not contain leaf nodes
            for child in results_by_url[url].get('definition',
                                                 {}).get('children', []):
                if child in results_by_url:
                    new_child_metadata = copy.deepcopy(my_metadata)
                    new_child_metadata.update(results_by_url[child].get(
                        'metadata', {}))
                    results_by_url[child]['metadata'] = new_child_metadata
                    metadata_to_inherit[child] = new_child_metadata
                    _compute_inherited_metadata(child)
                else:
                    # this is likely a leaf node, so let's record what metadata we need to inherit
                    metadata_to_inherit[child] = my_metadata

        if root is not None:
            _compute_inherited_metadata(root)

        return metadata_to_inherit
def clone_course(modulestore,
                 contentstore,
                 source_location,
                 dest_location,
                 delete_original=False):
    # check to see if the dest_location exists as an empty course
    # we need an empty course because the app layers manage the permissions and users
    if not modulestore.has_item(dest_location.course_id, dest_location):
        raise Exception(
            "An empty course at {0} must have already been created. Aborting..."
            .format(dest_location))

    # verify that the dest_location really is an empty course, which means only one with an optional 'overview'
    dest_modules = modulestore.get_items([
        dest_location.tag, dest_location.org, dest_location.course, None, None,
        None
    ])

    basically_empty = True
    for module in dest_modules:
        if module.location.category == 'course' or (
                module.location.category == 'about'
                and module.location.name == 'overview'):
            continue

        basically_empty = False
        break

    if not basically_empty:
        raise Exception(
            "Course at destination {0} is not an empty course. You can only clone into an empty course. Aborting..."
            .format(dest_location))

    # check to see if the source course is actually there
    if not modulestore.has_item(source_location.course_id, source_location):
        raise Exception(
            "Cannot find a course at {0}. Aborting".format(source_location))

    # Get all modules under this namespace which is (tag, org, course) tuple

    modules = modulestore.get_items([
        source_location.tag, source_location.org, source_location.course, None,
        None, None
    ])
    _clone_modules(modulestore, modules, source_location, dest_location)

    modules = modulestore.get_items([
        source_location.tag, source_location.org, source_location.course, None,
        None, 'draft'
    ])
    _clone_modules(modulestore, modules, source_location, dest_location)

    # now iterate through all of the assets and clone them
    # first the thumbnails
    thumbs = contentstore.get_all_content_thumbnails_for_course(
        source_location)
    for thumb in thumbs:
        thumb_loc = Location(thumb["_id"])
        content = contentstore.find(thumb_loc)
        content.location = content.location._replace(
            org=dest_location.org, course=dest_location.course)

        print "Cloning thumbnail {0} to {1}".format(thumb_loc,
                                                    content.location)

        contentstore.save(content)

    # now iterate through all of the assets, also updating the thumbnail pointer

    assets, __ = contentstore.get_all_content_for_course(source_location)
    for asset in assets:
        asset_loc = Location(asset["_id"])
        content = contentstore.find(asset_loc)
        content.location = content.location._replace(
            org=dest_location.org, course=dest_location.course)

        # be sure to update the pointer to the thumbnail
        if content.thumbnail_location is not None:
            content.thumbnail_location = content.thumbnail_location._replace(
                org=dest_location.org, course=dest_location.course)

        print "Cloning asset {0} to {1}".format(asset_loc, content.location)

        contentstore.save(content)

    return True
    def create(graceperiod=None,
               due=None,
               max_attempts=None,
               showanswer=None,
               rerandomize=None,
               force_save_button=None,
               attempts=None,
               problem_state=None,
               correct=False,
               done=None):
        """
        All parameters are optional, and are added to the created problem if specified.

        Arguments:
            graceperiod:
            due:
            max_attempts:
            showanswer:
            force_save_button:
            rerandomize: all strings, as specified in the policy for the problem

            problem_state: a dict to to be serialized into the instance_state of the
                module.

            attempts: also added to instance state.  Will be converted to an int.
        """
        location = Location([
            "i4x", "edX", "capa_test", "problem",
            "SampleProblem{0}".format(CapaFactory.next_num())
        ])
        model_data = {'data': CapaFactory.sample_problem_xml}

        if graceperiod is not None:
            model_data['graceperiod'] = graceperiod
        if due is not None:
            model_data['due'] = due
        if max_attempts is not None:
            model_data['max_attempts'] = max_attempts
        if showanswer is not None:
            model_data['showanswer'] = showanswer
        if force_save_button is not None:
            model_data['force_save_button'] = force_save_button
        if rerandomize is not None:
            model_data['rerandomize'] = rerandomize
        if done is not None:
            model_data['done'] = done

        descriptor = Mock(weight="1")
        if problem_state is not None:
            model_data.update(problem_state)
        if attempts is not None:
            # converting to int here because I keep putting "0" and "1" in the tests
            # since everything else is a string.
            model_data['attempts'] = int(attempts)

        system = test_system()
        system.render_template = Mock(
            return_value="<div>Test Template HTML</div>")
        module = CapaModule(system, location, descriptor, model_data)

        if correct:
            # TODO: probably better to actually set the internal state properly, but...
            module.get_score = lambda: {'score': 1, 'total': 1}
        else:
            module.get_score = lambda: {'score': 0, 'total': 1}

        return module
Exemple #13
0
    def test_encode_location(self):
        loc = Location('i4x', 'org', 'course', 'category', 'name')
        self.assertEqual(loc.url(), self.encoder.default(loc))

        loc = Location('i4x', 'org', 'course', 'category', 'name', 'version')
        self.assertEqual(loc.url(), self.encoder.default(loc))
Exemple #14
0
def test_center_login(request):
    ''' Log in students taking exams via Pearson

    Takes a POST request that contains the following keys:
        - code - a security code provided by  Pearson
        - clientCandidateID
        - registrationID
        - exitURL - the url that we redirect to once we're done
        - vueExamSeriesCode - a code that indicates the exam that we're using
    '''
    # errors are returned by navigating to the error_url, adding a query parameter named "code"
    # which contains the error code describing the exceptional condition.
    def makeErrorURL(error_url, error_code):
        log.error("generating error URL with error code {}".format(error_code))
        return "{}?code={}".format(error_url, error_code)

    # get provided error URL, which will be used as a known prefix for returning error messages to the
    # Pearson shell.
    error_url = request.POST.get("errorURL")

    # TODO: check that the parameters have not been tampered with, by comparing the code provided by Pearson
    # with the code we calculate for the same parameters.
    if 'code' not in request.POST:
        return HttpResponseRedirect(makeErrorURL(error_url, "missingSecurityCode"))
    code = request.POST.get("code")

    # calculate SHA for query string
    # TODO: figure out how to get the original query string, so we can hash it
    # and compare.

    if 'clientCandidateID' not in request.POST:
        return HttpResponseRedirect(makeErrorURL(error_url, "missingClientCandidateID"))
    client_candidate_id = request.POST.get("clientCandidateID")

    # TODO: check remaining parameters, and maybe at least log if they're not matching
    # expected values....
    # registration_id = request.POST.get("registrationID")
    # exit_url = request.POST.get("exitURL")

    # find testcenter_user that matches the provided ID:
    try:
        testcenteruser = TestCenterUser.objects.get(
            client_candidate_id=client_candidate_id)
    except TestCenterUser.DoesNotExist:
        log.error("not able to find demographics for cand ID {}".format(
            client_candidate_id))
        return HttpResponseRedirect(makeErrorURL(error_url, "invalidClientCandidateID"))

    # find testcenter_registration that matches the provided exam code:
    # Note that we could rely in future on either the registrationId or the exam code,
    # or possibly both.  But for now we know what to do with an ExamSeriesCode,
    # while we currently have no record of RegistrationID values at all.
    if 'vueExamSeriesCode' not in request.POST:
        # we are not allowed to make up a new error code, according to Pearson,
        # so instead of "missingExamSeriesCode", we use a valid one that is
        # inaccurate but at least distinct.  (Sigh.)
        log.error("missing exam series code for cand ID {}".format(
            client_candidate_id))
        return HttpResponseRedirect(makeErrorURL(error_url, "missingPartnerID"))
    exam_series_code = request.POST.get('vueExamSeriesCode')

    registrations = TestCenterRegistration.objects.filter(
        testcenter_user=testcenteruser, exam_series_code=exam_series_code)
    if not registrations:
        log.error("not able to find exam registration for exam {} and cand ID {}".format(
            exam_series_code, client_candidate_id))
        return HttpResponseRedirect(makeErrorURL(error_url, "noTestsAssigned"))

    # TODO: figure out what to do if there are more than one registrations....
    # for now, just take the first...
    registration = registrations[0]

    course_id = registration.course_id
    course = course_from_id(course_id)  # assume it will be found....
    if not course:
        log.error("not able to find course from ID {} for cand ID {}".format(
            course_id, client_candidate_id))
        return HttpResponseRedirect(makeErrorURL(error_url, "incorrectCandidateTests"))
    exam = course.get_test_center_exam(exam_series_code)
    if not exam:
        log.error("not able to find exam {} for course ID {} and cand ID {}".format(
            exam_series_code, course_id, client_candidate_id))
        return HttpResponseRedirect(makeErrorURL(error_url, "incorrectCandidateTests"))
    location = exam.exam_url
    log.info("proceeding with test of cand {} on exam {} for course {}: URL = {}".format(
        client_candidate_id, exam_series_code, course_id, location))

    # check if the test has already been taken
    timelimit_descriptor = modulestore().get_instance(
        course_id, Location(location))
    if not timelimit_descriptor:
        log.error("cand {} on exam {} for course {}: descriptor not found for location {}".format(
            client_candidate_id, exam_series_code, course_id, location))
        return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram"))

    timelimit_module_cache = ModelDataCache.cache_for_descriptor_descendents(
        course_id, testcenteruser.user,
        timelimit_descriptor, depth=None)
    timelimit_module = get_module_for_descriptor(
        request.user, request, timelimit_descriptor,
        timelimit_module_cache, course_id, position=None)
    if not timelimit_module.category == 'timelimit':
        log.error("cand {} on exam {} for course {}: non-timelimit module at location {}".format(
            client_candidate_id, exam_series_code, course_id, location))
        return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram"))

    if timelimit_module and timelimit_module.has_ended:
        log.warning("cand {} on exam {} for course {}: test already over at {}".format(
            client_candidate_id, exam_series_code, course_id, timelimit_module.ending_at))
        return HttpResponseRedirect(makeErrorURL(error_url, "allTestsTaken"))

    # check if we need to provide an accommodation:
    time_accommodation_mapping = {'ET12ET': 'ADDHALFTIME',
                                  'ET30MN': 'ADD30MIN',
                                  'ETDBTM': 'ADDDOUBLE', }

    time_accommodation_code = None
    for code in registration.get_accommodation_codes():
        if code in time_accommodation_mapping:
            time_accommodation_code = time_accommodation_mapping[code]

    if time_accommodation_code:
        timelimit_module.accommodation_code = time_accommodation_code
        log.info("cand {} on exam {} for course {}: receiving accommodation {}".format(
            client_candidate_id, exam_series_code, course_id, time_accommodation_code))

    # UGLY HACK!!!
    # Login assumes that authentication has occurred, and that there is a
    # backend annotation on the user object, indicating which backend
    # against which the user was authenticated.  We're authenticating here
    # against the registration entry, and assuming that the request given
    # this information is correct, we allow the user to be logged in
    # without a password.  This could all be formalized in a backend object
    # that does the above checking.
    # TODO: (brian) create a backend class to do this.
    # testcenteruser.user.backend = "%s.%s" % (backend.__module__,
    # backend.__class__.__name__)
    testcenteruser.user.backend = "%s.%s" % (
        "TestcenterAuthenticationModule", "TestcenterAuthenticationClass")
    login(request, testcenteruser.user)

    # And start the test:
    return jump_to(request, course_id, location)
 def test_none(self):
     self.assertEquals([None] * 6, Location(None).list())
Exemple #16
0
def get_hints(request, course_id, field):
    """
    Load all of the hints submitted to the course.

    Args:
    `request` -- Django request object.
    `course_id` -- The course id, like 'Me/19.002/test_course'
    `field` -- Either 'hints' or 'mod_queue'; specifies which set of hints to load.

    Keys in returned dict:
        - 'field': Same as input
        - 'other_field': 'mod_queue' if `field` == 'hints'; and vice-versa.
        - 'field_label', 'other_field_label': English name for the above.
        - 'all_hints': A list of [answer, pk dict] pairs, representing all hints.
          Sorted by answer.
        - 'id_to_name': A dictionary mapping problem id to problem name.
    """
    if field == 'mod_queue':
        other_field = 'hints'
        field_label = 'Hints Awaiting Moderation'
        other_field_label = 'Approved Hints'
    elif field == 'hints':
        other_field = 'mod_queue'
        field_label = 'Approved Hints'
        other_field_label = 'Hints Awaiting Moderation'
    # The course_id is of the form school/number/classname.
    # We want to use the course_id to find all matching definition_id's.
    # To do this, just take the school/number part - leave off the classname.
    chopped_id = '/'.join(course_id.split('/')[:-1])
    chopped_id = re.escape(chopped_id)
    all_hints = XModuleContentField.objects.filter(
        field_name=field, definition_id__regex=chopped_id)
    # big_out_dict[problem id] = [[answer, {pk: [hint, votes]}], sorted by answer]
    # big_out_dict maps a problem id to a list of [answer, hints] pairs, sorted in order of answer.
    big_out_dict = {}
    # id_to name maps a problem id to the name of the problem.
    # id_to_name[problem id] = Display name of problem
    id_to_name = {}

    for hints_by_problem in all_hints:
        loc = Location(hints_by_problem.definition_id)
        name = location_to_problem_name(course_id, loc)
        if name is None:
            continue
        id_to_name[hints_by_problem.definition_id] = name

        def answer_sorter(thing):
            """
            `thing` is a tuple, where `thing[0]` contains an answer, and `thing[1]` contains
            a dict of hints.  This function returns an index based on `thing[0]`, which
            is used as a key to sort the list of things.
            """
            try:
                return float(thing[0])
            except ValueError:
                # Put all non-numerical answers first.
                return float('-inf')

        # Answer list contains [answer, dict_of_hints] pairs.
        answer_list = sorted(json.loads(hints_by_problem.value).items(),
                             key=answer_sorter)
        big_out_dict[hints_by_problem.definition_id] = answer_list

    render_dict = {
        'field': field,
        'other_field': other_field,
        'field_label': field_label,
        'other_field_label': other_field_label,
        'all_hints': big_out_dict,
        'id_to_name': id_to_name
    }
    return render_dict
 def test_invalid_locations(self, loc):
     with self.assertRaises(InvalidLocationError):
         Location(loc)
 def location(self):
     return Location('i4x://org/course/category/{}'.format(self.url_name))
 def test_string_roundtrip(self, url):
     self.assertEquals(url, Location(url).url())
     self.assertEquals(url, str(Location(url)))
Exemple #20
0
def clone_course(modulestore,
                 contentstore,
                 source_location,
                 dest_location,
                 delete_original=False):
    # first check to see if the modulestore is Mongo backed
    if not isinstance(modulestore, MongoModuleStore):
        raise Exception(
            "Expected a MongoModuleStore in the runtime. Aborting....")

    # check to see if the dest_location exists as an empty course
    # we need an empty course because the app layers manage the permissions and users
    if not modulestore.has_item(dest_location):
        raise Exception(
            "An empty course at {0} must have already been created. Aborting..."
            .format(dest_location))

    # verify that the dest_location really is an empty course, which means only one with an optional 'overview'
    dest_modules = modulestore.get_items([
        dest_location.tag, dest_location.org, dest_location.course, None, None,
        None
    ])

    basically_empty = True
    for module in dest_modules:
        if module.location.category == 'course' or (
                module.location.category == 'about'
                and module.location.name == 'overview'):
            continue

        basically_empty = False
        break

    if not basically_empty:
        raise Exception(
            "Course at destination {0} is not an empty course. You can only clone into an empty course. Aborting..."
            .format(dest_location))

    # check to see if the source course is actually there
    if not modulestore.has_item(source_location):
        raise Exception(
            "Cannot find a course at {0}. Aborting".format(source_location))

    # Get all modules under this namespace which is (tag, org, course) tuple

    modules = modulestore.get_items([
        source_location.tag, source_location.org, source_location.course, None,
        None, None
    ])

    for module in modules:
        original_loc = Location(module.location)

        if original_loc.category != 'course':
            module.location = module.location._replace(
                tag=dest_location.tag,
                org=dest_location.org,
                course=dest_location.course)
        else:
            # on the course module we also have to update the module name
            module.location = module.location._replace(
                tag=dest_location.tag,
                org=dest_location.org,
                course=dest_location.course,
                name=dest_location.name)

        print "Cloning module {0} to {1}....".format(original_loc,
                                                     module.location)

        modulestore.update_item(module.location, module._model_data._kvs._data)

        # repoint children
        if module.has_children:
            new_children = []
            for child_loc_url in module.children:
                child_loc = Location(child_loc_url)
                child_loc = child_loc._replace(tag=dest_location.tag,
                                               org=dest_location.org,
                                               course=dest_location.course)
                new_children.append(child_loc.url())

            modulestore.update_children(module.location, new_children)

        # save metadata
        modulestore.update_metadata(module.location,
                                    module._model_data._kvs._metadata)

    # now iterate through all of the assets and clone them
    # first the thumbnails
    thumbs = contentstore.get_all_content_thumbnails_for_course(
        source_location)
    for thumb in thumbs:
        thumb_loc = Location(thumb["_id"])
        content = contentstore.find(thumb_loc)
        content.location = content.location._replace(
            org=dest_location.org, course=dest_location.course)

        print "Cloning thumbnail {0} to {1}".format(thumb_loc,
                                                    content.location)

        contentstore.save(content)

    # now iterate through all of the assets, also updating the thumbnail pointer

    assets = contentstore.get_all_content_for_course(source_location)
    for asset in assets:
        asset_loc = Location(asset["_id"])
        content = contentstore.find(asset_loc)
        content.location = content.location._replace(
            org=dest_location.org, course=dest_location.course)

        # be sure to update the pointer to the thumbnail
        if content.thumbnail_location is not None:
            content.thumbnail_location = content.thumbnail_location._replace(
                org=dest_location.org, course=dest_location.course)

        print "Cloning asset {0} to {1}".format(asset_loc, content.location)

        contentstore.save(content)

    return True
class OpenEndedChildTest(unittest.TestCase):
    """
    Test the open ended child class
    """
    location = Location(
        ["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"])

    metadata = json.dumps({'attempts': '10'})
    prompt = etree.XML("<prompt>This is a question prompt</prompt>")
    rubric = '''<rubric><rubric>
        <category>
        <description>Response Quality</description>
        <option>The response is not a satisfactory answer to the question.  It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option>
        <option>Second option</option>
        </category>
         </rubric></rubric>'''
    max_score = 1

    static_data = {
        'max_attempts': 20,
        'prompt': prompt,
        'rubric': rubric,
        'max_score': max_score,
        'display_name': 'Name',
        'accept_file_upload': False,
        'close_date': None,
        's3_interface': "",
        'open_ended_grading_interface': {},
        'skip_basic_checks': False,
        'control': {
            'required_peer_grading': 1,
            'peer_grader_count': 1,
            'min_to_calibrate': 3,
            'max_to_calibrate': 6,
            'peer_grade_finished_submissions_when_none_pending': False,
        }
    }
    definition = Mock()
    descriptor = Mock()

    def setUp(self):
        self.test_system = get_test_system()
        self.test_system.open_ended_grading_interface = None
        self.openendedchild = OpenEndedChild(self.test_system, self.location,
                                             self.definition, self.descriptor,
                                             self.static_data, self.metadata)

    def test_latest_answer_empty(self):
        answer = self.openendedchild.latest_answer()
        self.assertEqual(answer, "")

    def test_latest_score_empty(self):
        answer = self.openendedchild.latest_score()
        self.assertEqual(answer, None)

    def test_latest_post_assessment_empty(self):
        answer = self.openendedchild.latest_post_assessment(self.test_system)
        self.assertEqual(answer, "")

    def test_new_history_entry(self):
        new_answer = "New Answer"
        self.openendedchild.new_history_entry(new_answer)
        answer = self.openendedchild.latest_answer()
        self.assertEqual(answer, new_answer)

        new_answer = "Newer Answer"
        self.openendedchild.new_history_entry(new_answer)
        answer = self.openendedchild.latest_answer()
        self.assertEqual(new_answer, answer)

    def test_record_latest_score(self):
        new_answer = "New Answer"
        self.openendedchild.new_history_entry(new_answer)
        new_score = 3
        self.openendedchild.record_latest_score(new_score)
        score = self.openendedchild.latest_score()
        self.assertEqual(score, 3)

        new_score = 4
        self.openendedchild.new_history_entry(new_answer)
        self.openendedchild.record_latest_score(new_score)
        score = self.openendedchild.latest_score()
        self.assertEqual(score, 4)

    def test_record_latest_post_assessment(self):
        new_answer = "New Answer"
        self.openendedchild.new_history_entry(new_answer)

        post_assessment = "Post assessment"
        self.openendedchild.record_latest_post_assessment(post_assessment)
        self.assertEqual(
            post_assessment,
            self.openendedchild.latest_post_assessment(self.test_system))

    def test_get_score(self):
        new_answer = "New Answer"
        self.openendedchild.new_history_entry(new_answer)

        score = self.openendedchild.get_score()
        self.assertEqual(score['score'], 0)
        self.assertEqual(score['total'], self.static_data['max_score'])

        new_score = 4
        self.openendedchild.new_history_entry(new_answer)
        self.openendedchild.record_latest_score(new_score)
        score = self.openendedchild.get_score()
        self.assertEqual(score['score'], new_score)
        self.assertEqual(score['total'], self.static_data['max_score'])

    def test_reset(self):
        self.openendedchild.reset(self.test_system)
        state = json.loads(self.openendedchild.get_instance_state())
        self.assertEqual(state['child_state'], OpenEndedChild.INITIAL)

    def test_is_last_response_correct(self):
        new_answer = "New Answer"
        self.openendedchild.new_history_entry(new_answer)
        self.openendedchild.record_latest_score(self.static_data['max_score'])
        self.assertEqual(self.openendedchild.is_last_response_correct(),
                         'correct')

        self.openendedchild.new_history_entry(new_answer)
        self.openendedchild.record_latest_score(0)
        self.assertEqual(self.openendedchild.is_last_response_correct(),
                         'incorrect')
    def test_translate_location_read_only(self):
        """
        Test the variants of translate_location which don't create entries, just decode
        """
        # lookup before there are any maps
        org = 'foo_org'
        course = 'bar_course'
        old_style_course_id = '{}/{}/{}'.format(org, course, 'baz_run')
        with self.assertRaises(ItemNotFoundError):
            _ = loc_mapper().translate_location(old_style_course_id,
                                                Location(
                                                    'i4x', org, course,
                                                    'problem', 'abc123'),
                                                add_entry_if_missing=False)

        new_style_package_id = '{}.geek_dept.{}.baz_run'.format(org, course)
        block_map = {
            'abc123': {
                'problem': 'problem2',
                'vertical': 'vertical2'
            },
            'def456': {
                'problem': 'problem4'
            },
            'ghi789': {
                'problem': 'problem7'
            },
        }
        loc_mapper().create_map_entry(Location('i4x', org, course, 'course',
                                               'baz_run'),
                                      new_style_package_id,
                                      block_map=block_map)
        test_problem_locn = Location('i4x', org, course, 'problem', 'abc123')
        # only one course matches

        # look for w/ only the Location (works b/c there's only one possible course match). Will force
        # cache as default translation for this problemid
        self.translate_n_check(test_problem_locn, None, new_style_package_id,
                               'problem2', 'published')
        # look for non-existent problem
        with self.assertRaises(ItemNotFoundError):
            loc_mapper().translate_location(None,
                                            Location('i4x', org, course,
                                                     'problem', '1def23'),
                                            add_entry_if_missing=False)
        test_no_cat_locn = test_problem_locn.replace(category=None)
        with self.assertRaises(InvalidLocationError):
            loc_mapper().translate_location(old_style_course_id,
                                            test_no_cat_locn, False, False)
        test_no_cat_locn = test_no_cat_locn.replace(name='def456')
        # only one course matches
        self.translate_n_check(test_no_cat_locn, old_style_course_id,
                               new_style_package_id, 'problem4', 'published')

        # add a distractor course (note that abc123 has a different translation in this one)
        distractor_block_map = {
            'abc123': {
                'problem': 'problem3'
            },
            'def456': {
                'problem': 'problem4'
            },
            'ghi789': {
                'problem': 'problem7'
            },
        }
        test_delta_new_id = '{}.geek_dept.{}.{}'.format(
            org, course, 'delta_run')
        test_delta_old_id = '{}/{}/{}'.format(org, course, 'delta_run')
        loc_mapper().create_map_entry(Location('i4x', org, course, 'course',
                                               'delta_run'),
                                      test_delta_new_id,
                                      block_map=distractor_block_map)
        # test that old translation still works
        self.translate_n_check(test_problem_locn, old_style_course_id,
                               new_style_package_id, 'problem2', 'published')
        # and new returns new id
        self.translate_n_check(test_problem_locn, test_delta_old_id,
                               test_delta_new_id, 'problem3', 'published')
        # look for default translation of uncached Location (not unique; so, just verify it returns something)
        prob_locator = loc_mapper().translate_location(
            None,
            Location('i4x', org, course, 'problem', 'def456'),
            add_entry_if_missing=False)
        self.assertIsNotNone(prob_locator, "couldn't find ambiguous location")

        # make delta_run default course: anything not cached using None as old_course_id will use this
        loc_mapper().create_map_entry(Location('i4x', org, course, 'problem',
                                               '789abc123efg456'),
                                      test_delta_new_id,
                                      block_map=block_map)
        # now an uncached ambiguous query should return delta
        test_unused_locn = Location('i4x', org, course, 'problem', 'ghi789')
        self.translate_n_check(test_unused_locn, None, test_delta_new_id,
                               'problem7', 'published')

        # get the draft one (I'm sorry this is getting long)
        self.translate_n_check(test_unused_locn, None, test_delta_new_id,
                               'problem7', 'draft')
class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
    """
    Test the student flow in the combined open ended xmodule
    """
    problem_location = Location(
        ["i4x", "edX", "open_ended", "combinedopenended", "SampleQuestion"])
    answer = "blah blah"
    assessment = [0, 1]
    hint = "blah"

    def setUp(self):
        self.test_system = get_test_system()
        self.test_system.open_ended_grading_interface = None
        self.test_system.xqueue['interface'] = Mock(send_to_queue=Mock(
            side_effect=[1, "queued"]))
        self.setup_modulestore(COURSE)

    def test_open_ended_load_and_save(self):
        """
        See if we can load the module and save an answer
        @return:
        """
        # Load the module
        module = self.get_module_from_location(self.problem_location, COURSE)

        # Try saving an answer
        module.handle_ajax("save_answer", {"student_answer": self.answer})
        # Save our modifications to the underlying KeyValueStore so they can be persisted
        module.save()
        task_one_json = json.loads(module.task_states[0])
        self.assertEqual(task_one_json['child_history'][0]['answer'],
                         self.answer)

        module = self.get_module_from_location(self.problem_location, COURSE)
        task_one_json = json.loads(module.task_states[0])
        self.assertEqual(task_one_json['child_history'][0]['answer'],
                         self.answer)

    def test_open_ended_flow_reset(self):
        """
        Test the flow of the module if we complete the self assessment step and then reset
        @return:
        """
        assessment = [0, 1]
        module = self.get_module_from_location(self.problem_location, COURSE)

        #Simulate a student saving an answer
        html = module.handle_ajax("get_html", {})
        module.handle_ajax("save_answer", {"student_answer": self.answer})
        html = module.handle_ajax("get_html", {})

        #Mock a student submitting an assessment
        assessment_dict = MockQueryDict()
        assessment_dict.update({
            'assessment': sum(assessment),
            'score_list[]': assessment
        })
        module.handle_ajax("save_assessment", assessment_dict)
        task_one_json = json.loads(module.task_states[0])
        self.assertEqual(
            json.loads(task_one_json['child_history'][0]['post_assessment']),
            assessment)
        rubric = module.handle_ajax("get_combined_rubric", {})

        #Move to the next step in the problem
        module.handle_ajax("next_problem", {})
        self.assertEqual(module.current_task_number, 0)

        html = module.get_html()
        self.assertTrue(isinstance(html, basestring))

        rubric = module.handle_ajax("get_combined_rubric", {})
        self.assertTrue(isinstance(rubric, basestring))
        self.assertEqual(module.state, "assessing")
        module.handle_ajax("reset", {})
        self.assertEqual(module.current_task_number, 0)

    def test_open_ended_flow_correct(self):
        """
        Test a two step problem where the student first goes through the self assessment step, and then the
        open ended step.
        @return:
        """
        assessment = [1, 1]
        #Load the module
        module = self.get_module_from_location(self.problem_location, COURSE)

        #Simulate a student saving an answer
        module.handle_ajax("save_answer", {"student_answer": self.answer})
        status = module.handle_ajax("get_status", {})
        self.assertTrue(isinstance(status, basestring))

        #Mock a student submitting an assessment
        assessment_dict = MockQueryDict()
        assessment_dict.update({
            'assessment': sum(assessment),
            'score_list[]': assessment
        })
        module.handle_ajax("save_assessment", assessment_dict)
        task_one_json = json.loads(module.task_states[0])
        self.assertEqual(
            json.loads(task_one_json['child_history'][0]['post_assessment']),
            assessment)

        #Move to the next step in the problem
        try:
            module.handle_ajax("next_problem", {})
        except GradingServiceError:
            #This error is okay.  We don't have a grading service to connect to!
            pass
        self.assertEqual(module.current_task_number, 1)
        try:
            module.get_html()
        except GradingServiceError:
            #This error is okay.  We don't have a grading service to connect to!
            pass

        #Try to get the rubric from the module
        module.handle_ajax("get_combined_rubric", {})

        #Make a fake reply from the queue
        queue_reply = {
            'queuekey':
            "",
            'xqueue_body':
            json.dumps({
                'score':
                0,
                'feedback':
                json.dumps({
                    "spelling":
                    "Spelling: Ok.",
                    "grammar":
                    "Grammar: Ok.",
                    "markup-text":
                    " all of us can think of a book that we hope none of our children or any other children have taken off the shelf . but if i have the right to remove that book from the shelf that work i abhor then you also have exactly the same right and so does everyone else . and then we <bg>have no books left</bg> on the shelf for any of us . <bs>katherine</bs> <bs>paterson</bs> , author write a persuasive essay to a newspaper reflecting your vies on censorship <bg>in libraries . do</bg> you believe that certain materials , such as books , music , movies , magazines , <bg>etc . , should be</bg> removed from the shelves if they are found <bg>offensive ? support your</bg> position with convincing arguments from your own experience , observations <bg>, and or reading .</bg> "
                }),
                'grader_type':
                "ML",
                'success':
                True,
                'grader_id':
                1,
                'submission_id':
                1,
                'rubric_xml':
                "<rubric><category><description>Writing Applications</description><score>0</score><option points='0'> The essay loses focus, has little information or supporting details, and the organization makes it difficult to follow.</option><option points='1'> The essay presents a mostly unified theme, includes sufficient information to convey the theme, and is generally organized well.</option></category><category><description> Language Conventions </description><score>0</score><option points='0'> The essay demonstrates a reasonable command of proper spelling and grammar. </option><option points='1'> The essay demonstrates superior command of proper spelling and grammar.</option></category></rubric>",
                'rubric_scores_complete':
                True,
            })
        }

        module.handle_ajax("check_for_score", {})

        #Update the module with the fake queue reply
        module.handle_ajax("score_update", queue_reply)
        self.assertFalse(module.ready_to_reset)
        self.assertEqual(module.current_task_number, 1)

        #Get html and other data client will request
        module.get_html()

        module.handle_ajax("skip_post_assessment", {})

        #Get all results
        module.handle_ajax("get_combined_rubric", {})

        #reset the problem
        module.handle_ajax("reset", {})
        self.assertEqual(module.state, "initial")
    def test_translate_location_dwim(self):
        """
        Test the location translation mechanisms which try to do-what-i-mean by creating new
        entries for never seen queries.
        """
        org = 'foo_org'
        course = 'bar_course'
        old_style_course_id = '{}/{}/{}'.format(org, course, 'baz_run')
        problem_name = 'abc123abc123abc123abc123abc123f9'
        location = Location('i4x', org, course, 'problem', problem_name)
        new_style_package_id = '{}.{}.{}'.format(org, course, 'baz_run')
        self.translate_n_check(location, old_style_course_id,
                               new_style_package_id, 'problemabc', 'published',
                               True)
        # look for w/ only the Location (works b/c there's only one possible course match): causes cache
        self.translate_n_check(location, None, new_style_package_id,
                               'problemabc', 'published', True)

        # create an entry w/o a guid name
        other_location = Location('i4x', org, course, 'chapter', 'intro')
        self.translate_n_check(other_location, old_style_course_id,
                               new_style_package_id, 'intro', 'published',
                               True)

        # add a distractor course
        delta_new_package_id = '{}.geek_dept.{}.{}'.format(
            org, course, 'delta_run')
        delta_course_locn = Location('i4x', org, course, 'course', 'delta_run')
        loc_mapper().create_map_entry(
            delta_course_locn,
            delta_new_package_id,
            block_map={problem_name: {
                'problem': 'problem3'
            }})
        self.translate_n_check(location, old_style_course_id,
                               new_style_package_id, 'problemabc', 'published',
                               True)

        # add a new one to both courses (ensure name doesn't have same beginning)
        new_prob_name = uuid.uuid4().hex
        while new_prob_name.startswith('abc'):
            new_prob_name = uuid.uuid4().hex
        new_prob_locn = location.replace(name=new_prob_name)
        new_usage_id = 'problem{}'.format(new_prob_name[:3])
        self.translate_n_check(new_prob_locn, old_style_course_id,
                               new_style_package_id, new_usage_id, 'published',
                               True)
        self.translate_n_check(new_prob_locn, delta_course_locn.course_id,
                               delta_new_package_id, new_usage_id, 'published',
                               True)
        # look for w/ only the Location: causes caching and not unique; so, can't check which course
        prob_locator = loc_mapper().translate_location(
            None, new_prob_locn, add_entry_if_missing=True)
        self.assertIsNotNone(prob_locator, "couldn't find ambiguous location")

        # add a default course pointing to the delta_run
        loc_mapper().create_map_entry(
            Location('i4x', org, course, 'problem', '789abc123efg456'),
            delta_new_package_id,
            block_map={problem_name: {
                'problem': 'problem3'
            }})
        # now the ambiguous query should return delta
        again_prob_name = uuid.uuid4().hex
        while again_prob_name.startswith('abc') or again_prob_name.startswith(
                new_prob_name[:3]):
            again_prob_name = uuid.uuid4().hex
        again_prob_locn = location.replace(name=again_prob_name)
        again_usage_id = 'problem{}'.format(again_prob_name[:3])
        self.translate_n_check(again_prob_locn, old_style_course_id,
                               new_style_package_id, again_usage_id,
                               'published', True)
        self.translate_n_check(again_prob_locn, delta_course_locn.course_id,
                               delta_new_package_id, again_usage_id,
                               'published', True)
        self.translate_n_check(again_prob_locn, None, delta_new_package_id,
                               again_usage_id, 'published', True)
Exemple #25
0
    def _create(cls, target_class, **kwargs):
        """
        Uses ``**kwargs``:

        :parent_location: (required): the location of the parent module
            (e.g. the parent course or section)

        :category: the category of the resulting item.

        :data: (optional): the data for the item
            (e.g. XML problem definition for a problem item)

        :display_name: (optional): the display name of the item

        :metadata: (optional): dictionary of metadata attributes

        :boilerplate: (optional) the boilerplate for overriding field values

        :target_class: is ignored
        """

        # All class attributes (from this class and base classes) are
        # passed in via **kwargs. However, some of those aren't actual field values,
        # so pop those off for use separately

        DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
        # catch any old style users before they get into trouble
        assert 'template' not in kwargs
        parent_location = Location(kwargs.pop('parent_location', None))
        data = kwargs.pop('data', None)
        category = kwargs.pop('category', None)
        display_name = kwargs.pop('display_name', None)
        metadata = kwargs.pop('metadata', {})
        location = kwargs.pop('location')
        assert location != parent_location

        store = kwargs.pop('modulestore')

        # This code was based off that in cms/djangoapps/contentstore/views.py
        parent = kwargs.pop('parent', None) or store.get_item(parent_location)

        if 'boilerplate' in kwargs:
            template_id = kwargs.pop('boilerplate')
            clz = XBlock.load_class(category, select=prefer_xmodules)
            template = clz.get_template(template_id)
            assert template is not None
            metadata.update(template.get('metadata', {}))
            if not isinstance(data, basestring):
                data.update(template.get('data'))

        # replace the display name with an optional parameter passed in from the caller
        if display_name is not None:
            metadata['display_name'] = display_name
        module = store.create_and_save_xmodule(location, metadata=metadata, definition_data=data)

        module = store.get_item(location)

        for attr, val in kwargs.items():
            setattr(module, attr, val)
        module.save()

        store.save_xmodule(module)

        if location.category not in DETACHED_CATEGORIES:
            parent.children.append(location.url())
            store.update_children(parent_location, parent.children)

        return store.get_item(location)
    def test_translate_locator(self):
        """
        tests translate_locator_to_location(BlockUsageLocator)
        """
        # lookup for non-existent course
        org = 'foo_org'
        course = 'bar_course'
        new_style_package_id = '{}.geek_dept.{}.baz_run'.format(org, course)
        prob_locator = BlockUsageLocator(package_id=new_style_package_id,
                                         block_id='problem2',
                                         branch='published')
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator)
        self.assertIsNone(prob_location, 'found entry in empty map table')

        loc_mapper().create_map_entry(Location('i4x', org, course, 'course',
                                               'baz_run'),
                                      new_style_package_id,
                                      block_map={
                                          'abc123': {
                                              'problem': 'problem2'
                                          },
                                          '48f23a10395384929234': {
                                              'chapter': 'chapter48f'
                                          },
                                          'baz_run': {
                                              'course': 'root'
                                          },
                                      })
        # only one course matches
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator)
        # default branch
        self.assertEqual(
            prob_location,
            Location('i4x', org, course, 'problem', 'abc123', None))
        # test get_course keyword
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator, get_course=True)
        self.assertEqual(
            prob_location,
            Location('i4x', org, course, 'course', 'baz_run', None))
        # explicit branch
        prob_locator = BlockUsageLocator(package_id=prob_locator.package_id,
                                         branch='draft',
                                         block_id=prob_locator.block_id)
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator)
        # Even though the problem was set as draft, we always return revision=None to work
        # with old mongo/draft modulestores.
        self.assertEqual(
            prob_location,
            Location('i4x', org, course, 'problem', 'abc123', None))
        prob_locator = BlockUsageLocator(package_id=new_style_package_id,
                                         block_id='problem2',
                                         branch='production')
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator)
        self.assertEqual(
            prob_location,
            Location('i4x', org, course, 'problem', 'abc123', None))
        # same for chapter except chapter cannot be draft in old system
        chap_locator = BlockUsageLocator(package_id=new_style_package_id,
                                         block_id='chapter48f',
                                         branch='production')
        chap_location = loc_mapper().translate_locator_to_location(
            chap_locator)
        self.assertEqual(
            chap_location,
            Location('i4x', org, course, 'chapter', '48f23a10395384929234'))
        # explicit branch
        chap_locator.branch = 'draft'
        chap_location = loc_mapper().translate_locator_to_location(
            chap_locator)
        self.assertEqual(
            chap_location,
            Location('i4x', org, course, 'chapter', '48f23a10395384929234'))
        chap_locator = BlockUsageLocator(package_id=new_style_package_id,
                                         block_id='chapter48f',
                                         branch='production')
        chap_location = loc_mapper().translate_locator_to_location(
            chap_locator)
        self.assertEqual(
            chap_location,
            Location('i4x', org, course, 'chapter', '48f23a10395384929234'))

        # look for non-existent problem
        prob_locator2 = BlockUsageLocator(package_id=new_style_package_id,
                                          branch='draft',
                                          block_id='problem3')
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator2)
        self.assertIsNone(prob_location, 'Found non-existent problem')

        # add a distractor course
        new_style_package_id = '{}.geek_dept.{}.{}'.format(
            org, course, 'delta_run')
        loc_mapper().create_map_entry(
            Location('i4x', org, course, 'course', 'delta_run'),
            new_style_package_id,
            block_map={'abc123': {
                'problem': 'problem3'
            }})
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator)
        self.assertEqual(
            prob_location,
            Location('i4x', org, course, 'problem', 'abc123', None))

        # add a default course pointing to the delta_run
        loc_mapper().create_map_entry(
            Location('i4x', org, course, 'problem', '789abc123efg456'),
            new_style_package_id,
            block_map={'abc123': {
                'problem': 'problem3'
            }})
        # now query delta (2 entries point to it)
        prob_locator = BlockUsageLocator(package_id=new_style_package_id,
                                         branch='production',
                                         block_id='problem3')
        prob_location = loc_mapper().translate_locator_to_location(
            prob_locator)
        self.assertEqual(prob_location,
                         Location('i4x', org, course, 'problem', 'abc123'))
Exemple #27
0
def test_none():
    assert_equals([None] * 6, Location(None).list())
 def test_location(self):
     input_list = ['tag', 'org', 'course', 'category', 'name']
     self.assertEquals("tag://org/course/category/name", Location(Location(input_list)).url())
Exemple #29
0
def test_html_id():
    loc = Location("tag://org/course/cat/name:more_name@rev")
    assert_equals(loc.html_id(), "tag-org-course-cat-name_more_name-rev")
Exemple #30
0
    def from_xml(cls, xml_data, system, org=None, course=None):
        """
        Creates an instance of this descriptor from the supplied xml_data.
        This may be overridden by subclasses

        xml_data: A string of xml that will be translated into data and children for
            this module
        system: A DescriptorSystem for interacting with external resources
        org and course are optional strings that will be used in the generated modules
            url identifiers
        """

        xml_object = etree.fromstring(xml_data)
        # VS[compat] -- just have the url_name lookup, once translation is done
        url_name = xml_object.get('url_name', xml_object.get('slug'))
        location = Location('i4x', org, course, xml_object.tag, url_name)

        # VS[compat] -- detect new-style each-in-a-file mode
        if is_pointer_tag(xml_object):
            # new style:
            # read the actual definition file--named using url_name.replace(':','/')
            filepath = cls._format_filepath(xml_object.tag,
                                            name_to_pathname(url_name))
            definition_xml = cls.load_file(filepath, system.resources_fs,
                                           location)
        else:
            definition_xml = xml_object
            filepath = None

        definition, children = cls.load_definition(
            definition_xml, system, location)  # note this removes metadata

        # VS[compat] -- make Ike's github preview links work in both old and
        # new file layouts
        if is_pointer_tag(xml_object):
            # new style -- contents actually at filepath
            definition['filename'] = [filepath, filepath]

        metadata = cls.load_metadata(definition_xml)

        # move definition metadata into dict
        dmdata = definition.get('definition_metadata', '')
        if dmdata:
            metadata['definition_metadata_raw'] = dmdata
            try:
                metadata.update(json.loads(dmdata))
            except Exception as err:
                log.debug('Error %s in loading metadata %s' % (err, dmdata))
                metadata['definition_metadata_err'] = str(err)

        # Set/override any metadata specified by policy
        k = policy_key(location)
        if k in system.policy:
            cls.apply_policy(metadata, system.policy[k])

        field_data = {}
        field_data.update(metadata)
        field_data.update(definition)
        field_data['children'] = children

        field_data['xml_attributes'] = {}
        field_data['xml_attributes']['filename'] = definition.get(
            'filename', ['', None])  # for git link
        for key, value in metadata.items():
            if key not in cls.fields:
                field_data['xml_attributes'][key] = value
        field_data['location'] = location
        field_data['category'] = xml_object.tag
        kvs = InheritanceKeyValueStore(initial_values=field_data)
        field_data = DbModel(kvs)

        return system.construct_xblock_from_class(
            cls,
            field_data,

            # We're loading a descriptor, so student_id is meaningless
            # We also don't have separate notions of definition and usage ids yet,
            # so we use the location for both
            ScopeIds(None, location.category, location, location))