def test_get_all_explorations(self): """Test get_all_explorations().""" exploration = Exploration.get( exp_services.create_new(self.owner_id, "A title", "A category", "A exploration_id") ) self.assertItemsEqual([e.id for e in exp_services.get_all_explorations()], [exploration.id]) exploration2 = Exploration.get( exp_services.create_new(self.owner_id, "A new title", "A category", "A new exploration_id") ) self.assertItemsEqual([e.id for e in exp_services.get_all_explorations()], [exploration.id, exploration2.id])
def test_creation_and_deletion_of_individual_explorations(self): """Test the create_new() and delete() methods.""" exploration = Exploration.get( exp_services.create_new(self.owner_id, "A title", "A category", "A exploration_id") ) exploration.put() retrieved_exploration = Exploration.get("A exploration_id") self.assertEqual(exploration.id, retrieved_exploration.id) self.assertEqual(exploration.title, retrieved_exploration.title) exploration.delete() with self.assertRaises(Exception): retrieved_exploration = Exploration.get("A exploration_id")
def get_exploration_stats(event_name, exploration_id): """Retrieves statistics for the given event name and exploration id.""" if event_name == STATS_ENUMS.exploration_visited: event_id = get_event_id(event_name, exploration_id) return Counter.get_value_by_id(event_id) if event_name == STATS_ENUMS.exploration_completed: event_id = get_event_id(event_name, exploration_id) return Counter.get_value_by_id(event_id) if event_name == STATS_ENUMS.rule_hit: result = {} exploration = Exploration.get(exploration_id) for state_id in exploration.state_ids: state = exploration.get_state_by_id(state_id) result[state.id] = { 'name': state.name, 'rules': {} } for handler in state.widget.handlers: for rule in handler.rules: rule_name = create_rule_name(rule) event_id = get_event_id( event_name, '.'.join( [exploration_id, state.id, rule_name])) journal = Journal.get(event_id, strict=False) result[state.id]['rules'][rule_name] = { 'answers': collections.Counter( journal.values).most_common(10) if journal else [], } return result if event_name == STATS_ENUMS.state_hit: result = {} exploration = Exploration.get(exploration_id) for state_id in exploration.state_ids: state = exploration.get_state_by_id(state_id) event_id = get_event_id( event_name, '.'.join([exploration_id, state.id])) result[state.id] = { 'name': state.name, 'count': Counter.get_value_by_id(event_id), } return result
def test_incomplete_and_default_flags(self): exp = Exploration.get(exp_services.create_new( '*****@*****.**', 'exploration', 'category', 'eid')) state_id = exp.init_state_id # Hit the default once, and do an incomplete twice. The result should # be classified as incomplete. for i in range(3): EventHandler.record_state_hit('eid', state_id) EventHandler.record_rule_hit( 'eid', state_id, Rule(name='Default', dest=state_id), extra_info='1') states = stats_services.get_top_ten_improvable_states([exp]) self.assertEquals(len(states), 1) self.assertEquals(states[0]['rank'], 2) self.assertEquals(states[0]['type'], 'incomplete') # Now hit the default two more times. The result should be classified # as default. for i in range(2): EventHandler.record_state_hit('eid', state_id) EventHandler.record_rule_hit( 'eid', state_id, Rule(name='Default', dest=state_id), extra_info='1') states = stats_services.get_top_ten_improvable_states([exp]) self.assertEquals(len(states), 1) self.assertEquals(states[0]['rank'], 3) self.assertEquals(states[0]['type'], 'default')
def get_or_create_param(exploration_id, param_name, obj_type=None): """Returns a ParamChange instance corresponding to the given inputs. If the parameter does not exist in the given exploration, it is added to the list of exploration parameters. If the obj_type is not specified it is taken to be 'UnicodeString'. If the obj_type does not match the obj_type for the parameter in the exploration, an Exception is raised. """ exploration = Exploration.get(exploration_id) for param in exploration.parameters: if param.name == param_name: if obj_type and param.obj_type != obj_type: raise Exception( 'Parameter %s has wrong obj_type: was %s, expected %s' % (param_name, obj_type, param.obj_type)) return ParamChange(name=param.name, obj_type=param.obj_type) # The parameter was not found, so add it. if not obj_type: obj_type = 'UnicodeString' exploration.parameters.append( Parameter(name=param_name, obj_type=obj_type)) exploration.put() return ParamChange(name=param_name, obj_type=obj_type)
def load_demos(): """Initializes the demo explorations.""" for index, exploration in enumerate(feconf.DEMO_EXPLORATIONS): if len(exploration) == 3: (exp_filename, title, category) = exploration image_filename = None elif len(exploration) == 4: (exp_filename, title, category, image_filename) = exploration else: raise Exception('Invalid demo exploration: %s' % exploration) image_id = None if image_filename: image_filepath = os.path.join( feconf.SAMPLE_IMAGES_DIR, image_filename) image_id = Image.create(utils.get_file_contents( image_filepath, raw_bytes=True)) yaml_content = utils.get_sample_exploration_yaml(exp_filename) exploration_id = create_from_yaml( yaml_content, None, title, category, exploration_id=str(index), image_id=image_id) exploration = Exploration.get(exploration_id) exploration.is_public = True exploration.put()
def test_get_top_ten_improvable_states(self): exp = Exploration.get(exp_services.create_new( '*****@*****.**', 'exploration', 'category', 'eid')) state_id = exp.init_state_id EventHandler.record_rule_hit( 'eid', state_id, Rule(name='Default', dest=state_id), extra_info='1') EventHandler.record_rule_hit( 'eid', state_id, Rule(name='Default', dest=state_id), extra_info='2') EventHandler.record_rule_hit( 'eid', state_id, Rule(name='Default', dest=state_id), extra_info='1') EventHandler.record_state_hit('eid', state_id) EventHandler.record_state_hit('eid', state_id) EventHandler.record_state_hit('eid', state_id) EventHandler.record_state_hit('eid', state_id) EventHandler.record_state_hit('eid', state_id) states = stats_services.get_top_ten_improvable_states([exp]) self.assertEquals(len(states), 1) self.assertEquals(states[0]['exp_id'], 'eid') self.assertEquals(states[0]['type'], 'default') self.assertEquals(states[0]['rank'], 3) self.assertEquals(states[0]['state_id'], exp.init_state_id)
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
def test_get_public_explorations(self): exploration = Exploration.get( exp_services.create_new(self.owner_id, "A title", "A category", "A exploration_id") ) self.assertEqual(exp_services.get_public_explorations(), []) exploration.is_public = True exploration.put() self.assertEqual([e.id for e in exp_services.get_public_explorations()], [exploration.id])
def export_state_to_dict(exploration_id, state_id): """Gets a Python dict representation of the state.""" exploration = Exploration.get(exploration_id) state = exploration.get_state_by_id(state_id) state_dict = export_state_internals_to_dict(exploration_id, state_id) state_dict.update({'id': state.id, 'name': state.name, 'unresolved_answers': state.unresolved_answers}) return state_dict
def setUp(self): """Loads the default widgets and creates a sample exploration.""" super(StateModelUnitTests, self).setUp() InteractiveWidget.load_default_widgets() self.user_id = '*****@*****.**' self.exploration = Exploration.get(exp_services.create_new( self.user_id, 'A title', 'A category', 'A exploration_id'))
def get(self): """Handles GET requests.""" if not Exploration.get('0', strict=False): admin.reload_demos() self.values.update({ 'gallery_login_url': users.create_login_url('/gallery'), }) self.render_template('pages/index.html')
def delete_demos(): """Deletes the demo explorations.""" explorations_to_delete = [] for int_id in range(len(feconf.DEMO_EXPLORATIONS)): exploration = Exploration.get(str(int_id), strict=False) if not exploration: # This exploration does not exist, so it cannot be deleted. logging.info('No exploration with id %s found.' % int_id) else: explorations_to_delete.append(exploration) for exploration in explorations_to_delete: exploration.delete()
def test_two_state_default_hit(self): SECOND_STATE = 'State 2' exp = Exploration.get(exp_services.create_new( '*****@*****.**', 'exploration', 'category', 'eid')) second_state = exp.add_state(SECOND_STATE) state_1_id = exp.init_state_id state_2_id = second_state.id # Hit the default rule of state 1 once, and the default rule of state 2 # twice. EventHandler.record_state_hit('eid', state_1_id) EventHandler.record_rule_hit( 'eid', state_1_id, Rule(name='Default', dest=state_1_id), extra_info='1') for i in range(2): EventHandler.record_state_hit('eid', state_2_id) EventHandler.record_rule_hit( 'eid', state_2_id, Rule(name='Default', dest=state_2_id), extra_info='1') states = stats_services.get_top_ten_improvable_states([exp]) self.assertEquals(len(states), 2) self.assertEquals(states[0]['rank'], 2) self.assertEquals(states[0]['type'], 'default') self.assertEquals(states[0]['state_id'], state_2_id) self.assertEquals(states[1]['rank'], 1) self.assertEquals(states[1]['type'], 'default') self.assertEquals(states[1]['state_id'], state_1_id) # Hit the default rule of state 1 two more times. for i in range(2): EventHandler.record_state_hit('eid', state_1_id) EventHandler.record_rule_hit( 'eid', state_1_id, Rule(name='Default', dest=state_1_id), extra_info='1') states = stats_services.get_top_ten_improvable_states([exp]) self.assertEquals(len(states), 2) self.assertEquals(states[0]['rank'], 3) self.assertEquals(states[0]['type'], 'default') self.assertEquals(states[0]['state_id'], state_1_id) self.assertEquals(states[1]['rank'], 2) self.assertEquals(states[1]['type'], 'default') self.assertEquals(states[1]['state_id'], state_2_id)
def export_to_yaml(exploration_id): """Returns a YAML version of the exploration.""" exploration = Exploration.get(exploration_id) params = [{ 'name': param.name, 'obj_type': param.obj_type, 'values': param.values } for param in exploration.parameters] states_list = [export_state_internals_to_dict( exploration_id, state_id, human_readable_dests=True) for state_id in exploration.state_ids] return utils.yaml_from_dict({ 'parameters': params, 'states': states_list })
def test_no_improvement_flag_hit(self): exp = Exploration.get(exp_services.create_new( '*****@*****.**', 'exploration', 'category', 'eid')) init_state = exp.init_state init_state.widget.handlers[0].rules = [ Rule(name='NotDefault', dest=init_state.id), Rule(name='Default', dest=init_state.id), ] init_state.put() EventHandler.record_rule_hit( 'eid', init_state.id, Rule(name='NotDefault', dest=init_state.id), extra_info='1') EventHandler.record_state_hit('eid', init_state.id) states = stats_services.get_top_ten_improvable_states([exp]) self.assertEquals(len(states), 0)
def post(self): """Handles POST requests.""" exploration_id = self.payload.get('exploration_id') forked_exploration = Exploration.get(exploration_id) if not forked_exploration.is_demo: raise self.InvalidInputException('Exploration cannot be forked.') # Get the demo exploration as a YAML file, so that new states can be # created. yaml_content = exp_services.export_to_yaml(forked_exploration.id) title = 'Copy of %s' % forked_exploration.title category = forked_exploration.category new_exploration_id = exp_services.create_from_yaml( yaml_content, self.user_id, title, category) self.render_json({'explorationId': new_exploration_id})
def test_export_to_yaml(self): """Test the export_to_yaml() method.""" exploration = Exploration.get( exp_services.create_new(self.owner_id, "A title", "A category", "A different exploration_id") ) exploration.add_state("New state") yaml_content = exp_services.export_to_yaml(exploration.id) self.assertEqual( yaml_content, """parameters: [] states: - content: [] name: '[untitled state]' param_changes: [] widget: handlers: - name: submit rules: - dest: '[untitled state]' feedback: [] inputs: {} name: Default param_changes: [] params: {} sticky: false widget_id: interactive-Continue - content: [] name: New state param_changes: [] widget: handlers: - name: submit rules: - dest: New state feedback: [] inputs: {} name: Default param_changes: [] params: {} sticky: false widget_id: interactive-Continue """, )
def export_state_internals_to_dict( exploration_id, state_id, human_readable_dests=False): """Gets a Python dict of the internals of the state.""" exploration = Exploration.get(exploration_id) state = exploration.get_state_by_id(state_id) state_dict = copy.deepcopy(state.to_dict(exclude=['unresolved_answers'])) # Remove the computed 'classifier' property. for handler in state_dict['widget']['handlers']: del handler['classifier'] if human_readable_dests: # Change the dest ids to human-readable names. for handler in state_dict['widget']['handlers']: for rule in handler['rules']: if rule['dest'] != feconf.END_DEST: dest_state = exploration.get_state_by_id(rule['dest']) rule['dest'] = dest_state.name return state_dict
def modify_using_dict(exploration_id, state_id, sdict): """Modifies the properties of a state using values from a dict.""" exploration = Exploration.get(exploration_id) state = exploration.get_state_by_id(state_id) state.content = [ Content(type=item['type'], value=item['value']) for item in sdict['content'] ] state.param_changes = [] for pc in sdict['param_changes']: instance = get_or_create_param( exploration_id, pc['name'], obj_type=pc['obj_type']) instance.values = pc['values'] state.param_changes.append(instance) wdict = sdict['widget'] state.widget = WidgetInstance( widget_id=wdict['widget_id'], sticky=wdict['sticky'], params=wdict['params'], handlers=[]) # Augment the list of parameters in state.widget with the default widget # params. for wp in InteractiveWidget.get(wdict['widget_id']).params: if wp.name not in wdict['params']: state.widget.params[wp.name] = wp.value for handler in wdict['handlers']: handler_rules = [Rule( name=rule['name'], inputs=rule['inputs'], dest=State._get_id_from_name(rule['dest'], exploration), feedback=rule['feedback'] ) for rule in handler['rules']] state.widget.handlers.append(AnswerHandlerInstance( name=handler['name'], rules=handler_rules)) state.put() return state
def test_get_editable_explorations(self): exploration = Exploration.get( exp_services.create_new(self.owner_id, "A title", "A category", "A exploration_id") ) exploration.add_editor(self.editor_id) exploration.put() def get_editable_ids(user_id): return [e.id for e in exp_services.get_editable_explorations(user_id)] self.assertEqual(get_editable_ids(self.owner_id), [exploration.id]) self.assertEqual(get_editable_ids(self.viewer_id), []) self.assertEqual(get_editable_ids(None), []) # Set the exploration's status to published. exploration.is_public = True exploration.put() self.assertEqual(get_editable_ids(self.owner_id), [exploration.id]) self.assertEqual(get_editable_ids(self.viewer_id), []) self.assertEqual(get_editable_ids(None), [])
def test_editor(self, exploration_id, state_id=None, **kwargs): """Gets the user and exploration id if the user can edit it. Args: self: the handler instance exploration_id: the exploration id state_id: the state id, if it exists **kwargs: any other arguments passed to the handler Returns: The user and exploration instance, if the user is authorized to edit this exploration. Also, the state instance, if one is supplied. Raises: self.NotLoggedInException: if there is no current user. self.UnauthorizedUserException: if the user exists but does not have the right credentials. """ if not self.user_id: self.redirect(users.create_login_url(self.request.uri)) return try: exploration = Exploration.get(exploration_id) except: raise self.PageNotFoundException if (not users.is_current_user_admin() and not exploration.is_editable_by(self.user_id)): raise self.UnauthorizedUserException( '%s does not have the credentials to edit this exploration.', self.user_id) if not state_id: return handler(self, exploration, **kwargs) try: state = exploration.get_state_by_id(state_id) except: raise self.PageNotFoundException return handler(self, exploration, state, **kwargs)
def get(self, exploration_id): """Populates the data on the individual exploration page.""" # TODO(sll): Maybe this should send a complete state machine to the # frontend, and all interaction would happen client-side? try: exploration = Exploration.get(exploration_id) except Exception as e: raise self.PageNotFoundException(e) init_state = exploration.init_state # TODO: get params from exploration specification instead params = self._get_exploration_params(exploration) params = get_params(init_state, params) init_html, init_widgets = parse_content_into_html( init_state.content, 0, params) interactive_widget_html = InteractiveWidget.get_raw_code( init_state.widget.widget_id, params=utils.parse_dict_with_params( init_state.widget.params, params) ) self.values.update({ 'block_number': 0, 'interactive_widget_html': interactive_widget_html, 'interactive_params': init_state.widget.params, 'oppia_html': init_html, 'params': params, 'state_id': exploration.init_state_id, 'title': exploration.title, 'widgets': init_widgets, }) if init_state.widget.widget_id in DEFAULT_ANSWERS: self.values['default_answer'] = ( DEFAULT_ANSWERS[init_state.widget.widget_id]) self.render_json(self.values) EventHandler.record_exploration_visited(exploration_id) EventHandler.record_state_hit(exploration_id, exploration.init_state_id)
def test_exploration_class(self): """Test the Exploration model class.""" exploration = ExplorationModel(id='The exploration hash id') # A new exploration should have a default title property. self.assertEqual(exploration.title, 'New exploration') # A new exploration should have a default is_public property. self.assertEqual(exploration.is_public, False) state = State(id='The state hash id') state.put() # The 'state_ids' property must be a list of strings. with self.assertRaises(SyntaxError): exploration.state_ids = 'A string' exploration.put() with self.assertRaises(ValidationError): exploration.state_ids = [state] exploration.put() exploration.state_ids = [state.id] # An Exploration must have a category. with self.assertRaises(ValidationError): exploration.put() exploration.category = 'The category' # The 'parameters' property must be a list of Parameter objects. with self.assertRaises(ValidationError): exploration.parameters = 'A string' exploration.put() exploration.parameters = [] parameter = Parameter(name='theParameter', obj_type='Int') with self.assertRaises(AttributeError): exploration.parameters = [parameter.key] exploration.parameters = [parameter] # The 'is_public' property must be a boolean. with self.assertRaises(ValidationError): exploration.is_public = 'true' exploration.put() exploration.is_public = True # The 'image_id' property must be a string. # TODO: This fails now because of the behaviour of CharField image = Image(id='The image') with self.assertRaises(ValidationError): exploration.image_id = image exploration.put() exploration.image_id = 'A string' exploration.editor_ids = ['A user id'] # Put and retrieve the exploration. exploration.put() retrieved_exploration = Exploration.get('The exploration hash id') self.assertEqual(retrieved_exploration.category, 'The category') self.assertEqual(retrieved_exploration.title, 'New exploration') self.assertEqual(retrieved_exploration.state_ids, [state.id]) self.assertEqual(retrieved_exploration.parameters, [parameter]) self.assertEqual(retrieved_exploration.is_public, True) self.assertEqual(retrieved_exploration.image_id, 'A string') self.assertEqual(retrieved_exploration.editor_ids, ['A user id'])
def post(self, exploration_id, state_id): """Handles feedback interactions with readers.""" values = {} exploration = Exploration.get(exploration_id) old_state = exploration.get_state_by_id(state_id) # The reader's answer. answer = self.payload.get('answer') # The answer handler (submit, click, etc.) handler = self.payload.get('handler') # The 0-based index of the last content block already on the page. block_number = self.payload.get('block_number') + 1 params = self.payload.get('params', {}) params['answer'] = answer rule = old_state.classify(handler, answer, params) new_state_id = rule.dest feedback = rule.get_feedback_string() recorded_answer = answer # TODO(sll): This is a special case for multiple-choice input # which should really be handled generically. if old_state.widget.widget_id == 'interactive-MultipleChoiceInput': recorded_answer = old_state.widget.params['choices'][int(answer)] if recorded_answer is not None: recorded_answer = json.dumps(recorded_answer) EventHandler.record_rule_hit( exploration_id, state_id, rule, recorded_answer) # Add this answer to the state's 'unresolved answers' list. if recorded_answer not in old_state.unresolved_answers: old_state.unresolved_answers[recorded_answer] = 0 old_state.unresolved_answers[recorded_answer] += 1 # TODO(sll): Make this async? old_state.put() html_output, widget_output = '', [] old_params = params if new_state_id == feconf.END_DEST: # This leads to a FINISHED state. new_state = None if feedback: html_output, widget_output = self._append_feedback( feedback, html_output, widget_output, block_number, old_params) EventHandler.record_exploration_completed(exploration_id) else: new_state = exploration.get_state_by_id(new_state_id) EventHandler.record_state_hit(exploration_id, new_state_id) if feedback: html_output, widget_output = self._append_feedback( feedback, html_output, widget_output, block_number, old_params) # Populate new parameters. params = get_params(new_state, existing_params=old_params) # Append text for the new state only if the new and old states # differ. if old_state.id != new_state.id: state_html, state_widgets = parse_content_into_html( new_state.content, block_number, params) # Separate text for the new state and feedback for the old state # by an additional line. if state_html and feedback: html_output += '<br>' html_output += state_html widget_output += state_widgets # Render the response in the customized html if # - the response is not rendered in the sticky interactive widget, and # - there is a static rendering html provided for that widget. sticky = ( new_state_id != feconf.END_DEST and new_state.widget.sticky and new_state.widget.widget_id == old_state.widget.widget_id ) custom_response = '' if not sticky: response_params = utils.parse_dict_with_params( old_state.widget.params, old_params) response_params['answer'] = old_params['answer'] response_params['iframe_content'] = False custom_response = InteractiveWidget.get_raw_static_code( old_state.widget.widget_id, response_params) if custom_response: response_params['iframe_content'] = True values['response_iframe'] = ( InteractiveWidget.get_raw_static_code( old_state.widget.widget_id, response_params) ) # Append reader's answer. response = custom_response if custom_response else answer if sticky: response = '' values['reader_html'] = self.jinja2_env.get_template( 'reader/reader_response.html').render({ 'response': response, 'custom_response': bool(custom_response), }) if new_state_id != feconf.END_DEST and new_state.widget.widget_id in DEFAULT_ANSWERS: values['default_answer'] = DEFAULT_ANSWERS[new_state.widget.widget_id] values['state_id'] = new_state_id values.update({ 'exploration_id': exploration_id, 'oppia_html': html_output, 'widgets': widget_output, 'block_number': block_number, 'params': params, 'finished': (new_state_id == feconf.END_DEST), }) if new_state_id != feconf.END_DEST: if sticky: values['interactive_widget_html'] = '' values['sticky_interactive_widget'] = True else: values['interactive_widget_html'] = ( InteractiveWidget.get_raw_code( new_state.widget.widget_id, params=utils.parse_dict_with_params( new_state.widget.params, params) ) ) else: values['interactive_widget_html'] = '' self.render_json(values)