Esempio n. 1
0
    def post(self, exploration_id):
        """Handles POST requests."""
        change_list = self.payload.get('change_list')
        version = self.payload.get('version')
        current_exploration = exp_services.get_exploration_by_id(
            exploration_id)

        if version != current_exploration.version:
            # TODO(sll): Improve the handling of merge conflicts.
            self.render_json({
                'is_version_of_draft_valid': False
            })
        else:
            utils.recursively_remove_key(change_list, '$$hashKey')

            summary = exp_services.get_summary_of_change_list(
                current_exploration, change_list)
            updated_exploration = exp_services.apply_change_list(
                exploration_id, change_list)
            warning_message = ''
            try:
                updated_exploration.validate(strict=True)
            except utils.ValidationError as e:
                warning_message = unicode(e)

            self.render_json({
                'summary': summary,
                'warning_message': warning_message
            })
Esempio n. 2
0
    def test_recursively_remove_key(self):
        """Test recursively_remove_key method."""
        d = {'a': 'b'}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {})

        d = {}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {})

        d = {'a': 'b', 'c': 'd'}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {'c': 'd'})

        d = {'a': 'b', 'c': {'a': 'b'}}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {'c': {}})

        d = ['a', 'b', {'c': 'd'}]
        utils.recursively_remove_key(d, 'c')
        self.assertEqual(d, ['a', 'b', {}])
Esempio n. 3
0
    def test_recursively_remove_key(self):
        """Test recursively_remove_key method."""
        d = {'a': 'b'}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {})

        d = {}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {})

        d = {'a': 'b', 'c': 'd'}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {'c': 'd'})

        d = {'a': 'b', 'c': {'a': 'b'}}
        utils.recursively_remove_key(d, 'a')
        self.assertEqual(d, {'c': {}})

        d = ['a', 'b', {'c': 'd'}]
        utils.recursively_remove_key(d, 'c')
        self.assertEqual(d, ['a', 'b', {}])
Esempio n. 4
0
    def post(self, exploration_id):
        """Handles POST requests."""
        change_list = self.payload.get('change_list')
        version = self.payload.get('version')
        current_exploration = exp_services.get_exploration_by_id(
            exploration_id)

        if version != current_exploration.version:
            # TODO(sll): Improve this.
            self.render_json({
                'error': (
                    'Sorry! Someone else has edited and committed changes to '
                    'this exploration while you were editing it. We suggest '
                    'opening another browser tab -- which will load the new '
                    'version of the exploration -- then transferring your '
                    'changes there. We will try to make this easier in the '
                    'future -- we have not done it yet because figuring out '
                    'how to merge different people\'s changes is hard. '
                    '(Trying to edit version %s, but the current version is '
                    '%s.).' % (version, current_exploration.version)
                )
            })
        else:
            utils.recursively_remove_key(change_list, '$$hashKey')

            summary = exp_services.get_summary_of_change_list(
                current_exploration, change_list)
            updated_exploration = exp_services.apply_change_list(
                exploration_id, change_list)
            warning_message = ''
            try:
                updated_exploration.validate(strict=True)
            except utils.ValidationError as e:
                warning_message = unicode(e)

            self.render_json({
                'summary': summary,
                'warning_message': warning_message
            })
Esempio n. 5
0
 def test_recursively_remove_key_for_multi_key_dict(self):
     # type: () -> None
     """Test recursively_remove_key method for multi key dict."""
     d = {'a': 'b', 'c': 'd'}
     utils.recursively_remove_key(d, 'a')
     self.assertEqual(d, {'c': 'd'})
Esempio n. 6
0
 def test_recursively_remove_key_for_single_key_dict(self):
     # type: () -> None
     """Test recursively_remove_key method for single key dict."""
     d = {'a': 'b'}
     utils.recursively_remove_key(d, 'a')
     self.assertEqual(d, {})
Esempio n. 7
0
 def test_recursively_remove_key_for_empty_dict(self):
     # type: () -> None
     """Test recursively_remove_key method for an empty dict."""
     d = {}  # type: Dict[None, None]
     utils.recursively_remove_key(d, 'a')
     self.assertEqual(d, {})
Esempio n. 8
0
 def test_recursively_remove_key_for_list(self):
     # type: () -> None
     """Test recursively_remove_key method for list."""
     l = ['a', 'b', {'c': 'd'}]
     utils.recursively_remove_key(l, 'c')
     self.assertEqual(l, ['a', 'b', {}])
Esempio n. 9
0
 def test_recursively_remove_key_for_dict_with_value_dict(self):
     # type: () -> None
     """Test recursively_remove_key method for dict with a value dict."""
     d = {'a': 'b', 'c': {'a': 'b'}}
     utils.recursively_remove_key(d, 'a')
     self.assertEqual(d, {'c': {}})
Esempio n. 10
0
 def test_recursively_remove_key_for_empty_dict(self) -> None:
     """Test recursively_remove_key method for an empty dict."""
     d: Dict[str, Any] = {}
     utils.recursively_remove_key(d, 'a')
     self.assertEqual(d, {})
def update_state(committer_id, exploration_id, state_id, new_state_name,
                 param_changes, interactive_widget, interactive_params,
                 interactive_rulesets, sticky_interactive_widget, content):
    """Updates the given state, and commits changes."""
    exploration = get_exploration_by_id(exploration_id)
    state = get_state_by_id(exploration_id, state_id)

    if new_state_name:
        if (state.name != new_state_name and
                exploration.has_state_named(new_state_name)):
            raise ValueError('Duplicate state name: %s' % new_state_name)
        state.name = new_state_name

    if param_changes:
        state.param_changes = []
        for param_change in param_changes:

            exp_param_spec = exploration.param_specs.get(param_change['name'])
            if exp_param_spec is None:
                raise Exception('No parameter named %s exists in this '
                                'exploration' % param_change['name'])

            # TODO(sll): Here (or when validating the state before committing),
            # check whether some sample generated values match the expected
            # obj_type.

            state.param_changes.append(param_domain.ParamChange(
                param_change['name'], param_change['generator_id'],
                param_change['customization_args']))

    if interactive_widget:
        state.widget.widget_id = interactive_widget

    if interactive_params is not None:
        state.widget.customization_args = interactive_params

    if sticky_interactive_widget is not None:
        if not isinstance(sticky_interactive_widget, bool):
            raise Exception(
                'Expected sticky_interactive_widget to be a boolean, '
                'received %s' % sticky_interactive_widget)
        state.widget.sticky = sticky_interactive_widget

    if interactive_rulesets:
        ruleset = interactive_rulesets['submit']
        utils.recursively_remove_key(ruleset, u'$$hashKey')

        state.widget.handlers = [
            exp_domain.AnswerHandlerInstance('submit', [])]

        generic_widget = widget_domain.Registry.get_widget_by_id(
            'interactive', state.widget.widget_id)

        # TODO(yanamal): Do additional calculations here to get the
        # parameter changes, if necessary.
        for rule_ind in range(len(ruleset)):
            rule = ruleset[rule_ind]
            state_rule = exp_domain.RuleSpec(
                rule.get('definition'), rule.get('dest'), rule.get('feedback'),
                rule.get('param_changes')                
            )

            if rule['description'] == feconf.DEFAULT_RULE_NAME:
                if (rule_ind != len(ruleset) - 1 or
                        rule['definition']['rule_type'] !=
                        rule_domain.DEFAULT_RULE_TYPE):
                    raise ValueError('Invalid ruleset: the last rule '
                                     'should be a default rule.')
            else:
                # TODO(sll): Generalize this to Boolean combinations of rules.
                matched_rule = generic_widget.get_rule_by_name(
                    'submit', state_rule.definition['name'])

                # Normalize and store the rule params.
                # TODO(sll): Generalize this to Boolean combinations of rules.
                rule_inputs = state_rule.definition['inputs']
                for param_name, value in rule_inputs.iteritems():
                    param_type = rule_domain.get_obj_type_for_param_name(
                        matched_rule, param_name)

                    if (not isinstance(value, basestring) or
                            '{{' not in value or '}}' not in value):
                        normalized_param = param_type.normalize(value)
                    else:
                        normalized_param = value

                    if normalized_param is None:
                        raise Exception(
                            '%s has the wrong type. Please replace it '
                            'with a %s.' % (value, param_type.__name__))

                    rule_inputs[param_name] = normalized_param

            state.widget.handlers[0].rule_specs.append(state_rule)

    if content:
        state.content = [
            exp_domain.Content(item['type'], item['value'])
            for item in content
        ]

    def _update_state_transaction(committer_id, exploration, state):
        save_state(committer_id, exploration.id, state)
        save_exploration(committer_id, exploration)

    transaction_services.run_in_transaction(
        _update_state_transaction, committer_id, exploration, state)
Esempio n. 12
0
    def put(self, exploration, state):
        """Saves updates to a state."""

        yaml_file = self.payload.get('yaml_file')
        if yaml_file and feconf.ALLOW_YAML_FILE_UPLOAD:
            # The user has uploaded a YAML file. Process only this action.
            state = exp_services.modify_using_dict(
                exploration.id, state.id, utils.dict_from_yaml(yaml_file))
            self.render_json(get_state_for_frontend(state, exploration))
            return

        state_name = self.payload.get('state_name')
        param_changes = self.payload.get('param_changes')
        interactive_widget = self.payload.get('interactive_widget')
        interactive_params = self.payload.get('interactive_params')
        interactive_rulesets = self.payload.get('interactive_rulesets')
        sticky_interactive_widget = self.payload.get(
            'sticky_interactive_widget')
        content = self.payload.get('content')
        unresolved_answers = self.payload.get('unresolved_answers')

        if 'state_name' in self.payload:
            # Replace the state name with this one, after checking validity.
            if state_name == feconf.END_DEST:
                raise self.InvalidInputException('Invalid state name: END')
            exploration.rename_state(state.id, state_name)

        if 'param_changes' in self.payload:
            state.param_changes = []
            for param_change in param_changes:
                instance = exp_services.get_or_create_param(
                    exploration.id, param_change['name'])
                instance.values = param_change['values']
                state.param_changes.append(instance)

        if interactive_widget:
            state.widget.widget_id = interactive_widget

        if interactive_params:
            state.widget.params = interactive_params

        if sticky_interactive_widget is not None:
            state.widget.sticky = sticky_interactive_widget

        if interactive_rulesets:
            ruleset = interactive_rulesets['submit']
            utils.recursively_remove_key(ruleset, u'$$hashKey')

            state.widget.handlers = [AnswerHandlerInstance(
                name='submit', rules=[])]

            # This is part of the state. The rules should be put into it.
            state_ruleset = state.widget.handlers[0].rules

            # TODO(yanamal): Do additional calculations here to get the
            # parameter changes, if necessary.
            for rule_ind in range(len(ruleset)):
                rule = ruleset[rule_ind]

                state_rule = Rule()
                state_rule.name = rule.get('name')
                state_rule.inputs = rule.get('inputs')
                state_rule.dest = rule.get('dest')
                state_rule.feedback = rule.get('feedback')

                # Generate the code to be executed.
                if rule['rule'] == 'Default':
                    # This is the default rule.
                    assert rule_ind == len(ruleset) - 1
                    state_rule.name = 'Default'
                    state_ruleset.append(state_rule)
                    continue

                # Normalize the params here, then store them.
                classifier_func = state_rule.name.replace(' ', '')
                first_bracket = classifier_func.find('(')
                mutable_rule = rule['rule']

                params = classifier_func[first_bracket + 1: -1].split(',')
                for index, param in enumerate(params):
                    if param not in rule['inputs']:
                        raise self.InvalidInputException(
                            'Parameter %s could not be replaced.' % param)

                    typed_object = state.get_typed_object(mutable_rule, param)
                    # TODO(sll): Make the following check more robust.
                    if (not isinstance(rule['inputs'][param], basestring) or
                            '{{' not in rule['inputs'][param] or
                            '}}' not in rule['inputs'][param]):
                        normalized_param = typed_object.normalize(
                            rule['inputs'][param])
                    else:
                        normalized_param = rule['inputs'][param]

                    if normalized_param is None:
                        raise self.InvalidInputException(
                            '%s has the wrong type. Please replace it with a '
                            '%s.' % (rule['inputs'][param],
                                     typed_object.__name__))

                    state_rule.inputs[param] = normalized_param

                state_ruleset.append(state_rule)

        if content:
            state.content = [Content(type=item['type'], value=item['value'])
                             for item in content]

        if 'unresolved_answers' in self.payload:
            state.unresolved_answers = {}
            for answer, count in unresolved_answers.iteritems():
                if count > 0:
                    state.unresolved_answers[answer] = count

        state.put()
        self.render_json(get_state_for_frontend(state, exploration))