def tearDown(self): """Deletes all widgets and explorations.""" InteractiveWidget.delete_all_widgets() explorations = exp_services.get_all_explorations() for exploration in explorations: exploration.delete() super(ExplorationDomainUnitTests, self).tearDown()
def setUp(self): """Loads the default widgets and creates dummy users.""" super(ExplorationServicesUnitTests, self).setUp() InteractiveWidget.load_default_widgets() self.owner_id = "*****@*****.**" self.editor_id = "*****@*****.**" self.viewer_id = "*****@*****.**"
def reload_widgets(): """Reload the default classifiers and widgets.""" Classifier.delete_all_classifiers() Classifier.load_default_classifiers() Widget.delete_all_widgets() InteractiveWidget.load_default_widgets() NonInteractiveWidget.load_default_widgets()
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 test_put_method(self): """Test that put() only works when called on a Widget subclass.""" widget = Widget( id='WidgetName', name='Widget Name', category='Category', template='Template') with self.assertRaises(NotImplementedError): widget.put() widget = InteractiveWidget( id='WidgetName', name='Widget Name', category='Category', template='Template', handlers=[AnswerHandler()]) widget.put() InteractiveWidget.delete_all_widgets()
def test_loading_and_deletion_of_widgets(self): """Test loading and deletion of the default widgets.""" self.assertEqual(InteractiveWidget.objects.count(), 0) self.assertEqual(NonInteractiveWidget.objects.count(), 0) InteractiveWidget.load_default_widgets() self.assertEqual(InteractiveWidget.objects.count(), 8) self.assertEqual(NonInteractiveWidget.objects.count(), 0) InteractiveWidget.delete_all_widgets() self.assertEqual(InteractiveWidget.objects.count(), 0) self.assertEqual(NonInteractiveWidget.objects.count(), 0)
def get_state_for_frontend(state, exploration): """Returns a representation of the given state for the frontend.""" state_repr = exp_services.export_state_to_dict(exploration.id, state.id) modified_state_dict = exp_services.export_state_internals_to_dict( exploration.id, state.id, human_readable_dests=True) # TODO(sll): The following is for backwards-compatibility and should be # deleted later. rules = {} for handler in state_repr['widget']['handlers']: rules[handler['name']] = handler['rules'] for item in rules[handler['name']]: if item['name'] == 'Default': item['rule'] = 'Default' else: item['rule'] = InteractiveWidget.get( state.widget.widget_id).get_readable_name( handler['name'], item['name'] ) state_repr['widget']['rules'] = rules state_repr['widget']['id'] = state_repr['widget']['widget_id'] state_repr['yaml'] = utils.yaml_from_dict(modified_state_dict) return state_repr
def get(self, widget_id): """Handles GET requests.""" try: self.render_json({ 'widget': InteractiveWidget.get_with_params(widget_id, {}), }) except: raise self.PageNotFoundException
def _pre_put_hook(self): """Ensures that the widget and at least one handler for it exists.""" # Every state should have an interactive widget. if not self.widget: self.widget = self.get_default_widget() elif not self.widget.handlers: self.widget.handlers = [self.get_default_handler()] # TODO(sll): Do other validation. # Add the corresponding AnswerHandler classifiers for easy reference. widget = InteractiveWidget.get(self.widget.widget_id) for curr_handler in self.widget.handlers: for w_handler in widget.handlers: if w_handler.name == curr_handler.name: curr_handler.classifier = w_handler.classifier
def test_parameterized_widget(self): """Test that parameterized widgets are correctly handled.""" self.assertEqual(InteractiveWidget.objects.count(), 0) Classifier.load_default_classifiers() InteractiveWidget.load_default_widgets() MUSIC_STAFF_ID = 'interactive-MusicStaff' widget = InteractiveWidget.get(MUSIC_STAFF_ID) self.assertEqual(widget.id, MUSIC_STAFF_ID) self.assertEqual(widget.name, 'Music staff') code = InteractiveWidget.get_raw_code(MUSIC_STAFF_ID) self.assertIn('GLOBALS.noteToGuess = JSON.parse(\'\\"', code) code = InteractiveWidget.get_raw_code(MUSIC_STAFF_ID, {'noteToGuess': 'abc'}) self.assertIn('GLOBALS.noteToGuess = JSON.parse(\'abc\');', code) # The get_with_params() method cannot be called directly on Widget. # It must be called on a subclass. with self.assertRaises(AttributeError): parameterized_widget_dict = Widget.get_with_params( MUSIC_STAFF_ID, {'noteToGuess': 'abc'}) with self.assertRaises(NotImplementedError): parameterized_widget_dict = Widget._get_with_params( MUSIC_STAFF_ID, {'noteToGuess': 'abc'}) parameterized_widget_dict = InteractiveWidget.get_with_params( MUSIC_STAFF_ID, {'noteToGuess': 'abc'}) self.assertItemsEqual(parameterized_widget_dict.keys(), [ 'id', 'name', 'category', 'description', 'template', 'static_template', 'params', 'handlers', 'raw']) self.assertEqual(parameterized_widget_dict['id'], MUSIC_STAFF_ID) self.assertIn('GLOBALS.noteToGuess = JSON.parse(\'abc\');', parameterized_widget_dict['raw']) self.assertEqual(parameterized_widget_dict['params'], {'noteToGuess': 'abc'}) InteractiveWidget.delete_all_widgets()
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 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 post(self, widget_id): """Handles POST requests, for parameterized widgets.""" params = self.payload.get('params', {}) if isinstance(params, list): new_params = {} for item in params: new_params[item['name']] = item['default_value'] params = new_params state_params_dict = {} state_params_given = self.payload.get('state_params') if state_params_given: for param in state_params_given: # Pick a random parameter for each key. state_params_dict[param['name']] = ( utils.get_random_choice(param['values'])) response = InteractiveWidget.get_with_params( widget_id, params=utils.parse_dict_with_params( params, state_params_dict) ) self.render_json({'widget': response})
def setUp(self): """Loads the default widgets.""" super(ExplorationDataUnitTests, self).setUp() InteractiveWidget.load_default_widgets()
def tearDown(self): """Deletes the dummy users and any other widgets and explorations.""" InteractiveWidget.delete_all_widgets() explorations = exp_services.get_all_explorations() for exploration in explorations: exploration.delete()
def tearDown(self): self.user.delete() InteractiveWidget.delete_all_widgets() explorations = exp_services.get_all_explorations() for exploration in explorations: exploration.delete()
def setUp(self): self.user = User(email='*****@*****.**') self.user.save() InteractiveWidget.load_default_widgets()
def test_pre_put_validation(self): """Test pre-put checks for widget handlers.""" widget = InteractiveWidget( id='WidgetName', name='Widget Name', category='Category', template='Template') with self.assertRaises(ValidationError): widget.handlers = [] widget.put() with self.assertRaises(ValidationError): widget.handlers = [AnswerHandler(), AnswerHandler()] widget.put() widget.handlers = [ AnswerHandler(name='click'), AnswerHandler(name='click')] with self.assertRaises(ValidationError): widget.put() widget.handlers = [ AnswerHandler(name='submit'), AnswerHandler(name='click')] widget.put() InteractiveWidget.delete_all_widgets()
def test_required_properties(self): """Test validation of required widget properties.""" widget = InteractiveWidget(id='Widget Name', name='Widget Name') with self.assertRaises(ValidationError): widget.put() widget.category = 'Category' with self.assertRaises(ValidationError): widget.put() widget.template = 'Template' with self.assertRaises(ValidationError): widget.put() widget.handlers = [AnswerHandler()] widget.put() InteractiveWidget.delete_all_widgets()
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)