Skip to content

Commit

Permalink
FS-4895: When creating a fund or a round check, whether given round s…
Browse files Browse the repository at this point in the history
…hortnames are available
  • Loading branch information
nuwan-samarasinghe authored and wjrm500 committed Dec 13, 2024
1 parent 6541583 commit 90740d6
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 66 deletions.
115 changes: 64 additions & 51 deletions app/blueprints/fund_builder/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
from app.db.queries.application import move_section_down
from app.db.queries.application import move_section_up
from app.db.queries.application import update_section
from app.db.queries.fund import add_fund
from app.db.queries.fund import add_fund, get_fund_by_short_name
from app.db.queries.fund import get_all_funds
from app.db.queries.fund import get_fund_by_id
from app.db.queries.fund import update_fund
from app.db.queries.round import add_round
from app.db.queries.round import add_round, get_round_by_short_name_and_fund_id
from app.db.queries.round import get_round_by_id
from app.db.queries.round import update_round
from app.export_config.generate_all_questions import print_html
Expand Down Expand Up @@ -254,42 +254,49 @@ def fund(fund_id=None):
else:
form = FundForm()

error = {}
if form.validate_on_submit():
if fund_id:
fund.name_json["en"] = form.name_en.data
fund.name_json["cy"] = form.name_cy.data
fund.title_json["en"] = form.title_en.data
fund.title_json["cy"] = form.title_cy.data
fund.description_json["en"] = form.description_en.data
fund.description_json["cy"] = form.description_cy.data
fund.welsh_available = form.welsh_available.data == "true"
fund.short_name = form.short_name.data
fund.audit_info = {"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "update"}
fund.funding_type = form.funding_type.data
fund.ggis_scheme_reference_number = (
form.ggis_scheme_reference_number.data if form.ggis_scheme_reference_number.data else ""
is_short_name_available = False
if form.data and form.data['short_name']:
fund_data = get_fund_by_short_name(form.data['short_name'])
is_short_name_available = fund_data and str(fund_data.fund_id) != fund_id
if not is_short_name_available:
if fund_id:
fund.name_json["en"] = form.name_en.data
fund.name_json["cy"] = form.name_cy.data
fund.title_json["en"] = form.title_en.data
fund.title_json["cy"] = form.title_cy.data
fund.description_json["en"] = form.description_en.data
fund.description_json["cy"] = form.description_cy.data
fund.welsh_available = form.welsh_available.data == "true"
fund.short_name = form.short_name.data
fund.audit_info = {"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "update"}
fund.funding_type = form.funding_type.data
fund.ggis_scheme_reference_number = (
form.ggis_scheme_reference_number.data if form.ggis_scheme_reference_number.data else ""
)
update_fund(fund)
flash(f"Updated fund {form.title_en.data}")
return redirect(url_for("build_fund_bp.view_fund", fund_id=fund.fund_id))

new_fund = Fund(
name_json={"en": form.name_en.data},
title_json={"en": form.title_en.data},
description_json={"en": form.description_en.data},
welsh_available=form.welsh_available.data == "true",
short_name=form.short_name.data,
audit_info={"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "create"},
funding_type=FundingType(form.funding_type.data),
ggis_scheme_reference_number=(
form.ggis_scheme_reference_number.data if form.ggis_scheme_reference_number.data else ""
),
)
update_fund(fund)
flash(f"Updated fund {form.title_en.data}")
return redirect(url_for("build_fund_bp.view_fund", fund_id=fund.fund_id))

new_fund = Fund(
name_json={"en": form.name_en.data},
title_json={"en": form.title_en.data},
description_json={"en": form.description_en.data},
welsh_available=form.welsh_available.data == "true",
short_name=form.short_name.data,
audit_info={"user": "dummy_user", "timestamp": datetime.now().isoformat(), "action": "create"},
funding_type=FundingType(form.funding_type.data),
ggis_scheme_reference_number=(
form.ggis_scheme_reference_number.data if form.ggis_scheme_reference_number.data else ""
),
)
add_fund(new_fund)
flash(f"Created fund {form.name_en.data}")
return redirect(url_for(BUILD_FUND_BP_DASHBOARD))

error = error_formatter(form.errors)
add_fund(new_fund)
flash(f"Created fund {form.name_en.data}")
return redirect(url_for(BUILD_FUND_BP_DASHBOARD))
error = {**error,
'short_name': [f'Given fund short name already exists.']}
error = error_formatter(form.errors, error)
return render_template("fund.html", form=form, fund_id=fund_id, error=error)


Expand All @@ -303,24 +310,30 @@ def round(round_id=None):
form = RoundForm()
params = {"all_funds": all_funds_as_govuk_select_items(get_all_funds())}
params["selected_fund_id"] = request.form.get("fund_id", None)

error = {}
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("build_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))

is_short_name_available = False
if form.data and form.data['short_name'] and form.data['fund_id']:
rond_data = get_round_by_short_name_and_fund_id(form.data['fund_id'],
form.data['short_name'])
is_short_name_available = rond_data and str(rond_data.round_id) != round_id
if not is_short_name_available:
if round_id:
update_existing_round(round, form)
flash(f"Updated round {round.title_json['en']}")
return redirect(url_for("build_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))
fund = get_fund_by_id(form.data['fund_id'])
error = {**error,
'short_name': [f'Given short name already exists in the fund {fund.title_json.get("en")}.']}
params["round_id"] = round_id
params["form"] = form

error = error_formatter(params["form"].errors)
error = error_formatter(params["form"].errors, error)
return render_template("round.html", **params, error=error)


Expand Down Expand Up @@ -399,19 +412,19 @@ def populate_form_with_round_data(round):
"is_feedback_survey_optional": (
"true"
if round.feedback_survey_config
and round.feedback_survey_config.get("is_feedback_survey_optional", "") == "true"
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"
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"
and round.feedback_survey_config.get("is_research_survey_optional", "") == "true"
else "false"
),
"eligibility_config": (
Expand Down
4 changes: 4 additions & 0 deletions app/db/queries/fund.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ def get_fund_by_id(id: str) -> Fund:
if not fund:
raise ValueError(f"Fund with id {id} not found")
return fund


def get_fund_by_short_name(short_name: str) -> Fund:
return db.session.query(Fund).filter_by(short_name=short_name).first()
4 changes: 4 additions & 0 deletions app/db/queries/round.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def get_round_by_id(id: str) -> Round:
return round


def get_round_by_short_name_and_fund_id(fund_id: str, short_name: str) -> Round:
return db.session.query(Round).filter_by(fund_id=fund_id, short_name=short_name).first()


def get_all_rounds() -> list:
stmt = select(Round).order_by(Round.short_name)
return db.session.scalars(stmt).all()
31 changes: 16 additions & 15 deletions app/shared/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,22 @@ def get_all_pages_in_parent_form(db, page_id):

return page_ids


# This formatter will read all the errors and then convert them to the required format to support error-summary display
def error_formatter(errors):
error = None
if errors:
errorsList = []
for field, errors in errors.items():
if isinstance(errors, list):
errorsList.extend([{'text': err, 'href': f'#{field}'} for err in errors])
elif isinstance(errors, dict):
# Check if any of the datetime fields have errors
if any(len(errors.get(key, '')) > 0 for key in ['day', 'month', 'years', 'hour', 'minute']):
errorsList.append({'text': "Enter valid datetime", 'href': f'#{field}'})
if errorsList:
error = {'titleText': "There is a problem", 'errorList': errorsList}
return error
def error_formatter(errors, custom_errors=None):
# Merge errors with custom_errors if provided
merged_errors = {**errors, **(custom_errors or {})}
# Generate the error list if there are errors
error_list = [
{'text': err, 'href': f'#{field}'}
for field, errs in merged_errors.items()
for err in (errs if isinstance(errs, list) else ['Enter valid datetime']
if isinstance(errs, dict) and any(
len(errs.get(key, '')) > 0 for key in ['day', 'month', 'year', 'hour', 'minute'])
else [])
]
# Return the formatted error structure if there are errors
return {'titleText': "There is a problem", 'errorList': error_list} if error_list else None


# Custom validator to check for spaces between letters
Expand All @@ -62,4 +63,4 @@ def no_spaces_between_letters(form, field):
if not field.data:
return
if re.search(r'\b\w+\s+\w+\b', field.data): # Matches sequences with spaces in between
raise ValidationError("Spaces between letters are not allowed.")
raise ValidationError("Spaces between letters are not allowed.")
92 changes: 92 additions & 0 deletions tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,38 @@ def test_create_fund(flask_test_client):
assert created_fund.__getattribute__(key) == value


@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
def test_create_fund_with_existing_short_name(flask_test_client):
"""
Tests that a fund can be successfully created using the /fund route
Verifies that the created fund has the correct attributes
"""
create_data = {
"name_en": "New Fund 2",
"title_en": "New Fund Title 2",
"description_en": "New Fund Description 2",
"welsh_available": "false",
"short_name": "SMP1",
"funding_type": FundingType.COMPETITIVE.value,
"ggis_scheme_reference_number": "G1-SCH-0000092415",
}
response = submit_form(flask_test_client, "/fund", create_data)
assert response.status_code == 200
create_data = {
"name_en": "New Fund 3",
"title_en": "New Fund Title 3",
"description_en": "New Fund Description 3",
"welsh_available": "false",
"short_name": "SMP1",
"funding_type": FundingType.COMPETITIVE.value,
"ggis_scheme_reference_number": "G1-SCH-0000092415",
}
response = submit_form(flask_test_client, "/fund", create_data)
assert response.status_code == 200
html = response.data.decode('utf-8')
assert '<a href="#short_name">Given fund short name already exists.</a>' in html, "Not having the fund short name already exists error"


@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
def test_update_fund(flask_test_client, seed_dynamic_data):
"""
Expand Down Expand Up @@ -115,6 +147,66 @@ def test_update_fund(flask_test_client, seed_dynamic_data):
elif key != "submit":
assert updated_fund.__getattribute__(key) == value

@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": "[email protected]",
"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_data = {**new_round_data, 'short_name': 'NR1234'}
response = submit_form(flask_test_client, "/round", new_round_data)
assert response.status_code == 200
new_round_data = {**new_round_data, 'short_name': 'NR123'}
response = submit_form(flask_test_client, "/round", new_round_data)
assert response.status_code == 200
html = response.data.decode('utf-8')
assert '<a href="#short_name">Given short name already exists in the fund funding to improve testing.</a>' in html, "Not having the fund round short name already exists error"


@pytest.mark.usefixtures("set_auth_cookie", "patch_validate_token_rs256_internal_user")
def test_create_new_round(flask_test_client, seed_dynamic_data):
Expand Down

0 comments on commit 90740d6

Please sign in to comment.