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.')
Exemple #2
0
    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))
Exemple #5
0
    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})
Exemple #7
0
    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
Exemple #8
0
    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('&nbsp;')).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.')
Exemple #10
0
    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 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
Exemple #14
0
    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
Exemple #16
0
    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 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)
Exemple #18
0
    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('&nbsp;')
                ).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
Exemple #19
0
    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
Exemple #21
0
    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('&nbsp;')
                ).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
Exemple #22
0
    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))
Exemple #24
0
    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})
Exemple #25
0
 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.')