Exemplo n.º 1
0
    def error_check(self, report_data):
        """
        Check all input for errors. The called functions will throw exceptions
        themselves. We are not trying to catch them, but to trigger them before
        we try to commit everything to the DB. This should prevent inconsistent data
        in the DB.
        :param report_data:
        :return:
        """
        section_api = SectionApi(autocommit=False)
        question_api = QuestionApi(autocommit=False)
        risk_factor_api = RiskFactorApi()

        # Database check
        if self.db_check(report_data) is not True:
            raise Exception(_('An unexpected error occurred.'))

        # Check for attributes
        if 'sections' not in report_data:
            raise RequiredAttributeMissing(
                _e['attr_missing'].format('sections'))
        cleaned_report = self.parse_input_data(report_data)

        # Check for duplicates (two sections with the same title)
        duplicate_sections = []
        for section in report_data['sections']:
            if 'questions' not in section:
                raise RequiredAttributeMissing(
                    _e['attr_missing'].format('questions'))

            # Check for attributes
            cleaned_section = section_api.parse_input_data(section)
            # Duplicate check
            if section['title'] not in duplicate_sections:
                duplicate_sections.append(section['title'])
            else:
                raise DatabaseItemAlreadyExists(_e['item_already_in'].format(
                    'Section', section['title'], 'Report', report_data['id']))

            duplicate_questions = []
            for question in section['questions']:
                # Check for attributes
                cleaned_question = question_api.parse_input_data(question)
                # Check for duplicates
                if question['question'] not in duplicate_questions:
                    duplicate_questions.append(question['question'])
                else:
                    raise DatabaseItemAlreadyExists(
                        _e['item_already_in'].format('Question',
                                                     question['question'],
                                                     'Section', section['id']))
                # Check for answers
                # We must use the cleaned data, because 'answers' is not required,
                # and attempting to loop over None gets an error.
                for answer_id in cleaned_question['answers']:
                    answer = question_api.get_answer(answer_id)
                # Check for risk_factor
                risk_factor = risk_factor_api.read(
                    cleaned_question['risk_factor_id'])
        return True
Exemplo n.º 2
0
def v_risk_factor_create():
    form = RiskFactorCreateForm()
    form.lang.choices = [(l.id, l.lang) for l in lang_api.list()]
    if request.method == 'POST' and form.validate_on_submit():
        a_api = RiskFactorApi()
        input_data = {
            'risk_factor': form.risk_factor.data,
            'value': form.value.data,
            'lang_id': form.lang.data
        }
        try:
            a_api.create(input_data)
        except RequiredAttributeMissing:
            flash(_('Missing required form input.'))
            return redirect(url_for('admin.v_risk_factor_create'))
        except DatabaseItemAlreadyExists:
            flash(_('An risk_factor called {0} already exists.').format(input_data['risk_factor']))
            return redirect(url_for('admin.v_risk_factor_create'))
        except Exception as e:
            flash(_('An unexpected error occurred.'))
            print(e)
            return redirect(url_for('admin.v_risk_factor_create'))
        else:
            flash(_('risk_factor created successfully.'))
            return redirect(url_for('admin.v_risk_factor_list'))
    else:
        return render_template('admin/risk_factor/create.html', action_url=url_for('admin.v_risk_factor_create'),
                               form=form)
Exemplo n.º 3
0
def v_risk_factor_delete(id):
    form = GenericDeleteForm()
    a_api = RiskFactorApi()
    try:
        existing_risk_factor = a_api.read(id)
    except DatabaseItemDoesNotExist:
        flash(_('This risk factor does not exist.'))
        return redirect(url_for('admin.v_risk_factor_list'))
    except Exception as e:
        flash(_('An unexpected error occurred.'))
        print(e)  # TODO: logging
        return redirect(url_for('admin.v_risk_factor_list'))
    if request.method == 'POST' and form.validate_on_submit():
        try:
            if a_api.delete(id) is True:
                flash(_('Risk factor removed.'))
                return redirect(url_for('admin.v_risk_factor_list'))
            else:
                flash(_('Risk factor could not be removed.'))
                return redirect(url_for('admin.v_risk_factor_list'))
        except Exception as e:
            flash(_('An unexpected error occurred.'))
            print(e)
            return redirect(url_for('admin.v_risk_factor_list'))
    else:
        return render_template('admin/generic/delete.html', action_url=url_for('admin.v_risk_factor_delete', id=id),
                               item_type=str(existing_risk_factor), item_identifier=existing_risk_factor.risk_factor,
                               form=form)
Exemplo n.º 4
0
 def test_update(self):
     en = LangApi().by_lang('en')
     r = RiskFactorApi().create({'risk_factor': 'Test', 'lang_id': en.id})
     r_b = RiskFactorApi().update(r.id, {
         'risk_factor': 'Foobar',
         'lang_id': en.id
     })
     assert r_b.risk_factor == 'Foobar'
     assert r_b == RiskFactorApi().read(r.id)
     assert RiskFactorApi().read(r.id).risk_factor == 'Foobar'
     self.assertIsInstance(r_b, RiskFactor)
Exemplo n.º 5
0
def v_report_edit(report_id):
    a_report = ReportApi()
    a_answer = AnswerApi()
    a_risk_factor = RiskFactorApi()
    a_lang = LangApi()
    try:
        existing_report = a_report.read(report_id)
    except DatabaseItemDoesNotExist:
        flash(_('No report with id {0} exists.').format(report_id))
        return redirect(url_for('admin.v_report_list'))
    # Do not use fallback_locale for the answers and risk_factor choices: if they don't exist, the administrator
    # must create them.
    report_lang = a_lang.read(existing_report.lang_id)
    return render_template('admin/report/edit.html', report=existing_report,
                           all_risk_factors=a_risk_factor.by_lang(report_lang.lang),
                           all_answers=a_answer.by_lang(report_lang.lang), languages=lang_api.list())
Exemplo n.º 6
0
 def test_create(self):
     en = LangApi().by_lang('en')
     r = RiskFactorApi().create({'risk_factor': 'Test', 'lang_id': en.id})
     assert r in scoremodel.db.session
     assert r.value == 1
     r_a = RiskFactorApi().create({
         'risk_factor': 'ValueTest',
         'lang_id': en.id,
         'value': 5
     })
     assert r_a.value == 5
     self.assertRaises(DatabaseItemAlreadyExists,
                       RiskFactorApi().create, {
                           'risk_factor': 'Test',
                           'lang_id': en.id
                       })
     self.assertIsInstance(r, RiskFactor)
Exemplo n.º 7
0
def v_user_report_summary(user_id, user_report_id):
    if current_user.id != user_id:
        flash(_('You can only view your own reports.'))
        abort(403)
    user_report = user_report_api.read(user_report_id)

    question_answers = {}
    all_scores = {}

    for section in user_report.template.sections:
        all_scores[section.id] = 0

    for question_answer in user_report.question_answers:
        question_answers[question_answer.question_id] = question_answer
        multiplication_factor = SectionApi().multiplication_factor(
            question_answer.question_template.section_id)
        all_scores[question_answer.question_template.section.id] += question_answer.score * \
                                                                    multiplication_factor

    highest_unanswered = []

    for question in ReportApi().questions_by_combined_weight(
            user_report.template.id):
        if question['question_id'] not in question_answers or question_answers[question['question_id']].score < \
                question['max_score']:
            try:
                highest_unanswered.append(QuestionApi().read(
                    question['question_id']))
            except DatabaseItemDoesNotExist:
                pass

    if len(highest_unanswered) >= 5:
        visible_unanswered = highest_unanswered[:5]
    else:
        visible_unanswered = highest_unanswered

    benchmarks_by_question = {}
    for bm_r in user_report.template.benchmark_reports:
        for bm in bm_r.benchmarks:
            if bm.question_id in benchmarks_by_question:
                benchmarks_by_question[bm.question_id].append(bm)
            else:
                benchmarks_by_question[bm.question_id] = [bm]

    # Create a color-range for the risk_factors
    risk_factors = [r.risk_factor for r in RiskFactorApi().list()]
    colored_risk_factors = Color().range(risk_factors)

    return render_template(
        'public/summary.html',
        report_template=user_report.template,
        user_report=user_report,
        user_report_creation_time='{:%Y-%m-%d %H:%M:%S}'.format(
            user_report.creation_time),
        highest_unanswered=visible_unanswered,
        benchmarks_by_question=benchmarks_by_question,
        question_answers_by_id=question_answers,
        colored_risk_factors=colored_risk_factors)
Exemplo n.º 8
0
 def test_complex(self):
     en = LangApi().by_lang('en')
     r = ReportApi().create({'title': 'Test', 'lang_id': en.id})
     s = SectionApi().create({'title': 'Test', 'report_id': r.id})
     ri = RiskFactorApi().create({'risk_factor': 'Test', 'lang_id': en.id})
     a = AnswerApi().create({'answer': 'Test', 'lang_id': en.id})
     q = QuestionApi().create({'question': 'Test', 'weight': 1, 'section_id': s.id, 'answers': [a.id],
                               'risk_factor_id': ri.id})
     assert q in scoremodel.db.session
     self.assertIsInstance(q, Question)
Exemplo n.º 9
0
def v_risk_factor_edit(id):
    form = RiskFactorCreateForm()
    form.lang.choices = [(l.id, l.lang) for l in lang_api.list()]
    a_api = RiskFactorApi()
    try:
        existing_risk_factor = a_api.read(risk_factor_id=id)
    except DatabaseItemDoesNotExist:
        flash(_('No risk_factor with id {0} exists.').format(id))
        return redirect(url_for('admin.v_risk_factor_list'))
    except Exception as e:
        flash(_('An unexpected error occurred.'))
        return redirect(url_for('admin.v_risk_factor_list'))
    if request.method == 'POST' and form.validate_on_submit():
        input_data = {
            'risk_factor': form.risk_factor.data,
            'value': form.value.data,
            'lang_id': form.lang.data
        }
        try:
            a_api.update(risk_factor_id=id, input_data=input_data)
        except DatabaseItemDoesNotExist:
            flash(_('No risk_factor with id {0}.').format(id))
            return redirect(url_for('admin.v_risk_factor_list'))
        except Exception as e:
            flash(_('An unexpected error occurred.'))
            return redirect(url_for('admin.v_risk_factor_list'))
        else:
            flash(_('Update successful.'))
            return redirect(url_for('admin.v_risk_factor_list'))
    else:
        # Fill in the values
        form.risk_factor.data = existing_risk_factor.risk_factor
        form.value.data = existing_risk_factor.value
        form.lang.data = existing_risk_factor.lang_id
        return render_template('admin/risk_factor/create.html', form=form,
                               action_url=url_for('admin.v_risk_factor_edit',
                                                  id=id))
Exemplo n.º 10
0
def v_user_report_check(user_id, user_report_id):
    if current_user.id != user_id:
        flash(_('You can only view your own reports.'))
        abort(403)
    user_report = user_report_api.read(user_report_id)

    # Get all question_answers for this report and order them by question_id, so we can compare
    # question.answer.answer_id to question_answers['question_id'].answer_id

    question_answers = {}
    all_scores = {}
    highest_answers = {}

    for section in user_report.template.sections:
        all_scores[section.id] = 0

    for question_answer in user_report.question_answers:
        question = question_answer.question_template
        question_answers[question.id] = question_answer
        multiplication_factor = SectionApi().multiplication_factor(
            question.section_id)
        all_scores[question.
                   section_id] += question_answer.score * multiplication_factor
        sorted_answers = sorted(question.answers,
                                key=lambda a: a.value,
                                reverse=True)
        if len(sorted_answers) > 0:
            highest_answers[question.id] = sorted_answers[0].value
        else:
            highest_answers[question.id] = 0

    benchmarks_by_section = user_report_api.benchmarks_by_section(
        user_report_id)

    # Create a color-range for the risk_factors
    risk_factors = [r.risk_factor for r in RiskFactorApi().list()]
    colored_risk_factors = Color().range(risk_factors)

    return render_template(
        'public/report.html',
        report_template=user_report.template,
        user_report=user_report,
        user_report_creation_time='{:%Y-%m-%d %H:%M:%S}'.format(
            user_report.creation_time),
        question_answers=question_answers,
        all_scores=all_scores,
        benchmarks_by_section=benchmarks_by_section,
        highest_answers=highest_answers,
        colored_risk_factors=colored_risk_factors)
Exemplo n.º 11
0
def v_user_report_section(user_id, user_report_id, section_id):
    section_api = SectionApi()
    if current_user.id != user_id:
        flash(_('You can only view your own reports.'))
        abort(403)
        return
    try:
        user_report = user_report_api.read(user_report_id)
    except DatabaseItemDoesNotExist as e:
        abort(404)
        return
    except Exception as e:
        flash(_('An unexpected error occurred.'))
        return redirect(url_for('site.v_index'))

    # Check whether current section is in this user_report
    if section_id not in [
            section.id for section in user_report.template.sections
    ]:
        abort(404)
        return

    current_section = section_api.read(section_id)

    # Get all question_answers for this report and order them by question_id, so we can compare
    # question.answer.answer_id to question_answers['question_id'].answer_id
    question_answers = {}
    for question_answer in user_report.question_answers:
        question_answers[question_answer.question_id] = question_answer

    benchmarks_by_section = user_report_api.benchmarks_by_section(
        user_report_id)

    # Create a color-range for the risk_factors
    risk_factors = [r.risk_factor for r in RiskFactorApi().list()]
    colored_risk_factors = Color().range(risk_factors)

    return render_template('public/section.html',
                           title=current_section.title,
                           section=current_section,
                           user_report_id=user_report_id,
                           question_answers=question_answers,
                           next_section=current_section.next_in_report,
                           previous_section=current_section.previous_in_report,
                           benchmarks_by_section=benchmarks_by_section,
                           colored_risk_factors=colored_risk_factors)
Exemplo n.º 12
0
def v_risk_factor_list():
    a_api = RiskFactorApi()
    l_risk_factors = a_api.list()
    return render_template('admin/generic/list.html', items=l_risk_factors, item_type='risk_factor',
                           name='risk_factor', canonical_name=_('Risk Factor'))
Exemplo n.º 13
0
 def __init__(self, question_id=None, autocommit=True):
     self.question_id = question_id
     self.a_section = scoremodel.modules.api.section.SectionApi()
     self.a_risk_factor = RiskFactorApi()
     self.autocommit = autocommit
Exemplo n.º 14
0
class QuestionApi(GenericApi):
    simple_attributes = [
        'question', 'context', 'risk', 'example', 'weight', 'order_in_section',
        'section_id', 'action', 'risk_factor_id'
    ]
    complex_params = ['answers']  # These should be a list in input_data
    possible_params = [
        'question', 'context', 'risk', 'example', 'weight', 'order_in_section',
        'action', 'risk_factor_id', 'answers', 'section_id'
    ]
    required_params = ['question', 'weight', 'section_id']

    def __init__(self, question_id=None, autocommit=True):
        self.question_id = question_id
        self.a_section = scoremodel.modules.api.section.SectionApi()
        self.a_risk_factor = RiskFactorApi()
        self.autocommit = autocommit

    def create(self, input_data):
        """
        Create a new question. The data input variable contains all the attributes for the "question" entity
        in the database as a dict. For simple attributes, this is a string or integer value, but for actions,
        answers and risk factors, it is a dictionary containing the attributes for the respective entity
        in the database.
        The function will fail when a question with the same "question" attribute already exists in the same
        section. Submitting actions, answers or risk factors that already exist will not result in an error.
        The function returns the question sqlalchemy object.
        :param input_data
        :return:
        """
        cleaned_data = self.parse_input_data(input_data)
        # Check whether this question already exists
        if self.db_exists(cleaned_data['question'],
                          cleaned_data['section_id']):
            raise DatabaseItemAlreadyExists(_e['item_exists'].format(
                Question, cleaned_data['question']))
        risk_factor = None
        if cleaned_data['risk_factor_id'] is not None:
            risk_factor = self.a_risk_factor.read(
                cleaned_data['risk_factor_id'])
        created_question = self.db_create(
            cleaned_data, self.a_section.read(cleaned_data['section_id']),
            self.db_get_answers(cleaned_data['answers']), risk_factor)
        return created_question

    def read(self, question_id):
        """
        Get a question from the database by its ID
        :param question_id:
        :return:
        """
        existing_question = Question.query.filter(
            Question.id == question_id).first()
        if existing_question is None:
            raise DatabaseItemDoesNotExist(_e['item_not_exists'].format(
                Question, question_id))
        return existing_question

    def update(self, question_id, input_data):
        """
        Update a question identified by question_id. The variable input_data must contain all variables, both
        those that are to be changed and those that remain the same. If you only send the changed ones, the others
        will be set to None. It follows the same logic as self.create(), but it doesn't die when the question already
        exists (but it does when it doesn't).
        :param question_id:
        :param input_data:
        :param section_id:
        :return:
        """
        existing_question = self.read(question_id)
        cleaned_data = self.parse_input_data(input_data)
        risk_factor = None
        if cleaned_data['risk_factor_id'] is not None:
            risk_factor = self.a_risk_factor.read(
                cleaned_data['risk_factor_id'])
        updated_question = self.db_update(
            existing_question, cleaned_data,
            self.a_section.read(cleaned_data['section_id']),
            self.db_get_answers(cleaned_data['answers']), risk_factor)
        return updated_question

    def delete(self, question_id):
        """
        Delete a question by its ID. Fails when it doesn't exist.
        :param question_id:
        :return:
        """
        existing_question = self.read(question_id)
        db.session.delete(existing_question)
        self.store()
        return True

    def parse_input_data(self, input_data):
        """
        Clean the input data dict: remove all non-supported attributes and check whether all the required
        parameters have been filled. All missing parameters are set to None
        :param input_data:
        :return:
        """
        # Solve legacy applications that use risk_factors (a list) instead of risk_factor (an object). Multiple
        # risk factors for one question are no longer supported.
        if 'risk_factors' in input_data:
            raise RequiredAttributeMissing(
                _('Error: risk_factors was provided!'))
        return self.clean_input_data(Question, input_data,
                                     self.possible_params,
                                     self.required_params, self.complex_params)

    def get_answer(self, answer_id):
        # Check whether this answer exists
        a_answer = AnswerApi()
        o_answer = a_answer.read(answer_id)
        return o_answer

    def list(self):
        raise MethodNotImplemented

    def remove_answers(self, question_entity):
        for answer in question_entity.answers:
            question_entity.answers.remove(answer)
        self.store()
        return question_entity

    def query(self, question_question):
        """
        Select a question by its name ("question"): this attribute is unique
        :param question_question:
        :return:
        """
        existing_question = Question.query.filter(
            Question.question == question_question).first()
        if not existing_question:
            raise DatabaseItemDoesNotExist(_e['item_not_exists'].format(
                Question, question_question))
        return existing_question

    def store(self):
        if self.autocommit:
            db.session.commit()

    def db_get_answers(self, answer_ids):
        """
        From a list of answer ids, get the list of answer database objects
        :param answer_ids:
        :return:
        """
        answers = []
        for answer_id in answer_ids:
            answers.append(self.get_answer(answer_id))
        return answers

    def db_update(self, existing_question, cleaned_data, section, answers,
                  risk_factor):
        """
        See self.db_create() why this function exists.
        :param existing_question:
        :param cleaned_data:
        :param section:
        :param answers:
        :param risk_factor:
        :return:
        """
        existing_question = self.update_simple_attributes(
            existing_question, self.simple_attributes, cleaned_data)
        # Update the section
        existing_question.section = section
        # Update answers
        existing_question = self.remove_answers(existing_question)
        if answers is not None and type(answers) is list:
            for answer in answers:
                existing_question.answers.append(answer)
        # Update risk factors
        if risk_factor is not None:
            existing_question.risk_factor = risk_factor
        # Set maximum_score
        sorted_answers = sorted(existing_question.answers,
                                key=lambda answer: answer.value,
                                reverse=True)
        if len(sorted_answers) > 0:
            existing_question.maximum_score = sorted_answers[0].value * existing_question.weight *\
                                              existing_question.risk_factor.value
        else:
            existing_question.maximum_score = 0
        self.store()
        # Update the maximum_score of the parent section
        scoremodel.modules.api.section.SectionApi().set_maximum_score(
            existing_question.section_id)
        return existing_question

    def db_create(self, cleaned_data, section, answers=None, risk_factor=None):
        """
        Create a question. This is a collection of all the write actions to the database, so we can wrap
        them in a transaction. We have to separate the "read" (query) actions as SQLAlchemy commits everything
        before querying (http://docs.sqlalchemy.org/en/latest/orm/session_basics.html).
        :param cleaned_data:
        :param section:
        :param answers:
        :param risk_factor:
        :return:
        """
        new_question = Question(question=cleaned_data['question'],
                                context=cleaned_data['context'],
                                risk=cleaned_data['risk'],
                                example=cleaned_data['example'],
                                weight=cleaned_data['weight'],
                                order=cleaned_data['order_in_section'],
                                action=cleaned_data['action'])
        db.session.add(new_question)
        # Add to the section
        new_question.section = section
        # Add the answers
        if answers is not None and type(answers) is list:
            for answer in answers:
                new_question.answers.append(answer)
        # Add the risk factor
        if risk_factor is not None:
            new_question.risk_factor = risk_factor
        # Store everything in the database
        self.store()
        # Set maximum_score
        sorted_answers = sorted(new_question.answers,
                                key=lambda a: a.value,
                                reverse=True)
        if len(sorted_answers) > 0:
            new_question.maximum_score = sorted_answers[
                0].value * new_question.weight * new_question.risk_factor.value
        else:
            new_question.maximum_score = 0
        self.store()
        # Update the maximum_score of the parent section
        scoremodel.modules.api.section.SectionApi().set_maximum_score(
            new_question.section_id)
        # Return the question object
        return new_question

    def db_exists(self, question_question, section_id):
        """

        :param question_question:
        :param section_id:
        :return:
        """
        existing_question = Question.query.filter(
            and_(Question.question == question_question,
                 Question.section_id == section_id)).first()
        if existing_question:
            return True
        return False

    def maximum_score(self, question_id):
        """
        Compute the maximum score for a question.
        value of highest answer * value of the risk_factor * weight of the question
        :param question_id:
        :return:
        """
        existing_question = self.read(question_id)
        return existing_question.maximum_score
Exemplo n.º 15
0
 def test_delete(self):
     en = LangApi().by_lang('en')
     r = RiskFactorApi().create({'risk_factor': 'Test', 'lang_id': en.id})
     assert RiskFactorApi().delete(r.id) is True
     assert r not in scoremodel.db.session
Exemplo n.º 16
0
 def test_read(self):
     en = LangApi().by_lang('en')
     r = RiskFactorApi().create({'risk_factor': 'Test', 'lang_id': en.id})
     assert r == RiskFactorApi().read(r.id)