diff --git a/app/blueprints/fund/templates/fund_config.html b/app/blueprints/fund/templates/fund_config.html
index 81954370..7de1eb9d 100644
--- a/app/blueprints/fund/templates/fund_config.html
+++ b/app/blueprints/fund/templates/fund_config.html
@@ -369,11 +369,11 @@
Application Rounds
"classes": "govuk-button--secondary"
}) + govukButton({
"text": "Clone this round",
- "href": url_for("build_fund_bp.clone_round", round_id=round.round_id, fund_id=fund.fund_id),
+ "href": url_for("round_bp.clone_round", round_id=round.round_id, fund_id=fund.fund_id),
"classes": "govuk-button--secondary"
}) + govukButton({
"text": "Edit Round",
- "href": url_for("build_fund_bp.round", round_id=round.round_id),
+ "href": url_for("round_bp.round", round_id=round.round_id),
"classes": "govuk-button--secondary"
})
diff --git a/app/blueprints/fund_builder/routes.py b/app/blueprints/fund_builder/routes.py
index 8d83105f..5a2064e1 100644
--- a/app/blueprints/fund_builder/routes.py
+++ b/app/blueprints/fund_builder/routes.py
@@ -3,7 +3,6 @@
import secrets
import shutil
import string
-from datetime import datetime
from random import randint
import requests
@@ -11,7 +10,6 @@
Blueprint,
Response,
after_this_request,
- flash,
g,
redirect,
render_template,
@@ -22,12 +20,9 @@
from fsd_utils.authentication.decorators import login_requested
from app.all_questions.metadata_utils import generate_print_data_for_sections
-from app.blueprints.fund_builder.forms.round import RoundForm, get_datetime
from app.blueprints.fund_builder.forms.section import SectionForm
-from app.db.models.round import Round
from app.db.queries.application import (
clone_single_form,
- clone_single_round,
delete_form_from_section,
delete_section_from_round,
get_all_template_forms,
@@ -40,8 +35,8 @@
move_section_up,
update_section,
)
-from app.db.queries.fund import get_all_funds, get_fund_by_id
-from app.db.queries.round import add_round, get_round_by_id, update_round
+from app.db.queries.fund import get_fund_by_id
+from app.db.queries.round import get_round_by_id
from app.export_config.generate_all_questions import print_html
from app.export_config.generate_assessment_config import (
generate_assessment_config_for_round,
@@ -52,7 +47,6 @@
generate_form_jsons_for_round,
)
from app.export_config.generate_fund_round_html import generate_all_round_html
-from app.shared.helpers import error_formatter
from config import Config
BUILD_FUND_BP_DASHBOARD = "build_fund_bp.dashboard"
@@ -189,278 +183,6 @@ def build_application(round_id):
return render_template("build_application.html", round=round, fund=fund, breadcrumb_items=breadcrumb_items)
-@build_fund_bp.route("/fund//round//clone")
-def clone_round(round_id, fund_id):
- cloned = clone_single_round(
- round_id=round_id,
- new_fund_id=fund_id,
- new_short_name=f"R-C{randint(0, 999)}", # NOSONAR
- )
- flash(f"Cloned new round: {cloned.short_name}")
-
- return redirect(url_for("fund_bp.view_fund", fund_id=fund_id))
-
-
-@build_fund_bp.route("/round", methods=["GET", "POST"])
-@build_fund_bp.route("/round/", methods=["GET", "POST"])
-def round(round_id=None):
- """
- Renders a template to select a fund and add or update a round to that fund. If saved, validates the round form data
- and saves to DB
- """
- form = RoundForm()
- all_funds = get_all_funds()
- params = {"all_funds": all_funds_as_govuk_select_items(all_funds)}
- params["selected_fund_id"] = request.form.get("fund_id", None)
- params["welsh_availability"] = json.dumps({str(fund.fund_id): fund.welsh_available for fund in all_funds})
-
- if round_id:
- round = get_round_by_id(round_id)
- form = populate_form_with_round_data(round)
-
- if form.validate_on_submit():
- if round_id:
- update_existing_round(round, form)
- flash(f"Updated round {round.title_json['en']}")
- return redirect(url_for("fund_bp.view_fund", fund_id=round.fund_id))
- create_new_round(form)
- flash(f"Created round {form.title_en.data}")
- return redirect(url_for(BUILD_FUND_BP_DASHBOARD))
-
- params["round_id"] = round_id
- params["form"] = form
-
- error = error_formatter(params["form"])
- return render_template("round.html", **params, error=error)
-
-
-def _convert_json_data_for_form(data) -> str:
- if isinstance(data, dict):
- return json.dumps(data)
- return str(data)
-
-
-def _convert_form_data_to_json(data) -> dict:
- if data:
- return json.loads(data)
- return {}
-
-
-def populate_form_with_round_data(round):
- """
- Populate a RoundForm with data from a Round object.
-
- :param Round round: The round object to populate the form with
- :return: A RoundForm populated with the round data
- """
- round_data = {
- "fund_id": round.fund_id,
- "round_id": round.round_id,
- "title_en": round.title_json.get("en", ""),
- "title_cy": round.title_json.get("cy", ""),
- "short_name": round.short_name,
- "opens": round.opens,
- "deadline": round.deadline,
- "assessment_start": round.assessment_start,
- "reminder_date": round.reminder_date,
- "assessment_deadline": round.assessment_deadline,
- "prospectus_link": round.prospectus_link,
- "privacy_notice_link": round.privacy_notice_link,
- "contact_us_banner_en": round.contact_us_banner_json.get("en", "") if round.contact_us_banner_json else "",
- "contact_us_banner_cy": round.contact_us_banner_json.get("cy", "") if round.contact_us_banner_json else "",
- "reference_contact_page_over_email": "true" if round.reference_contact_page_over_email else "false",
- "contact_email": round.contact_email,
- "contact_phone": round.contact_phone,
- "contact_textphone": round.contact_textphone,
- "support_times": round.support_times,
- "support_days": round.support_days,
- "instructions_en": round.instructions_json.get("en", "") if round.instructions_json else "",
- "instructions_cy": round.instructions_json.get("cy", "") if round.instructions_json else "",
- "feedback_link": round.feedback_link,
- "project_name_field_id": round.project_name_field_id,
- "application_guidance_en": (
- round.application_guidance_json.get("en", "") if round.application_guidance_json else ""
- ),
- "application_guidance_cy": (
- round.application_guidance_json.get("cy", "") if round.application_guidance_json else ""
- ),
- "guidance_url": round.guidance_url,
- "all_uploaded_documents_section_available": (
- "true" if round.all_uploaded_documents_section_available else "false"
- ),
- "application_fields_download_available": "true" if round.application_fields_download_available else "false",
- "display_logo_on_pdf_exports": "true" if round.display_logo_on_pdf_exports else "false",
- "mark_as_complete_enabled": "true" if round.mark_as_complete_enabled else "false",
- "is_expression_of_interest": "true" if round.is_expression_of_interest else "false",
- "has_feedback_survey": (
- "true"
- if round.feedback_survey_config and round.feedback_survey_config.get("has_feedback_survey", "") == "true"
- else "false"
- ),
- "has_section_feedback": (
- "true"
- if round.feedback_survey_config and round.feedback_survey_config.get("has_section_feedback", "") == "true"
- else "false"
- ),
- "has_research_survey": (
- "true"
- if round.feedback_survey_config and round.feedback_survey_config.get("has_research_survey", "") == "true"
- else "false"
- ),
- "is_feedback_survey_optional": (
- "true"
- if round.feedback_survey_config
- and round.feedback_survey_config.get("is_feedback_survey_optional", "") == "true"
- else "false"
- ),
- "is_section_feedback_optional": (
- "true"
- if round.feedback_survey_config
- and round.feedback_survey_config.get("is_section_feedback_optional", "") == "true"
- else "false"
- ),
- "is_research_survey_optional": (
- "true"
- if round.feedback_survey_config
- and round.feedback_survey_config.get("is_research_survey_optional", "") == "true"
- else "false"
- ),
- "eligibility_config": (
- "true"
- if round.eligibility_config and round.eligibility_config.get("has_eligibility", "") == "true"
- else "false"
- ),
- "eoi_decision_schema_en": (
- _convert_json_data_for_form(round.eoi_decision_schema.get("en", "")) if round.eoi_decision_schema else ""
- ),
- "eoi_decision_schema_cy": (
- _convert_json_data_for_form(round.eoi_decision_schema.get("cy", "")) if round.eoi_decision_schema else ""
- ),
- }
- return RoundForm(data=round_data)
-
-
-def update_existing_round(round, form):
- """
- Update a Round object with the data from a RoundForm.
-
- :param Round round: The round object to update
- :param RoundForm form: The form with the new round data
- """
- round.title_json = {"en": form.title_en.data or None, "cy": form.title_cy.data or None}
- round.short_name = form.short_name.data
- round.feedback_survey_config = {
- "has_feedback_survey": form.has_feedback_survey.data == "true",
- "has_section_feedback": form.has_section_feedback.data == "true",
- "has_research_survey": form.has_research_survey.data == "true",
- "is_feedback_survey_optional": form.is_feedback_survey_optional.data == "true",
- "is_section_feedback_optional": form.is_section_feedback_optional.data == "true",
- "is_research_survey_optional": form.is_research_survey_optional.data == "true",
- }
- round.opens = get_datetime(form.opens)
- round.deadline = get_datetime(form.deadline)
- round.assessment_start = get_datetime(form.assessment_start)
- round.reminder_date = get_datetime(form.reminder_date)
- round.assessment_deadline = get_datetime(form.assessment_deadline)
- round.prospectus_link = form.prospectus_link.data
- round.privacy_notice_link = form.privacy_notice_link.data
- round.reference_contact_page_over_email = form.reference_contact_page_over_email.data == "true"
- round.contact_email = form.contact_email.data
- round.contact_phone = form.contact_phone.data
- round.contact_textphone = form.contact_textphone.data
- round.support_times = form.support_times.data
- round.support_days = form.support_days.data
- round.feedback_link = form.feedback_link.data
- round.project_name_field_id = form.project_name_field_id.data
- round.guidance_url = form.guidance_url.data
- round.all_uploaded_documents_section_available = form.all_uploaded_documents_section_available.data == "true"
- round.application_fields_download_available = form.application_fields_download_available.data == "true"
- round.display_logo_on_pdf_exports = form.display_logo_on_pdf_exports.data == "true"
- round.mark_as_complete_enabled = form.mark_as_complete_enabled.data == "true"
- round.is_expression_of_interest = form.is_expression_of_interest.data == "true"
- round.short_name = form.short_name.data
- round.contact_us_banner_json = {
- "en": form.contact_us_banner_en.data or None,
- "cy": form.contact_us_banner_cy.data or None,
- }
- round.instructions_json = {"en": form.instructions_en.data or None, "cy": form.instructions_cy.data or None}
- round.application_guidance_json = {
- "en": form.application_guidance_en.data or None,
- "cy": form.application_guidance_cy.data or None,
- }
- round.guidance_url = form.guidance_url.data
- round.all_uploaded_documents_section_available = form.all_uploaded_documents_section_available.data == "true"
- round.application_fields_download_available = form.application_fields_download_available.data == "true"
- round.display_logo_on_pdf_exports = form.display_logo_on_pdf_exports.data == "true"
- round.mark_as_complete_enabled = form.mark_as_complete_enabled.data == "true"
- round.is_expression_of_interest = form.is_expression_of_interest.data == "true"
- round.eligibility_config = {"has_eligibility": form.eligibility_config.data == "true"}
- round.eoi_decision_schema = {
- "en": _convert_form_data_to_json(form.eoi_decision_schema_en.data),
- "cy": _convert_form_data_to_json(form.eoi_decision_schema_cy.data),
- }
- round.audit_info = {"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "update"}
- update_round(round)
-
-
-def create_new_round(form):
- """
- Create a new Round object with the data from a RoundForm and save it to the database.
-
- :param RoundForm form: The form with the new round data
- """
- new_round = Round(
- fund_id=form.fund_id.data,
- audit_info={"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "create"},
- title_json={"en": form.title_en.data or None, "cy": form.title_cy.data or None},
- short_name=form.short_name.data,
- opens=get_datetime(form.opens),
- deadline=get_datetime(form.deadline),
- assessment_start=get_datetime(form.assessment_start),
- reminder_date=get_datetime(form.reminder_date),
- assessment_deadline=get_datetime(form.assessment_deadline),
- prospectus_link=form.prospectus_link.data,
- privacy_notice_link=form.privacy_notice_link.data,
- contact_us_banner_json={
- "en": form.contact_us_banner_en.data or None,
- "cy": form.contact_us_banner_cy.data or None,
- },
- reference_contact_page_over_email=form.reference_contact_page_over_email.data == "true",
- contact_email=form.contact_email.data,
- contact_phone=form.contact_phone.data,
- contact_textphone=form.contact_textphone.data,
- support_times=form.support_times.data,
- support_days=form.support_days.data,
- instructions_json={"en": form.instructions_en.data or None, "cy": form.instructions_cy.data or None},
- feedback_link=form.feedback_link.data,
- project_name_field_id=form.project_name_field_id.data,
- application_guidance_json={
- "en": form.application_guidance_en.data or None,
- "cy": form.application_guidance_cy.data or None,
- },
- guidance_url=form.guidance_url.data,
- all_uploaded_documents_section_available=form.all_uploaded_documents_section_available.data == "true",
- application_fields_download_available=form.application_fields_download_available.data == "true",
- display_logo_on_pdf_exports=form.display_logo_on_pdf_exports.data == "true",
- mark_as_complete_enabled=form.mark_as_complete_enabled.data == "true",
- is_expression_of_interest=form.is_expression_of_interest.data == "true",
- feedback_survey_config={
- "has_feedback_survey": form.has_feedback_survey.data == "true",
- "has_section_feedback": form.has_section_feedback.data == "true",
- "has_research_survey": form.has_research_survey.data == "true",
- "is_feedback_survey_optional": form.is_feedback_survey_optional.data == "true",
- "is_section_feedback_optional": form.is_section_feedback_optional.data == "true",
- "is_research_survey_optional": form.is_research_survey_optional.data == "true",
- },
- eligibility_config={"has_eligibility": form.eligibility_config.data == "true"},
- eoi_decision_schema={
- "en": _convert_form_data_to_json(form.eoi_decision_schema_en.data),
- "cy": _convert_form_data_to_json(form.eoi_decision_schema_cy.data),
- },
- )
- add_round(new_round)
-
-
@build_fund_bp.route("/preview/", methods=["GET"])
def preview_form(form_id):
"""
diff --git a/app/blueprints/fund_builder/forms/round.py b/app/blueprints/round/forms.py
similarity index 100%
rename from app/blueprints/fund_builder/forms/round.py
rename to app/blueprints/round/forms.py
diff --git a/app/blueprints/round/routes.py b/app/blueprints/round/routes.py
new file mode 100644
index 00000000..b19c29a9
--- /dev/null
+++ b/app/blueprints/round/routes.py
@@ -0,0 +1,302 @@
+import json
+from datetime import datetime
+from random import randint
+
+from flask import (
+ Blueprint,
+ flash,
+ redirect,
+ render_template,
+ request,
+ url_for,
+)
+
+from app.blueprints.fund_builder.routes import all_funds_as_govuk_select_items
+from app.blueprints.round.forms import RoundForm, get_datetime
+from app.db.models.round import Round
+from app.db.queries.application import clone_single_round
+from app.db.queries.fund import get_all_funds
+from app.db.queries.round import add_round, get_round_by_id, update_round
+from app.shared.helpers import error_formatter
+
+BUILD_FUND_BP_DASHBOARD = "build_fund_bp.dashboard"
+
+# Blueprint for routes used by v1 of FAB - using the DB
+round_bp = Blueprint(
+ "round_bp",
+ __name__,
+ url_prefix="/round",
+ template_folder="templates",
+)
+
+
+@round_bp.route("//clone")
+def clone_round(round_id, fund_id):
+ cloned = clone_single_round(
+ round_id=round_id,
+ new_fund_id=fund_id,
+ new_short_name=f"R-C{randint(0, 999)}", # NOSONAR
+ )
+ flash(f"Cloned new round: {cloned.short_name}")
+
+ return redirect(url_for("fund_bp.view_fund", fund_id=fund_id))
+
+
+@round_bp.route("", methods=["GET", "POST"])
+@round_bp.route("/", methods=["GET", "POST"])
+def round(round_id=None):
+ """
+ Renders a template to select a fund and add or update a round to that fund. If saved, validates the round form data
+ and saves to DB
+ """
+ form = RoundForm()
+ all_funds = get_all_funds()
+ params = {"all_funds": all_funds_as_govuk_select_items(all_funds)}
+ params["selected_fund_id"] = request.form.get("fund_id", None)
+ params["welsh_availability"] = json.dumps({str(fund.fund_id): fund.welsh_available for fund in all_funds})
+
+ if round_id:
+ round = get_round_by_id(round_id)
+ form = populate_form_with_round_data(round)
+
+ if form.validate_on_submit():
+ if round_id:
+ update_existing_round(round, form)
+ flash(f"Updated round {round.title_json['en']}")
+ return redirect(url_for("fund_bp.view_fund", fund_id=round.fund_id))
+ create_new_round(form)
+ flash(f"Created round {form.title_en.data}")
+ return redirect(url_for(BUILD_FUND_BP_DASHBOARD))
+
+ params["round_id"] = round_id
+ params["form"] = form
+
+ error = error_formatter(params["form"])
+ return render_template("round.html", **params, error=error)
+
+
+def _convert_json_data_for_form(data) -> str:
+ if isinstance(data, dict):
+ return json.dumps(data)
+ return str(data)
+
+
+def _convert_form_data_to_json(data) -> dict:
+ if data:
+ return json.loads(data)
+ return {}
+
+
+def populate_form_with_round_data(round):
+ """
+ Populate a RoundForm with data from a Round object.
+
+ :param Round round: The round object to populate the form with
+ :return: A RoundForm populated with the round data
+ """
+ round_data = {
+ "fund_id": round.fund_id,
+ "round_id": round.round_id,
+ "title_en": round.title_json.get("en", ""),
+ "title_cy": round.title_json.get("cy", ""),
+ "short_name": round.short_name,
+ "opens": round.opens,
+ "deadline": round.deadline,
+ "assessment_start": round.assessment_start,
+ "reminder_date": round.reminder_date,
+ "assessment_deadline": round.assessment_deadline,
+ "prospectus_link": round.prospectus_link,
+ "privacy_notice_link": round.privacy_notice_link,
+ "contact_us_banner_en": round.contact_us_banner_json.get("en", "") if round.contact_us_banner_json else "",
+ "contact_us_banner_cy": round.contact_us_banner_json.get("cy", "") if round.contact_us_banner_json else "",
+ "reference_contact_page_over_email": "true" if round.reference_contact_page_over_email else "false",
+ "contact_email": round.contact_email,
+ "contact_phone": round.contact_phone,
+ "contact_textphone": round.contact_textphone,
+ "support_times": round.support_times,
+ "support_days": round.support_days,
+ "instructions_en": round.instructions_json.get("en", "") if round.instructions_json else "",
+ "instructions_cy": round.instructions_json.get("cy", "") if round.instructions_json else "",
+ "feedback_link": round.feedback_link,
+ "project_name_field_id": round.project_name_field_id,
+ "application_guidance_en": (
+ round.application_guidance_json.get("en", "") if round.application_guidance_json else ""
+ ),
+ "application_guidance_cy": (
+ round.application_guidance_json.get("cy", "") if round.application_guidance_json else ""
+ ),
+ "guidance_url": round.guidance_url,
+ "all_uploaded_documents_section_available": (
+ "true" if round.all_uploaded_documents_section_available else "false"
+ ),
+ "application_fields_download_available": "true" if round.application_fields_download_available else "false",
+ "display_logo_on_pdf_exports": "true" if round.display_logo_on_pdf_exports else "false",
+ "mark_as_complete_enabled": "true" if round.mark_as_complete_enabled else "false",
+ "is_expression_of_interest": "true" if round.is_expression_of_interest else "false",
+ "has_feedback_survey": (
+ "true"
+ if round.feedback_survey_config and round.feedback_survey_config.get("has_feedback_survey", "") == "true"
+ else "false"
+ ),
+ "has_section_feedback": (
+ "true"
+ if round.feedback_survey_config and round.feedback_survey_config.get("has_section_feedback", "") == "true"
+ else "false"
+ ),
+ "has_research_survey": (
+ "true"
+ if round.feedback_survey_config and round.feedback_survey_config.get("has_research_survey", "") == "true"
+ else "false"
+ ),
+ "is_feedback_survey_optional": (
+ "true"
+ if round.feedback_survey_config
+ and round.feedback_survey_config.get("is_feedback_survey_optional", "") == "true"
+ else "false"
+ ),
+ "is_section_feedback_optional": (
+ "true"
+ if round.feedback_survey_config
+ and round.feedback_survey_config.get("is_section_feedback_optional", "") == "true"
+ else "false"
+ ),
+ "is_research_survey_optional": (
+ "true"
+ if round.feedback_survey_config
+ and round.feedback_survey_config.get("is_research_survey_optional", "") == "true"
+ else "false"
+ ),
+ "eligibility_config": (
+ "true"
+ if round.eligibility_config and round.eligibility_config.get("has_eligibility", "") == "true"
+ else "false"
+ ),
+ "eoi_decision_schema_en": (
+ _convert_json_data_for_form(round.eoi_decision_schema.get("en", "")) if round.eoi_decision_schema else ""
+ ),
+ "eoi_decision_schema_cy": (
+ _convert_json_data_for_form(round.eoi_decision_schema.get("cy", "")) if round.eoi_decision_schema else ""
+ ),
+ }
+ return RoundForm(data=round_data)
+
+
+def update_existing_round(round, form):
+ """
+ Update a Round object with the data from a RoundForm.
+
+ :param Round round: The round object to update
+ :param RoundForm form: The form with the new round data
+ """
+ round.title_json = {"en": form.title_en.data or None, "cy": form.title_cy.data or None}
+ round.short_name = form.short_name.data
+ round.feedback_survey_config = {
+ "has_feedback_survey": form.has_feedback_survey.data == "true",
+ "has_section_feedback": form.has_section_feedback.data == "true",
+ "has_research_survey": form.has_research_survey.data == "true",
+ "is_feedback_survey_optional": form.is_feedback_survey_optional.data == "true",
+ "is_section_feedback_optional": form.is_section_feedback_optional.data == "true",
+ "is_research_survey_optional": form.is_research_survey_optional.data == "true",
+ }
+ round.opens = get_datetime(form.opens)
+ round.deadline = get_datetime(form.deadline)
+ round.assessment_start = get_datetime(form.assessment_start)
+ round.reminder_date = get_datetime(form.reminder_date)
+ round.assessment_deadline = get_datetime(form.assessment_deadline)
+ round.prospectus_link = form.prospectus_link.data
+ round.privacy_notice_link = form.privacy_notice_link.data
+ round.reference_contact_page_over_email = form.reference_contact_page_over_email.data == "true"
+ round.contact_email = form.contact_email.data
+ round.contact_phone = form.contact_phone.data
+ round.contact_textphone = form.contact_textphone.data
+ round.support_times = form.support_times.data
+ round.support_days = form.support_days.data
+ round.feedback_link = form.feedback_link.data
+ round.project_name_field_id = form.project_name_field_id.data
+ round.guidance_url = form.guidance_url.data
+ round.all_uploaded_documents_section_available = form.all_uploaded_documents_section_available.data == "true"
+ round.application_fields_download_available = form.application_fields_download_available.data == "true"
+ round.display_logo_on_pdf_exports = form.display_logo_on_pdf_exports.data == "true"
+ round.mark_as_complete_enabled = form.mark_as_complete_enabled.data == "true"
+ round.is_expression_of_interest = form.is_expression_of_interest.data == "true"
+ round.short_name = form.short_name.data
+ round.contact_us_banner_json = {
+ "en": form.contact_us_banner_en.data or None,
+ "cy": form.contact_us_banner_cy.data or None,
+ }
+ round.instructions_json = {"en": form.instructions_en.data or None, "cy": form.instructions_cy.data or None}
+ round.application_guidance_json = {
+ "en": form.application_guidance_en.data or None,
+ "cy": form.application_guidance_cy.data or None,
+ }
+ round.guidance_url = form.guidance_url.data
+ round.all_uploaded_documents_section_available = form.all_uploaded_documents_section_available.data == "true"
+ round.application_fields_download_available = form.application_fields_download_available.data == "true"
+ round.display_logo_on_pdf_exports = form.display_logo_on_pdf_exports.data == "true"
+ round.mark_as_complete_enabled = form.mark_as_complete_enabled.data == "true"
+ round.is_expression_of_interest = form.is_expression_of_interest.data == "true"
+ round.eligibility_config = {"has_eligibility": form.eligibility_config.data == "true"}
+ round.eoi_decision_schema = {
+ "en": _convert_form_data_to_json(form.eoi_decision_schema_en.data),
+ "cy": _convert_form_data_to_json(form.eoi_decision_schema_cy.data),
+ }
+ round.audit_info = {"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "update"}
+ update_round(round)
+
+
+def create_new_round(form):
+ """
+ Create a new Round object with the data from a RoundForm and save it to the database.
+
+ :param RoundForm form: The form with the new round data
+ """
+ new_round = Round(
+ fund_id=form.fund_id.data,
+ audit_info={"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "create"},
+ title_json={"en": form.title_en.data or None, "cy": form.title_cy.data or None},
+ short_name=form.short_name.data,
+ opens=get_datetime(form.opens),
+ deadline=get_datetime(form.deadline),
+ assessment_start=get_datetime(form.assessment_start),
+ reminder_date=get_datetime(form.reminder_date),
+ assessment_deadline=get_datetime(form.assessment_deadline),
+ prospectus_link=form.prospectus_link.data,
+ privacy_notice_link=form.privacy_notice_link.data,
+ contact_us_banner_json={
+ "en": form.contact_us_banner_en.data or None,
+ "cy": form.contact_us_banner_cy.data or None,
+ },
+ reference_contact_page_over_email=form.reference_contact_page_over_email.data == "true",
+ contact_email=form.contact_email.data,
+ contact_phone=form.contact_phone.data,
+ contact_textphone=form.contact_textphone.data,
+ support_times=form.support_times.data,
+ support_days=form.support_days.data,
+ instructions_json={"en": form.instructions_en.data or None, "cy": form.instructions_cy.data or None},
+ feedback_link=form.feedback_link.data,
+ project_name_field_id=form.project_name_field_id.data,
+ application_guidance_json={
+ "en": form.application_guidance_en.data or None,
+ "cy": form.application_guidance_cy.data or None,
+ },
+ guidance_url=form.guidance_url.data,
+ all_uploaded_documents_section_available=form.all_uploaded_documents_section_available.data == "true",
+ application_fields_download_available=form.application_fields_download_available.data == "true",
+ display_logo_on_pdf_exports=form.display_logo_on_pdf_exports.data == "true",
+ mark_as_complete_enabled=form.mark_as_complete_enabled.data == "true",
+ is_expression_of_interest=form.is_expression_of_interest.data == "true",
+ feedback_survey_config={
+ "has_feedback_survey": form.has_feedback_survey.data == "true",
+ "has_section_feedback": form.has_section_feedback.data == "true",
+ "has_research_survey": form.has_research_survey.data == "true",
+ "is_feedback_survey_optional": form.is_feedback_survey_optional.data == "true",
+ "is_section_feedback_optional": form.is_section_feedback_optional.data == "true",
+ "is_research_survey_optional": form.is_research_survey_optional.data == "true",
+ },
+ eligibility_config={"has_eligibility": form.eligibility_config.data == "true"},
+ eoi_decision_schema={
+ "en": _convert_form_data_to_json(form.eoi_decision_schema_en.data),
+ "cy": _convert_form_data_to_json(form.eoi_decision_schema_cy.data),
+ },
+ )
+ add_round(new_round)
diff --git a/app/blueprints/fund_builder/templates/round.html b/app/blueprints/round/templates/round.html
similarity index 100%
rename from app/blueprints/fund_builder/templates/round.html
rename to app/blueprints/round/templates/round.html
diff --git a/app/create_app.py b/app/create_app.py
index d2873a31..0e97eebf 100644
--- a/app/create_app.py
+++ b/app/create_app.py
@@ -8,6 +8,7 @@
from app.blueprints.fund.routes import fund_bp
from app.blueprints.fund_builder.routes import build_fund_bp
+from app.blueprints.round.routes import round_bp
from app.blueprints.templates.routes import template_bp
PUBLIC_ROUTES = [
@@ -31,6 +32,7 @@ def create_app() -> Flask:
init_sentry()
flask_app = Flask("__name__", static_url_path="/assets")
flask_app.register_blueprint(fund_bp)
+ flask_app.register_blueprint(round_bp)
flask_app.register_blueprint(build_fund_bp)
flask_app.register_blueprint(template_bp)
diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html
index 260ff05e..5c85bab0 100644
--- a/app/templates/dashboard.html
+++ b/app/templates/dashboard.html
@@ -24,7 +24,7 @@ Fund Configuration (PoC)
Create a Fund
- Create a Round
+ Create a Round
Manage Application Configuration
diff --git a/tests/blueprints/fund/__init__.py b/tests/blueprints/fund/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/blueprints/fund_builder/forms/test_round.py b/tests/blueprints/fund_builder/forms/test_round.py
index df568ce2..f977402e 100644
--- a/tests/blueprints/fund_builder/forms/test_round.py
+++ b/tests/blueprints/fund_builder/forms/test_round.py
@@ -1,7 +1,7 @@
import pytest
from wtforms.validators import ValidationError
-from app.blueprints.fund_builder.forms.round import validate_flexible_url
+from app.blueprints.round.forms import validate_flexible_url
class MockField:
diff --git a/tests/blueprints/round/__init__.py b/tests/blueprints/round/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/blueprints/round/test_routes.py b/tests/blueprints/round/test_routes.py
new file mode 100644
index 00000000..74c43ad7
--- /dev/null
+++ b/tests/blueprints/round/test_routes.py
@@ -0,0 +1,199 @@
+import pytest
+
+from app.db.models import Round
+from app.db.queries.round import get_round_by_id
+from tests.helpers import submit_form
+
+
+@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
+def test_create_round_with_existing_short_name(flask_test_client, seed_dynamic_data):
+ """
+ Tests that a round can be successfully created using the /round route
+ Verifies that the created round has the correct attributes
+ """
+ test_fund = seed_dynamic_data["funds"][0]
+ new_round_data = {
+ "fund_id": test_fund.fund_id,
+ "title_en": "New Round",
+ "short_name": "NR123",
+ "opens-day": "01",
+ "opens-month": "10",
+ "opens-year": "2024",
+ "opens-hour": "09",
+ "opens-minute": "00",
+ "deadline-day": "01",
+ "deadline-month": "12",
+ "deadline-year": "2024",
+ "deadline-hour": "17",
+ "deadline-minute": "00",
+ "assessment_start-day": "02",
+ "assessment_start-month": "12",
+ "assessment_start-year": "2024",
+ "assessment_start-hour": "09",
+ "assessment_start-minute": "00",
+ "reminder_date-day": "15",
+ "reminder_date-month": "11",
+ "reminder_date-year": "2024",
+ "reminder_date-hour": "09",
+ "reminder_date-minute": "00",
+ "assessment_deadline-day": "15",
+ "assessment_deadline-month": "12",
+ "assessment_deadline-year": "2024",
+ "assessment_deadline-hour": "17",
+ "assessment_deadline-minute": "00",
+ "prospectus_link": "http://example.com/prospectus",
+ "privacy_notice_link": "http://example.com/privacy",
+ "contact_email": "contact@example.com",
+ "submit": "Submit",
+ "contact_phone": "1234567890",
+ "contact_textphone": "0987654321",
+ "support_times": "9am - 5pm",
+ "support_days": "Monday to Friday",
+ "feedback_link": "http://example.com/feedback",
+ "project_name_field_id": 1,
+ "guidance_url": "http://example.com/guidance",
+ }
+
+ error_html = (
+ 'Short name: Given short name already exists in the fund funding to improve testing.'
+ )
+
+ # Test works fine with first round
+ response = submit_form(flask_test_client, "/round", new_round_data)
+ assert response.status_code == 200
+ assert error_html not in response.data.decode("utf-8"), "Error HTML found in response"
+
+ # Test works fine with second round but with different short name
+ new_round_data = {**new_round_data, "short_name": "NR1234"}
+ response = submit_form(flask_test_client, "/round", new_round_data)
+ assert response.status_code == 200
+ assert error_html not in response.data.decode("utf-8"), "Error HTML found in response"
+
+ # Test doesn't work with third round with same short name as firsrt
+ new_round_data = {**new_round_data, "short_name": "NR123"}
+ response = submit_form(flask_test_client, "/round", new_round_data)
+ assert response.status_code == 200
+ assert error_html in response.data.decode("utf-8"), "Error HTML not found in response"
+
+
+@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
+def test_create_new_round(flask_test_client, seed_dynamic_data):
+ """
+ Tests that a round can be successfully created using the /round route
+ Verifies that the created round has the correct attributes
+ """
+ test_fund = seed_dynamic_data["funds"][0]
+ new_round_data = {
+ "fund_id": test_fund.fund_id,
+ "title_en": "New Round",
+ "short_name": "NR123",
+ "opens-day": "01",
+ "opens-month": "10",
+ "opens-year": "2024",
+ "opens-hour": "09",
+ "opens-minute": "00",
+ "deadline-day": "01",
+ "deadline-month": "12",
+ "deadline-year": "2024",
+ "deadline-hour": "17",
+ "deadline-minute": "00",
+ "assessment_start-day": "02",
+ "assessment_start-month": "12",
+ "assessment_start-year": "2024",
+ "assessment_start-hour": "09",
+ "assessment_start-minute": "00",
+ "reminder_date-day": "15",
+ "reminder_date-month": "11",
+ "reminder_date-year": "2024",
+ "reminder_date-hour": "09",
+ "reminder_date-minute": "00",
+ "assessment_deadline-day": "15",
+ "assessment_deadline-month": "12",
+ "assessment_deadline-year": "2024",
+ "assessment_deadline-hour": "17",
+ "assessment_deadline-minute": "00",
+ "prospectus_link": "http://example.com/prospectus",
+ "privacy_notice_link": "http://example.com/privacy",
+ "contact_email": "contact@example.com",
+ "submit": "Submit",
+ "contact_phone": "1234567890",
+ "contact_textphone": "0987654321",
+ "support_times": "9am - 5pm",
+ "support_days": "Monday to Friday",
+ "feedback_link": "http://example.com/feedback",
+ "project_name_field_id": 1,
+ "guidance_url": "http://example.com/guidance",
+ }
+
+ response = submit_form(flask_test_client, "/round", new_round_data)
+ assert response.status_code == 200
+
+ new_round = Round.query.filter_by(short_name="NR123").first()
+ assert new_round is not None
+ assert new_round.title_json["en"] == "New Round"
+ assert new_round.short_name == "NR123"
+
+
+@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
+def test_update_existing_round(flask_test_client, seed_dynamic_data):
+ """
+ Tests that a round can be successfully updated using the /round/ route
+ Verifies that the updated round has the correct attributes
+ """
+ update_round_data = {
+ "title_en": "Updated Round",
+ "short_name": "UR123",
+ "opens-day": "01",
+ "opens-month": "10",
+ "opens-year": "2024",
+ "opens-hour": "09",
+ "opens-minute": "00",
+ "deadline-day": "01",
+ "deadline-month": "12",
+ "deadline-year": "2024",
+ "deadline-hour": "17",
+ "deadline-minute": "00",
+ "assessment_start-day": "02",
+ "assessment_start-month": "12",
+ "assessment_start-year": "2024",
+ "assessment_start-hour": "09",
+ "assessment_start-minute": "00",
+ "reminder_date-day": "15",
+ "reminder_date-month": "11",
+ "reminder_date-year": "2024",
+ "reminder_date-hour": "09",
+ "reminder_date-minute": "00",
+ "assessment_deadline-day": "15",
+ "assessment_deadline-month": "12",
+ "assessment_deadline-year": "2024",
+ "assessment_deadline-hour": "17",
+ "assessment_deadline-minute": "00",
+ "prospectus_link": "http://example.com/updated_prospectus",
+ "privacy_notice_link": "http://example.com/updated_privacy",
+ "contact_email": "updated_contact@example.com",
+ "submit": "Submit",
+ "contact_phone": "1234567890",
+ "contact_textphone": "0987654321",
+ "support_times": "9am - 5pm",
+ "support_days": "Monday to Friday",
+ "feedback_link": "http://example.com/feedback",
+ "project_name_field_id": 1,
+ "guidance_url": "http://example.com/guidance",
+ "has_feedback_survey": "true",
+ }
+
+ test_round = seed_dynamic_data["rounds"][0]
+ response = submit_form(flask_test_client, f"/round/{test_round.round_id}", update_round_data)
+ assert response.status_code == 200
+
+ updated_round = get_round_by_id(test_round.round_id)
+ assert updated_round.title_json["en"] == "Updated Round"
+ assert updated_round.short_name == "UR123"
+ assert updated_round.feedback_survey_config == {
+ "has_feedback_survey": True,
+ "has_section_feedback": False,
+ "has_research_survey": False,
+ "is_feedback_survey_optional": False,
+ "is_section_feedback_optional": False,
+ "is_research_survey_optional": False,
+ }
diff --git a/tests/test_routes.py b/tests/test_routes.py
index 79f7180f..1bcabda0 100644
--- a/tests/test_routes.py
+++ b/tests/test_routes.py
@@ -3,204 +3,7 @@
import pytest
from wtforms.validators import ValidationError
-from app.blueprints.fund_builder.forms.round import validate_json_field
-from app.db.models import Round
-from app.db.queries.round import get_round_by_id
-from tests.helpers import submit_form
-
-
-@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
-def test_create_round_with_existing_short_name(flask_test_client, seed_dynamic_data):
- """
- Tests that a round can be successfully created using the /round route
- Verifies that the created round has the correct attributes
- """
- test_fund = seed_dynamic_data["funds"][0]
- new_round_data = {
- "fund_id": test_fund.fund_id,
- "title_en": "New Round",
- "short_name": "NR123",
- "opens-day": "01",
- "opens-month": "10",
- "opens-year": "2024",
- "opens-hour": "09",
- "opens-minute": "00",
- "deadline-day": "01",
- "deadline-month": "12",
- "deadline-year": "2024",
- "deadline-hour": "17",
- "deadline-minute": "00",
- "assessment_start-day": "02",
- "assessment_start-month": "12",
- "assessment_start-year": "2024",
- "assessment_start-hour": "09",
- "assessment_start-minute": "00",
- "reminder_date-day": "15",
- "reminder_date-month": "11",
- "reminder_date-year": "2024",
- "reminder_date-hour": "09",
- "reminder_date-minute": "00",
- "assessment_deadline-day": "15",
- "assessment_deadline-month": "12",
- "assessment_deadline-year": "2024",
- "assessment_deadline-hour": "17",
- "assessment_deadline-minute": "00",
- "prospectus_link": "http://example.com/prospectus",
- "privacy_notice_link": "http://example.com/privacy",
- "contact_email": "contact@example.com",
- "submit": "Submit",
- "contact_phone": "1234567890",
- "contact_textphone": "0987654321",
- "support_times": "9am - 5pm",
- "support_days": "Monday to Friday",
- "feedback_link": "http://example.com/feedback",
- "project_name_field_id": 1,
- "guidance_url": "http://example.com/guidance",
- }
-
- error_html = (
- 'Short name: Given short name already exists in the fund funding to improve testing.'
- )
-
- # Test works fine with first round
- response = submit_form(flask_test_client, "/round", new_round_data)
- assert response.status_code == 200
- assert error_html not in response.data.decode("utf-8"), "Error HTML found in response"
-
- # Test works fine with second round but with different short name
- new_round_data = {**new_round_data, "short_name": "NR1234"}
- response = submit_form(flask_test_client, "/round", new_round_data)
- assert response.status_code == 200
- assert error_html not in response.data.decode("utf-8"), "Error HTML found in response"
-
- # Test doesn't work with third round with same short name as firsrt
- new_round_data = {**new_round_data, "short_name": "NR123"}
- response = submit_form(flask_test_client, "/round", new_round_data)
- assert response.status_code == 200
- assert error_html in response.data.decode("utf-8"), "Error HTML not found in response"
-
-
-@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
-def test_create_new_round(flask_test_client, seed_dynamic_data):
- """
- Tests that a round can be successfully created using the /round route
- Verifies that the created round has the correct attributes
- """
- test_fund = seed_dynamic_data["funds"][0]
- new_round_data = {
- "fund_id": test_fund.fund_id,
- "title_en": "New Round",
- "short_name": "NR123",
- "opens-day": "01",
- "opens-month": "10",
- "opens-year": "2024",
- "opens-hour": "09",
- "opens-minute": "00",
- "deadline-day": "01",
- "deadline-month": "12",
- "deadline-year": "2024",
- "deadline-hour": "17",
- "deadline-minute": "00",
- "assessment_start-day": "02",
- "assessment_start-month": "12",
- "assessment_start-year": "2024",
- "assessment_start-hour": "09",
- "assessment_start-minute": "00",
- "reminder_date-day": "15",
- "reminder_date-month": "11",
- "reminder_date-year": "2024",
- "reminder_date-hour": "09",
- "reminder_date-minute": "00",
- "assessment_deadline-day": "15",
- "assessment_deadline-month": "12",
- "assessment_deadline-year": "2024",
- "assessment_deadline-hour": "17",
- "assessment_deadline-minute": "00",
- "prospectus_link": "http://example.com/prospectus",
- "privacy_notice_link": "http://example.com/privacy",
- "contact_email": "contact@example.com",
- "submit": "Submit",
- "contact_phone": "1234567890",
- "contact_textphone": "0987654321",
- "support_times": "9am - 5pm",
- "support_days": "Monday to Friday",
- "feedback_link": "http://example.com/feedback",
- "project_name_field_id": 1,
- "guidance_url": "http://example.com/guidance",
- }
-
- response = submit_form(flask_test_client, "/round", new_round_data)
- assert response.status_code == 200
-
- new_round = Round.query.filter_by(short_name="NR123").first()
- assert new_round is not None
- assert new_round.title_json["en"] == "New Round"
- assert new_round.short_name == "NR123"
-
-
-@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
-def test_update_existing_round(flask_test_client, seed_dynamic_data):
- """
- Tests that a round can be successfully updated using the /round/ route
- Verifies that the updated round has the correct attributes
- """
- update_round_data = {
- "title_en": "Updated Round",
- "short_name": "UR123",
- "opens-day": "01",
- "opens-month": "10",
- "opens-year": "2024",
- "opens-hour": "09",
- "opens-minute": "00",
- "deadline-day": "01",
- "deadline-month": "12",
- "deadline-year": "2024",
- "deadline-hour": "17",
- "deadline-minute": "00",
- "assessment_start-day": "02",
- "assessment_start-month": "12",
- "assessment_start-year": "2024",
- "assessment_start-hour": "09",
- "assessment_start-minute": "00",
- "reminder_date-day": "15",
- "reminder_date-month": "11",
- "reminder_date-year": "2024",
- "reminder_date-hour": "09",
- "reminder_date-minute": "00",
- "assessment_deadline-day": "15",
- "assessment_deadline-month": "12",
- "assessment_deadline-year": "2024",
- "assessment_deadline-hour": "17",
- "assessment_deadline-minute": "00",
- "prospectus_link": "http://example.com/updated_prospectus",
- "privacy_notice_link": "http://example.com/updated_privacy",
- "contact_email": "updated_contact@example.com",
- "submit": "Submit",
- "contact_phone": "1234567890",
- "contact_textphone": "0987654321",
- "support_times": "9am - 5pm",
- "support_days": "Monday to Friday",
- "feedback_link": "http://example.com/feedback",
- "project_name_field_id": 1,
- "guidance_url": "http://example.com/guidance",
- "has_feedback_survey": "true",
- }
-
- test_round = seed_dynamic_data["rounds"][0]
- response = submit_form(flask_test_client, f"/round/{test_round.round_id}", update_round_data)
- assert response.status_code == 200
-
- updated_round = get_round_by_id(test_round.round_id)
- assert updated_round.title_json["en"] == "Updated Round"
- assert updated_round.short_name == "UR123"
- assert updated_round.feedback_survey_config == {
- "has_feedback_survey": True,
- "has_section_feedback": False,
- "has_research_survey": False,
- "is_feedback_survey_optional": False,
- "is_section_feedback_optional": False,
- "is_research_survey_optional": False,
- }
+from app.blueprints.round.forms import validate_json_field
@pytest.mark.parametrize("input_json_string", [(None), (""), ("{}"), (""), ("{}"), ('{"1":"2"}')])