Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FS-4710: Add form sections for form json #79

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
]
}
},
}
}
2 changes: 1 addition & 1 deletion .github/workflows/copilot_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
- dev
- test
- uat

push:
# Ignore README markdown and the docs folder
# Only automatically deploy when something in the app or tests folder has changed
Expand Down
13 changes: 9 additions & 4 deletions app/blueprints/fund_builder/forms/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
from wtforms import FileField
from wtforms import HiddenField
from wtforms import StringField
from wtforms.validators import DataRequired, Regexp
from wtforms.validators import DataRequired
from wtforms.validators import Regexp


class TemplateUploadForm(FlaskForm):
template_name = StringField("Template Name", validators=[DataRequired()])
file = FileField("Upload File", validators=[DataRequired()])


nuwan-samarasinghe marked this conversation as resolved.
Show resolved Hide resolved
class TemplateFormForm(FlaskForm):

form_id=HiddenField()
form_id = HiddenField()

template_name = StringField(
"Template Name",
description="Name of this template, only used in FAB",
Expand All @@ -26,5 +28,8 @@ class TemplateFormForm(FlaskForm):
url_path = StringField(
"URL Path",
description="The portion of the URL that equates to the form name. Use only letters, numbers and hyphens (-)",
validators=[DataRequired(), Regexp("^[a-z0-9-]*$", message="URL Path must only contain lowercase letters, numbers and hyphens (-)")],
validators=[
DataRequired(),
Regexp("^[a-z0-9-]*$", message="URL Path must only contain lowercase letters, numbers and hyphens (-)"),
],
)
5 changes: 4 additions & 1 deletion app/blueprints/fund_builder/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,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
Expand All @@ -538,7 +539,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
Expand Down
37 changes: 23 additions & 14 deletions app/blueprints/templates/routes.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import json

from flask import Blueprint, request
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from werkzeug.utils import secure_filename

from app.blueprints.fund_builder.forms.templates import TemplateFormForm, TemplateUploadForm
from app.blueprints.fund_builder.forms.templates import TemplateFormForm
from app.blueprints.fund_builder.forms.templates import TemplateUploadForm
from app.db.models.application_config import Form
from app.db.queries.application import delete_form, get_all_template_forms, get_form_by_id, update_form
from app.db.queries.application import delete_form
from app.db.queries.application import get_all_template_forms
from app.db.queries.application import get_all_template_sections
from app.db.queries.application import get_form_by_id
from app.db.queries.application import get_form_by_template_name
from app.db.queries.application import update_form

# Blueprint for routes used by FAB PoC to manage templates
template_bp = Blueprint(
Expand Down Expand Up @@ -90,19 +95,23 @@ def view_templates():
def edit_form_template(form_id):
template_form = TemplateFormForm()
params = {
"breadcrumb_items": [
{"text": "Home", "href": url_for("build_fund_bp.index")},
{"text": "Manage Templates", "href": url_for("template_bp.view_templates")},
{"text": "Rename Template", "href": "#"}
],}
"breadcrumb_items": [
{"text": "Home", "href": url_for("build_fund_bp.index")},
{"text": "Manage Templates", "href": url_for("template_bp.view_templates")},
{"text": "Rename Template", "href": "#"},
],
}

if request.method == "POST":
if template_form.validate_on_submit():
update_form(form_id=form_id, new_form_config={
"runner_publish_name":template_form.url_path.data,
"name_in_apply_json":{"en": template_form.tasklist_name.data},
"template_name":template_form.template_name.data
})
update_form(
form_id=form_id,
new_form_config={
"runner_publish_name": template_form.url_path.data,
"name_in_apply_json": {"en": template_form.tasklist_name.data},
"template_name": template_form.template_name.data,
},
)
return redirect(url_for("template_bp.view_templates"))
params["template_form"] = template_form
return render_template("edit_form_template.html", **params)
Expand All @@ -118,5 +127,5 @@ def edit_form_template(form_id):
template_form.url_path.data = existing_form.runner_publish_name
params["template_form"] = template_form
return render_template("edit_form_template.html", **params)

return redirect(url_for("template_bp.view_templates"))
2 changes: 1 addition & 1 deletion app/blueprints/templates/templates/edit_form_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ <h1 class="govuk-heading-l">{{pageHeading}}</h1>
</form>
</fieldset>
</div>
{% endblock %}
{% endblock %}
34 changes: 19 additions & 15 deletions app/db/migrations/versions/~2024_10_16_1347-da30746cec39_.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,40 @@
Create Date: 2024-10-16 13:47:16.968837
"""
from alembic import op
import sqlalchemy as sa

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'da30746cec39'
down_revision = 'ca61d3b746f6'
revision = "da30746cec39"
down_revision = "ca61d3b746f6"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###

op.execute("ALTER TYPE componenttype ADD VALUE 'MULTI_INPUT_FIELD';")
with op.batch_alter_table('component', schema=None) as batch_op:
batch_op.add_column(sa.Column('children', postgresql.JSON(none_as_null=True, astext_type=sa.Text()), nullable=True))
batch_op.add_column(sa.Column('schema', postgresql.JSON(astext_type=sa.Text()), nullable=True))
with op.batch_alter_table('page', schema=None) as batch_op:
batch_op.add_column(sa.Column('options', postgresql.JSON(none_as_null=True, astext_type=sa.Text()), nullable=True))
with op.batch_alter_table("component", schema=None) as batch_op:
batch_op.add_column(
sa.Column("children", postgresql.JSON(none_as_null=True, astext_type=sa.Text()), nullable=True)
)
batch_op.add_column(sa.Column("schema", postgresql.JSON(astext_type=sa.Text()), nullable=True))
with op.batch_alter_table("page", schema=None) as batch_op:
batch_op.add_column(
sa.Column("options", postgresql.JSON(none_as_null=True, astext_type=sa.Text()), nullable=True)
)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('component', schema=None) as batch_op:
batch_op.drop_column('children')
batch_op.drop_column('schema')
with op.batch_alter_table('page', schema=None) as batch_op:
batch_op.drop_column('options')
with op.batch_alter_table("component", schema=None) as batch_op:
batch_op.drop_column("children")
batch_op.drop_column("schema")
with op.batch_alter_table("page", schema=None) as batch_op:
batch_op.drop_column("options")
# ### end Alembic commands ###
146 changes: 146 additions & 0 deletions app/db/migrations/versions/~2024_10_18_0344-ca6ccf18956b_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""empty message

Revision ID: ca6ccf18956b
Revises: da30746cec39
Create Date: 2024-10-18 03:44:16.320217

"""

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "ca6ccf18956b"
down_revision = "da30746cec39"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("page", schema=None) as batch_op:
batch_op.add_column(
sa.Column("section", postgresql.JSON(none_as_null=True, astext_type=sa.Text()), nullable=True)
)

with op.batch_alter_table("round", schema=None) as batch_op:
batch_op.alter_column(
"application_reminder_sent", existing_type=sa.BOOLEAN(), server_default=None, existing_nullable=False
)
batch_op.alter_column(
"reference_contact_page_over_email",
existing_type=sa.BOOLEAN(),
server_default=None,
existing_nullable=False,
)
batch_op.alter_column("support_times", existing_type=sa.VARCHAR(), server_default=None, existing_nullable=False)
batch_op.alter_column("support_days", existing_type=sa.VARCHAR(), server_default=None, existing_nullable=False)
batch_op.alter_column(
"project_name_field_id", existing_type=sa.VARCHAR(), server_default=None, existing_nullable=False
)
batch_op.alter_column(
"all_uploaded_documents_section_available",
existing_type=sa.BOOLEAN(),
server_default=None,
existing_nullable=False,
)
batch_op.alter_column(
"application_fields_download_available",
existing_type=sa.BOOLEAN(),
server_default=None,
existing_nullable=False,
)
batch_op.alter_column(
"display_logo_on_pdf_exports", existing_type=sa.BOOLEAN(), server_default=None, existing_nullable=False
)
batch_op.alter_column(
"mark_as_complete_enabled", existing_type=sa.BOOLEAN(), server_default=None, existing_nullable=False
)
batch_op.alter_column(
"is_expression_of_interest", existing_type=sa.BOOLEAN(), server_default=None, existing_nullable=False
)
batch_op.alter_column(
"section_base_path",
existing_type=sa.INTEGER(),
server_default=sa.text("nextval('section_base_path_seq')"),
existing_nullable=True,
)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("round", schema=None) as batch_op:
batch_op.alter_column(
"section_base_path",
existing_type=sa.INTEGER(),
server_default=sa.text("nextval('section_base_path_seq'::regclass)"),
existing_nullable=True,
)
batch_op.alter_column(
"is_expression_of_interest",
existing_type=sa.BOOLEAN(),
server_default=sa.text("false"),
existing_nullable=False,
)
batch_op.alter_column(
"mark_as_complete_enabled",
existing_type=sa.BOOLEAN(),
server_default=sa.text("false"),
existing_nullable=False,
)
batch_op.alter_column(
"display_logo_on_pdf_exports",
existing_type=sa.BOOLEAN(),
server_default=sa.text("false"),
existing_nullable=False,
)
batch_op.alter_column(
"application_fields_download_available",
existing_type=sa.BOOLEAN(),
server_default=sa.text("false"),
existing_nullable=False,
)
batch_op.alter_column(
"all_uploaded_documents_section_available",
existing_type=sa.BOOLEAN(),
server_default=sa.text("false"),
existing_nullable=False,
)
batch_op.alter_column(
"project_name_field_id",
existing_type=sa.VARCHAR(),
server_default=sa.text("''::character varying"),
existing_nullable=False,
)
batch_op.alter_column(
"support_days",
existing_type=sa.VARCHAR(),
server_default=sa.text("''::character varying"),
existing_nullable=False,
)
batch_op.alter_column(
"support_times",
existing_type=sa.VARCHAR(),
server_default=sa.text("''::character varying"),
existing_nullable=False,
)
batch_op.alter_column(
"reference_contact_page_over_email",
existing_type=sa.BOOLEAN(),
server_default=sa.text("false"),
existing_nullable=False,
)
batch_op.alter_column(
"application_reminder_sent",
existing_type=sa.BOOLEAN(),
server_default=sa.text("false"),
existing_nullable=False,
)

with op.batch_alter_table("page", schema=None) as batch_op:
batch_op.drop_column("section")

# ### end Alembic commands ###
5 changes: 3 additions & 2 deletions app/db/models/application_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class ComponentType(Enum):
FILE_UPLOAD_FIELD = "FileUploadField"
MONTH_YEAR_FIELD = "MonthYearField"
TIME_FIELD = "TimeField"
MULTI_INPUT_FIELD="MultiInputField"
MULTI_INPUT_FIELD = "MultiInputField"


READ_ONLY_COMPONENTS = [
Expand Down Expand Up @@ -165,7 +165,8 @@ class Page(BaseModel):
)
source_template_id = Column(UUID(as_uuid=True), nullable=True)
controller = Column(String(), nullable=True)
options=Column(JSON(none_as_null=True))
options = Column(JSON(none_as_null=True))
section = Column(JSON(none_as_null=True))

def __repr__(self):
return f"Page(/{self.display_path} - {self.name_in_apply_json['en']}, Components: {self.components})"
Expand Down
8 changes: 6 additions & 2 deletions app/export_config/generate_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def build_component(component: Component) -> dict:
"type": component.type.value if component.type else None,
"content": component.content,
"options": component.options or {},
"schema": {},
"schema": component.schema or {},
"title": component.title,
"name": component.runner_component_name,
}
Expand Down Expand Up @@ -109,7 +109,7 @@ def build_component(component: Component) -> dict:
built_component.update({"values": {"type": "listRef"}})

if component.type is ComponentType.MULTI_INPUT_FIELD:
built_component.update({"children":component.children})
built_component.update({"children": component.children})

return built_component

Expand All @@ -127,6 +127,8 @@ def build_page(page: Page = None) -> dict:
"title": page.name_in_apply_json["en"],
}
)
if page.section:
built_page.update({"section": page.section})
if page.options:
built_page.update({"options": page.options})
# Having a 'null' controller element breaks the form-json, needs to not be there if blank
Expand Down Expand Up @@ -269,6 +271,8 @@ def build_form_json(form: Form) -> dict:
# Build the basic page structure
for page in form.pages:
results["pages"].append(build_page(page=page))
if page.section:
results["sections"].append(page.section)

# start page is the page with the controller ending start.js
start_page = _find_page_by_controller(form.pages, "start.js")
Expand Down
Loading