Пример #1
0
    def test_incomplete_and_default_flags(self):
        InteractiveWidget.load_default_widgets()
        exp = Exploration.create(
            User(email='*****@*****.**'), 'exploration', 'category', 'eid')

        state_id = exp.init_state.get().id

        # Hit the default once, and do an incomplete twice. The result should
        # be classified as incomplete.

        for i in range(3):
            EventHandler.record_state_hit('eid', state_id)

        EventHandler.record_rule_hit(
            'eid', state_id, Rule(name='Default', dest=state_id),
            extra_info='1')

        states = Statistics.get_top_ten_improvable_states(['eid'])
        self.assertEquals(len(states), 1)
        self.assertEquals(states[0]['rank'], 2)
        self.assertEquals(states[0]['type'], 'incomplete')

        # Now hit the default two more times. The result should be classified
        # as default.

        for i in range(2):
            EventHandler.record_state_hit('eid', state_id)
            EventHandler.record_rule_hit(
                'eid', state_id, Rule(name='Default', dest=state_id),
                extra_info='1')

        states = Statistics.get_top_ten_improvable_states(['eid'])
        self.assertEquals(len(states), 1)
        self.assertEquals(states[0]['rank'], 3)
        self.assertEquals(states[0]['type'], 'default')
Пример #2
0
    def test_get_top_ten_improvable_states(self):
        InteractiveWidget.load_default_widgets()
        exp = Exploration.create(
            User(email='*****@*****.**'), 'exploration', 'category', 'eid')

        state_id = exp.init_state.get().id

        EventHandler.record_rule_hit(
            'eid', state_id, Rule(name='Default', dest=state_id),
            extra_info='1')
        EventHandler.record_rule_hit(
            'eid', state_id, Rule(name='Default', dest=state_id),
            extra_info='2')
        EventHandler.record_rule_hit(
            'eid', state_id, Rule(name='Default', dest=state_id),
            extra_info='1')

        EventHandler.record_state_hit('eid', state_id)
        EventHandler.record_state_hit('eid', state_id)
        EventHandler.record_state_hit('eid', state_id)
        EventHandler.record_state_hit('eid', state_id)
        EventHandler.record_state_hit('eid', state_id)

        states = Statistics.get_top_ten_improvable_states(['eid'])
        self.assertEquals(len(states), 1)
        self.assertEquals(states[0]['exp_id'], 'eid')
        self.assertEquals(states[0]['type'], 'default')
        self.assertEquals(states[0]['rank'], 3)
        self.assertEquals(states[0]['state_id'], exp.init_state.get().id)
Пример #3
0
def reload_widgets():
    """Reload the default classifiers and widgets."""
    Classifier.delete_all_classifiers()
    Classifier.load_default_classifiers()

    Widget.delete_all_widgets()
    InteractiveWidget.load_default_widgets()
    NonInteractiveWidget.load_default_widgets()
Пример #4
0
    def setUp(self):
        """Loads the default widgets and create sample users and explorations."""
        super(StateModelUnitTests, self).setUp()
        InteractiveWidget.load_default_widgets()

        self.user = User(email="*****@*****.**")
        self.another_user = User(email="*****@*****.**")

        self.exploration = Exploration.create(self.user, "A title", "A category", "A exploration_id")
        self.exploration.put()
Пример #5
0
def reload_demos():
    """Reload default classifiers, widgets, and explorations (in that order)."""
    Classifier.delete_all_classifiers()
    Classifier.load_default_classifiers()

    Widget.delete_all_widgets()
    InteractiveWidget.load_default_widgets()
    NonInteractiveWidget.load_default_widgets()

    Exploration.delete_demo_explorations()
    Exploration.load_demo_explorations()
Пример #6
0
    def test_loading_and_deletion_of_widgets(self):
        """Test loading and deletion of the default widgets."""
        self.assertEqual(Widget.query().count(), 0)

        InteractiveWidget.load_default_widgets()
        self.assertEqual(Widget.query().count(), 7)
        self.assertEqual(InteractiveWidget.query().count(), 7)
        self.assertEqual(NonInteractiveWidget.query().count(), 0)

        Widget.delete_all_widgets()
        self.assertEqual(Widget.query().count(), 0)
Пример #7
0
    def setUp(self):
        """Loads the default widgets."""
        super(ExplorationServicesUnitTests, self).setUp()
        InteractiveWidget.load_default_widgets()

        self.owner = User(email='*****@*****.**')
        self.editor = User(email='*****@*****.**')
        self.viewer = User(email='*****@*****.**')

        self.exploration = Exploration.create(
            self.owner, 'A title', 'A category', 'A exploration_id')
        self.exploration.editors.append(self.editor)
        self.exploration.put()
Пример #8
0
    def test_two_state_default_hit(self):
        SECOND_STATE = 'State 2'

        InteractiveWidget.load_default_widgets()
        exp = Exploration.create(
            User(email='*****@*****.**'), 'exploration', 'category', 'eid')
        second_state = exp.add_state(SECOND_STATE)

        state_1_id = exp.init_state.get().id
        state_2_id = second_state.id

        # Hit the default rule of state 1 once, and the default rule of state 2
        # twice.
        EventHandler.record_state_hit('eid', state_1_id)
        EventHandler.record_rule_hit(
            'eid', state_1_id, Rule(name='Default', dest=state_1_id),
            extra_info='1')

        for i in range(2):
            EventHandler.record_state_hit('eid', state_2_id)
            EventHandler.record_rule_hit(
                'eid', state_2_id, Rule(name='Default', dest=state_2_id),
                extra_info='1')

        states = Statistics.get_top_ten_improvable_states(['eid'])
        self.assertEquals(len(states), 2)
        self.assertEquals(states[0]['rank'], 2)
        self.assertEquals(states[0]['type'], 'default')
        self.assertEquals(states[0]['state_id'], state_2_id)
        self.assertEquals(states[1]['rank'], 1)
        self.assertEquals(states[1]['type'], 'default')
        self.assertEquals(states[1]['state_id'], state_1_id)

        # Hit the default rule of state 1 two more times.

        for i in range(2):
            EventHandler.record_state_hit('eid', state_1_id)
            EventHandler.record_rule_hit(
                'eid', state_1_id, Rule(name='Default', dest=state_1_id),
                extra_info='1')

        states = Statistics.get_top_ten_improvable_states(['eid'])
        self.assertEquals(len(states), 2)
        self.assertEquals(states[0]['rank'], 3)
        self.assertEquals(states[0]['type'], 'default')
        self.assertEquals(states[0]['state_id'], state_1_id)
        self.assertEquals(states[1]['rank'], 2)
        self.assertEquals(states[1]['type'], 'default')
        self.assertEquals(states[1]['state_id'], state_2_id)
Пример #9
0
    def get(self, exploration_id):
        """Populates the data on the individual exploration page."""
        # TODO(sll): Maybe this should send a complete state machine to the
        # frontend, and all interaction would happen client-side?
        exploration = Exploration.get(exploration_id)
        init_state = exploration.init_state.get()
        params = self.get_params(init_state)
        init_html, init_widgets = parse_content_into_html(
            init_state.content, 0, params)
        interactive_widget_html = InteractiveWidget.get_raw_code(
            init_state.widget.widget_id,
            params=utils.parse_dict_with_params(
                init_state.widget.params, params)
        )

        self.values.update({
            'block_number': 0,
            'interactive_widget_html': interactive_widget_html,
            'interactive_params': init_state.widget.params,
            'oppia_html': init_html,
            'params': params,
            'state_id': init_state.id,
            'title': exploration.title,
            'widgets': init_widgets,
        })
        if init_state.widget.widget_id in DEFAULT_ANSWERS:
            self.values['default_answer'] = (
                DEFAULT_ANSWERS[init_state.widget.widget_id])
        self.response.write(json.dumps(self.values))

        EventHandler.record_exploration_visited(exploration_id)
        EventHandler.record_state_hit(exploration_id, init_state.id)
Пример #10
0
    def post(self, widget_id):
        """Handles POST requests, for parameterized widgets."""
        payload = json.loads(self.request.get('payload'))

        params = payload.get('params', {})
        if isinstance(params, list):
            new_params = {}
            for item in params:
                new_params[item['name']] = item['default_value']
            params = new_params

        state_params_dict = {}
        state_params_given = payload.get('state_params')
        if state_params_given:
            for param in state_params_given:
                # Pick a random parameter for each key.
                state_params_dict[param['name']] = (
                    utils.get_random_choice(param['values']))

        response = InteractiveWidget.get_with_params(
            widget_id, params=utils.parse_dict_with_params(
                params, state_params_dict)
        )

        self.render_json({'widget': response})
Пример #11
0
def get_state_for_frontend(state, exploration):
    """Returns a representation of the given state for the frontend."""

    state_repr = state.as_dict()
    modified_state_dict = state.internals_as_dict(human_readable_dests=True)

    # TODO(sll): The following is for backwards-compatibility and should be
    # deleted later.
    rules = {}
    for handler in state_repr['widget']['handlers']:
        rules[handler['name']] = handler['rules']
        for item in rules[handler['name']]:
            if item['name'] == 'Default':
                item['rule'] = 'Default'
            else:
                item['rule'] = InteractiveWidget.get(
                    state.widget.widget_id).get_readable_name(
                        handler['name'], item['name']
                    )
    state_repr['widget']['rules'] = rules
    state_repr['widget']['id'] = state_repr['widget']['widget_id']
    state_repr['widget']['sticky'] = state_repr['widget']['sticky']

    state_repr['yaml'] = utils.yaml_from_dict(modified_state_dict)
    return state_repr
Пример #12
0
 def get(self, widget_id):
     """Handles GET requests."""
     try:
         self.render_json({
             'widget': InteractiveWidget.get_with_params(widget_id, {}),
         })
     except:
         raise self.PageNotFoundException
Пример #13
0
    def test_no_improvement_flag_hit(self):
        InteractiveWidget.load_default_widgets()
        exp = Exploration.create(
            User(email='*****@*****.**'), 'exploration', 'category', 'eid')

        init_state = exp.init_state.get()
        state_id = init_state.id
        init_state.widget.handlers[0].rules = [
            Rule(name='NotDefault', dest=state_id),
            Rule(name='Default', dest=state_id),
        ]
        init_state.put()

        EventHandler.record_rule_hit(
            'eid', state_id, Rule(name='NotDefault', dest=state_id),
            extra_info='1')
        EventHandler.record_state_hit('eid', state_id)

        states = Statistics.get_top_ten_improvable_states(['eid'])
        self.assertEquals(len(states), 0)
Пример #14
0
    def _pre_put_hook(self):
        """Ensures that the widget and at least one handler for it exists."""
        if not self.widget:
            self.widget = self.get_default_widget()
        elif not self.widget.handlers:
            self.widget.handlers = [self.get_default_handler()]
        # TODO(sll): Do other validation.

        # Add the corresponding AnswerHandler classifiers for easy reference.
        widget = InteractiveWidget.get(self.widget.widget_id)
        for curr_handler in self.widget.handlers:
            for w_handler in widget.handlers:
                if w_handler.name == curr_handler.name:
                    curr_handler.classifier = w_handler.classifier
Пример #15
0
    def get_classifier_info(self, widget_id, handler_name, rule, state_params):
        classifier_func = rule.name.replace(' ', '')
        first_bracket = classifier_func.find('(')
        mutable_rule = InteractiveWidget.get(widget_id).get_readable_name(
            handler_name, rule.name)

        func_name = classifier_func[: first_bracket]
        str_params = classifier_func[first_bracket + 1: -1].split(',')

        param_list = []
        for index, param in enumerate(str_params):
            parsed_param = rule.inputs[param]
            if isinstance(parsed_param, basestring) and '{{' in parsed_param:
                parsed_param = utils.parse_with_jinja(
                    parsed_param, state_params)

            typed_object = self.get_typed_object(mutable_rule, param)
            normalized_param = typed_object.normalize(parsed_param)
            param_list.append(normalized_param)

        return func_name, param_list
Пример #16
0
    def get(self, exploration_id):
        """Populates the data on the individual exploration page."""
        # TODO(sll): Maybe this should send a complete state machine to the
        # frontend, and all interaction would happen client-side?
        try:
            exploration = Exploration.get(exploration_id)
        except Exception as e:
            raise self.PageNotFoundException(e)

        init_state = exploration.init_state
        # TODO: get params from exploration specification instead
        params = self._get_exploration_params(exploration)
        params = get_params(init_state, params)
        init_html, init_widgets = parse_content_into_html(
            init_state.content, 0, params)
        interactive_widget_html = InteractiveWidget.get_raw_code(
            init_state.widget.widget_id,
            params=utils.parse_dict_with_params(
                init_state.widget.params, params)
        )

        self.values.update({
            'block_number': 0,
            'interactive_widget_html': interactive_widget_html,
            'interactive_params': init_state.widget.params,
            'oppia_html': init_html,
            'params': params,
            'state_id': exploration.init_state_id,
            'title': exploration.title,
            'widgets': init_widgets,
        })
        if init_state.widget.widget_id in DEFAULT_ANSWERS:
            self.values['default_answer'] = (
                DEFAULT_ANSWERS[init_state.widget.widget_id])
        self.render_json(self.values)

        EventHandler.record_exploration_visited(exploration_id)
        EventHandler.record_state_hit(exploration_id, exploration.init_state_id)
Пример #17
0
 def setUp(self):
     """Loads the default widgets."""
     super(ExplorationDataUnitTests, self).setUp()
     InteractiveWidget.load_default_widgets()
Пример #18
0
 def setUp(self):
     """Loads the default widgets."""
     super(ExplorationModelUnitTests, self).setUp()
     InteractiveWidget.load_default_widgets()
     self.user = User(email='*****@*****.**')
     self.another_user = User(email='*****@*****.**')
Пример #19
0
    def post(self, exploration_id, state_id):
        """Handles feedback interactions with readers."""
        values = {'error': []}

        exploration = Exploration.get(exploration_id)
        state = State.get(state_id, parent=exploration.key)
        old_state = state

        payload = json.loads(self.request.get('payload'))

        # The 0-based index of the last content block already on the page.
        block_number = payload.get('block_number') + 1
        # The reader's answer.
        answer = payload.get('answer')
        # The answer handler (submit, click, etc.)
        handler = payload.get('handler')

        params = payload.get('params', {})
        # Add the reader's answer to the parameter list.
        params['answer'] = answer

        dest_id, feedback, rule, recorded_answer = state.transition(
            answer, params, handler)

        if recorded_answer is not None:
            recorded_answer = json.dumps(recorded_answer)
            EventHandler.record_rule_hit(
                exploration_id, state_id, rule, recorded_answer)
            # Add this answer to the state's 'unresolved answers' list.
            if recorded_answer not in old_state.unresolved_answers:
                old_state.unresolved_answers[recorded_answer] = 0
            old_state.unresolved_answers[recorded_answer] += 1
            # TODO(sll): Make this async?
            old_state.put()

        assert dest_id

        html_output, widget_output = '', []
        # TODO(sll): The following is a special-case for multiple choice input,
        # in which the choice text must be displayed instead of the choice
        # number. We might need to find a way to do this more generically.
        if state.widget.widget_id == 'interactive-MultipleChoiceInput':
            answer = state.widget.params['choices'][int(answer)]

        # Append reader's answer.
        values['reader_html'] = feconf.JINJA_ENV.get_template(
            'reader/reader_response.html').render({'response': answer})

        if dest_id == feconf.END_DEST:
            # This leads to a FINISHED state.
            if feedback:
                html_output, widget_output = self._append_feedback(
                    feedback, html_output, widget_output, block_number, params)
            EventHandler.record_exploration_completed(exploration_id)
        else:
            state = State.get(dest_id, parent=exploration.key)
            EventHandler.record_state_hit(exploration_id, dest_id)

            if feedback:
                html_output, widget_output = self._append_feedback(
                    feedback, html_output, widget_output, block_number, params)

            # Populate new parameters.
            params = get_params(state, existing_params=params)
            # Append text for the new state only if the new and old states
            # differ.
            if old_state.id != state.id:
                state_html, state_widgets = parse_content_into_html(
                    state.content, block_number, params)
                # Separate text for the new state and feedback for the old state
                # by an additional line.
                if state_html and feedback:
                    html_output += '<br>'
                html_output += state_html
                widget_output += state_widgets

        if state.widget.widget_id in DEFAULT_ANSWERS:
            values['default_answer'] = DEFAULT_ANSWERS[state.widget.widget_id]
        values.update({
            'exploration_id': exploration.id, 'state_id': state.id,
            'oppia_html': html_output, 'widgets': widget_output,
            'block_number': block_number, 'params': params,
            'finished': (dest_id == feconf.END_DEST),
        })

        if dest_id != feconf.END_DEST:
            if state.widget.sticky and (
                    state.widget.widget_id == old_state.widget.widget_id):
                values['interactive_widget_html'] = ''
                values['sticky_interactive_widget'] = True
            else:
                values['interactive_widget_html'] = (
                    InteractiveWidget.get_raw_code(
                        state.widget.widget_id,
                        params=utils.parse_dict_with_params(
                            state.widget.params, params)
                    )
                )
        else:
            values['interactive_widget_html'] = ''

        self.render_json(values)
Пример #20
0
    def post(self, exploration_id, state_id):
        """Handles feedback interactions with readers."""
        values = {'error': []}

        exploration = Exploration.get(exploration_id)
        state = exploration.get_state_by_id(state_id)
        old_state = state

        # The 0-based index of the last content block already on the page.
        block_number = self.payload.get('block_number') + 1
        # The reader's answer.
        answer = self.payload.get('answer')
        # The answer handler (submit, click, etc.)
        handler = self.payload.get('handler')

        params = self.payload.get('params', {})
        # Add the reader's answer to the parameter list.
        params['answer'] = answer

        dest_id, feedback, rule, recorded_answer = state.transition(
            answer, params, handler)
        assert dest_id

        if recorded_answer is not None:
            recorded_answer = json.dumps(recorded_answer)
            EventHandler.record_rule_hit(
                exploration_id, state_id, rule, recorded_answer)
            # Add this answer to the state's 'unresolved answers' list.
            if recorded_answer not in old_state.unresolved_answers:
                old_state.unresolved_answers[recorded_answer] = 0
            old_state.unresolved_answers[recorded_answer] += 1
            # TODO(sll): Make this async?
            old_state.put()

        html_output, widget_output = '', []
        old_params = params

        if dest_id == feconf.END_DEST:
            # This leads to a FINISHED state.
            if feedback:
                html_output, widget_output = self._append_feedback(
                    feedback, html_output, widget_output, block_number,
                    old_params)
            EventHandler.record_exploration_completed(exploration_id)
        else:
            state = exploration.get_state_by_id(dest_id)
            EventHandler.record_state_hit(exploration_id, dest_id)

            if feedback:
                html_output, widget_output = self._append_feedback(
                    feedback, html_output, widget_output, block_number,
                    old_params)

            # Populate new parameters.
            params = get_params(state, existing_params=old_params)
            # Append text for the new state only if the new and old states
            # differ.
            if old_state.id != state.id:
                state_html, state_widgets = parse_content_into_html(
                    state.content, block_number, params)
                # Separate text for the new state and feedback for the old state
                # by an additional line.
                if state_html and feedback:
                    html_output += '<br>'
                html_output += state_html
                widget_output += state_widgets

        # Render the response in the customized html if
        # - the response is not rendered in the sticky interactive widget, and
        # - there is a static rendering html provided for that widget.
        sticky = (
            dest_id != feconf.END_DEST and
            state.widget.sticky and
            state.widget.widget_id == old_state.widget.widget_id
        )
        custom_response = ''
        if not sticky:
            response_params = utils.parse_dict_with_params(
                old_state.widget.params, old_params)
            response_params['answer'] = old_params['answer']
            response_params['iframe_content'] = False
            custom_response = InteractiveWidget.get_raw_static_code(
                old_state.widget.widget_id, response_params)

            if custom_response:
                response_params['iframe_content'] = True
                values['response_iframe'] = (
                    InteractiveWidget.get_raw_static_code(
                        old_state.widget.widget_id, response_params)
                )

        # Append reader's answer.
        response = custom_response if custom_response else answer
        if sticky:
            response = ''
        values['reader_html'] = feconf.JINJA_ENV.get_template(
            'reader/reader_response.html').render({
                'response': response,
                'custom_response': bool(custom_response),
            })

        if state.widget.widget_id in DEFAULT_ANSWERS:
            values['default_answer'] = DEFAULT_ANSWERS[state.widget.widget_id]
        values.update({
            'exploration_id': exploration_id, 'state_id': state.id,
            'oppia_html': html_output, 'widgets': widget_output,
            'block_number': block_number, 'params': params,
            'finished': (dest_id == feconf.END_DEST),
        })

        if dest_id != feconf.END_DEST:
            if sticky:
                values['interactive_widget_html'] = ''
                values['sticky_interactive_widget'] = True
            else:
                values['interactive_widget_html'] = (
                    InteractiveWidget.get_raw_code(
                        state.widget.widget_id,
                        params=utils.parse_dict_with_params(
                            state.widget.params, params)
                    )
                )
        else:
            values['interactive_widget_html'] = ''

        self.render_json(values)
Пример #21
0
 def get(self, widget_id):
     """Handles GET requests."""
     response = InteractiveWidget.get_with_params(widget_id)
     self.response.write(json.dumps({'widget': response}))