Skip to content

Commit

Permalink
FS-4927 - Add intermediary page 'Select a Fund' before 'Create a Round'
Browse files Browse the repository at this point in the history
  • Loading branch information
wjrm500 committed Jan 3, 2025
1 parent 07622f7 commit 35f8906
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 61 deletions.
2 changes: 1 addition & 1 deletion app/blueprints/index/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ <h2 class="govuk-heading-s">Fund Configuration (PoC)</h2>
<a class="govuk-link" href="{{ url_for("fund_bp.fund") }}">Create a Fund</a>
</li>
<li class="govuk-body">
<a class="govuk-link" href="{{ url_for("round_bp.create_round") }}">Create a Round</a>
<a class="govuk-link" href="{{ url_for("round_bp.select_fund") }}">Create a Round</a>
</li>
<li class="govuk-body">
<a class="govuk-link" href="{{ url_for("fund_bp.view_fund") }}">Manage Application Configuration</a>
Expand Down
78 changes: 45 additions & 33 deletions app/blueprints/round/routes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from random import randint

from flask import (
Expand All @@ -17,9 +16,9 @@
update_existing_round,
)
from app.db.queries.clone import clone_single_round
from app.db.queries.fund import get_all_funds
from app.db.queries.fund import get_all_funds, get_fund_by_id
from app.db.queries.round import get_round_by_id
from app.shared.helpers import all_funds_as_govuk_select_items, error_formatter
from app.shared.helpers import error_formatter

INDEX_BP_DASHBOARD = "index_bp.dashboard"

Expand All @@ -31,64 +30,77 @@
)


@round_bp.route("/select-fund", methods=["GET", "POST"])
def select_fund():
"""
Intermediary page to select a Fund before creating a Round.
"""
if request.method == "POST":
fund_id = request.form.get("fund_id")
if not fund_id:
raise ValueError("Fund ID is required to create a round")
return redirect(url_for("round_bp.create_round", fund_id=fund_id))
fund_dropdown_items = [{"value": "", "text": "Select a fund"}]
for fund in get_all_funds():
fund_dropdown_items.append(
{"value": str(fund.fund_id), "text": fund.short_name + " - " + fund.title_json["en"]}
)
return render_template("select_fund.html", fund_dropdown_items=fund_dropdown_items)


@round_bp.route("/create", methods=["GET", "POST"])
def create_round():
"""
Renders a template to select a fund and create a new round under that fund.
If submitted, validates the round form data and saves a new round to the DB.
Create a new round for a chosen fund.
Expects a ?fund_id=... in the query string, set by the select_fund route or other means.
"""
form = RoundForm()
all_funds = get_all_funds()

# Prepare parameters for the template
params = {
"all_funds": all_funds_as_govuk_select_items(all_funds),
"selected_fund_id": request.form.get("fund_id", None),
"welsh_availability": json.dumps({str(fund.fund_id): fund.welsh_available for fund in all_funds}),
"round_id": None,
"form": form,
}

fund_id = request.args.get("fund_id", None)
if not fund_id:
raise ValueError("Fund ID is required to create a round")
fund = get_fund_by_id(fund_id)
if form.validate_on_submit():
new_round = create_new_round(form)
flash(f"Created round {new_round.title_json['en']}")
return redirect(url_for(INDEX_BP_DASHBOARD))

params = {
"form": form,
"fund": fund,
"round_id": None, # Since we're creating a new round, there's no round ID yet
}
error = error_formatter(form)
return render_template("round.html", **params, error=error)


@round_bp.route("/<round_id>", methods=["GET", "POST"])
def edit_round(round_id):
"""
Renders a template to view or update an existing round.
If submitted, validates the round form data and updates the existing round in the DB.
Edit an existing round.
"""
existing_round = get_round_by_id(round_id)
form = populate_form_with_round_data(existing_round, RoundForm)

all_funds = get_all_funds()

# Prepare parameters for the template
params = {
"all_funds": all_funds_as_govuk_select_items(all_funds),
"selected_fund_id": request.form.get("fund_id", None),
"welsh_availability": json.dumps({str(fund.fund_id): fund.welsh_available for fund in all_funds}),
"round_id": round_id,
"form": form,
}

if not existing_round:
raise ValueError(f"Round with ID {round_id} not found")
form = RoundForm()
if request.method == "GET":
form = populate_form_with_round_data(existing_round, RoundForm)
if form.validate_on_submit():
update_existing_round(existing_round, form)
flash(f"Updated round {existing_round.title_json['en']}")
return redirect(url_for("fund_bp.view_fund", fund_id=existing_round.fund_id))

params = {
"form": form,
"fund": get_fund_by_id(existing_round.fund_id),
"round_id": round_id,
}
error = error_formatter(form)
return render_template("round.html", **params, error=error)


@round_bp.route("/<round_id>/clone")
def clone_round(round_id, fund_id):
"""
Clone an existing round.
"""
cloned = clone_single_round(
round_id=round_id,
new_fund_id=fund_id,
Expand Down
20 changes: 3 additions & 17 deletions app/blueprints/round/templates/round.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,12 @@
}) }}
{% endif %}
<h1 class="govuk-heading-l">{{ pageHeading }}</h1>
<p class="govuk-body">Fund: {{ fund.title_json["en"] }}</p>
<div class="govuk-form-group">
<fieldset class="govuk-fieldset">
<form method="POST">
{{ form.hidden_tag()}}
{% if not round_id %}
{{ govukSelect({
"id": form.fund_id.id,
"name": form.fund_id.name,
"label": {
"text": form.fund_id.label
},
"items": all_funds,
"value": selected_fund_id,
"errorMessage": {
"text": form.fund_id.errors[0] if form.fund_id.errors else ""
} if form.fund_id.errors else None
})}}
{% endif %}
<input type="hidden" name="fund_id" value="{{ fund.fund_id }}" />
{{input(form.title_en)}}
{{input(form.title_cy, classes="welsh-field")}}
{{input(form.short_name)}}
Expand Down Expand Up @@ -85,13 +73,11 @@ <h1 class="govuk-heading-l">{{ pageHeading }}</h1>

<script>
// Store Welsh availability mapping passed from the route handler
const welshAvailability = {{ welsh_availability | safe }};
const isWelshAvailable = {{ fund.welsh_available | safe }};

function toggleWelshFields() {
const fundSelect = document.querySelector('#fund_id');
const isWelshAvailable = welshAvailability[fundSelect.value];
const welshFields = document.querySelectorAll('.welsh-field');

welshFields.forEach(field => {
const fieldContainer = field.closest('.govuk-form-group');
if (isWelshAvailable) {
Expand Down
44 changes: 44 additions & 0 deletions app/templates/select_fund.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends "base.html" %}
{% from "govuk_frontend_jinja/components/button/macro.html" import govukButton %}
{% from "govuk_frontend_jinja/components/select/macro.html" import govukSelect %}

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-full">
<h1 class="govuk-heading-l">Select a fund</h1>
<p class="govuk-body">Select or add a new fund for this application</p>

<form method="POST">
{{ govukSelect({
"id": "fund_id",
"name": "fund_id",
"items": fund_dropdown_items,
"label": "",
"attributes": {
"required": "required"
},
"classes": "govuk-!-width-one-half",
"formGroup": {
"classes": "govuk-!-margin-bottom-3"
}
}) }}

<p class="govuk-body govuk-!-margin-top-1 govuk-!-margin-bottom-6">
<a href="{{ url_for('fund_bp.fund') }}" class="govuk-link">Add a new fund</a>
</p>

<div class="govuk-button-group">
{{ govukButton({
"text": "Continue",
"type": "submit"
}) }}
{{ govukButton({
"text": "Cancel",
"href": url_for("index_bp.dashboard"),
"classes": "govuk-button--secondary"
}) }}
</div>
</form>
</div>
</div>
{% endblock content %}
41 changes: 31 additions & 10 deletions tests/blueprints/round/test_routes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import pytest
from flask import url_for

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_select_fund(flask_test_client, seed_dynamic_data):
"""
Test the /rounds/select-fund route to ensure a user cannot proceed without selecting a fund
and is redirected to /rounds/create if a valid fund is selected.
"""
# Attempt to submit without choosing a fund
with pytest.raises(ValueError, match="Fund ID is required to create a round"):
flask_test_client.post("/rounds/select-fund", data={"fund_id": ""}, follow_redirects=True)

# Submit with a valid fund
test_fund = seed_dynamic_data["funds"][0]
response = flask_test_client.post(
"/rounds/select-fund", data={"fund_id": str(test_fund.fund_id)}, follow_redirects=False
)

# Should redirect to /rounds/create?fund_id=...
assert response.status_code == 302
assert url_for("round_bp.create_round", fund_id=test_fund.fund_id) in response.location


@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):
"""
Expand Down Expand Up @@ -44,7 +66,6 @@ def test_create_round_with_existing_short_name(flask_test_client, seed_dynamic_d
"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",
Expand All @@ -57,21 +78,22 @@ def test_create_round_with_existing_short_name(flask_test_client, seed_dynamic_d
error_html = (
'<a href="#short_name">Short name: Given short name already exists in the fund funding to improve testing.</a>'
)
url = f"/rounds/create?fund_id={test_fund.fund_id}"

# Test works fine with first round
response = submit_form(flask_test_client, "/rounds/create", new_round_data)
response = submit_form(flask_test_client, url, 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, "/rounds/create", new_round_data)
new_round_data["short_name"] = "NR1234"
response = submit_form(flask_test_client, url, 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, "/rounds/create", new_round_data)
# Test doesn't work with third round with same short name as first
new_round_data["short_name"] = "NR123"
response = submit_form(flask_test_client, url, new_round_data)
assert response.status_code == 200
assert error_html in response.data.decode("utf-8"), "Error HTML not found in response"

Expand Down Expand Up @@ -115,7 +137,6 @@ def test_create_new_round(flask_test_client, seed_dynamic_data):
"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",
Expand All @@ -125,7 +146,7 @@ def test_create_new_round(flask_test_client, seed_dynamic_data):
"guidance_url": "http://example.com/guidance",
}

response = submit_form(flask_test_client, "/rounds/create", new_round_data)
response = submit_form(flask_test_client, f"/rounds/create?fund_id={test_fund.fund_id}", new_round_data)
assert response.status_code == 200

new_round = Round.query.filter_by(short_name="NR123").first()
Expand All @@ -141,6 +162,7 @@ def test_update_existing_round(flask_test_client, seed_dynamic_data):
Verifies that the updated round has the correct attributes
"""
update_round_data = {
"fund_id": seed_dynamic_data["funds"][0].fund_id,
"title_en": "Updated Round",
"short_name": "UR123",
"opens-day": "01",
Expand Down Expand Up @@ -171,7 +193,6 @@ def test_update_existing_round(flask_test_client, seed_dynamic_data):
"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",
Expand Down

0 comments on commit 35f8906

Please sign in to comment.