Example #1
0
    def test_parse_string(self):
        parsed_str = jinja_utils.parse_string('{{test}}', {'test': 'hi'})
        self.assertEqual(parsed_str, 'hi')

        # Some parameters are missing.
        parsed_str = jinja_utils.parse_string('{{test}} and {{test2}}',
                                              {'test2': 'hi'})
        self.assertEqual(parsed_str, ' and hi')

        # All parameters are missing.
        parsed_str = jinja_utils.parse_string('{{test}} and {{test2}}', {})
        self.assertEqual(parsed_str, ' and ')

        # The string has no parameters.
        parsed_str = jinja_utils.parse_string('no params', {'param': 'hi'})
        self.assertEqual(parsed_str, 'no params')

        # Integer parameters are used.
        parsed_str = jinja_utils.parse_string('int {{i}}', {'i': 2})
        self.assertEqual(parsed_str, 'int 2')

        # Invalid input string is used.
        with self.assertRaisesRegexp(Exception,
                                     'Unable to parse string with Jinja: {{'):
            jinja_utils.parse_string('{{', {'a': 3, 'b': 0})

        # Invalid expression is used.
        parsed_str = jinja_utils.parse_string('{{ a/b }}', {'a': 1, 'b': 0})
        self.assertEqual(parsed_str,
                         python_utils.UNICODE('[CONTENT PARSING ERROR]'))
    def get_reader_response_html(self, state_customization_args,
                                 context_params, answer):
        """Gets the parameterized HTML and iframes for a reader response."""
        if not self.is_interactive:
            raise Exception(
                'This method should only be called for interactive widgets.')

        parameters = self._get_widget_param_instances(
            state_customization_args, context_params)
        parameters['answer'] = answer

        html, iframe = self._response_template_and_iframe
        html = jinja_utils.parse_string(html, parameters)
        iframe = jinja_utils.parse_string(iframe, parameters)
        return html, iframe
 def get_raw_code(self, state_customization_args, context_params,
                  preview_mode=False):
     """Gets the raw code for a parameterized widget."""
     return jinja_utils.parse_string(
         self.template,
         self._get_widget_param_instances(
             state_customization_args, context_params,
             preview_mode=preview_mode))
def evaluate_rule(definition, param_specs, answer_type, context_params, answer):
    """Evaluates a rule definition using context_params. Returns a boolean."""

    if 'rule_type' not in definition:
        raise Exception('No rule type specified when constructing rule.')

    elif definition['rule_type'] == DEFAULT_RULE_TYPE:
        return True

    elif definition['rule_type'] == ATOMIC_RULE_TYPE:
        subject_name = definition['subject']

        if subject_name == 'answer':
            subject_type = answer_type
        else:
            subject_type = param_specs[subject_name].obj_type

        all_rule_classes = get_rules_for_input_type(subject_type)
        rule = next(r for r in all_rule_classes
                    if r.__name__ == definition['name'])

        param_list = []
        param_defns = get_param_list(rule.description)
        for (param_name, obj_cls) in param_defns:
            parsed_param = definition['inputs'][param_name]
            if (isinstance(parsed_param, basestring) and '{{' in parsed_param):
                parsed_param = jinja_utils.parse_string(
                    parsed_param, context_params)
            normalized_param = obj_cls.normalize(parsed_param)
            param_list.append(normalized_param)

        if subject_name == 'answer':
            subject = answer
        else:
            subject = context_params[subject_name]

        return rule(*param_list).eval(subject)

    elif definition['rule_type'] == AND_RULE_TYPE:
        for child_dict in definition['children']:
            if not evaluate_rule(
                    child_dict, param_specs, answer_type, context_params, answer):
                return False
        return True

    elif definition['rule_type'] == OR_RULE_TYPE:
        for child_dict in definition['children']:
            if evaluate_rule(
                    child_dict, param_specs, answer_type, context_params, answer):
                return True
        return False

    elif definition['rule_type'] == NOT_RULE_TYPE:
        return (not evaluate_rule(
            definition['child'], param_specs, answer_type, context_params, answer))

    else:
        raise Exception('Unrecognized rule type %s' % definition['rule_type'])
Example #5
0
    def submit_answer(self,
                      exploration_id,
                      state_name,
                      answer,
                      params=None,
                      unused_exploration_version=None):
        """Submits an answer as an exploration player and returns the
        corresponding dict. This function has strong parallels to code in
        PlayerServices.js which has the non-test code to perform the same
        functionality. This is replicated here so backend tests may utilize the
        functionality of PlayerServices.js without being able to access it.

        TODO(bhenning): Replicate this in an end-to-end Protractor test to
        protect against code skew here.
        """
        if params is None:
            params = {}

        exploration = exp_services.get_exploration_by_id(exploration_id)

        # First, the answer must be classified.
        classify_result = self.post_json(
            '/explorehandler/classify/%s' % exploration_id, {
                'old_state': exploration.states[state_name].to_dict(),
                'params': params,
                'answer': answer
            })

        # Next, ensure the submission is recorded.
        self.post_json(
            '/explorehandler/answer_submitted_event/%s' % exploration_id, {
                'answer': answer,
                'params': params,
                'version': exploration.version,
                'old_state_name': state_name,
                'answer_group_index': classify_result['answer_group_index'],
                'rule_spec_index': classify_result['rule_spec_index']
            })

        # Now the next state's data must be calculated.
        outcome = classify_result['outcome']
        new_state = exploration.states[outcome['dest']]
        params['answer'] = answer
        new_params = self.get_updated_param_dict(params,
                                                 new_state.param_changes,
                                                 exploration.param_specs)

        return {
            'feedback_html':
            jinja_utils.parse_string(
                utils.get_random_choice(outcome['feedback'])
                if outcome['feedback'] else '', params),
            'question_html':
            new_state.content[0].to_html(new_params),
            'state_name':
            outcome['dest']
        }
Example #6
0
    def to_html(self, params):
        """Exports this content object to an HTML string.

        The content object is parameterized using the parameters in `params`.
        """
        if not isinstance(params, dict):
            raise Exception(
                'Expected context params for parsing content to be a dict, '
                'received %s' % params)

        return '<div>%s</div>' % jinja_utils.parse_string(self.value, params)
Example #7
0
    def test_parse_string(self):
        parsed_str = jinja_utils.parse_string('{{test}}', {'test': 'hi'})
        self.assertEqual(parsed_str, 'hi')

        # Some parameters are missing.
        parsed_str = jinja_utils.parse_string('{{test}} and {{test2}}',
                                              {'test2': 'hi'})
        self.assertEqual(parsed_str, ' and hi')

        # All parameters are missing.
        parsed_str = jinja_utils.parse_string('{{test}} and {{test2}}', {})
        self.assertEqual(parsed_str, ' and ')

        # The string has no parameters.
        parsed_str = jinja_utils.parse_string('no params', {'param': 'hi'})
        self.assertEqual(parsed_str, 'no params')

        # Integer parameters are used.
        parsed_str = jinja_utils.parse_string('int {{i}}', {'i': 2})
        self.assertEqual(parsed_str, 'int 2')
Example #8
0
    def test_parse_string(self):
        parsed_str = jinja_utils.parse_string('{{test}}', {'test': 'hi'})
        self.assertEqual(parsed_str, 'hi')

        # Some parameters are missing.
        parsed_str = jinja_utils.parse_string(
            '{{test}} and {{test2}}', {'test2': 'hi'})
        self.assertEqual(parsed_str, ' and hi')

        # All parameters are missing.
        parsed_str = jinja_utils.parse_string('{{test}} and {{test2}}', {})
        self.assertEqual(parsed_str, ' and ')

        # The string has no parameters.
        parsed_str = jinja_utils.parse_string('no params', {'param': 'hi'})
        self.assertEqual(parsed_str, 'no params')

        # Integer parameters are used.
        parsed_str = jinja_utils.parse_string('int {{i}}', {'i': 2})
        self.assertEqual(parsed_str, 'int 2')
Example #9
0
    def submit_answer(
            self, exploration_id, state_name, answer,
            params=None, unused_exploration_version=None):
        """Submits an answer as an exploration player and returns the
        corresponding dict. This function has strong parallels to code in
        PlayerServices.js which has the non-test code to perform the same
        functionality. This is replicated here so backend tests may utilize the
        functionality of PlayerServices.js without being able to access it.

        TODO(bhenning): Replicate this in an end-to-end Protractor test to
        protect against code skew here.
        """
        if params is None:
            params = {}

        exploration = exp_services.get_exploration_by_id(exploration_id)

        # First, the answer must be classified.
        classify_result = self.post_json(
            '/explorehandler/classify/%s' % exploration_id, {
                'old_state': exploration.states[state_name].to_dict(),
                'params': params,
                'answer': answer
            }
        )

        # Next, ensure the submission is recorded.
        self.post_json(
            '/explorehandler/answer_submitted_event/%s' % exploration_id, {
                'answer': answer,
                'params': params,
                'version': exploration.version,
                'old_state_name': state_name,
                'answer_group_index': classify_result['answer_group_index'],
                'rule_spec_index': classify_result['rule_spec_index']
            }
        )

        # Now the next state's data must be calculated.
        outcome = classify_result['outcome']
        new_state = exploration.states[outcome['dest']]
        params['answer'] = answer
        new_params = self.get_updated_param_dict(
            params, new_state.param_changes, exploration.param_specs)

        return {
            'feedback_html': jinja_utils.parse_string(
                utils.get_random_choice(outcome['feedback'])
                if outcome['feedback'] else '', params),
            'question_html': new_state.content[0].to_html(new_params),
            'state_name': outcome['dest']
        }
    def get_stats_log_html(self, state_customization_args,
                           context_params, answer):
        """Gets the HTML for recording a reader response for the stats log.

        Returns an HTML string.
        """
        if not self.is_interactive:
            raise Exception(
                'This method should only be called for interactive widgets.')

        parameters = self._get_widget_param_instances(
            state_customization_args, context_params)
        parameters['answer'] = answer

        return jinja_utils.parse_string(self._stats_log_template, parameters)
Example #11
0
def submit_answer_in_tests(
        exploration_id, state_name, answer, params, handler_name, version):
    """This function should only be used by tests."""
    params['answer'] = answer

    exploration = exp_services.get_exploration_by_id(
        exploration_id, version=version)
    exp_param_specs = exploration.param_specs
    old_state = exploration.states[state_name]

    rule_spec = classify(
        exploration_id, exp_param_specs, old_state, handler_name,
        answer, params)

    old_interaction_instance = (
        interaction_registry.Registry.get_interaction_by_id(
            old_state.interaction.id))
    normalized_answer = old_interaction_instance.normalize_answer(
        answer, handler_name)
    # TODO(sll): Should this also depend on `params`?
    event_services.AnswerSubmissionEventHandler.record(
        exploration_id, version, state_name, handler_name, rule_spec,
        old_interaction_instance.get_stats_log_html(
            old_state.interaction.customization_args, normalized_answer))

    # This is necessary due to legacy reasons with the old 'END' pseudostate
    # TODO(bhenning): remove the need for this in this function (should be
    # basing terminal logic on whether it is in a terminal state, not
    # specifically in the 'END' state)
    _OLD_END_DEST = 'END'
    new_state = (
        None if rule_spec.dest == _OLD_END_DEST
        else exploration.states[rule_spec.dest])
    finished = (rule_spec.dest == _OLD_END_DEST)
    new_params = _get_updated_param_dict(
        params, {} if finished else new_state.param_changes,
        exp_param_specs)

    return {
        'feedback_html': jinja_utils.parse_string(
            rule_spec.get_feedback_string(), params),
        'finished': finished,
        'params': new_params,
        'question_html': (
            new_state.content[0].to_html(new_params)
            if not finished else ''),
        'state_name': rule_spec.dest if not finished else None,
    }
Example #12
0
    def get_interactive_widget_tag(self, state_customization_args,
                                   context_params, preview_mode=False):
        """Gets the widgetParams attribute value for an interactive widget."""
        if state_customization_args is None:
            state_customization_args = {}

        tag_name = ('oppia-interactive-%s' %
                    utils.camelcase_to_hyphenated(self.id))

        attr_strings = []
        for param in self.params:
            value_generator = param.generator(**param.init_args)
            # Use the given customization args. If they do not exist, use the
            # default customization args for the parameter.
            args_to_use = (
                state_customization_args[param.name]
                if param.name in state_customization_args
                else param.customization_args
            )

            try:
                generated_value = value_generator.generate_value(
                    context_params, **args_to_use)
            except Exception:
                if preview_mode:
                    generated_value = value_generator.default_value
                else:
                    raise

            # Normalize the generated values to the correct obj_type.
            param_value = (
                obj_services.Registry.get_object_class_by_type(
                    param.obj_type).normalize(generated_value))

            prefix = '%s-with-' % utils.camelcase_to_hyphenated(param.name)
            for arg in param.customization_args:
                arg_name = '%s%s' % (
                    prefix, utils.camelcase_to_hyphenated(arg))
                attr_strings.append(
                    jinja_utils.parse_string(
                        '{{arg_name}}="{{arg_value}}"', {
                            'arg_name': arg_name,
                            'arg_value': json.dumps(param_value),
                        }
                    )
                )

        return '<%s %s></%s>' % (tag_name, ' '.join(attr_strings), tag_name)
Example #13
0
    def get_stats_log_html(self, state_customization_args, answer):
        """Gets the HTML for recording a learner's response in the stats log.

        Returns an HTML string.
        """
        customization_args = {
            ca_spec.name: (
                state_customization_args[ca_spec.name]['value']
                if ca_spec.name in state_customization_args
                else ca_spec.default_value
            ) for ca_spec in self.customization_arg_specs
        }
        customization_args['answer'] = answer

        return jinja_utils.parse_string(
            self._stats_log_template, customization_args, autoescape=False)
Example #14
0
    def get_stats_log_html(self, state_customization_args, answer):
        """Gets the HTML for recording a learner's response in the stats log.

        Returns an HTML string.
        """
        customization_args = {
            ca_spec.name: (
                state_customization_args[ca_spec.name]['value']
                if ca_spec.name in state_customization_args
                else ca_spec.default_value
            ) for ca_spec in self.customization_arg_specs
        }
        customization_args['answer'] = answer

        return jinja_utils.parse_string(
            self._stats_log_template, customization_args, autoescape=False)
Example #15
0
    def get_reader_response_html(self, state_customization_args,
                                 context_params, answer, sticky):
        """Gets the parameterized HTML and iframes for a reader response."""
        if not self.is_interactive:
            raise Exception(
                'This method should only be called for interactive widgets.')

        parameters = self._get_widget_param_instances(state_customization_args,
                                                      context_params)
        parameters['answer'] = answer

        # The widget stays through the state transition because it is marked
        # sticky in the exploration and the new state uses the same widget.
        parameters['stateSticky'] = sticky

        return jinja_utils.parse_string(self._response_template, parameters)
Example #16
0
    def get_reader_response_html(self, state_customization_args,
                                 context_params, answer, sticky):
        """Gets the parameterized HTML and iframes for a reader response."""
        if not self.is_interactive:
            raise Exception(
                'This method should only be called for interactive widgets.')

        parameters = self._get_widget_param_instances(
            state_customization_args, context_params)
        parameters['answer'] = answer

        # The widget stays through the state transition because it is marked
        # sticky in the exploration and the new state uses the same widget.
        parameters['stateSticky'] = sticky

        return jinja_utils.parse_string(self._response_template, parameters)
Example #17
0
    def get_stats_log_html(self, state_customization_args, context_params,
                           answer):
        """Gets the HTML for recording a reader response for the stats log.

        Returns an HTML string.
        """
        if not self.is_interactive:
            raise Exception(
                'This method should only be called for interactive widgets.')

        parameters = self._get_widget_param_instances(state_customization_args,
                                                      context_params)
        parameters['answer'] = answer

        return jinja_utils.parse_string(self._stats_log_template,
                                        parameters,
                                        autoescape=False)
Example #18
0
    def get_interactive_widget_tag(self,
                                   state_customization_args,
                                   context_params,
                                   preview_mode=False):
        """Gets the widgetParams attribute value for an interactive widget."""
        if state_customization_args is None:
            state_customization_args = {}

        tag_name = ('oppia-interactive-%s' %
                    utils.camelcase_to_hyphenated(self.id))

        attr_strings = []
        for param in self.params:
            value_generator = param.generator(**param.init_args)
            # Use the given customization args. If they do not exist, use the
            # default customization args for the parameter.
            args_to_use = (state_customization_args[param.name]
                           if param.name in state_customization_args else
                           param.customization_args)

            try:
                generated_value = value_generator.generate_value(
                    context_params, **args_to_use)
            except Exception:
                if preview_mode:
                    generated_value = value_generator.default_value
                else:
                    raise

            # Normalize the generated values to the correct obj_type.
            param_value = (obj_services.Registry.get_object_class_by_type(
                param.obj_type).normalize(generated_value))

            prefix = '%s-with-' % utils.camelcase_to_hyphenated(param.name)
            for arg in param.customization_args:
                arg_name = '%s%s' % (prefix,
                                     utils.camelcase_to_hyphenated(arg))
                attr_strings.append(
                    jinja_utils.parse_string(
                        '{{arg_name}}="{{arg_value}}"', {
                            'arg_name': arg_name,
                            'arg_value': json.dumps(param_value),
                        }))

        return '<%s %s></%s>' % (tag_name, ' '.join(attr_strings), tag_name)
Example #19
0
def evaluate_rule(rule_spec, answer_type, context_params, answer, fs):
    """Evaluates a rule spec. Returns a float between 0.0 and 1.0."""
    all_rule_classes = get_rules_for_obj_type(answer_type)
    rule = next(r for r in all_rule_classes
                if r.__name__ == rule_spec.rule_type)

    param_list = []
    param_defns = get_param_list(rule.description)
    for (param_name, obj_cls) in param_defns:
        parsed_param = rule_spec.inputs[param_name]
        if (isinstance(parsed_param, basestring) and '{{' in parsed_param):
            parsed_param = jinja_utils.parse_string(
                parsed_param, context_params, autoescape=False)
        normalized_param = obj_cls.normalize(parsed_param)
        param_list.append(normalized_param)

    constructed_rule = rule(*param_list)
    constructed_rule.set_fs(fs)
    return constructed_rule.eval(answer)
Example #20
0
def evaluate_rule(rule_spec, answer_type, context_params, answer, fs):
    """Evaluates a rule spec. Returns a float between 0.0 and 1.0."""
    all_rule_classes = get_rules_for_obj_type(answer_type)
    rule = next(r for r in all_rule_classes
                if r.__name__ == rule_spec.rule_type)

    param_list = []
    param_defns = get_param_list(rule.description)
    for (param_name, obj_cls) in param_defns:
        parsed_param = rule_spec.inputs[param_name]
        if (isinstance(parsed_param, basestring) and '{{' in parsed_param):
            parsed_param = jinja_utils.parse_string(
                parsed_param, context_params, autoescape=False)
        normalized_param = obj_cls.normalize(parsed_param)
        param_list.append(normalized_param)

    constructed_rule = rule(*param_list)
    constructed_rule.set_fs(fs)
    return constructed_rule.eval(answer)
Example #21
0
def submit_answer_in_tests(
        exploration_id, state_name, answer, params, handler_name, version):
    """This function should only be used by tests."""
    params['answer'] = answer

    exploration = exp_services.get_exploration_by_id(
        exploration_id, version=version)
    exp_param_specs = exploration.param_specs
    old_state = exploration.states[state_name]

    rule_spec = classify(
        exploration_id, exp_param_specs, old_state, handler_name,
        answer, params)

    old_interaction_instance = (
        interaction_registry.Registry.get_interaction_by_id(
            old_state.interaction.id))
    normalized_answer = old_interaction_instance.normalize_answer(
        answer, handler_name)
    # TODO(sll): Should this also depend on `params`?
    event_services.AnswerSubmissionEventHandler.record(
        exploration_id, version, state_name, handler_name, rule_spec,
        old_interaction_instance.get_stats_log_html(
            old_state.interaction.customization_args, normalized_answer))

    new_state = (
        None if rule_spec.dest == feconf.END_DEST
        else exploration.states[rule_spec.dest])
    finished = (rule_spec.dest == feconf.END_DEST)
    new_params = _get_updated_param_dict(
        params, {} if finished else new_state.param_changes,
        exp_param_specs)

    return {
        'feedback_html': jinja_utils.parse_string(
            rule_spec.get_feedback_string(), params),
        'finished': finished,
        'params': new_params,
        'question_html': (
            new_state.content[0].to_html(new_params)
            if not finished else ''),
        'state_name': rule_spec.dest if not finished else None,
    }
Example #22
0
    def post(self, exploration_id, escaped_state_name):
        """Handles feedback interactions with readers."""
        old_state_name = self.unescape_state_name(escaped_state_name)
        # The reader's answer.
        answer = self.payload.get('answer')
        # The answer handler (submit, click, etc.)
        handler = self.payload.get('handler')
        # Parameters associated with the reader.
        old_params = self.payload.get('params', {})
        old_params['answer'] = answer
        # The reader's state history.
        state_history = self.payload['state_history']
        # The version of the exploration.
        version = self.payload.get('version')

        values = {}
        exploration = exp_services.get_exploration_by_id(
            exploration_id, version=version)
        old_state = exploration.states[old_state_name]
        old_widget = widget_registry.Registry.get_widget_by_id(
            feconf.INTERACTIVE_PREFIX, old_state.widget.widget_id)

        answer = old_widget.normalize_answer(answer, handler)

        rule = exploration.classify(
            old_state_name, handler, answer, old_params)
        feedback = rule.get_feedback_string()
        new_state_name = rule.dest
        new_state = (
            None if new_state_name == feconf.END_DEST
            else exploration.states[new_state_name])

        stats_services.EventHandler.record_state_hit(
            exploration_id, new_state_name,
            (new_state_name not in state_history))
        state_history.append(new_state_name)

        # If the new state widget is the same as the old state widget, and the
        # new state widget is sticky, do not render the reader response. The
        # interactive widget in the frontend should take care of this.
        # TODO(sll): This special-casing is not great; we should
        # make the interface for updating the frontend more generic so that
        # all the updates happen in the same place. Perhaps in the non-sticky
        # case we should call a frontend method named appendFeedback() or
        # similar.
        sticky = (
            new_state_name != feconf.END_DEST and
            new_state.widget.sticky and
            new_state.widget.widget_id == old_state.widget.widget_id
        )

        self._append_answer_to_stats_log(
            old_state, answer, exploration_id, old_state_name, old_params,
            handler, rule)

        # Append the reader's answer to the response HTML.
        reader_response_html = old_widget.get_reader_response_html(
            old_state.widget.customization_args, old_params, answer, sticky)
        values['reader_response_html'] = reader_response_html

        # Add Oppia's feedback to the response HTML.
        feedback_html = '<div>%s</div>' % jinja_utils.parse_string(
            feedback, old_params)

        # Add the content for the new state to the response HTML.
        finished = (new_state_name == feconf.END_DEST)
        state_has_changed = (old_state_name != new_state_name)
        new_params, question_html, interactive_html = (
            self._append_content(
                exploration, sticky, finished, old_params, new_state,
                new_state_name, state_has_changed))

        values.update({
            'interactive_html': interactive_html,
            'exploration_id': exploration_id,
            'state_name': new_state_name,
            'feedback_html': feedback_html,
            'question_html': question_html,
            'params': new_params,
            'finished': finished,
            'state_history': state_history,
        })

        self.render_json(values)
Example #23
0
    def post(self, exploration_id, escaped_state_name):
        """Handles feedback interactions with readers."""
        old_state_name = self.unescape_state_name(escaped_state_name)
        # The reader's answer.
        answer = self.payload.get('answer')
        # The answer handler (submit, click, etc.)
        handler = self.payload.get('handler')
        # Parameters associated with the reader.
        old_params = self.payload.get('params', {})
        old_params['answer'] = answer
        # The reader's state history.
        state_history = self.payload['state_history']
        # The version of the exploration.
        version = self.payload.get('version')

        values = {}
        exploration = exp_services.get_exploration_by_id(
            exploration_id, version=version)
        old_state = exploration.states[old_state_name]
        old_widget = widget_registry.Registry.get_widget_by_id(
            feconf.INTERACTIVE_PREFIX, old_state.widget.widget_id)

        answer = old_widget.normalize_answer(answer, handler)

        rule = exploration.classify(
            old_state_name, handler, answer, old_params)
        feedback = rule.get_feedback_string()
        new_state_name = rule.dest
        new_state = (
            None if new_state_name == feconf.END_DEST
            else exploration.states[new_state_name])

        stats_services.EventHandler.record_state_hit(
            exploration_id, new_state_name,
            (new_state_name not in state_history))
        state_history.append(new_state_name)

        # If the new state widget is the same as the old state widget, and the
        # new state widget is sticky, do not render the reader response. The
        # interactive widget in the frontend should take care of this.
        # TODO(sll): This special-casing is not great; we should
        # make the interface for updating the frontend more generic so that
        # all the updates happen in the same place. Perhaps in the non-sticky
        # case we should call a frontend method named appendFeedback() or
        # similar.
        sticky = (
            new_state_name != feconf.END_DEST and
            new_state.widget.sticky and
            new_state.widget.widget_id == old_state.widget.widget_id
        )

        self._append_answer_to_stats_log(
            old_state, answer, exploration_id, exploration.version,
            old_state_name, old_params, handler, rule)

        # Append the reader's answer to the response HTML.
        reader_response_html = old_widget.get_reader_response_html(
            old_state.widget.customization_args, old_params, answer, sticky)
        values['reader_response_html'] = reader_response_html

        # Add Oppia's feedback to the response HTML.
        feedback_html = '<div>%s</div>' % jinja_utils.parse_string(
            feedback, old_params)

        # Add the content for the new state to the response HTML.
        finished = (new_state_name == feconf.END_DEST)
        state_has_changed = (old_state_name != new_state_name)
        new_params, question_html, interactive_html = (
            self._append_content(
                exploration, sticky, finished, old_params, new_state,
                new_state_name, state_has_changed))

        values.update({
            'interactive_html': interactive_html,
            'exploration_id': exploration_id,
            'state_name': new_state_name,
            'feedback_html': feedback_html,
            'question_html': question_html,
            'params': new_params,
            'finished': finished,
            'state_history': state_history,
        })

        self.render_json(values)
def evaluate_rule(definition, param_specs, answer_type, context_params, answer,
                  fs):
    """Evaluates a rule definition using context_params. Returns a boolean."""

    if 'rule_type' not in definition:
        raise Exception('No rule type specified when constructing rule.')

    elif definition['rule_type'] == DEFAULT_RULE_TYPE:
        return True

    elif definition['rule_type'] == ATOMIC_RULE_TYPE:
        subject_name = definition['subject']

        if subject_name == 'answer':
            subject_type = answer_type
        else:
            subject_type = param_specs[subject_name].obj_type

        all_rule_classes = get_rules_for_obj_type(subject_type)
        rule = next(r for r in all_rule_classes
                    if r.__name__ == definition['name'])

        param_list = []
        param_defns = get_param_list(rule.description)
        for (param_name, obj_cls) in param_defns:
            parsed_param = definition['inputs'][param_name]
            if (isinstance(parsed_param, basestring) and '{{' in parsed_param):
                parsed_param = jinja_utils.parse_string(parsed_param,
                                                        context_params,
                                                        autoescape=False)
            normalized_param = obj_cls.normalize(parsed_param)
            param_list.append(normalized_param)

        if subject_name == 'answer':
            subject = answer
        else:
            subject = context_params[subject_name]

        constructed_rule = rule(*param_list)
        constructed_rule.set_fs(fs)
        return constructed_rule.eval(subject)

    elif definition['rule_type'] == AND_RULE_TYPE:
        for child_dict in definition['children']:
            if not evaluate_rule(child_dict, param_specs, answer_type,
                                 context_params, answer):
                return False
        return True

    elif definition['rule_type'] == OR_RULE_TYPE:
        for child_dict in definition['children']:
            if evaluate_rule(child_dict, param_specs, answer_type,
                             context_params, answer):
                return True
        return False

    elif definition['rule_type'] == NOT_RULE_TYPE:
        return (not evaluate_rule(definition['child'], param_specs,
                                  answer_type, context_params, answer))

    else:
        raise Exception('Unrecognized rule type %s' % definition['rule_type'])
def export_content_to_html(content_array, block_number, params=None,
                           escape_text_strings=True):
    """Takes a Content array and transforms it into HTML.

    Args:
        content_array: an array, each of whose members is of type Content. This
            object has two keys: type and value. The 'type' is one of the
            following:
                - 'text'; then the value is a text string
                - 'image'; then the value is an image ID
                - 'video'; then the value is a video ID
                - 'widget'; then the value is a JSON-encoded dict with keys
                    'id' and 'params', from which the raw widget HTML can be
                    constructed
        block_number: the number of content blocks preceding this one.
        params: any parameters used for templatizing text strings.
        escape_text_strings: True if values supplied with content of type 'text'
            should be escaped after Jinja evaluation; False otherwise.

    Returns:
        the HTML string representing the array.

    Raises:
        InvalidInputException: if content has no 'type' attribute, or an
            invalid 'type' attribute.
    """
    if params is None:
        params = {}

    JINJA_ENV = jinja_utils.get_jinja_env(feconf.FRONTEND_TEMPLATES_DIR)

    html, widget_array = '', []
    for content in content_array:
        if content.type in ['text', 'image', 'video']:
            value = (jinja_utils.parse_string(content.value, params)
                     if content.type == 'text' else content.value)

            if escape_text_strings:
                value = cgi.escape(value)

            html += JINJA_ENV.get_template('reader/content.html').render({
                'type': content.type,
                'value': value,
            })
        elif content.type == 'widget':
            # Ignore empty widget specifications.
            if not content.value:
                continue

            widget_dict = json.loads(content.value)
            widget = widget_domain.Registry.get_widget_by_id(
                feconf.NONINTERACTIVE_PREFIX, widget_dict['id'])
            widget_array_len = len(widget_array)
            html += JINJA_ENV.get_template('reader/content.html').render({
                'blockIndex': block_number,
                'index': widget_array_len,
                'type': content.type,
            })
            widget_array.append({
                'blockIndex': block_number,
                'index': widget_array_len,
                'raw': widget.get_raw_code(
                    widget_dict['customization_args'], params),
            })
        else:
            raise utils.InvalidInputException(
                'Invalid content type %s', content.type)
    return html, widget_array