def delete(self): """Remove a question from the datastore in response to DELETE.""" key = self.request.get('key') if not self.assert_xsrf_token_or_fail( self.request, self.XSRF_TOKEN, {'key': key}): return if not CourseOutlineRights.can_delete(self): transforms.send_json_response( self, 401, 'Access denied.', {'key': key}) return question = QuestionDAO.load(key) if not question: transforms.send_json_response( self, 404, 'Question not found.', {'key': key}) return used_by = QuestionDAO.used_by(question.id) if used_by: group_names = ['"%s"' % x for x in used_by] transforms.send_json_response( self, 403, ('Question in use by question groups:\n%s.\nPlease delete it ' 'from those groups and try again.') % ',\n'.join(group_names), {'key': key}) return QuestionDAO.delete(question) transforms.send_json_response(self, 200, 'Deleted.')
def delete(self): """Remove a question from the datastore in response to DELETE.""" key = self.request.get('key') if not self.assert_xsrf_token_or_fail(self.request, self.XSRF_TOKEN, {'key': key}): return if not CourseOutlineRights.can_delete(self): transforms.send_json_response(self, 401, 'Access denied.', {'key': key}) return question = QuestionDAO.load(key) if not question: transforms.send_json_response(self, 404, 'Question not found.', {'key': key}) return used_by = QuestionDAO.used_by(question.id) if used_by: group_names = ['"%s"' % x for x in used_by] transforms.send_json_response( self, 403, ('Question in use by question groups:\n%s.\nPlease delete it ' 'from those groups and try again.') % ',\n'.join(group_names), {'key': key}) return QuestionDAO.delete(question) transforms.send_json_response(self, 200, 'Deleted.')
def _get_questions_by_question_id(cls, questions_by_usage_id): ret = {} ret['single'] = {} ret['grouped'] = {} for question in questions_by_usage_id.values(): question_single = QuestionDAO.load(question['id']) if question_single: ret['single'][question['id']] = question_single else: question_group = QuestionGroupDAO.load(question['id']) if question_group: ret['grouped'][question['id']] = {} for item in question_group.items: ret['grouped'][question['id']][item['question']] = QuestionDAO.load(item['question']) return ret
def get(self): """Get the data to populate the question editor form.""" key = self.request.get('key') if not CourseOutlineRights.can_view(self): transforms.send_json_response( self, 401, 'Access denied.', {'key': key}) return if key: question = QuestionDAO.load(key) payload_dict = question.dict else: payload_dict = { 'version': self.SCHEMA_VERSION, 'question': '', 'description': '', 'graders': [ { 'score': '1.0', 'matcher': 'case_insensitive', 'response': '', 'feedback': ''}]} transforms.send_json_response( self, 200, 'Success', payload_dict=payload_dict, xsrf_token=XsrfTokenManager.create_xsrf_token(self.XSRF_TOKEN))
def get(self): """Get the data to populate the question editor form.""" key = self.request.get('key') if not CourseOutlineRights.can_view(self): transforms.send_json_response(self, 401, 'Access denied.', {'key': key}) return if key: question = QuestionDAO.load(key) payload_dict = question.dict else: payload_dict = { 'version': self.SCHEMA_VERSION, 'question': '', 'description': '', 'graders': [{ 'score': '1.0', 'matcher': 'case_insensitive', 'response': '', 'feedback': '' }] } transforms.send_json_response( self, 200, 'Success', payload_dict=payload_dict, xsrf_token=XsrfTokenManager.create_xsrf_token(self.XSRF_TOKEN))
def put(self): """Store a question in the datastore in response to a PUT.""" request = transforms.loads(self.request.get('request')) key = request.get('key') if not self.assert_xsrf_token_or_fail( request, self.XSRF_TOKEN, {'key': key}): return if not CourseOutlineRights.can_edit(self): transforms.send_json_response( self, 401, 'Access denied.', {'key': key}) return payload = request.get('payload') question_dict = transforms.loads(payload) question_dict['description'] = question_dict['description'].strip() question_dict, errors = self.import_and_validate(question_dict, key) if errors: self.validation_error('\n'.join(errors), key=key) return if key: question = QuestionDTO(key, question_dict) else: question = QuestionDTO(None, question_dict) question.type = self.TYPE key_after_save = QuestionDAO.save(question) transforms.send_json_response( self, 200, 'Saved.', payload_dict={'key': key_after_save})
def list_question_groups(self): """Prepare a list of question groups.""" if not filer.is_editable_fs(self.app_context): return safe_dom.NodeList() all_questions = QuestionDAO.get_all() output = safe_dom.NodeList() if all_questions: output.append( safe_dom.Element('a', className='gcb-button gcb-pull-right', href='dashboard?action=add_question_group'). add_text('Add Question Group')).append( safe_dom.Element('div', style='clear: both; padding-top: 2px;')) output.append(safe_dom.Element('h3').add_text('Question Groups')) # TODO(jorr): Hook this into the datastore all_question_groups = QuestionGroupDAO.get_all() if all_question_groups: ol = safe_dom.Element('ol') for question_group in all_question_groups: edit_url = 'dashboard?action=edit_question_group&key=%s' % ( question_group.id) li = safe_dom.Element('li') li.add_text(question_group.description).add_child( safe_dom.Entity(' ')).add_child( safe_dom.Element('a', href=edit_url).add_text('[Edit]')) ol.add_child(li) output.append(ol) else: output.append(safe_dom.Element('blockquote').add_text('< none >')) return output
def list_questions(self): """Prepare a list of the question bank contents.""" if not filer.is_editable_fs(self.app_context): return safe_dom.NodeList() output = safe_dom.NodeList().append( safe_dom.Element('a', className='gcb-button gcb-pull-right', href='dashboard?action=add_mc_question'). add_text('Add Multiple Choice')).append( safe_dom.Element('a', className='gcb-button gcb-pull-right', href='dashboard?action=add_sa_question'). add_text('Add Short Answer')).append( safe_dom.Element( 'div', style='clear: both; padding-top: 2px;')).append( safe_dom.Element('h3').add_text('Question Bank')) all_questions = QuestionDAO.get_all() if all_questions: ol = safe_dom.Element('ol') for question in all_questions: edit_url = 'dashboard?action=edit_question&key=%s' % question.id li = safe_dom.Element('li') li.add_text(question.description).add_child( safe_dom.Entity(' ')).add_child( safe_dom.Element('a', href=edit_url).add_text('[Edit]')) ol.add_child(li) output.append(ol) else: output.append(safe_dom.Element('blockquote').add_text('< none >')) return output
def validate_no_description_collision(self, description, key, errors): descriptions = { q.description for q in QuestionDAO.get_all() if not key or q.id != long(key) } if description in descriptions: errors.append( 'The description must be different from existing questions.')
def get(self): """Get the data to populate the question editor form.""" def export(q_dict): p_dict = copy.deepcopy(q_dict) # InputEx does not correctly roundtrip booleans, so pass strings p_dict['multiple_selections'] = ( 'true' if q_dict.get('multiple_selections') else 'false') return p_dict key = self.request.get('key') if not CourseOutlineRights.can_view(self): transforms.send_json_response(self, 401, 'Access denied.', {'key': key}) return if key: question = QuestionDAO.load(key) payload_dict = export(question.dict) else: payload_dict = { 'version': self.SCHEMA_VERSION, 'question': '', 'description': '', 'multiple_selections': 'false', 'choices': [{ 'score': '1', 'text': '', 'feedback': '' }, { 'score': '0', 'text': '', 'feedback': '' }, { 'score': '0', 'text': '', 'feedback': '' }, { 'score': '0', 'text': '', 'feedback': '' }] } transforms.send_json_response( self, 200, 'Success', payload_dict=payload_dict, xsrf_token=XsrfTokenManager.create_xsrf_token(self.XSRF_TOKEN))
def _get_questions_by_question_id(cls, questions_by_usage_id): ''' Retrieves every question in the course returning them in a dict: { id:questionDAO, ... } @param questions_by_usage_id.values() is a dict: {unit, lesson, sequence, weight, quid} ''' ret = {} ret['single'] = {} ret['grouped'] = {} for question in questions_by_usage_id.values(): question_single = QuestionDAO.load(question['id']) if question_single: ret['single'][question['id']] = question_single else: question_group = QuestionGroupDAO.load(question['id']) if question_group: ret['grouped'][question['id']] = {} for item in question_group.items: ret['grouped'][question['id']][item['question']] = QuestionDAO.load(item['question']) return ret
def is_deletion_allowed(self, question): used_by = QuestionDAO.used_by(question.id) if used_by: group_names = sorted(['"%s"' % x.description for x in used_by]) transforms.send_json_response( self, 403, ('Question in use by question groups:\n%s.\nPlease delete it ' 'from those groups and try again.') % ',\n'.join(group_names), {'key': question.id}) return False else: return True
def _get_questions_by_question_id(cls, questions_by_usage_id): ''' Retrieves every question in the course returning them in a dict: { id:questionDAO, ... } @param questions_by_usage_id.values() is a dict: {unit, lesson, sequence, weight, quid} ''' ret = {} ret['single'] = {} ret['grouped'] = {} for question in questions_by_usage_id.values(): question_single = QuestionDAO.load(question['id']) if question_single: ret['single'][question['id']] = question_single else: question_group = QuestionGroupDAO.load(question['id']) if question_group: ret['grouped'][question['id']] = {} for item in question_group.items: ret['grouped'][question['id']][ item['question']] = QuestionDAO.load( item['question']) return ret
def post_add_to_question_group(self): try: question_id = long(self.request.get('question_id')) question_dto = QuestionDAO.load(question_id) if question_dto is None: raise ValueError() except ValueError: transforms.send_json_response( self, 500, 'Invalid question id.', {'question-id': self.request.get('question_id')} ) return try: group_id = long(self.request.get('group_id')) group_dto = QuestionGroupDAO.load(group_id) if group_dto is None: raise ValueError() except ValueError: transforms.send_json_response( self, 500, 'Invalid question group id.', {'group-id': self.request.get('group_id')} ) return weight = self.request.get('weight') try: float(weight) except ValueError: transforms.send_json_response( self, 500, 'Invalid weight. Must be a numeric value.', { 'weight': weight}) return group_dto.add_question(question_id, weight) QuestionGroupDAO.save(group_dto) transforms.send_json_response( self, 200, '%s added to %s.' % ( question_dto.description, group_dto.description ), { 'group-id': group_dto.id, 'question-id': question_dto.id } ) return
def get_edit_question(self): key = self.request.get('key') question = QuestionDAO.load(key) if not question: raise Exception('No question found') if question.type == QuestionDTO.MULTIPLE_CHOICE: self.render_page( self.prepare_template(McQuestionRESTHandler, key=key)) elif question.type == QuestionDTO.SHORT_ANSWER: self.render_page( self.prepare_template(SaQuestionRESTHandler, key=key)) else: raise Exception('Unknown question type: %s' % question.type)
def list_question_groups(self): """Prepare a list of question groups.""" if not filer.is_editable_fs(self.app_context): return safe_dom.NodeList() all_questions = QuestionDAO.get_all() output = safe_dom.NodeList() if all_questions: output.append( safe_dom.Element( 'a', className='gcb-button gcb-pull-right', href='dashboard?action=add_question_group' ).add_text('Add Question Group') ).append( safe_dom.Element( 'div', style='clear: both; padding-top: 2px;' ) ) output.append( safe_dom.Element('h3').add_text('Question Groups') ) # TODO(jorr): Hook this into the datastore all_question_groups = QuestionGroupDAO.get_all() if all_question_groups: ol = safe_dom.Element('ol') for question_group in all_question_groups: edit_url = 'dashboard?action=edit_question_group&key=%s' % ( question_group.id) li = safe_dom.Element('li') li.add_text( question_group.description ).add_child( safe_dom.Entity(' ') ).add_child( safe_dom.Element('a', href=edit_url).add_text('[Edit]')) ol.add_child(li) output.append(ol) else: output.append(safe_dom.Element('blockquote').add_text('< none >')) return output
def post_add_to_question_group(self): try: question_id = long(self.request.get('question_id')) question_dto = QuestionDAO.load(question_id) if question_dto is None: raise ValueError() except ValueError: transforms.send_json_response( self, 500, 'Invalid question id.', {'question-id': self.request.get('question_id')}) return try: group_id = long(self.request.get('group_id')) group_dto = QuestionGroupDAO.load(group_id) if group_dto is None: raise ValueError() except ValueError: transforms.send_json_response( self, 500, 'Invalid question group id.', {'group-id': self.request.get('group_id')}) return weight = self.request.get('weight') try: float(weight) except ValueError: transforms.send_json_response( self, 500, 'Invalid weight. Must be a numeric value.', {'weight': weight}) return group_dto.add_question(question_id, weight) QuestionGroupDAO.save(group_dto) transforms.send_json_response( self, 200, '%s added to %s.' % (question_dto.description, group_dto.description), { 'group-id': group_dto.id, 'question-id': question_dto.id }) return
def put(self): """Store a QuestionGroupDTO and QuestionDTO in the datastore.""" request = transforms.loads(self.request.get('request')) if not self.assert_xsrf_token_or_fail(request, self.XSRF_TOKEN, {'key': None}): return if not roles.Roles.is_course_admin(self.app_context): transforms.send_json_response(self, 401, 'Access denied.') return payload = request.get('payload') json_dict = transforms.loads(payload) errors = [] try: python_dict = transforms.json_to_dict( json_dict, self.get_schema().get_json_schema_dict()) questions = gift.GiftParser.parse_questions( python_dict['questions']) self.validate_question_descriptions(questions, errors) self.validate_group_description(python_dict['description'], errors) if not errors: dtos = self.convert_to_dtos(questions) question_ids = QuestionDAO.save_all(dtos) self.create_group(python_dict['description'], question_ids) except ValueError as e: errors.append(str(e)) except gift.ParseError as e: errors.append(str(e)) except CollisionError as e: errors.append(str(e)) if errors: self.validation_error('\n'.join(errors)) return msg = 'Saved: %s.' % python_dict['description'] transforms.send_json_response(self, 200, msg) return
def list_questions(self): """Prepare a list of the question bank contents.""" if not filer.is_editable_fs(self.app_context): return safe_dom.NodeList() output = safe_dom.NodeList().append( safe_dom.Element( 'a', className='gcb-button gcb-pull-right', href='dashboard?action=add_mc_question' ).add_text('Add Multiple Choice') ).append( safe_dom.Element( 'a', className='gcb-button gcb-pull-right', href='dashboard?action=add_sa_question' ).add_text('Add Short Answer') ).append( safe_dom.Element('div', style='clear: both; padding-top: 2px;') ).append( safe_dom.Element('h3').add_text('Question Bank') ) all_questions = QuestionDAO.get_all() if all_questions: ol = safe_dom.Element('ol') for question in all_questions: edit_url = 'dashboard?action=edit_question&key=%s' % question.id li = safe_dom.Element('li') li.add_text( question.description ).add_child( safe_dom.Entity(' ') ).add_child( safe_dom.Element('a', href=edit_url).add_text('[Edit]')) ol.add_child(li) output.append(ol) else: output.append(safe_dom.Element('blockquote').add_text('< none >')) return output
def get_schema(cls): """Return the InputEx schema for the question group editor.""" question_group = schema_fields.FieldRegistry( 'Question Group', description='question_group') question_group.add_property(schema_fields.SchemaField( 'version', '', 'string', optional=True, hidden=True)) question_group.add_property(schema_fields.SchemaField( 'description', 'Description', 'string', optional=True)) question_group.add_property(schema_fields.SchemaField( 'introduction', 'Introduction', 'html', optional=True)) item_type = schema_fields.FieldRegistry( 'Item', extra_schema_dict_values={'className': 'question-group-item'}) item_type.add_property(schema_fields.SchemaField( 'weight', 'Weight', 'string', optional=True, extra_schema_dict_values={'className': 'question-group-weight'})) question_select_data = [ (q.id, q.description) for q in QuestionDAO.get_all()] item_type.add_property(schema_fields.SchemaField( 'question', 'Question', 'string', optional=True, select_data=question_select_data, extra_schema_dict_values={'className': 'question-group-question'})) item_array = schema_fields.FieldArray( 'items', '', item_type=item_type, extra_schema_dict_values={ 'className': 'question-group-items', 'sortable': 'true', 'listAddLabel': 'Add an item', 'listRemoveLabel': 'Delete item'}) question_group.add_property(item_array) return question_group
def get(self): """Get the data to populate the question editor form.""" def export(q_dict): p_dict = copy.deepcopy(q_dict) # InputEx does not correctly roundtrip booleans, so pass strings p_dict['multiple_selections'] = ( 'true' if q_dict.get('multiple_selections') else 'false') return p_dict key = self.request.get('key') if not CourseOutlineRights.can_view(self): transforms.send_json_response( self, 401, 'Access denied.', {'key': key}) return if key: question = QuestionDAO.load(key) payload_dict = export(question.dict) else: payload_dict = { 'version': self.SCHEMA_VERSION, 'question': '', 'description': '', 'multiple_selections': 'false', 'choices': [ {'score': '1', 'text': '', 'feedback': ''}, {'score': '0', 'text': '', 'feedback': ''}, {'score': '0', 'text': '', 'feedback': ''}, {'score': '0', 'text': '', 'feedback': ''} ]} transforms.send_json_response( self, 200, 'Success', payload_dict=payload_dict, xsrf_token=XsrfTokenManager.create_xsrf_token(self.XSRF_TOKEN))
def put(self): """Store a question in the datastore in response to a PUT.""" request = transforms.loads(self.request.get('request')) key = request.get('key') if not self.assert_xsrf_token_or_fail(request, self.XSRF_TOKEN, {'key': key}): return if not CourseOutlineRights.can_edit(self): transforms.send_json_response(self, 401, 'Access denied.', {'key': key}) return payload = request.get('payload') question_dict = transforms.loads(payload) question_dict['description'] = question_dict['description'].strip() question_dict, errors = self.import_and_validate(question_dict, key) if errors: self.validation_error('\n'.join(errors), key=key) return if key: question = QuestionDTO(key, question_dict) else: question = QuestionDTO(None, question_dict) question.type = self.TYPE key_after_save = QuestionDAO.save(question) transforms.send_json_response(self, 200, 'Saved.', payload_dict={'key': key_after_save})
def get_clone_question(self): original_question = QuestionDAO.load(self.request.get('key')) cloned_question = QuestionDAO.clone(original_question) cloned_question.description += ' (clone)' QuestionDAO.save(cloned_question) self.redirect(self.get_action_url('assets', {'tab': 'questions'}))
def post_clone_question(self): original_question = QuestionDAO.load(self.request.get('key')) cloned_question = QuestionDAO.clone(original_question) cloned_question.description += ' (clone)' QuestionDAO.save(cloned_question)
def validate_question_descriptions(self, questions, errors): descriptions = [q.description for q in QuestionDAO.get_all()] for question in questions: if question['description'] in descriptions: errors.append(('The description must be different ' 'from existing questions.'))
def validate_no_description_collision(self, description, key, errors): descriptions = {q.description for q in QuestionDAO.get_all() if not key or q.id != long(key)} if description in descriptions: errors.append( 'The description must be different from existing questions.')