def ask_brief_clarification_question(brief_id):
    brief = get_brief(data_api_client, brief_id, allowed_statuses=['live'])

    if brief['clarificationQuestionsAreClosed']:
        abort(404)

    if not is_supplier_eligible_for_brief(data_api_client,
                                          current_user.supplier_id, brief):
        return _render_not_eligible_for_brief_error_page(
            brief, clarification_question=True)

    form = AskClarificationQuestionForm(brief)
    if form.validate_on_submit():
        send_brief_clarification_question(data_api_client, brief,
                                          form.clarification_question.data)
        flash(CLARIFICATION_QUESTION_SENT_MESSAGE.format(brief=brief),
              "success")

    errors = govuk_errors(get_errors_from_wtform(form))

    return render_template(
        "briefs/clarification_question.html",
        brief=brief,
        form=form,
        errors=errors,
    ), 200 if not errors else 400
コード例 #2
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,
                          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']))
コード例 #3
0
def update_service(service_id, section_id, question_slug=None):
    service = data_api_client.get_service(service_id)
    if service is None:
        abort(404)
    service = service['services']

    # we don't actually need the framework here; using this to 404 if framework for the service is not live
    # TODO remove `expired` from below. It's a temporary fix to allow access to DOS2 as it's expired.
    get_framework_or_404(data_api_client,
                         service['frameworkSlug'],
                         allowed_statuses=['live', 'expired'])

    content = content_loader.get_manifest(
        service['frameworkSlug'],
        'edit_service_as_admin',
    ).filter(service, inplace_allowed=True)
    section = content.get_section(section_id)
    if question_slug is not None:
        # Overwrite section with single question section for 'question per page' editing.
        section = section.get_question_as_section(question_slug)
    if section is None or not section.editable:
        abort(404)

    errors = None
    posted_data = section.get_data(request.form)

    uploaded_documents, document_errors = upload_service_documents(
        s3.S3(current_app.config['DM_S3_DOCUMENT_BUCKET']), 'documents',
        current_app.config['DM_ASSETS_URL'], service, request.files, section)

    if document_errors:
        errors = section.get_error_messages(document_errors)
    else:
        posted_data.update(uploaded_documents)

    if not errors and section.has_changes_to_save(service, posted_data):
        try:
            data_api_client.update_service(
                service_id,
                posted_data,
                current_user.email_address,
                user_role='admin',
            )
        except HTTPError as e:
            errors = section.get_error_messages(e.message)

    if errors:
        return render_template(
            "edit_section.html",
            section=section,
            service_data=section.unformat_data(dict(service, **posted_data)),
            service_id=service_id,
            errors=govuk_errors(errors),
        ), 400

    return redirect(url_for(".view_service", service_id=service_id))
コード例 #4
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
コード例 #5
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
コード例 #6
0
def process_login():
    form = LoginForm()
    next_url = request.args.get('next')
    if form.validate_on_submit():
        user_json = data_api_client.authenticate_user(form.email_address.data,
                                                      form.password.data)
        if not user_json:
            current_app.logger.info(
                "login.fail: failed to log in {email_hash}",
                extra={'email_hash': hash_string(form.email_address.data)})
            errors = govuk_errors({
                "email_address": {
                    "message": "Enter your email address",
                    "input_name": "email_address",
                },
                "password": {
                    "message": "Enter your password",
                    "input_name": "password",
                },
            })
            return render_template(
                "auth/login.html",
                form=form,
                errors=errors,
                error_summary_description_text=NO_ACCOUNT_MESSAGE,
                next=next_url), 403

        user = User.from_json(user_json)

        login_user(user)
        current_app.logger.info("login.success: role={role} user={email_hash}",
                                extra={
                                    'role':
                                    user.role,
                                    'email_hash':
                                    hash_string(form.email_address.data)
                                })
        return redirect_logged_in_user(next_url)

    else:
        errors = get_errors_from_wtform(form)
        return render_template("auth/login.html",
                               form=form,
                               errors=errors,
                               next=next_url), 400
コード例 #7
0
def create_new_brief(framework_slug, lot_slug):

    framework, lot = get_framework_and_lot(framework_slug,
                                           lot_slug,
                                           data_api_client,
                                           allowed_statuses=['live'],
                                           must_allow_brief=True)

    content = content_loader.get_manifest(framework_slug, 'edit_brief').filter(
        {'lot': lot['slug']})

    section = content.get_section(content.get_next_editable_section_id())

    update_data = section.get_data(request.form)

    try:
        brief = data_api_client.create_brief(
            framework_slug,
            lot_slug,
            current_user.id,
            update_data,
            updated_by=current_user.email_address,
            page_questions=section.get_field_names())["briefs"]
    except HTTPError as e:
        update_data = section.unformat_data(update_data)
        errors = govuk_errors(section.get_error_messages(e.message))

        return render_template("buyers/create_brief_question.html",
                               data=update_data,
                               brief={},
                               framework=framework,
                               lot=lot,
                               section=section,
                               question=section.questions[0],
                               errors=errors), 400

    return redirect(
        url_for(".view_brief_overview",
                framework_slug=framework_slug,
                lot_slug=lot_slug,
                brief_id=brief['id']))
コード例 #8
0
def test_govuk_errors(dm_errors, expected_output):
    assert govuk_errors(dm_errors) == expected_output
コード例 #9
0
def edit_service_submission(framework_slug, lot_slug, service_id, section_id, question_slug=None):
    """
        Also accepts URL parameter `force_continue_button` which will allow rendering of a 'Save and continue' button,
        used for when copying services.
    """
    framework, lot = get_framework_and_lot_or_404(data_api_client, framework_slug, lot_slug, allowed_statuses=['open'])

    # Suppliers must have registered interest in a framework before they can edit draft services
    if not get_supplier_framework_info(data_api_client, framework_slug):
        abort(404)

    force_return_to_summary = framework['framework'] == "digital-outcomes-and-specialists"
    force_continue_button = request.args.get('force_continue_button')
    next_question = None

    try:
        draft = data_api_client.get_draft_service(service_id)['services']
    except HTTPError as e:
        abort(e.status_code)

    if draft['lotSlug'] != lot_slug or draft['frameworkSlug'] != framework_slug:
        abort(404)

    if not is_service_associated_with_supplier(draft):
        abort(404)

    content = content_loader.get_manifest(framework_slug, 'edit_submission').filter(draft, inplace_allowed=True)
    section = content.get_section(section_id)
    if section and (question_slug is not None):
        next_question = section.get_question_by_slug(section.get_next_question_slug(question_slug))
        section = section.get_question_as_section(question_slug)

    if section is None or not section.editable:
        abort(404)

    errors = None
    if request.method == "POST":
        update_data = section.get_data(request.form)

        if request.files:
            uploader = s3.S3(current_app.config['DM_SUBMISSIONS_BUCKET'])
            documents_url = url_for('.dashboard', _external=True) + '/assets/'
            # This utils method filters out any empty documents and validates against service document rules
            uploaded_documents, document_errors = upload_service_documents(
                uploader, 'submissions', documents_url, draft, request.files, section,
                public=False)

            if document_errors:
                errors = section.get_error_messages(document_errors, question_descriptor_from="question")
            else:
                update_data.update(uploaded_documents)

        if not errors and section.has_changes_to_save(draft, update_data):
            try:
                data_api_client.update_draft_service(
                    service_id,
                    update_data,
                    current_user.email_address,
                    page_questions=section.get_field_names()
                )
            except HTTPError as e:
                update_data = section.unformat_data(update_data)
                errors = govuk_errors(section.get_error_messages(e.message, question_descriptor_from="question"))

        if not errors:
            if next_question and not force_return_to_summary:
                return redirect(url_for(".edit_service_submission",
                                        framework_slug=framework['slug'],
                                        lot_slug=draft['lotSlug'],
                                        service_id=service_id,
                                        section_id=section_id,
                                        question_slug=next_question.slug))
            else:
                return redirect(url_for(".view_service_submission",
                                        framework_slug=framework['slug'],
                                        lot_slug=draft['lotSlug'],
                                        service_id=service_id,
                                        _anchor=section_id))

        update_data.update(
            (k, draft[k]) for k in ('serviceName', 'lot', 'lotName',) if k in draft and k not in update_data
        )
        service_data = update_data
        # fall through to regular GET path to display errors
    else:
        service_data = section.unformat_data(draft)

    session_timeout = displaytimeformat(datetime.utcnow() + timedelta(hours=1))

    return render_template(
        "services/edit_submission_section.html",
        section=section,
        framework=framework,
        lot=lot,
        next_question=next_question,
        service_data=service_data,
        service_id=service_id,
        force_return_to_summary=force_return_to_summary,
        force_continue_button=force_continue_button,
        session_timeout=session_timeout,
        errors=errors,
    )
def edit_brief_response(brief_id, brief_response_id, question_id=None):
    edit_single_question_flow = request.endpoint.endswith(
        '.edit_single_question')

    brief = get_brief(data_api_client, brief_id, allowed_statuses=['live'])
    brief_response = data_api_client.get_brief_response(
        brief_response_id)['briefResponses']

    if brief_response['briefId'] != brief['id'] or brief_response[
            'supplierId'] != current_user.supplier_id:
        abort(404)

    if not is_supplier_eligible_for_brief(data_api_client,
                                          current_user.supplier_id, brief):
        return _render_not_eligible_for_brief_error_page(brief)

    framework, lot = get_framework_and_lot(
        data_api_client,
        brief['frameworkSlug'],
        brief['lotSlug'],
        allowed_statuses=['live', 'expired'])

    max_day_rate = None
    role = brief.get('specialistRole')
    if role:
        brief_service = data_api_client.find_services(
            supplier_id=current_user.supplier_id,
            framework=brief['frameworkSlug'],
            status="published",
            lot=brief["lotSlug"],
        )["services"][0]
        max_day_rate = brief_service.get(role + "PriceMax")

    content = content_loader.get_manifest(brief['frameworkSlug'],
                                          'edit_brief_response').filter({
                                              'lot':
                                              lot['slug'],
                                              'brief':
                                              brief,
                                              'max_day_rate':
                                              max_day_rate
                                          })

    section = content.get_section(content.get_next_editable_section_id())
    if section is None or not section.editable:
        abort(404)

    # If a question in a brief is optional and is unanswered by the buyer, the brief will have the key but will have no
    # data. The question will be skipped in the brief response flow (see below). If a user attempts to access the
    # question by directly visiting the url, this check will return a 404. It has been created specifically for nice to
    # have requirements, and works because briefs and responses share the same key for this question/response.
    if question_id in brief.keys() and not brief[question_id]:
        abort(404)

    # If a question is to be skipped in the normal flow (due to the reason above), we update the next_question_id.
    next_question_id = section.get_next_question_id(question_id)
    if next_question_id in brief.keys() and not brief[next_question_id]:
        next_question_id = section.get_next_question_id(next_question_id)

    def redirect_to_next_page():
        return redirect(
            url_for('.edit_brief_response',
                    brief_id=brief_id,
                    brief_response_id=brief_response_id,
                    question_id=next_question_id))

    # If no question_id in url then redirect to first question
    if question_id is None:
        return redirect_to_next_page()

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

    # Unformat brief response into data for form
    service_data = question.unformat_data(brief_response)

    status_code = 200
    errors = {}
    if request.method == 'POST':
        try:
            data_api_client.update_brief_response(brief_response_id,
                                                  question.get_data(
                                                      request.form),
                                                  current_user.email_address,
                                                  page_questions=[question.id])

        except HTTPError as e:
            errors = govuk_errors(question.get_error_messages(e.message))
            status_code = 400
            service_data = question.unformat_data(
                question.get_data(request.form))

        else:
            if next_question_id and not edit_single_question_flow:
                return redirect_to_next_page()
            else:
                if edit_single_question_flow:
                    flash(APPLICATION_UPDATED_MESSAGE, "success")
                return redirect(
                    url_for('.check_brief_response_answers',
                            brief_id=brief_id,
                            brief_response_id=brief_response_id))

    previous_question_id = section.get_previous_question_id(question_id)
    # Skip previous question if the brief has no nice to have requirements
    if previous_question_id in brief.keys(
    ) and not brief[previous_question_id]:
        previous_question_id = section.get_previous_question_id(
            previous_question_id)

    previous_question_url = None
    if previous_question_id:
        previous_question_url = url_for('.edit_brief_response',
                                        brief_id=brief_id,
                                        brief_response_id=brief_response_id,
                                        question_id=previous_question_id)

    return render_template("briefs/edit_brief_response_question.html",
                           brief=brief,
                           errors=errors,
                           is_last_page=False if next_question_id else True,
                           previous_question_url=previous_question_url,
                           question=question,
                           service_data=service_data), status_code