Ejemplo n.º 1
0
def supplier_questions(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live', 'expired'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief,
                            framework_slug,
                            lot_slug,
                            current_user.id,
                            allowed_statuses=['live']):
        abort(404)

    # Get Q&A in format suitable for govukSummaryList
    for index, question in enumerate(brief['clarificationQuestions']):
        question["key"] = {
            "html":
            f"{str(index + 1)}. "
            f"{text_to_html(question['question'], format_links=True, preserve_line_breaks=True)}"
        }
        question["value"] = {
            "html":
            text_to_html(question["answer"],
                         format_links=True,
                         preserve_line_breaks=True)
        }

    return render_template("buyers/supplier_questions.html", brief=brief)
def download_brief_responses(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ):
        abort(404)

    if brief['status'] != "closed":
        abort(404)

    responses = get_sorted_responses_for_brief(brief, data_api_client)
    rows = prepared_response_contents_for_brief(brief, responses)

    first = rows[0].keys() if responses else []
    rows = [first] + [_.values() for _ in rows]

    transposed = list(six.moves.zip_longest(*rows))

    outdata = io.StringIO()
    with csvx.Writer(outdata) as csv_out:
        csv_out.write_rows(transposed)
        csvdata = outdata.getvalue()

    return Response(
        csvdata,
        mimetype='text/csv',
        headers={
            "Content-Disposition": "attachment;filename=responses-to-requirements-{}.csv".format(brief['id']),
            "Content-Type": "text/csv; header=present"
        }
    ), 200
def edit_brief_question(framework_slug, lot_slug, brief_id, section_slug,
                        question_id):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(
        brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    section = content.get_section(section_slug)
    if section is None or not section.editable:
        abort(404)

    question = section.get_question(question_id)
    if not question:
        abort(404)

    return render_template("buyers/edit_brief_question.html",
                           brief=section.unformat_data(brief),
                           section=section,
                           question=question), 200
Ejemplo n.º 4
0
def view_brief_section_summary(framework_slug, lot_slug, brief_id, section_slug):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ) or not brief_can_be_edited(brief):
        abort(404)

    if not has_permission_to_edit_brief(brief):
        return redirect('/2/request-access/create_drafts')

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    sections = content.summary(brief)
    section = sections.get_section(section_slug)

    remove_non_cascade_fields(brief, section, 'description-of-training')

    if not section:
        abort(404)

    return render_template(
        "buyers/section_summary.html",
        brief=brief,
        section=section
    ), 200
Ejemplo n.º 5
0
def download_brief_responses(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ):
        abort(404)

    if brief['status'] != "closed":
        abort(404)

    responses = get_sorted_responses_for_brief(brief, data_api_client)
    rows = prepared_response_contents_for_brief(brief, responses)

    first = rows[0].keys() if responses else []
    rows = [first] + [_.values() for _ in rows]

    transposed = list(six.moves.zip_longest(*rows))

    outdata = io.StringIO()
    with csvx.Writer(outdata) as csv_out:
        csv_out.write_rows(transposed)
        csvdata = outdata.getvalue()

    return Response(
        csvdata,
        mimetype='text/csv',
        headers={
            "Content-Disposition": "attachment;filename=responses-to-requirements-{}.csv".format(brief['id']),
            "Content-Type": "text/csv; header=present"
        }
    ), 200
def get_work_order_question(work_order_id, question_slug):
    try:
        work_order = data_api_client.get_work_order(work_order_id)['workOrder']
    except APIError as e:
        abort(e.status_code)

    brief = data_api_client.get_brief(work_order['briefId'])["briefs"]
    if not is_brief_associated_with_user(
            brief, current_user.id
    ):
        abort(404)

    if questions.get(question_slug, None) is None:
        abort(404)

    form = FormFactory(question_slug)
    value = work_order.get(question_slug, None)

    if value is not None:
        if questions[question_slug].get('type') == 'address':
            form.abn.data = value['abn']
            form.contact.data = value['contact']
            form.name.data = value['name']
        else:
            form[question_slug].data = value

    return render_template_with_csrf(
        'workorder/work-order-question.html',
        work_order_id=work_order_id,
        question_slug=question_slug,
        form=form
    )
def copy_brief(framework_slug, lot_slug, brief_id):
    if request.method == "GET":
        abort(404)

    brief = data_api_client.get_brief(brief_id)["briefs"]
    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id,
            allow_withdrawn=True):
        abort(404)

    new_brief = data_api_client.copy_brief(
        brief_id, current_user.email_address)['briefs']

    # Get first question for 'edit_brief'
    content = content_loader.get_manifest(framework_slug, 'edit_brief').filter(
        {'lot': lot_slug})
    section = content.get_section(content.get_next_editable_section_id())

    # Redirect to first question with new (copy of) brief
    return redirect(
        url_for('.edit_brief_question',
                framework_slug=new_brief["frameworkSlug"],
                lot_slug=new_brief["lotSlug"],
                brief_id=new_brief["id"],
                section_slug=section.slug,
                question_id=section.questions[0].id))
Ejemplo n.º 8
0
def edit_brief_question(framework_slug, lot_slug, brief_id, section_slug, question_id):

    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ) or not brief_can_be_edited(brief):
        abort(404)

    if not has_permission_to_edit_brief(brief):
        return redirect('/2/request-access/create_drafts')

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter(
        {'lot': brief['lotSlug']}
    )
    section = content.get_section(section_slug)
    if section is None or not section.editable:
        abort(404)

    remove_non_cascade_fields(brief, section, question_id)

    question = section.get_question(question_id)
    if not question:
        abort(404)

    return render_template_with_csrf(
        "buyers/edit_brief_question.html",
        brief=brief,
        section=section,
        question=question
    )
def edit_brief_question(framework_slug, lot_slug, brief_id, section_slug, question_id):

    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter(
        {'lot': brief['lotSlug']}
    )
    section = content.get_section(section_slug)
    if section is None or not section.editable:
        abort(404)

    remove_non_cascade_fields(brief, section, question_id)

    question = section.get_question(question_id)
    if not question:
        abort(404)

    return render_template_with_csrf(
        "buyers/edit_brief_question.html",
        brief=brief,
        section=section,
        question=question
    )
Ejemplo n.º 10
0
def view_brief_overview(framework_slug, lot_slug, brief_id):
    framework, lot = get_framework_and_lot(
        framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    sections = content.summary(brief)
    delete_requested = True if request.args.get('delete_requested') else False

    completed_sections = {}
    for section in sections:
        required, optional = count_unanswered_questions([section])
        completed_sections[section.slug] = True if required == 0 else False

    brief['clarificationQuestions'] = [
        dict(question, number=index+1)
        for index, question in enumerate(brief['clarificationQuestions'])
    ]

    return render_template(
        "buyers/brief_overview.html",
        framework=framework,
        confirm_remove=request.args.get("confirm_remove", None),
        brief=brief,
        sections=sections,
        completed_sections=completed_sections,
        step_sections=[section.step for section in sections if hasattr(section, 'step')],
        delete_requested=delete_requested,
    ), 200
def preview_brief(framework_slug, lot_slug, brief_id):
    # Displays draft content in tabs for the user to see what their published brief will look like
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(
        brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})

    # Check that all questions have been answered
    unanswered_required, unanswered_optional = count_unanswered_questions(
        content.summary(brief))
    if unanswered_required > 0:
        return render_template("buyers/preview_brief.html",
                               content=content,
                               unanswered_required=unanswered_required,
                               brief=brief), 400

    return render_template("buyers/preview_brief.html",
                           content=content,
                           unanswered_required=unanswered_required,
                           brief=brief), 200
Ejemplo n.º 12
0
def update_brief_submission(framework_slug, lot_slug, brief_id, section_id, question_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    section = content.get_section(section_id)
    if section is None or not section.editable:
        abort(404)

    question = section.get_question(question_id)
    if not question:
        abort(404)

    update_data = question.get_data(request.form)

    try:
        data_api_client.update_brief(
            brief_id,
            update_data,
            updated_by=current_user.email_address,
            page_questions=question.form_fields
        )
    except HTTPError as e:
        update_data = section.unformat_data(update_data)
        errors = section.get_error_messages(e.message)

        # we need the brief_id to build breadcrumbs and the update_data to fill in the form.
        brief.update(update_data)
        return render_template(
            "buyers/edit_brief_question.html",
            brief=brief,
            section=section,
            question=question,
            errors=errors
        ), 400

    if section.has_summary_page:
        return redirect(
            url_for(
                ".view_brief_section_summary",
                framework_slug=brief['frameworkSlug'],
                lot_slug=brief['lotSlug'],
                brief_id=brief['id'],
                section_slug=section.slug)
        )

    return redirect(
        url_for(
            ".view_brief_overview",
            framework_slug=brief['frameworkSlug'],
            lot_slug=brief['lotSlug'],
            brief_id=brief['id']
        )
    )
def update_brief_submission(framework_slug, lot_slug, brief_id, section_id,
                            question_id):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(
        brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    section = content.get_section(section_id)
    if section is None or not section.editable:
        abort(404)

    question = section.get_question(question_id)
    if not question:
        abort(404)

    update_data = question.get_data(request.form)

    try:
        data_api_client.update_brief(brief_id,
                                     update_data,
                                     updated_by=current_user.email_address,
                                     page_questions=question.form_fields)
    except HTTPError as e:
        update_data = section.unformat_data(update_data)
        errors = govuk_errors(section.get_error_messages(e.message))

        # we need the brief_id to build breadcrumbs and the update_data to fill in the form.
        brief.update(update_data)

        return render_template("buyers/edit_brief_question.html",
                               brief=brief,
                               section=section,
                               question=question,
                               errors=errors), 400

    if section.has_summary_page:
        return redirect(
            url_for(".view_brief_section_summary",
                    framework_slug=brief['frameworkSlug'],
                    lot_slug=brief['lotSlug'],
                    brief_id=brief['id'],
                    section_slug=section.slug))

    return redirect(
        url_for(".view_brief_overview",
                framework_slug=brief['frameworkSlug'],
                lot_slug=brief['lotSlug'],
                brief_id=brief['id']))
Ejemplo n.º 14
0
def award_brief(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(
        framework_slug,
        lot_slug,
        data_api_client,
        allowed_statuses=['live', 'expired'],
        must_allow_brief=True,
    )
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief,
                            framework_slug,
                            lot_slug,
                            current_user.id,
                            allowed_statuses=['closed']):
        abort(404)

    brief_responses = data_api_client.find_brief_responses(
        brief['id'], status="submitted,pending-awarded")['briefResponses']
    if not brief_responses:
        return redirect(
            url_for(".view_brief_responses",
                    framework_slug=brief['frameworkSlug'],
                    lot_slug=brief['lotSlug'],
                    brief_id=brief['id']))

    form = AwardedBriefResponseForm(brief_responses)
    form_options = form.brief_response.govuk_options

    if form.validate_on_submit():
        try:
            data_api_client.update_brief_award_brief_response(
                brief_id, form.data['brief_response'],
                current_user.email_address)
        except HTTPError:
            abort(500, "Unexpected API error when awarding brief response")

        return redirect(
            url_for(".award_brief_details",
                    framework_slug=brief['frameworkSlug'],
                    lot_slug=brief['lotSlug'],
                    brief_id=brief['id'],
                    brief_response_id=form.data['brief_response']))

    pending_brief_responses = list(
        filter(lambda x: x.get('awardDetails', {}).get('pending'),
               brief_responses))
    form['brief_response'].data = pending_brief_responses[0][
        "id"] if pending_brief_responses else None
    errors = get_errors_from_wtform(form)

    return render_template("buyers/award.html",
                           brief=brief,
                           form=form,
                           form_options=form_options,
                           errors=errors), 200 if not errors else 400
Ejemplo n.º 15
0
def delete_a_brief(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    data_api_client.delete_brief(brief_id, current_user.email_address)
    flash({"requirements_deleted": brief.get("title")})
    return redirect(url_for('.buyer_dashboard'))
Ejemplo n.º 16
0
def award_or_cancel_brief(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(
        framework_slug,
        lot_slug,
        data_api_client,
        allowed_statuses=['live', 'expired'],
        must_allow_brief=True,
    )
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief,
                            framework_slug,
                            lot_slug,
                            current_user.id,
                            allowed_statuses=[
                                "awarded", "cancelled", "unsuccessful",
                                "closed"
                            ]):
        abort(404)

    form = AwardOrCancelBriefForm(brief)
    already_awarded = brief['status'] in [
        "awarded", "cancelled", "unsuccessful"
    ]

    if already_awarded is False and form.validate_on_submit():
        answer = form.data.get('award_or_cancel_decision')
        if answer == 'back':
            flash(BRIEF_UPDATED_MESSAGE.format(brief=brief), "success")
            return redirect(url_for('.buyer_dos_requirements'))
        elif answer == 'yes':
            return redirect(
                url_for('.award_brief',
                        framework_slug=framework_slug,
                        lot_slug=lot_slug,
                        brief_id=brief_id))
        elif answer == 'no':
            return redirect(
                url_for('.cancel_award_brief',
                        framework_slug=framework_slug,
                        lot_slug=lot_slug,
                        brief_id=brief_id))
        else:
            # We should never get here as the form validates the answers against the available choices.
            abort(500, "Unexpected answer to award or cancel brief")

    errors = get_errors_from_wtform(form)

    return render_template(
        "buyers/award_or_cancel_brief.html",
        brief=brief,
        form=form,
        errors=errors,
        already_awarded=already_awarded,
    ), 200 if not errors else 400
Ejemplo n.º 17
0
def download_brief_responses_xlsx(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          status='live',
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id,
                            data_api_client):
        abort(404)

    if brief['status'] != "closed":
        abort(404)

    responses = get_sorted_responses_for_brief(brief, data_api_client)
    rows = prepared_response_contents_for_brief(brief, responses)

    first = list(rows[0].keys()) if responses else []
    rows = [first] + [list(_.values()) for _ in rows]

    outdata = io.BytesIO()

    workbook = xlsxwriter.Workbook(outdata)
    bold = workbook.add_format({'bold': True})

    sheet = workbook.add_worksheet('Responses')

    COLUMN_WIDTH = 25

    for column_letter, r in zip(ascii_uppercase, rows):
        sheet.set_column('{x}:{x}'.format(x=column_letter), COLUMN_WIDTH)

        for row_number, c in enumerate(r, start=1):
            cell = '{}{}'.format(column_letter, row_number)

            if column_letter == 'A':
                sheet.write(cell, str(c), bold)
            else:
                sheet.write(cell, str(c))

    workbook.close()

    return Response(
        outdata.getvalue(),
        mimetype=
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        headers={
            "Content-Disposition":
            "attachment;filename=responses-to-requirements-{}.xlsx".format(
                brief['id']),
            "Content-Type":
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        }), 200
def select_seller_for_work_order(framework_slug, lot_slug, brief_id):
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ):
        abort(404)

    form = WorkOrderSellerForm(data_api_client=data_api_client, brief_id=brief_id)

    return render_template_with_csrf('workorder/select-seller.html',
                                     brief=brief,
                                     form=form)
Ejemplo n.º 19
0
def award_brief_details(framework_slug, lot_slug, brief_id, brief_response_id):
    get_framework_and_lot(
        framework_slug,
        lot_slug,
        data_api_client,
        allowed_statuses=['live', 'expired'],
        must_allow_brief=True,
    )
    brief = data_api_client.get_brief(brief_id)["briefs"]
    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id):
        abort(404)
    brief_response = data_api_client.get_brief_response(
        brief_response_id)["briefResponses"]
    if not brief_response.get(
            'status') == 'pending-awarded' or not brief_response.get(
                'briefId') == brief.get('id'):
        abort(404)
    # get questions
    content = content_loader.get_manifest(brief['frameworkSlug'],
                                          'award_brief')
    section_id = content.get_next_editable_section_id()
    section = content.get_section(section_id)

    if request.method == "POST":
        award_data = section.get_data(request.form)
        try:
            data_api_client.update_brief_award_details(
                brief_id,
                brief_response_id,
                award_data,
                updated_by=current_user.email_address)
        except HTTPError as e:
            award_data = section.unformat_data(award_data)
            errors = govuk_errors(section.get_error_messages(e.message))

            return render_template("buyers/award_details.html",
                                   brief=brief,
                                   data=award_data,
                                   errors=errors,
                                   pending_brief_response=brief_response,
                                   section=section), 400

        flash(BRIEF_UPDATED_MESSAGE.format(brief=brief), "success")

        return redirect(url_for(".buyer_dos_requirements"))

    return render_template("buyers/award_details.html",
                           brief=brief,
                           data={},
                           pending_brief_response=brief_response,
                           section=section), 200
Ejemplo n.º 20
0
def view_brief_responses(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id):
        abort(404)

    failed_count, eligible_count = counts_for_failed_and_eligible_brief_responses(brief["id"], data_api_client)

    return render_template(
        "buyers/brief_responses.html",
        response_counts={"failed": failed_count, "eligible": eligible_count},
        brief=brief
    ), 200
def preview_brief_source(framework_slug, lot_slug, brief_id):
    # This view's response currently is what will populate the iframes in the view above
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    # Check that all questions have been answered
    editable_content = content_loader.get_manifest(
        brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    unanswered_required, unanswered_optional = count_unanswered_questions(
        editable_content.summary(brief))
    if unanswered_required > 0:
        abort(400, 'There are still unanswered required questions')

    important_dates = get_publishing_dates(brief)

    display_content = content_loader.get_manifest(
        brief['frameworkSlug'], 'display_brief'
    ).filter({
        "lot": brief[
            "lotSlug"],  # 'lot' is deprecated so we can't rely on it being in brief object
        **brief,
    })

    # Get attributes in format suitable for govukSummaryList
    brief_summary = display_content.summary(brief)
    for section in brief_summary:
        section.summary_list = to_summary_list_rows(section.questions,
                                                    format_links=True,
                                                    filter_empty=False,
                                                    open_links_in_new_tab=True)

    # TODO: move preview_brief_source templates/includes into shared FE toolkit pattern to ensure it's kept in sync
    html = render_template("buyers/preview_brief_source.html",
                           content=display_content,
                           content_summary=brief_summary,
                           unanswered_required=unanswered_required,
                           brief=brief,
                           important_dates=important_dates)
    response_headers = {"X-Frame-Options": "sameorigin"}

    return html, 200, response_headers
def delete_a_brief(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live', 'expired'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    data_api_client.delete_brief(brief_id, current_user.email_address)
    flash(BRIEF_DELETED_MESSAGE.format(brief=brief), "success")
    return redirect(url_for(".buyer_dos_requirements"))
def get_work_order(work_order_id):
    try:
        work_order = data_api_client.get_work_order(work_order_id)['workOrder']
    except APIError as e:
        abort(e.status_code)

    brief = data_api_client.get_brief(work_order['briefId'])["briefs"]
    if not is_brief_associated_with_user(
            brief, current_user.id
    ):
        abort(404)

    return render_template_with_csrf('workorder/work-order-instruction-list.html',
                                     work_order=work_order,
                                     questions=questions)
Ejemplo n.º 24
0
def delete_a_brief(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ) or not brief_can_be_edited(brief):
        abort(404)

    if not has_permission_to_edit_brief(brief):
        return redirect('/2/request-access/create_drafts')

    data_api_client.delete_brief(brief_id, current_user.email_address)
    flash({"requirements_deleted": brief.get("title")})
    return redirect('/2/buyer-dashboard')
Ejemplo n.º 25
0
def view_brief_overview(framework_slug, lot_slug, brief_id):
    if lot_slug == 'digital-professionals' or lot_slug == 'training':
        return redirect('/2/brief/{}/overview'.format(brief_id))
    if lot_slug in ['rfx', 'atm', 'specialist', 'training2']:
        return redirect('/2/brief/{}/overview/{}'.format(brief_id, lot_slug))

    framework, lot = get_framework_and_lot(framework_slug,
                                           lot_slug,
                                           data_api_client,
                                           status='live',
                                           must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id,
                            data_api_client) and not allowed_email_domain(
                                current_user.id, brief, data_api_client):
        abort(404)

    content = content_loader.get_manifest(
        brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    sections = content.summary(brief)
    delete_requested = True if request.args.get('delete_requested') else False

    completed_sections = {}
    for section in sections:
        required, optional = count_unanswered_questions([section])
        if section_has_at_least_one_required_question(section):
            completed_sections[section.slug] = True if required == 0 else False
        else:
            completed_sections[section.slug] = True if optional == 0 else False

    brief['clarificationQuestions'] = [
        dict(question, number=index + 1)
        for index, question in enumerate(brief['clarificationQuestions'])
    ]

    return render_template_with_csrf(
        "buyers/brief_overview.html",
        framework=framework,
        confirm_remove=request.args.get("confirm_remove", None),
        brief=brief,
        sections=sections,
        completed_sections=completed_sections,
        step_sections=[
            section.step for section in sections if hasattr(section, 'step')
        ],
        delete_requested=delete_requested,
    )
def download_brief_responses_xlsx(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ):
        abort(404)

    if brief['status'] != "closed":
        abort(404)

    responses = get_sorted_responses_for_brief(brief, data_api_client)
    rows = prepared_response_contents_for_brief(brief, responses)

    first = rows[0].keys() if responses else []
    rows = [first] + [_.values() for _ in rows]

    outdata = io.BytesIO()

    workbook = xlsxwriter.Workbook(outdata)
    bold = workbook.add_format({'bold': True})

    sheet = workbook.add_worksheet('Responses')

    COLUMN_WIDTH = 25

    for column_letter, r in zip(ascii_uppercase, rows):
        sheet.set_column('{x}:{x}'.format(x=column_letter), COLUMN_WIDTH)

        for row_number, c in enumerate(r, start=1):
            cell = '{}{}'.format(column_letter, row_number)

            if column_letter == 'A':
                sheet.write(cell, unicode(c), bold)
            else:
                sheet.write(cell, unicode(c))

    workbook.close()

    return Response(
        outdata.getvalue(),
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        headers={
            "Content-Disposition": "attachment;filename=responses-to-requirements-{}.xlsx".format(brief['id']),
            "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        }
    ), 200
Ejemplo n.º 27
0
def add_supplier_question(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live', 'expired'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief,
                            framework_slug,
                            lot_slug,
                            current_user.id,
                            allowed_statuses=['live']):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'],
                                          "clarification_question").filter({})
    section = content.get_section(content.get_next_editable_section_id())
    update_data = section.get_data(request.form)

    errors = {}
    status_code = 200

    if request.method == "POST":
        try:
            data_api_client.add_brief_clarification_question(
                brief_id, update_data['question'], update_data['answer'],
                current_user.email_address)

            return redirect(
                url_for('.supplier_questions',
                        framework_slug=brief['frameworkSlug'],
                        lot_slug=brief['lotSlug'],
                        brief_id=brief['id']))
        except HTTPError as e:
            if e.status_code != 400:
                raise
            brief.update(update_data)
            errors = govuk_errors(section.get_error_messages(e.message))
            status_code = 400

    return render_template("buyers/edit_brief_question.html",
                           brief=brief,
                           section=section,
                           question=section.questions[0],
                           button_label="Publish question and answer",
                           errors=errors), status_code
def view_brief_timeline(framework_slug, lot_slug, brief_id):
    TZ = current_app.config['DM_TIMEZONE']

    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]
    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ) or brief.get('status') != 'live':
        abort(404)

    return render_template(
        "buyers/brief_publish_confirmation.html",
        email_address=brief['users'][0]['emailAddress'],
        published=True,
        current_date=pendulum.now(TZ),
        brief=brief
    )
Ejemplo n.º 29
0
def view_brief_timeline(framework_slug, lot_slug, brief_id):
    TZ = current_app.config['DM_TIMEZONE']

    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]
    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ) or brief.get('status') != 'live':
        abort(404)

    return render_template(
        "buyers/brief_publish_confirmation.html",
        email_address=brief['users'][0]['emailAddress'],
        published=True,
        current_date=pendulum.now(TZ),
        brief=brief
    )
def add_supplier_question(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ) and not allowed_email_domain(current_user.id, brief, data_api_client):
        abort(404)

    if brief["status"] != "live":
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], "clarification_question")
    section = content.get_section(content.get_next_editable_section_id())
    update_data = section.get_data(request.form)

    errors = {}
    status_code = 200

    if request.method == "POST":
        try:
            data_api_client.add_brief_clarification_question(brief_id,
                                                             update_data['question'],
                                                             update_data['answer'],
                                                             current_user.email_address)

            return redirect(
                url_for('.supplier_questions', framework_slug=brief['frameworkSlug'], lot_slug=brief['lotSlug'],
                        brief_id=brief['id']))
        except HTTPError as e:
            if e.status_code != 400:
                raise
            brief.update(update_data)
            errors = section.get_error_messages(e.message)
            status_code = 400

    return render_template_with_csrf(
        "buyers/edit_brief_question.html",
        status_code=status_code,
        brief=brief,
        section=section,
        question=section.questions[0],
        button_label="Publish question and answer",
        errors=errors
    )
Ejemplo n.º 31
0
def download_brief_response_attachment(framework_slug, lot_slug, brief_id, response_id, attachment_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ):
        abort(404)

    if brief['status'] != "closed":
        abort(404)

    response = data_api_client.get_brief_response(response_id)
    if not response or not response.get('briefResponses', {}).get('attachedDocumentURL'):
        abort(404)

    try:
        slug = response['briefResponses']['attachedDocumentURL'][attachment_id]
    except IndexError:
        abort(404)

    if slug is None:
        abort(404)

    try:
        # try newer file storage
        mimetype = mimetypes.guess_type(slug)[0] or 'binary/octet-stream'
        return Response(
            s3_download_file(
                current_app.config.get('S3_BUCKET_NAME', None),
                slug,
                os.path.join(
                    brief['frameworkSlug'],
                    'documents',
                    'brief-' + str(brief_id),
                    'supplier-' + str(response['briefResponses']['supplierCode'])
                )
            ),
            mimetype=mimetype
        )
    except botocore.exceptions.ClientError:
        url = get_signed_url(current_app.config['S3_BUCKET_NAME'], slug, None)
        if not url:
            abort(404)
        return redirect(url)
def view_brief_timeline(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live', 'expired'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]
    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or brief.get('status') != 'live':
        abort(404)

    dates = get_publishing_dates(brief)

    return render_template("buyers/brief_publish_confirmation.html",
                           email_address=brief['users'][0]['emailAddress'],
                           published=True,
                           brief=brief,
                           dates=dates), 200
def delete_a_brief_warning(framework_slug, lot_slug, brief_id):
    framework, lot = get_framework_and_lot(
        framework_slug,
        lot_slug,
        data_api_client,
        allowed_statuses=['live', 'expired'],
        must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    return render_template(
        "buyers/delete_brief.html",
        framework=framework,
        brief=brief,
    ), 200
Ejemplo n.º 34
0
def supplier_questions(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id):
        abort(404)

    if brief["status"] != "live":
        abort(404)

    brief['clarificationQuestions'] = [
        dict(question, number=index+1)
        for index, question in enumerate(brief['clarificationQuestions'])
    ]

    return render_template(
        "buyers/supplier_questions.html",
        brief=brief,
    )
Ejemplo n.º 35
0
def view_brief_section_summary(framework_slug, lot_slug, brief_id, section_slug):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    sections = content.summary(brief)
    section = sections.get_section(section_slug)

    if not section:
        abort(404)

    return render_template(
        "buyers/section_summary.html",
        brief=brief,
        section=section
    ), 200
Ejemplo n.º 36
0
def get_brief_by_id(framework_slug, brief_id):
    briefs = data_api_client.get_brief(brief_id)
    brief = briefs.get('briefs')
    if brief['status'] not in ['live', 'closed']:
        abort(404, "Opportunity '{}' can not be found".format(brief_id))

    brief['clarificationQuestions'] = [
        dict(question, number=index+1)
        for index, question in enumerate(brief['clarificationQuestions'])
    ]

    brief_content = content_loader.get_builder('digital-outcomes-and-specialists', 'display_brief').filter(
        brief
    )
    return render_template(
        'brief.html',
        brief=brief,
        content=brief_content
    )
def create_new_work_order(framework_slug, lot_slug, brief_id):
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ):
        abort(404)

    form = WorkOrderSellerForm(formdata=request.form, data_api_client=data_api_client, brief_id=brief_id)

    if not form.validate():
        return render_template_with_csrf(
            'workorder/select-seller.html',
            status_code=400,
            brief=brief,
            form=form
        )

    try:
        seller = data_api_client.get_supplier(form.seller.data)['supplier']

        work_order = data_api_client.create_work_order(
            briefId=brief_id,
            supplierCode=form.seller.data,
            workOrder=_create_work_order_from_brief(brief, seller)
        )['workOrder']

    except APIError as e:
        form.errors['seller'] = [e.message]
        return render_template_with_csrf(
            'workorder/select-seller.html',
            status_code=e.status_code,
            brief=brief,
            form=form,
        )

    return redirect(
        url_for(
            'buyers.get_work_order',
            work_order_id=work_order['id'],
        )
    )
def view_brief_section_summary(framework_slug, lot_slug, brief_id,
                               section_slug):
    get_framework_and_lot(framework_slug,
                          lot_slug,
                          data_api_client,
                          allowed_statuses=['live', 'expired'],
                          must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug,
                            current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(
        brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    sections = content.summary(brief)
    section = sections.get_section(section_slug)

    if not section:
        abort(404)

    section.summary_list = []
    for question in section.questions:
        section.summary_list.append(
            to_summary_list_row(question,
                                action_link=url_for(
                                    'buyers.edit_brief_question',
                                    framework_slug=framework_slug,
                                    lot_slug=lot_slug,
                                    brief_id=brief_id,
                                    section_slug=section_slug,
                                    question_id=question.id)))

    # Show preview link if all mandatory questions have been answered
    unanswered_required, unanswered_optional = count_unanswered_questions(
        sections)
    show_dos_preview_link = (unanswered_required == 0)

    return render_template("buyers/section_summary.html",
                           brief=brief,
                           section=section,
                           show_dos_preview_link=show_dos_preview_link), 200
def view_brief_overview(framework_slug, lot_slug, brief_id):
    if lot_slug == 'digital-professionals' or lot_slug == 'training':
        return redirect('/2/brief/{}/overview'.format(brief_id))
    if lot_slug in ['rfx', 'atm']:
        return redirect('/2/brief/{}/overview/{}'.format(brief_id, lot_slug))

    framework, lot = get_framework_and_lot(
        framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ) and not allowed_email_domain(current_user.id, brief, data_api_client):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    sections = content.summary(brief)
    delete_requested = True if request.args.get('delete_requested') else False

    completed_sections = {}
    for section in sections:
        required, optional = count_unanswered_questions([section])
        if section_has_at_least_one_required_question(section):
            completed_sections[section.slug] = True if required == 0 else False
        else:
            completed_sections[section.slug] = True if optional == 0 else False

    brief['clarificationQuestions'] = [
        dict(question, number=index+1)
        for index, question in enumerate(brief['clarificationQuestions'])
    ]

    return render_template_with_csrf(
        "buyers/brief_overview.html",
        framework=framework,
        confirm_remove=request.args.get("confirm_remove", None),
        brief=brief,
        sections=sections,
        completed_sections=completed_sections,
        step_sections=[section.step for section in sections if hasattr(section, 'step')],
        delete_requested=delete_requested,
    )
def update_work_order_question(work_order_id, question_slug):
    try:
        work_order = data_api_client.get_work_order(work_order_id)['workOrder']
    except APIError as e:
        abort(e.status_code)

    brief = data_api_client.get_brief(work_order['briefId'])["briefs"]
    if not is_brief_associated_with_user(brief, current_user.id):
        abort(404)

    if questions.get(question_slug, None) is None:
        abort(404)

    form = FormFactory(question_slug, formdata=request.form)

    if not form.validate():
        return render_template_with_csrf(
            'workorder/work-order-question.html',
            status_code=400,
            work_order_id=work_order_id,
            question_slug=question_slug,
            form=form
        )

    if questions[question_slug].get("type") == 'address':
        data = {question_slug: {
            'abn': request.form['abn'],
            'name': request.form['name'],
            'contact': request.form['contact']}
        }
    else:
        data = {question_slug: request.form[question_slug]}

    data_api_client.update_work_order(work_order_id, data)

    return redirect(
        url_for(
            'buyers.get_work_order',
            work_order_id=work_order_id,
        )
    )
Ejemplo n.º 41
0
def publish_brief(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    brief_users = brief['users'][0]
    brief_user_name = brief_users['name']

    sections = content.summary(brief)
    question_and_answers = {}
    question_and_answers_content = sections.get_question('questionAndAnswerSessionDetails')
    question_and_answers['id'] = question_and_answers_content['id']

    for section in sections:
        if section.get_question('questionAndAnswerSessionDetails') == question_and_answers_content:
            question_and_answers['slug'] = section['id']

    unanswered_required, unanswered_optional = count_unanswered_questions(sections)
    if request.method == 'POST':
        if unanswered_required > 0:
            abort(400, 'There are still unanswered required questions')
        data_api_client.update_brief_status(brief_id, 'live', brief_user_name)
        return redirect(
            url_for('.view_brief_overview', framework_slug=brief['frameworkSlug'], lot_slug=brief['lotSlug'],
                    brief_id=brief['id']))
    else:
        email_address = brief_users['emailAddress']
        dates = get_publishing_dates()

        return render_template(
            "buyers/brief_publish_confirmation.html",
            email_address=email_address,
            question_and_answers=question_and_answers,
            unanswered_required=unanswered_required,
            sections=sections,
            brief=brief,
            dates=dates
        ), 200
def download_brief_response_attachment(framework_slug, lot_slug, brief_id, response_id, attachment_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ):
        abort(404)

    if brief['status'] != "closed":
        abort(404)

    response = data_api_client.get_brief_response(response_id)
    if not response or not response.get('briefResponses', {}).get('attachedDocumentURL'):
        abort(404)

    try:
        slug = response['briefResponses']['attachedDocumentURL'][attachment_id]
    except IndexError:
        abort(404)

    if slug is None:
        abort(404)

    try:
        # try newer file storage
        file = s3_download_file(slug, os.path.join(brief['frameworkSlug'], 'documents',
                                                   'brief-' + str(brief_id),
                                                   'supplier-' + str(response['briefResponses']['supplierCode'])))

        mimetype = mimetypes.guess_type(slug)[0] or 'binary/octet-stream'
        return Response(file, mimetype=mimetype)
    except botocore.exceptions.ClientError:
        url = get_signed_url(current_app.config['S3_BUCKET_NAME'], slug, None)
        if not url:
            abort(404)
        return redirect(url)
def view_brief_responses(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(
        framework_slug,
        lot_slug,
        data_api_client,
        allowed_statuses=['live', 'expired'],
        must_allow_brief=True,
    )
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief,
                            framework_slug,
                            lot_slug,
                            current_user.id,
                            allowed_statuses=CLOSED_PUBLISHED_BRIEF_STATUSES):
        abort(404)

    brief_responses = data_api_client.find_brief_responses(
        brief_id)['briefResponses']

    brief_responses_required_evidence = (
        None if not brief_responses else
        not is_legacy_brief_response(brief_responses[0], brief=brief))

    counter = Counter()

    for response in brief_responses:
        counter[all(response['essentialRequirements'])] += 1

    return render_template(
        "buyers/brief_responses.html",
        response_counts={
            "failed": counter[False],
            "eligible": counter[True]
        },
        brief_responses_required_evidence=brief_responses_required_evidence,
        brief=brief), 200
def update_brief_submission(framework_slug, lot_slug, brief_id, section_id, question_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    section = content.get_section(section_id)
    if section is None or not section.editable:
        abort(404)

    question = section.get_question(question_id)
    if not question:
        abort(404)

    update_data = question.get_data(request.form)

    remove_non_cascade_fields(brief, section, question_id, update_data)
    question_ids = section.get_section_question_ids()

    question_id_index = None
    if question_id in question_ids:
        question_id_index = question_ids.index(question_id)

    try:
        data_api_client.update_brief(
            brief_id,
            update_data,
            updated_by=current_user.email_address,
            page_questions=question.form_fields
        )
    except HTTPError as e:
        update_data = section.unformat_data(update_data)
        mapped = {}
        for k, v in e.message.iteritems():
            if ((k == 'sellerEmailList' or k == 'sellerEmail')
                    and v.startswith('email_not_found~')):
                mapped[k] = v.split('~')[0]
            else:
                mapped[k] = v

        errors = section.get_error_messages(mapped)

        for k, v in errors.iteritems():
            if ((k == 'sellerEmailList' or k == 'sellerEmail')
                    and e.message[k].startswith('email_not_found~')):
                v['message'] = '{} {}'.format(e.message[k].split('~')[1], v['message'])

        # we need the brief_id to build breadcrumbs and the update_data to fill in the form.
        brief.update(update_data)
        return render_template_with_csrf(
            "buyers/edit_brief_question.html",
            status_code=400,
            brief=brief,
            section=section,
            question=question,
            errors=errors
        )

    if section.has_summary_page:
        # If there are more than 1 questions and it is not the last one.
        if (question_id_index is not None and
                len(question_ids) > 1 and
                question_id_index != len(question_ids) - 1):
            return redirect(
                url_for(
                    '.edit_brief_question',
                    framework_slug=brief['frameworkSlug'],
                    lot_slug=brief['lotSlug'],
                    brief_id=brief['id'],
                    section_slug=section.slug,
                    question_id=question_ids[question_id_index + 1]))

        response = __navigate_next(content, brief, lot_slug, section_id)
        if response:
            return response

        if lot_slug != 'training':
            return redirect(
                url_for(
                    ".view_brief_section_summary",
                    framework_slug=brief['frameworkSlug'],
                    lot_slug=brief['lotSlug'],
                    brief_id=brief['id'],
                    section_slug=section.slug))

    else:
        response = __navigate_next(content, brief, lot_slug, section_id)
        if response:
            return response

    return redirect(
        url_for(
            ".view_brief_overview",
            framework_slug=brief['frameworkSlug'],
            lot_slug=brief['lotSlug'],
            brief_id=brief['id']
        )
    )
def publish_brief(framework_slug, lot_slug, brief_id):
    if lot_slug == 'digital-outcome':
        abort(404)
    TZ = current_app.config['DM_TIMEZONE']

    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id
    ) or not brief_can_be_edited(brief):
        abort(404)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    brief_users = brief['users'][0]
    brief_user_name = brief_users['name']

    sections = content.summary(brief)
    question_and_answers = {}
    question_and_answers_content = sections.get_question('questionAndAnswerSessionDetails')
    question_and_answers['id'] = question_and_answers_content['id']

    for section in sections:
        if section.get_question('questionAndAnswerSessionDetails') == question_and_answers_content:
            question_and_answers['slug'] = section['id']
        for question_id in section.get_section_question_ids():
            remove_non_cascade_fields(brief, section, question_id)

    unanswered_required, unanswered_optional = count_unanswered_questions(sections)

    if request.method == 'POST':
        if unanswered_required > 0:
            abort(400, 'There are still unanswered required questions')
        data_api_client.publish_brief(brief_id, brief_user_name)

        brief_url = '/2/brief/{}/published'.format(brief_id)

        brief_url_external = url_for('main.get_brief_by_id', framework_slug=brief['frameworkSlug'],
                                     brief_id=brief['id'], _external=True)

        send_new_opportunity_email_to_sellers(brief, brief_url_external)

        notification_message = '{}\n{}\nBy: {} ({})'.format(
            brief['title'],
            brief['organisation'],
            current_user.name,
            current_user.email_address
        )
        notify_team('A buyer has published a new opportunity', notification_message, brief_url_external)

        return redirect(brief_url)
    else:

        email_address = brief_users['emailAddress']

        return render_template_with_csrf(
            "buyers/brief_publish_confirmation.html",
            email_address=email_address,
            question_and_answers=question_and_answers,
            unanswered_required=unanswered_required,
            sections=sections,
            brief=brief,
            current_date=pendulum.now(TZ)
        )
Ejemplo n.º 46
0
def publish_brief(framework_slug, lot_slug, brief_id):
    if lot_slug in ['digital-outcome', 'digital-professionals', 'training']:
        abort(404)
    TZ = current_app.config['DM_TIMEZONE']

    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ) or not brief_can_be_edited(brief):
        abort(404)

    if not has_permission_to_edit_brief(brief):
        return redirect('/2/request-access/publish_opportunities')

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})

    sections = content.summary(brief)
    question_and_answers = {}
    question_and_answers_content = sections.get_question('questionAndAnswerSessionDetails')
    question_and_answers['id'] = question_and_answers_content['id']

    for section in sections:
        if section.get_question('questionAndAnswerSessionDetails') == question_and_answers_content:
            question_and_answers['slug'] = section['id']
        for question_id in section.get_section_question_ids():
            remove_non_cascade_fields(brief, section, question_id)

    unanswered_required, unanswered_optional = count_unanswered_questions(sections)

    if request.method == 'POST':
        if unanswered_required > 0:
            abort(400, 'There are still unanswered required questions')

        if not current_user.has_permission('publish_opportunities'):
            return redirect('/2/request-access/publish_opportunities')

        data_api_client.publish_brief(brief_id, current_user.name)

        brief_url = '/2/brief/{}/published'.format(brief_id)

        brief_url_external = url_for('main.get_brief_by_id', framework_slug=brief['frameworkSlug'],
                                     brief_id=brief['id'], _external=True)

        send_new_opportunity_email_to_sellers(brief, brief_url_external)

        notification_message = '{}\n{}\nBy: {} ({})'.format(
            brief['title'],
            brief['organisation'],
            current_user.name,
            current_user.email_address
        )
        notify_team('A buyer has published a new opportunity', notification_message, brief_url_external)

        return redirect(brief_url)
    else:
        email_address = current_user.email_address

        return render_template_with_csrf(
            "buyers/brief_publish_confirmation.html",
            email_address=email_address,
            question_and_answers=question_and_answers,
            unanswered_required=unanswered_required,
            sections=sections,
            brief=brief,
            current_date=pendulum.now(TZ)
        )
Ejemplo n.º 47
0
def download_brief_responses(framework_slug, lot_slug, brief_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(brief, framework_slug, lot_slug, current_user.id):
        abort(404)

    if brief['status'] != "closed":
        abort(404)

    sorted_brief_responses = get_sorted_responses_for_brief(brief, data_api_client)

    content = content_loader.get_manifest(brief['frameworkSlug'], 'output_brief_response').filter(
        {'lot': brief['lotSlug']}
    )
    section = content.get_section('view-response-to-requirements')

    column_headings = []
    question_key_sequence = []
    boolean_list_questions = []
    csv_rows = []

    # Build header row from manifest and add it to the list of rows
    for question in section.questions:
        question_key_sequence.append(question.id)
        if question['type'] == 'boolean_list' and question.id in brief:
            column_headings.extend(brief[question.id])
            boolean_list_questions.append(question.id)
        else:
            column_headings.append(question.name)
    csv_rows.append(column_headings)

    # Add a row for each eligible response received
    for brief_response in sorted_brief_responses:
        if all_essentials_are_true(brief_response):
            row = []
            for key in question_key_sequence:
                if key in boolean_list_questions:
                    row.extend(brief_response.get(key))
                else:
                    row.append(brief_response.get(key))
            csv_rows.append(row)

    def iter_csv(rows):
        class Line(object):
            def __init__(self):
                self._line = None

            def write(self, line):
                self._line = line

            def read(self):
                return self._line

        line = Line()
        writer = unicodecsv.writer(line, lineterminator='\n')
        for row in rows:
            writer.writerow(row)
            yield line.read()

    return Response(
        iter_csv(csv_rows),
        mimetype='text/csv',
        headers={
            "Content-Disposition": "attachment;filename=responses-to-requirements-{}.csv".format(brief['id']),
            "Content-Type": "text/csv; header=present"
        }
    ), 200
Ejemplo n.º 48
0
def update_brief_submission(framework_slug, lot_slug, brief_id, section_id, question_id):
    get_framework_and_lot(framework_slug, lot_slug, data_api_client, status='live', must_allow_brief=True)
    brief = data_api_client.get_brief(brief_id)["briefs"]

    if not is_brief_correct(
            brief, framework_slug, lot_slug, current_user.id, data_api_client
    ) or not brief_can_be_edited(brief):
        abort(404)

    if not has_permission_to_edit_brief(brief):
        return redirect('/2/request-access/create_drafts')

    content = content_loader.get_manifest(brief['frameworkSlug'], 'edit_brief').filter({'lot': brief['lotSlug']})
    section = content.get_section(section_id)
    if section is None or not section.editable:
        abort(404)

    question = section.get_question(question_id)
    if not question:
        abort(404)

    update_data = question.get_data(request.form)

    remove_non_cascade_fields(brief, section, question_id, update_data)
    question_ids = section.get_section_question_ids()

    question_id_index = None
    if question_id in question_ids:
        question_id_index = question_ids.index(question_id)

    try:
        data_api_client.update_brief(
            brief_id,
            update_data,
            updated_by=current_user.email_address,
            page_questions=question.form_fields
        )
    except HTTPError as e:
        update_data = section.unformat_data(update_data)
        mapped = {}
        for k, v in e.message.iteritems():
            if ((k == 'sellerEmailList' or k == 'sellerEmail')
                    and v.startswith('email_not_found~')):
                mapped[k] = v.split('~')[0]
            else:
                mapped[k] = v

        errors = section.get_error_messages(mapped)

        for k, v in errors.iteritems():
            if ((k == 'sellerEmailList' or k == 'sellerEmail')
                    and e.message[k].startswith('email_not_found~')):
                v['message'] = '{} {}'.format(e.message[k].split('~')[1], v['message'])

        # we need the brief_id to build breadcrumbs and the update_data to fill in the form.
        brief.update(update_data)
        return render_template_with_csrf(
            "buyers/edit_brief_question.html",
            status_code=400,
            brief=brief,
            section=section,
            question=question,
            errors=errors
        )

    if section.has_summary_page:
        # If there are more than 1 questions and it is not the last one.
        if (question_id_index is not None and
                len(question_ids) > 1 and
                question_id_index != len(question_ids) - 1):
            return redirect(
                url_for(
                    '.edit_brief_question',
                    framework_slug=brief['frameworkSlug'],
                    lot_slug=brief['lotSlug'],
                    brief_id=brief['id'],
                    section_slug=section.slug,
                    question_id=question_ids[question_id_index + 1]))

        response = __navigate_next(content, brief, lot_slug, section_id)
        if response:
            return response

        if lot_slug != 'training':
            return redirect(
                url_for(
                    ".view_brief_section_summary",
                    framework_slug=brief['frameworkSlug'],
                    lot_slug=brief['lotSlug'],
                    brief_id=brief['id'],
                    section_slug=section.slug))

    else:
        response = __navigate_next(content, brief, lot_slug, section_id)
        if response:
            return response

    return redirect(
        url_for(
            ".view_brief_overview",
            framework_slug=brief['frameworkSlug'],
            lot_slug=brief['lotSlug'],
            brief_id=brief['id']
        )
    )
def get_brief_by_id(framework_slug, brief_id):
    briefs = data_api_client.get_brief(brief_id)
    brief = briefs.get('briefs')
    if brief['lotSlug'] in ['rfx', 'atm']:
        return redirect('/2/%s/opportunities/%s' % (framework_slug, brief_id), 301)
    if brief['status'] not in ['live', 'closed']:
        if not current_user.is_authenticated or brief['users'][0]['id'] != current_user.id:
            abort(404, "Opportunity '{}' can not be found".format(brief_id))

    if current_user.is_authenticated and current_user.role == 'supplier':
        brief_responses = data_api_client.find_brief_responses(
            brief_id, current_user.supplier_code)["briefResponses"]
        selected_for_brief = _is_supplier_selected_for_brief(brief)
    else:
        brief_responses = None
        selected_for_brief = False

    brief['clarificationQuestions'] = [
        dict(question, number=index+1)
        for index, question in enumerate(brief['clarificationQuestions'])
    ]

    brief_content = content_loader.get_builder(framework_slug, 'display_brief').filter(
        brief
    )

    sections = brief_content.summary(brief)
    unanswered_required, unanswered_optional = count_unanswered_questions(sections)

    brief_of_current_user = False
    if not current_user.is_anonymous and len(brief.get('users')) > 0:
        brief_of_current_user = brief['users'][0]['id'] == current_user.id

    is_restricted_brief = brief.get('sellerSelector', '') in ('someSellers', 'oneSeller')

    brief_published_date = brief['dates'].get('published_date', None)
    feature_date = current_app.config['MULTI_CANDIDATE_PUBLISHED_DATE']

    published_date = pendulum.parse(brief_published_date) if brief_published_date else feature_date.subtract(days=1)
    application_url = "/2/brief/{}/respond".format(brief['id'])
    application_specialist_url = application_url
    application_specialist_submitted_url = None

    if published_date >= feature_date:
        application_specialist_url = "/2/brief/{}/specialist/respond".format(brief['id'])
        application_specialist_submitted_url = "/2/brief/{}/specialist/respond/submitted".format(brief['id'])

    application_training_url = "/2/brief/{}/training/respond".format(brief['id'])

    add_case_study_url = None

    profile_application_status = None
    supplier = None
    unassessed_domains = {}
    assessed_domains = {}
    profile_url = None
    supplier_assessments = {}
    supplier_framework = None

    if current_user.is_authenticated:
        if current_user.supplier_code is not None:
            supplier = data_api_client.get_supplier(
                current_user.supplier_code
            ).get('supplier', None)

        profile_application_id = current_user.application_id

        if supplier is not None:
            profile_url = '/supplier/{}'.format(supplier.get('code'))
            assessed_domains = supplier.get('domains').get('assessed', None)
            unassessed_domains = supplier.get('domains').get('unassessed', None)
            legacy_domains = supplier.get('domains').get('legacy', None)

            if profile_application_id is None:
                profile_application_id = supplier.get('application_id', None)

            supplier_code = supplier.get('code')
            supplier_assessments = data_api_client.req.assessments().supplier(supplier_code).get()

            if len(legacy_domains) != 0:
                for i in range(len(legacy_domains)):
                    supplier_assessments['assessed'].append(legacy_domains[i])

            supplier_framework_ids = supplier.get('frameworks')
            for i in range(len(supplier_framework_ids)):
                if supplier.get('frameworks')[i].get('framework_id') == 7:
                    supplier_framework = 'digital-marketplace'
            if supplier_framework is None:
                supplier_framework = 'digital-service-professionals'

        if profile_application_id is not None:
            try:
                profile_application = data_api_client.req.applications(profile_application_id).get()

                if unassessed_domains is None:
                    unassessed_domains = profile_application.get(
                        'application').get('supplier').get('domains', None).get('unassessed', None)
                if assessed_domains is None:
                    assessed_domains = profile_application.get(
                        'application').get('supplier').get('domains', None).get('assessed', None)

                profile_application_status = profile_application.get('application').get('status', None)
                if profile_application.get('application').get('type') == 'edit':
                    profile_application_status = 'approved'

            except APIError:
                pass
            except HTTPError:
                pass

    domain_id = None
    if brief.get('areaOfExpertise'):
        current_domain = data_api_client.req.domain(brief['areaOfExpertise']).get()
        domain_id = current_domain['domain']['id']
    elif brief['lotSlug'] == 'training':
        domain_id = 15  # training

    return render_template_with_csrf(
        'brief.html',
        add_case_study_url=add_case_study_url,
        application_url=application_url,
        application_specialist_url=application_specialist_url,
        application_specialist_submitted_url=application_specialist_submitted_url,
        application_training_url=application_training_url,
        assessed_domains=assessed_domains,
        brief=brief,
        brief_responses=brief_responses,
        brief_of_current_user=brief_of_current_user,
        content=brief_content,
        domain_id=domain_id,
        is_restricted_brief=is_restricted_brief,
        selected_for_brief=selected_for_brief,
        profile_application_status=profile_application_status,
        profile_url=profile_url,
        unassessed_domains=unassessed_domains,
        supplier_assessments=supplier_assessments,
        supplier_framework=supplier_framework,
        unanswered_required=unanswered_required,
        training_domain_name='Training, Learning and Development'
    )
def get_brief_response_preview_by_id(framework_slug, brief_id):
    briefs = data_api_client.get_brief(brief_id)
    brief = briefs.get('briefs')
    brief_url = url_for('main.index', _external=True) + '{}/opportunities/{}'.format(framework_slug, brief['id'])

    if brief['status'] not in ['live', 'closed']:
        if not current_user.is_authenticated or brief['users'][0]['id'] != current_user.id:
            abort(404, "Opportunity '{}' can not be found".format(brief_id))

    hypothetical_dates = brief['dates'].get('hypothetical', None)
    if hypothetical_dates is None:
        published_date = brief['dates'].get('published_date', None)
        closing_time = brief['dates'].get('closing_time', None)
    else:
        published_date = hypothetical_dates.get('published_date', None)
        closing_time = hypothetical_dates.get('closing_time', None)

    outdata = io.BytesIO()

    workbook = xlsxwriter.Workbook(outdata)
    bold_header = workbook.add_format({'bg_color': '#e8f5fa', 'bold': True, 'text_wrap':  True})
    bold_question = workbook.add_format({'bg_color': '#f3f3f3', 'valign': 'top', 'text_wrap':  True,
                                         'border': 1, 'border_color': "#AAAAAA", 'bold': True})
    bold_red = workbook.add_format({'bold': True, 'font_color': '#fc0d1b', 'text_wrap':  True})
    italic_header = workbook.add_format({'bg_color': '#e8f5fa', 'italic': True})
    italic_lightgrey = workbook.add_format({'italic': True, 'font_color': '#999999'})
    italic_darkgrey_question = workbook.add_format({'italic': True, 'font_color': '#666666', 'bg_color': '#f3f3f3',
                                                    'valign': 'top', 'text_wrap':  True,
                                                    'border': 1, 'border_color': "#AAAAAA"})
    darkgrey = workbook.add_format({'font_color': '#666666', 'text_wrap':  True})
    heading = workbook.add_format({'bold': True, 'font_size': '14', 'text_wrap':  True})
    header = workbook.add_format({'bg_color': '#e8f5fa'})
    cta = workbook.add_format({'bg_color': 'd9ead4', 'align': 'center',
                               'color': 'blue', 'underline': 1, 'text_wrap':  True})
    bold_cta = workbook.add_format({'bg_color': 'd9ead4', 'bold': True, 'align': 'center'})
    question = workbook.add_format({'bg_color': '#f3f3f3', 'valign': 'top', 'text_wrap':  True,
                                    'border': 1, 'border_color': "#AAAAAA"})
    link = workbook.add_format({'bg_color': '#e8f5fa', 'color': 'blue', 'underline': 1})
    right_border_question = workbook.add_format({'right': 1, 'right_color': 'black', 'bg_color': '#f3f3f3',
                                                 'valign': 'top', 'text_wrap':  True, 'border': 1,
                                                 'border_color': "#AAAAAA"})
    sheet = workbook.add_worksheet('Response')

    sheet.set_column('E:E', 50)
    sheet.set_column('D:D', 5)
    sheet.set_column('C:C', 50)
    sheet.set_column('B:B', 30)
    sheet.set_column('A:A', 30)

    sheet.merge_range(0, 0, 0, 2, '',  italic_header)
    sheet.write_url('A1', brief_url)
    sheet.write_rich_string('A1',  italic_header,
                            'Use this template if you are waiting to be assessed, or want to collaborate '
                            'with others, before submitting your response to this brief.\n'
                            'If you have been assessed and are ready to submit, you will need to '
                            'copy and paste your answers from this template into \n', link, brief_url)
    sheet.write_string('D1', '', right_border_question)

    df = DateFormatter(current_app.config['DM_TIMEZONE'])
    sheet.write_string('E1', brief['title'], heading)
    sheet.write_string('E2', brief['summary'], darkgrey)
    sheet.write_string('E3', 'For: '+brief['organisation'], darkgrey)
    sheet.write_string('E4', 'Published: '+df.dateformat(published_date), darkgrey)
    sheet.write_string('E5', 'Closing date for application: ' +
                       df.datetimeformat(closing_time), bold_red)

    sheet.write_string('A2', 'Guidance', bold_question)
    sheet.write_string('B2', 'Question', bold_question)
    sheet.write_string('C2', 'Answer', bold_question)
    sheet.write_string('D2', '', right_border_question)
    sheet.write_string('A3', '', header)
    sheet.write_string('B3', 'Essential skills and experience', bold_header)
    sheet.write_string('D3', '', right_border_question)

    e_start = 4
    e = 4
    for essential in brief['essentialRequirements']:
        sheet.write_string('B'+str(e),  essential,  question)
        sheet.write_string('C'+str(e),  '150 words', italic_lightgrey)
        sheet.write_string('D'+str(e), '', right_border_question)
        e += 1
    sheet.merge_range(e_start-1, 0, e-2, 0, 'Essential skills and experience\n'
                                            'As a guide to answering the skills and experience criteria, '
                                            'you could explain:\n'
                                            '- What the situation was\n'
                                            '- The work the specialist or team completed\n'
                                            '- What the results were \n'
                                            'You can reuse examples if you wish. \n'
                                            '\n'
                                            'You must have all essential skills and experience '
                                            'to apply for this opportunity.\n'
                                            '150 words max ', italic_darkgrey_question)
    sheet.write_string('A'+str(e), '', header)
    sheet.write_string('B'+str(e), 'Nice to have skills and experience', bold_header)
    sheet.write_string('D'+str(e), '', right_border_question)
    n_start = e+1
    n = e+1
    for nice in brief['niceToHaveRequirements']:
        sheet.write_string('B'+str(n),  nice,  question)
        sheet.write_string('C'+str(n),  '150 words', italic_lightgrey)
        sheet.write_string('D'+str(n), '', right_border_question)
        n += 1
    sheet.merge_range(n_start-1, 0, n-2, 0, '', question)

    sheet.write_string('A'+str(n), '', question)
    sheet.write_string('B'+str(n), '', question)
    sheet.write_string('D'+str(n), '',  right_border_question)
    sheet.write_string('A'+str(n+1), '', question)
    sheet.write_string('B'+str(n+1), "When can you start?", bold_question)
    sheet.write_string('D'+str(n+1), '', right_border_question)
    if brief['lotSlug'] == 'digital-professionals':
        sheet.write_string('A'+str(n+2), '', question)
        sheet.write_string('B'+str(n+2), "What is your daily rate, including GST?", bold_question)
        sheet.write_string('D'+str(n+2), '', right_border_question)
        sheet.write_string('A'+str(n+3), '', question)
        sheet.write_rich_string('B'+str(n+3), bold_question, "Attach a resume
",
                                question, "Use an Open Document Format (ODF) or PDF/A (eg. .pdf, .odt). "
                                          "The maximum file size of each document is 5MB. "
                                          "You can upload a maximum of 3 candidate CVs.", question)
        sheet.write_string('D'+str(n+3), '', right_border_question)
        n = n + 2
    sheet.write_string('A'+str(n+2), '', question)
    sheet.write_string('B'+str(n+2), '', question)
    sheet.write_string('D'+str(n+2), '', right_border_question)
    sheet.write_string('A'+str(n+3), "All communication about your application will be sent to this address",
                       italic_darkgrey_question)
    sheet.write_string('B'+str(n+3), "Contact email:", bold_question)
    sheet.write_string('D'+str(n+3), '', right_border_question)
    sheet.write_string('A'+str(n+4), '', question)
    sheet.write_string('B'+str(n+4), '', question)
    sheet.write_string('C'+str(n+4), '', question)
    sheet.write_string('D'+str(n+4), '', right_border_question)
    sheet.write_string('C'+str(n+5), 'Ready to apply?', bold_cta)
    sheet.write_url('C'+str(n+6), brief_url, cta, brief_url)
    workbook.close()

    return Response(
        outdata.getvalue(),
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        headers={
            "Content-Disposition": "attachment;filename=brief-response-template-{}.xlsx".format(brief['id']),
            "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        }
    ), 200