diff --git a/app/blueprints/fund_builder/forms/fund.py b/app/blueprints/fund_builder/forms/fund.py index 3a8f552..52b21b4 100644 --- a/app/blueprints/fund_builder/forms/fund.py +++ b/app/blueprints/fund_builder/forms/fund.py @@ -1,3 +1,5 @@ +from enum import Enum + from flask_wtf import FlaskForm from wtforms import HiddenField from wtforms import RadioField @@ -5,6 +7,27 @@ from wtforms.validators import DataRequired from wtforms.validators import Length +from app.db.models.fund import FundingType + + +class GovUkRadioEnumField(RadioField): + source_enum: Enum + gov_uk_choices: list + + def __init__(self, name: str, _prefix, _translations, label: str, _form, source_enum: Enum): + super().__init__( + name=name, + label=label, + _form=_form, + _prefix=_prefix, + _translations=_translations, + choices=[(value.name, value.value) for value in source_enum], + ) + self.source_enum = source_enum + self.gov_uk_choices = [ + {"text": value.get_text_for_display(), "value": value.value} for value in self.source_enum + ] + class FundForm(FlaskForm): fund_id = HiddenField("Fund ID") @@ -13,3 +36,4 @@ class FundForm(FlaskForm): short_name = StringField("Short Name", validators=[DataRequired(), Length(max=6)]) description_en = StringField("Description", validators=[DataRequired()]) welsh_available = RadioField("Welsh Available", choices=[("true", "Yes"), ("false", "No")], default="false") + funding_type = GovUkRadioEnumField(label="Funding Type", source_enum=FundingType) diff --git a/app/blueprints/fund_builder/routes.py b/app/blueprints/fund_builder/routes.py index 4ad8753..0942e60 100644 --- a/app/blueprints/fund_builder/routes.py +++ b/app/blueprints/fund_builder/routes.py @@ -21,6 +21,7 @@ from app.blueprints.fund_builder.forms.round import get_datetime from app.blueprints.fund_builder.forms.section import SectionForm from app.db.models.fund import Fund +from app.db.models.fund import FundingType from app.db.models.round import Round from app.db.queries.application import clone_single_form from app.db.queries.application import clone_single_round @@ -221,6 +222,7 @@ def fund(fund_id=None): "short_name": fund.short_name, "description_en": fund.description_json.get("en", ""), "welsh_available": "true" if fund.welsh_available else "false", + "funding_type": fund.funding_type.value, } form = FundForm(data=fund_data) else: @@ -234,6 +236,7 @@ def fund(fund_id=None): 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 update_fund(fund) flash(f"Updated fund {form.title_en.data}") else: @@ -244,6 +247,7 @@ def fund(fund_id=None): 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), ) add_fund(new_fund) flash(f"Created fund {form.name_en.data}") @@ -522,6 +526,7 @@ def view_form_questions(round_id, form_id): "view_questions.html", round=round, fund=fund, question_html=html, title=form.name_in_apply_json["en"] ) + def create_export_zip(directory_to_zip, zip_file_name) -> str: # Output zip file path (temporary) output_zip_path = Config.TEMP_FILE_PATH / zip_file_name @@ -538,7 +543,9 @@ def create_export_files(round_id): generate_config_for_round(round_id) round_short_name = get_round_by_id(round_id).short_name - output_zip_path = create_export_zip(directory_to_zip=Config.TEMP_FILE_PATH / round_short_name, zip_file_name=round_short_name) + output_zip_path = create_export_zip( + directory_to_zip=Config.TEMP_FILE_PATH / round_short_name, zip_file_name=round_short_name + ) # Ensure the file is removed after sending it @after_this_request diff --git a/app/blueprints/fund_builder/templates/fund.html b/app/blueprints/fund_builder/templates/fund.html index dcff26d..80f6858 100644 --- a/app/blueprints/fund_builder/templates/fund.html +++ b/app/blueprints/fund_builder/templates/fund.html @@ -2,6 +2,7 @@ {% set pageHeading %}{{ 'Update a Fund' if fund_id else 'Create a Fund' }} {% endset %} {% from "macros/wtfToGovUk.html" import input %} {% from "macros/wtfToGovUk.html" import yes_no %} +{% from "macros/wtfToGovUk.html" import radios_from_enum %} {%- from "govuk_frontend_jinja/components/button/macro.html" import govukButton -%} {% block content %}
@@ -18,6 +19,7 @@

{{pageHeading}}

{{input(form.short_name)}} {{input(form.description_en)}} {{yes_no(form.welsh_available)}} + {{radios_from_enum(form.funding_type)}} {{ govukButton({ "text": "Save" }) }} diff --git a/app/blueprints/fund_builder/templates/fund_config.html b/app/blueprints/fund_builder/templates/fund_config.html index f1db988..2ca6d2b 100644 --- a/app/blueprints/fund_builder/templates/fund_config.html +++ b/app/blueprints/fund_builder/templates/fund_config.html @@ -68,7 +68,15 @@

Fund Meta Data

"value": { "text": fund.welsh_available }, - } + }, + { + "key": { + "text": "Funding Type" + }, + "value": { + "text": fund.funding_type.get_text_for_display() + }, + } ] }) }} {{ govukButton({ diff --git a/app/db/migrations/versions/~2024_10_18_1008-95a6033bea37_.py b/app/db/migrations/versions/~2024_10_18_1008-95a6033bea37_.py new file mode 100644 index 0000000..9b4a8ac --- /dev/null +++ b/app/db/migrations/versions/~2024_10_18_1008-95a6033bea37_.py @@ -0,0 +1,40 @@ +"""Add funding_type to fund table + +Revision ID: 95a6033bea37 +Revises: da30746cec39 +Create Date: 2024-10-18 10:08:54.071885 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "95a6033bea37" +down_revision = "da30746cec39" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + funding_type_enum = postgresql.ENUM("COMPETITIVE", "UNCOMPETED", "EOI", name="fundingtype") + funding_type_enum.create(op.get_bind()) + with op.batch_alter_table("fund", schema=None) as batch_op: + batch_op.add_column(sa.Column("funding_type", funding_type_enum, nullable=True)) + op.execute(sa.text("UPDATE fund SET funding_type='COMPETITIVE'")) + with op.batch_alter_table("fund", schema=None) as batch_op: + batch_op.alter_column("funding_type", nullable=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + with op.batch_alter_table("fund", schema=None) as batch_op: + batch_op.drop_column("funding_type") + op.execute(sa.text("DROP TYPE fundingtype;")) + + # ### end Alembic commands ### diff --git a/app/db/models/fund.py b/app/db/models/fund.py index fa07743..56209df 100644 --- a/app/db/models/fund.py +++ b/app/db/models/fund.py @@ -1,10 +1,12 @@ import uuid from dataclasses import dataclass +from enum import Enum from typing import List from flask_sqlalchemy.model import DefaultMeta from sqlalchemy import Column from sqlalchemy import ForeignKey +from sqlalchemy.dialects.postgresql import ENUM from sqlalchemy.dialects.postgresql import JSON from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.ext.mutable import MutableDict @@ -19,6 +21,23 @@ BaseModel: DefaultMeta = db.Model +class FundingType(Enum): + COMPETITIVE = "COMPETITIVE" + UNCOMPETED = "UNCOMPETED" + EOI = "EOI" + + def get_text_for_display(self): + match self: + case FundingType.COMPETITIVE: + return "Competitive" + case FundingType.EOI: + return "EOI" + case FundingType.UNCOMPETED: + return "Uncompeted" + case _: + return self.value + + @dataclass class Organisation(BaseModel): organisation_id = Column( @@ -57,3 +76,4 @@ class Fund(BaseModel): owner_organisation_id = Column(UUID(as_uuid=True), ForeignKey("organisation.organisation_id"), nullable=True) # Define the relationship to access the owning Organisation directly owner_organisation: Mapped["Organisation"] = relationship("Organisation", back_populates="funds") + funding_type = Column(ENUM(FundingType), nullable=False, unique=False) diff --git a/app/templates/macros/wtfToGovUk.html b/app/templates/macros/wtfToGovUk.html index d3e2d0c..bcbf2e6 100644 --- a/app/templates/macros/wtfToGovUk.html +++ b/app/templates/macros/wtfToGovUk.html @@ -91,3 +91,21 @@ "hint":{"text":form_field.description} })}} {%endmacro%} + + +{% macro radios_from_enum(form_field) %} +{{ govukRadios({ +"name": form_field.name, +"classes": "govuk-radios--inline", +"fieldset": { +"legend": { + "text": form_field.label, + "isPageHeading": false, + } +}, +"items": form_field.gov_uk_choices, +"value": form_field.data +})}} + +{{items}} +{%endmacro%}