def edit_section(framework_slug, service_id, section_id):
    service = data_api_client.get_service(service_id)
    if service is None:
        abort(404)
    service = service['services']

    if not is_service_associated_with_supplier(service):
        abort(404)

    if service["frameworkSlug"] != framework_slug:
        abort(404)

    # we don't actually need the framework here; using this to 404 if framework for the service is not live
    get_framework_or_404(data_api_client, service['frameworkSlug'], allowed_statuses=['live'])

    try:
        content = content_loader.get_manifest(service["frameworkSlug"], 'edit_service').filter(
            service,
            inplace_allowed=True,
        )
    except ContentNotFoundError:
        abort(404)
    section = content.get_section(section_id)
    if section is None or not section.editable:
        abort(404)

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

    return render_template(
        "services/edit_section.html",
        section=section,
        service_data=service,
        service_id=service_id,
        session_timeout=session_timeout,
    )
def test_displaytimeformat(dt, formatted_time):
    assert displaytimeformat(dt) == formatted_time
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 = 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 start_new_draft_service(framework_slug, lot_slug):
    """Page to kick off creation of a new service."""

    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 create draft services
    if not get_supplier_framework_info(data_api_client, framework_slug):
        abort(404)

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

    section = content.get_section(content.get_next_editable_section_id())
    if section is None:
        section = content.get_section(content.get_next_edit_questions_section_id(None))
        if section is None:
            abort(404)

        section = section.get_question_as_section(section.get_next_question_slug())

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

    if request.method == 'POST':
        update_data = section.get_data(request.form)

        try:
            draft_service = data_api_client.create_new_draft_service(
                framework_slug, lot['slug'], current_user.supplier_id, update_data,
                current_user.email_address, page_questions=section.get_field_names()
            )['services']
        except HTTPError as e:
            update_data = section.unformat_data(update_data)
            errors = section.get_error_messages(e.message)

            return render_template(
                "services/edit_submission_section.html",
                framework=framework,
                section=section,
                session_timeout=session_timeout,
                service_data=update_data,
                errors=errors
            ), 400

        return redirect(
            url_for(
                ".view_service_submission",
                framework_slug=framework['slug'],
                lot_slug=draft_service['lotSlug'],
                service_id=draft_service['id'],
            )
        )

    return render_template(
        "services/edit_submission_section.html",
        framework=framework,
        lot=lot,
        service_data={},
        section=section,
        session_timeout=session_timeout,
        force_continue_button=True,
    ), 200
def update_section(framework_slug, service_id, section_id):
    service = data_api_client.get_service(service_id)
    if service is None:
        abort(404)
    service = service['services']

    if not is_service_associated_with_supplier(service):
        abort(404)

    if service["frameworkSlug"] != framework_slug:
        abort(404)

    # we don't actually need the framework here; using this to 404 if framework for the service is not live
    get_framework_or_404(data_api_client, service['frameworkSlug'], allowed_statuses=['live'])

    try:
        content = content_loader.get_manifest(service["frameworkSlug"], 'edit_service').filter(
            service,
            inplace_allowed=True,
        )
    except ContentNotFoundError:
        abort(404)
    section = content.get_section(section_id)
    if section is None or not section.editable:
        abort(404)

    posted_data = section.get_data(request.form)

    errors = None
    # This utils method filters out any empty documents and validates against service document rules
    uploaded_documents, document_errors = upload_service_documents(
        s3.S3(current_app.config['DM_DOCUMENTS_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)
        except HTTPError as e:
            errors = section.get_error_messages(e.message)

    if errors:
        session_timeout = displaytimeformat(datetime.utcnow() + timedelta(hours=1))
        return render_template(
            "services/edit_section.html",
            section=section,
            service_data=service,
            service_id=service_id,
            session_timeout=session_timeout,
            errors=errors,
        ), 400
    flash(SERVICE_UPDATED_MESSAGE, "success")
    return redirect(url_for(".edit_service", service_id=service_id, framework_slug=service["frameworkSlug"]))