Пример #1
0
    def test_yaml_dict_conversion(self):
        """Test yaml_from_dict and dict_from_yaml methods."""
        test_dicts = [{}, {'a': 'b'}, {'a': 2}, {'a': ['b', 2, {'c': 3.5}]}]

        for adict in test_dicts:
            yaml_str = utils.yaml_from_dict(adict)
            yaml_dict = utils.dict_from_yaml(yaml_str)
            self.assertEqual(adict, yaml_dict)

        with self.assertRaises(utils.InvalidInputException):
            yaml_str = utils.dict_from_yaml('{')
Пример #2
0
    def test_yaml_import_and_export_with_gadgets(self):
        """Test from_yaml() and to_yaml() methods including gadgets."""

        EXP_ID = 'An exploration_id'
        exploration_with_gadgets = exp_domain.Exploration.from_yaml(
            EXP_ID, 'A title', 'Category', SAMPLE_YAML_CONTENT_WITH_GADGETS)
        generated_yaml = exploration_with_gadgets.to_yaml()

        generated_yaml_as_dict = utils.dict_from_yaml(generated_yaml)
        sample_yaml_as_dict = utils.dict_from_yaml(
            SAMPLE_YAML_CONTENT_WITH_GADGETS)
        self.assertEqual(generated_yaml_as_dict, sample_yaml_as_dict)
Пример #3
0
    def create_from_yaml(cls, yaml_file, user, title, category, exploration_id=None, image_id=None):
        """Creates an exploration from a YAML file."""
        init_state_name = yaml_file[: yaml_file.index(":\n")]
        if not init_state_name or "\n" in init_state_name:
            raise Exception(
                "Invalid YAML file: the name of the initial state "
                "should be left-aligned on the first line and "
                "followed by a colon"
            )

        exploration = cls.create(
            user, title, category, exploration_id=exploration_id, init_state_name=init_state_name, image_id=image_id
        )

        init_state = State.get_by_name(init_state_name, exploration)

        try:
            exploration_dict = utils.dict_from_yaml(yaml_file)
            state_list = []

            for state_name, state_description in exploration_dict.iteritems():
                state = init_state if state_name == init_state_name else exploration.add_state(state_name)
                state_list.append({"state": state, "desc": state_description})

            for index, state in enumerate(state_list):
                State.modify_using_dict(exploration, state["state"], state["desc"])
        except Exception:
            exploration.delete()
            raise

        return exploration
Пример #4
0
    def load_default_widgets(cls):
        """Loads the default widgets.

        Assumes that everything is valid (directories exist, widget config files
        are formatted correctly, etc.).
        """
        widget_ids = os.listdir(feconf.INTERACTIVE_WIDGETS_DIR)

        for widget_id in widget_ids:
            widget_dir = os.path.join(feconf.INTERACTIVE_WIDGETS_DIR, widget_id)
            widget_conf_filename = '%s.config.yaml' % widget_id
            with open(os.path.join(widget_dir, widget_conf_filename)) as f:
                conf = utils.dict_from_yaml(f.read().decode('utf-8'))

            conf['id'] = '%s-%s' % (feconf.INTERACTIVE_PREFIX, widget_id)
            conf['params'] = [Parameter(**param) for param in conf['params']]
            conf['handlers'] = [AnswerHandler(**ah) for ah in conf['handlers']]
            conf['template'] = utils.get_file_contents(
                os.path.join(widget_dir, '%s.html' % widget_id))
            conf['static_template'] = ''
            static_path = os.path.join(widget_dir, '%s.static.html' % widget_id)
            if os.path.exists(static_path):
                conf['static_template'] = utils.get_file_contents(static_path)

            widget = cls(**conf)
            widget.put()
Пример #5
0
    def test_default_explorations_are_valid(self):
        """Test the default explorations."""
        exploration_files = os.listdir(
            os.path.join(feconf.SAMPLE_EXPLORATIONS_DIR))

        self.assertTrue(feconf.DEMO_EXPLORATIONS,
                        msg='There must be at least one demo exploration.')

        derived_exploration_filenames = [
            ('%s.yaml' % item[0]) for item in feconf.DEMO_EXPLORATIONS]
        self.assertItemsEqual(exploration_files, derived_exploration_filenames,
                              msg='Files in data/explorations do not match '
                                  'the demo explorations in feconf.py.')

        for filename in exploration_files:
            filepath = os.path.join(feconf.SAMPLE_EXPLORATIONS_DIR, filename)
            self.assertTrue(
                os.path.isfile(filepath), msg='%s is not a file.' % filepath)

            # Convert each exploration into a dict, and verify it.
            with open(filepath) as f:
                exploration_yaml = f.read().decode('utf-8')
                exploration_dict = utils.dict_from_yaml(exploration_yaml)
                try:
                    exp_services.verify_exploration_dict(exploration_dict)
                except Exception as e:
                    raise Exception('%s: %s' % (filepath, e))
Пример #6
0
    def _migrate_to_latest_yaml_version(cls, yaml_content):
        try:
            collection_dict = utils.dict_from_yaml(yaml_content)
        except Exception as e:
            raise Exception(
                'Please ensure that you are uploading a YAML text file, not '
                'a zip file. The YAML parser returned the following error: %s'
                % e)

        collection_schema_version = collection_dict.get('schema_version')
        if collection_schema_version is None:
            raise Exception('Invalid YAML file: no schema version specified.')
        if not (1 <= collection_schema_version
                <= feconf.CURRENT_COLLECTION_SCHEMA_VERSION):
            raise Exception(
                'Sorry, we can only process v1 to v%s collection YAML files at '
                'present.' % feconf.CURRENT_COLLECTION_SCHEMA_VERSION)

        while (collection_schema_version <
               feconf.CURRENT_COLLECTION_SCHEMA_VERSION):
            conversion_fn = getattr(
                cls, '_convert_v%s_dict_to_v%s_dict' % (
                    collection_schema_version, collection_schema_version + 1))
            collection_dict = conversion_fn(collection_dict)
            collection_schema_version += 1

        return collection_dict
Пример #7
0
def create_from_yaml(
    yaml_content, user_id, title, category, exploration_id=None,
        image_id=None):
    """Creates an exploration from a YAML text string."""
    exploration_dict = utils.dict_from_yaml(yaml_content)
    init_state_name = exploration_dict['states'][0]['name']

    exploration = Exploration.get(create_new(
        user_id, title, category, exploration_id=exploration_id,
        init_state_name=init_state_name, image_id=image_id))

    init_state = State.get_by_name(init_state_name, exploration)

    try:
        exploration.parameters = [Parameter(
            name=param['name'], obj_type=param['obj_type'],
            values=param['values']
        ) for param in exploration_dict['parameters']]

        state_list = []
        for state_description in exploration_dict['states']:
            state_name = state_description['name']
            state = (init_state if state_name == init_state_name
                     else exploration.add_state(state_name))
            state_list.append({'state': state, 'desc': state_description})

        for index, state in enumerate(state_list):
            modify_using_dict(exploration.id, state['state'].id, state['desc'])
    except Exception:
        exploration.delete()
        raise

    return exploration.id
Пример #8
0
    def verify_exploration_yaml(self, exploration_yaml):
        """Verifies an exploration YAML file."""
        init_state_name = exploration_yaml[:exploration_yaml.find(':\n')]
        if not init_state_name or '\n' in init_state_name:
            raise Exception('Invalid YAML file: the name of the initial state '
                            'should be left-aligned on the first line and '
                            'followed by a colon')

        exploration_dict = utils.dict_from_yaml(exploration_yaml)
        self.verify_exploration_dict(exploration_dict, init_state_name)
Пример #9
0
    def from_yaml(cls, collection_id, yaml_content):
        try:
            collection_dict = utils.dict_from_yaml(yaml_content)
        except Exception as e:
            raise Exception(
                'Please ensure that you are uploading a YAML text file, not '
                'a zip file. The YAML parser returned the following error: %s'
                % e)

        collection_dict['id'] = collection_id
        return Collection.from_dict(collection_dict)
Пример #10
0
 def get_readable_name(self, handler_name, rule_name):
     """Get the human-readable name for a rule."""
     for handler in self.handlers:
         if handler.name == handler_name:
             classifier = handler.classifier
             with open(os.path.join(
                     feconf.SAMPLE_CLASSIFIERS_DIR,
                     classifier,
                     '%sRules.yaml' % classifier)) as f:
                 rules = utils.dict_from_yaml(f.read().decode('utf-8'))
                 return rules[rule_name]['name']
     raise Exception('No rule name found for %s' % rule_name)
Пример #11
0
    def get_static_asset_filepath(self):
        """Returns filepath for referencing static files on disk.
        examples: '' or 'build/1234'
        """
        cache_slug_filepath = ''
        if feconf.IS_MINIFIED or not feconf.DEV_MODE:
            yaml_file_content = utils.dict_from_yaml(
                utils.get_file_contents('cache_slug.yaml'))
            cache_slug = yaml_file_content['cache_slug']
            cache_slug_filepath = os.path.join('build', cache_slug)

        return cache_slug_filepath
Пример #12
0
    def load_default_classifiers(cls):
        """Loads the default classifiers."""
        classifier_ids = cls.get_classifier_ids()

        for classifier_id in classifier_ids:
            rules_filepath = os.path.join(
                feconf.SAMPLE_CLASSIFIERS_DIR, classifier_id,
                '%sRules.yaml' % classifier_id)
            with open(rules_filepath) as f:
                rule_dict = utils.dict_from_yaml(f.read().decode('utf-8'))
                rules = []
                for rule in rule_dict:
                    r_spec = RuleSpec(rule=rule, name=rule_dict[rule]['name'])
                    if 'checks' in rule_dict[rule]:
                        r_spec.checks = rule_dict[rule]['checks']
                    rules.append(r_spec)
                Classifier(id=classifier_id, rules=rules).put()
Пример #13
0
    def load_default_widgets(cls):
        """Loads the default widgets.

        Assumes that everything is valid (directories exist, widget config files
        are formatted correctly, etc.).
        """
        widget_ids = os.listdir(feconf.SAMPLE_WIDGETS_DIR)

        for widget_id in widget_ids:
            widget_dir = os.path.join(feconf.SAMPLE_WIDGETS_DIR, widget_id)
            widget_conf_filename = "%s.config.yaml" % widget_id
            with open(os.path.join(widget_dir, widget_conf_filename)) as f:
                conf = utils.dict_from_yaml(f.read().decode("utf-8"))

            conf["params"] = [Parameter(**param) for param in conf["params"]]
            conf["handlers"] = [AnswerHandler(**ah) for ah in conf["handlers"]]
            conf["template"] = utils.get_file_contents(os.path.join(widget_dir, "%s.html" % widget_id))

            widget = cls(**conf)
            widget.put()
Пример #14
0
    def load_default_widgets(cls):
        """Loads the default widgets."""
        widget_ids = os.listdir(feconf.NONINTERACTIVE_WIDGETS_DIR)

        for widget_id in widget_ids:
            widget_dir = os.path.join(
                feconf.NONINTERACTIVE_WIDGETS_DIR, widget_id)
            widget_conf_filename = '%s.config.yaml' % widget_id
            with open(os.path.join(widget_dir, widget_conf_filename)) as f:
                conf = utils.dict_from_yaml(f.read().decode('utf-8'))

            conf['id'] = '%s-%s' % (feconf.NONINTERACTIVE_PREFIX, widget_id)
            conf['params'] = [Parameter(**param) for param in conf['params']]
            # TODO(sll): Should this be a template *location* instead?
            conf['template'] = utils.get_file_contents(
                os.path.join(widget_dir, '%s.html' % widget_id))
            conf['static_template'] = ''

            widget = cls(**conf)
            widget.put()
Пример #15
0
    def get_with_params(cls, widget_id, params=None):
        """Gets a parameterized widget."""
        if params is None:
            params = {}

        widget = cls.get(widget_id)

        result = copy.deepcopy(widget.to_dict())
        result['id'] = widget_id
        result['raw'] = cls.get_raw_code(widget_id, params)
        # TODO(sll): Restructure this so that it is
        # {key: {value: ..., obj_type: ...}}
        result['params'] = dict((param.name, params.get(param.name, param.value))
                                for param in widget.params)
        if 'handlers' in result:
            actions = {}
            for item in result['handlers']:
                actions[item['name']] = {'classifier': item['classifier']}
            result['actions'] = actions
            del result['handlers']

        for unused_action, properties in result['actions'].iteritems():
            classifier = properties['classifier']
            if classifier:
                with open(os.path.join(
                        feconf.SAMPLE_CLASSIFIERS_DIR,
                        classifier,
                        '%sRules.yaml' % classifier)) as f:
                    rules = utils.dict_from_yaml(f.read().decode('utf-8'))
                    rule_dict = {}
                    for rule in rules:
                        rule_dict[rules[rule]['name']] = {'classifier': rule}
                        if 'checks' in rules[rule]:
                            rule_dict[rules[rule]['name']]['checks'] = (
                                rules[rule]['checks'])
                    properties['rules'] = rule_dict

        return result
Пример #16
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))
Пример #17
0
    def test_default_widgets_are_valid(self):
        """Test the default widgets."""
        widget_ids = os.listdir(os.path.join(feconf.INTERACTIVE_WIDGETS_DIR))

        for widget_id in widget_ids:
            # Check that the widget_id name is valid.
            self.assertTrue(self.is_camel_cased(widget_id))

            # Check that the widget directory exists.
            widget_dir = os.path.join(feconf.INTERACTIVE_WIDGETS_DIR, widget_id)
            self.assertTrue(os.path.isdir(widget_dir))

            # In this directory there should only be a config.yaml file, an html
            # entry-point file, and (optionally) a static.html and a directory
            # named 'static'.
            dir_contents = os.listdir(widget_dir)
            self.assertLessEqual(len(dir_contents), 4)

            if len(dir_contents) == 3:
                self.assertIn('static', dir_contents)
                static_dir = os.path.join(widget_dir, 'static')
                self.assertTrue(os.path.isdir(static_dir))

            config_filepath = os.path.join(
                widget_dir, '%s.config.yaml' % widget_id)
            html_entry_point = os.path.join(widget_dir, '%s.html' % widget_id)

            self.assertTrue(os.path.isfile(config_filepath))
            self.assertTrue(os.path.isfile(html_entry_point))

            # Read the widget configuration from config.yaml.
            with open(config_filepath) as f:
                widget_config = utils.dict_from_yaml(f.read().decode('utf-8'))

            WIDGET_CONFIG_SCHEMA = [
                ('id', basestring), ('name', basestring),
                ('category', basestring), ('description', basestring),
                ('handlers', list), ('params', list)
            ]

            # Check that the configuration file contains the correct top-level
            # keys, and that these keys have the correct types.
            self.verify_dict_keys_and_types(widget_config, WIDGET_CONFIG_SCHEMA)

            # Check that the specified widget id is the same as the directory
            # name.
            self.assertTrue(widget_config['id'], widget_id)

            for handler in widget_config['handlers']:
                HANDLER_KEYS = ['name', 'classifier']
                self.assertItemsEqual(HANDLER_KEYS, handler.keys())
                self.assertTrue(isinstance(handler['name'], basestring))
                # The default classifier may be None (e.g. in the case of the
                # 'Continue' widget).
                if handler['classifier'] is not None:
                    self.assertTrue(isinstance(
                        handler['classifier'], basestring))
                    # Check that the classifier directory exists.
                    classifier_dir = os.path.join(
                        feconf.SAMPLE_CLASSIFIERS_DIR, handler['classifier'])
                    self.assertTrue(
                        os.path.isdir(classifier_dir),
                        msg='Classifier %s does not exist' % classifier_dir)

            for param in widget_config['params']:
                PARAM_KEYS = ['name', 'description', 'obj_type', 'values']
                self.assertItemsEqual(PARAM_KEYS, param.keys())
                self.assertTrue(isinstance(param['name'], basestring))
                self.assertTrue(self.is_alphanumeric_string(param['name']))
                self.assertTrue(isinstance(param['description'], basestring))

                # Check that the default values have the correct types.
                obj_class = get_object_class(param['obj_type'])
                self.assertIsNotNone(obj_class)
                self.assertTrue(isinstance(param['values'], list))
                for value in param['values']:
                    obj_class.normalize(value)
Пример #18
0
 def verify_exploration_yaml(self, exploration_yaml):
     """Verifies an exploration YAML file."""
     exploration_dict = utils.dict_from_yaml(exploration_yaml)
     self.verify_exploration_dict(exploration_dict)
Пример #19
0
    def from_yaml(cls, exploration_id, title, category, yaml_content):
        """Creates and returns exploration from a YAML text string."""
        exploration_dict = utils.dict_from_yaml(yaml_content)

        exploration_schema_version = exploration_dict.get('schema_version')
        if exploration_schema_version is None:
            raise Exception('Invalid YAML file: no schema version specified.')
        if not (1 <= exploration_schema_version
                <= cls.CURRENT_EXPLORATION_SCHEMA_VERSION):
            raise Exception(
                'Sorry, we can only process v1 and v2 YAML files at present.')
        if exploration_schema_version == 1:
            exploration_dict = cls._convert_v1_dict_to_v2_dict(
                exploration_dict)

        exploration = cls.create_default_exploration(
            exploration_id, title, category)
        exploration.param_specs = {
            ps_name: param_domain.ParamSpec.from_dict(ps_val) for
            (ps_name, ps_val) in exploration_dict['param_specs'].iteritems()
        }

        init_state_name = exploration_dict['init_state_name']
        exploration.rename_state(exploration.init_state_name, init_state_name)
        exploration.add_states([
            state_name for state_name in exploration_dict['states']
            if state_name != init_state_name])

        for (state_name, sdict) in exploration_dict['states'].iteritems():
            state = exploration.states[state_name]

            state.content = [
                Content(item['type'], html_cleaner.clean(item['value']))
                for item in sdict['content']
            ]

            state.param_changes = [param_domain.ParamChange(
                pc['name'], pc['generator_id'], pc['customization_args']
            ) for pc in sdict['param_changes']]

            for pc in state.param_changes:
                if pc.name not in exploration.param_specs:
                    raise Exception('Parameter %s was used in a state but not '
                                    'declared in the exploration param_specs.'
                                    % pc.name)

            wdict = sdict['widget']
            widget_handlers = [AnswerHandlerInstance.from_dict({
                'name': handler['name'],
                'rule_specs': [{
                    'definition': rule_spec['definition'],
                    'dest': rule_spec['dest'],
                    'feedback': [html_cleaner.clean(feedback)
                                 for feedback in rule_spec['feedback']],
                    'param_changes': rule_spec.get('param_changes', []),
                } for rule_spec in handler['rule_specs']],
            }) for handler in wdict['handlers']]

            state.widget = WidgetInstance(
                wdict['widget_id'], wdict['customization_args'],
                widget_handlers, wdict['sticky'])

            exploration.states[state_name] = state

        exploration.default_skin = exploration_dict['default_skin']
        exploration.param_changes = [
            param_domain.ParamChange.from_dict(pc)
            for pc in exploration_dict['param_changes']]

        return exploration
 def mock_read_travis_yml_file():
     travis_ci_file = python_utils.open_file(
         os.path.join(DUMMY_CONF_FILES, '.dummy_travis.yml'),
         'r').read()
     travis_ci_dict = utils.dict_from_yaml(travis_ci_file)
     return travis_ci_dict
def create_from_yaml(
    yaml_content, user_id, title, category, exploration_id=None,
        image_id=None):
    """Creates an exploration from a YAML text string."""
    exploration_dict = utils.dict_from_yaml(yaml_content)

    exploration_schema_version = exploration_dict.get('schema_version')

    if exploration_schema_version != CURRENT_EXPLORATION_SCHEMA_VERSION:
        raise Exception('Sorry, we can only process v1 YAML files at present.')

    init_state_name = exploration_dict['states'][0]['name']

    exploration_id = create_new(
        user_id, title, category, exploration_id=exploration_id,
        init_state_name=init_state_name, image_id=image_id)

    try:
        exploration_param_specs = {
            ps_name: param_domain.ParamSpec.from_dict(ps_val)
            for (ps_name, ps_val) in exploration_dict['param_specs'].iteritems()
        }

        for sdict in exploration_dict['states']:
            if sdict['name'] != init_state_name:
                add_state(user_id, exploration_id, sdict['name'])

        for sdict in exploration_dict['states']:
            state = get_state_by_name(exploration_id, sdict['name'])

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

            state.param_changes = [param_domain.ParamChange(
                pc['name'], pc['generator_id'], pc['customization_args']
            ) for pc in sdict['param_changes']]

            for pc in state.param_changes:
                if pc.name not in exploration_param_specs:
                    raise Exception('Parameter %s was used in a state but not '
                                    'declared in the exploration param_specs.'
                                    % pc.name)

            wdict = sdict['widget']
            widget_handlers = [exp_domain.AnswerHandlerInstance.from_dict({
                'name': handler['name'],
                'rule_specs': [{
                    'definition': rule_spec['definition'],
                    'dest': convert_state_name_to_id(
                        exploration_id, rule_spec['dest']),
                    'feedback': rule_spec['feedback'],
                    'param_changes': rule_spec.get('param_changes', []),
                } for rule_spec in handler['rule_specs']],
            }) for handler in wdict['handlers']]

            state.widget = exp_domain.WidgetInstance(
                wdict['widget_id'], wdict['customization_args'],
                widget_handlers, wdict['sticky'])

            save_state(user_id, exploration_id, state)

        exploration = get_exploration_by_id(exploration_id)
        exploration.default_skin = exploration_dict['default_skin']
        exploration.param_changes = [param_domain.ParamChange(
            pc['name'], pc['generator_id'], pc['customization_args']
        ) for pc in exploration_dict['param_changes']]
        exploration.param_specs = exploration_param_specs
        save_exploration(user_id, exploration)
    except Exception:
        delete_exploration(user_id, exploration_id, force_deletion=True)
        raise

    return exploration_id