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
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
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))
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 )
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
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']))
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
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'))
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
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)
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
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)
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')
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
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 )
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 )
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
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, )
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
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, ) )
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) )
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) )
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
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