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 })
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', {}])
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 })
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'})
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, {})
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, {})
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', {}])
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': {}})
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)
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))