Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam-W1 committed Aug 22, 2024
1 parent 6df417e commit a0a3685
Show file tree
Hide file tree
Showing 21 changed files with 1,717 additions and 74 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Script output directory
app/export/config_generator/output/
6 changes: 5 additions & 1 deletion app/all_questions/metadata_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,11 @@ def determine_title_and_text_for_component(
extract_from_html(soup, text)
update_wording_for_multi_input_fields(text)

if component["type"].casefold() in FIELD_TYPES_WITH_MAX_WORDS and not is_child:
if (
component["type"].casefold() in FIELD_TYPES_WITH_MAX_WORDS
and not is_child
and "maxWords" in component["options"]
):
text.append(f"(Max {component['options']['maxWords']} words)")

if "list" in component:
Expand Down
38 changes: 38 additions & 0 deletions app/blueprints/fund_builder/routes.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import json
import os
import shutil
from datetime import datetime
from random import randint

import requests
from flask import Blueprint
from flask import Response
from flask import after_this_request
from flask import flash
from flask import redirect
from flask import render_template
from flask import request
from flask import send_file
from flask import url_for

from app.all_questions.metadata_utils import generate_print_data_for_sections
Expand All @@ -25,6 +29,14 @@
from app.db.queries.round import get_round_by_id
from app.export.config_generator.generate_all_questions import print_html
from app.export.config_generator.generate_form import build_form_json
from app.export.config_generator.generate_fund_round_config import (
generate_application_display_config,
)
from app.export.config_generator.generate_fund_round_config import generate_fund_config
from app.export.config_generator.generate_fund_round_form_jsons import (
generate_form_jsons_for_round,
)
from app.export.config_generator.generate_fund_round_html import generate_all_round_html
from config import Config

# Blueprint for routes used by v1 of FAB - using the DB
Expand Down Expand Up @@ -207,3 +219,29 @@ def view_all_questions(round_id):
)
html = print_html(print_data)
return render_template("view_questions.html", round=round, fund=fund, question_html=html)


@build_fund_bp.route("/create_export_files/<round_id>", methods=["GET"])
def create_export_files(round_id):
generate_form_jsons_for_round(round_id)
generate_all_round_html(round_id)
generate_application_display_config(round_id)
generate_fund_config(round_id)
round_short_name = get_round_by_id(round_id).short_name

# Directory to zip
directory_to_zip = f"app/export/config_generator/output/{round_short_name}/"
# Output zip file path (temporary)
output_zip_path = f"app/export/config_generator/output/{round_short_name}.zip"

# Create a zip archive of the directory
shutil.make_archive(output_zip_path.replace(".zip", ""), "zip", directory_to_zip)

# Ensure the file is removed after sending it
@after_this_request
def remove_file(response):
os.remove(output_zip_path)
return response

# Return the zipped folder for the user to download
return send_file(output_zip_path, as_attachment=True, download_name=f"{round_short_name}.zip")
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ <h2 class="govuk-heading-m">{{ fund.short_name }} - {{ round.short_name }}</h2>
"classes": "govuk-button--secondary"
})
}}
{{ govukButton({
"text": "Create export files for round",
"href": url_for("build_fund_bp.create_export_files", round_id=round.round_id),
"classes": "govuk-button--secondary"
})
}}
{% for section in round.sections %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-full">
Expand Down
4 changes: 2 additions & 2 deletions app/blueprints/self_serve/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-m">What do you want to do?</h1>
<h2 class="govuk-heading-s">Template Setup</h2>
<h2 class="govuk-heading-s">Template Administration</h2>
<ul class="govuk-body">
<li class="govuk-body"><a class="govuk-link" href="{{ url_for('self_serve_bp.question')}}">Add a
Question</a></li>
Expand All @@ -38,7 +38,7 @@ <h2 class="govuk-heading-s">Template Setup</h2>
<li class="govuk-body"><a class="govuk-link" href="{{ url_for('self_serve_bp.section', template='edit') }}">Edit/Delete a
Section</a></li>
</ul>
<h2 class="govuk-heading-s">Fund Metadata</h2>
<h2 class="govuk-heading-s">Fund Configuration</h2>
<ul class="govuk-body">
<li class="govuk-body"><a class="govuk-link" href="{{url_for('build_fund_bp.fund')}}">Add a Fund</a></li>
<li class="govuk-body"><a class="govuk-link" href="{{url_for('build_fund_bp.round')}}">Add a Round</a></li>
Expand Down
34 changes: 34 additions & 0 deletions app/db/migrations/versions/~2024_08_19_1303-4ec629449867_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""empty message
Revision ID: 4ec629449867
Revises:
Create Date: 2024-08-19 13:03:32.320669
"""

from alembic import op

# revision identifiers, used by Alembic.
revision = "4ec629449867"
down_revision = "ab7d40d652d5"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.execute("ALTER TYPE componenttype ADD VALUE 'PARA';")
op.execute("ALTER TYPE componenttype ADD VALUE 'DATE_PARTS_FIELD';")
op.execute("ALTER TYPE componenttype ADD VALUE 'CHECKBOXES_FIELD';")
op.execute("ALTER TYPE componenttype ADD VALUE 'CLIENT_SIDE_FILE_UPLOAD_FIELD';")
op.execute("ALTER TYPE componenttype ADD VALUE 'WEBSITE_FIELD';")
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
# Note: PostgreSQL does not support removing values from enums directly.
# To fully support downgrade, consider creating a new enum without the values and swapping them,
# or use a workaround that suits your database version and requirements.
# ### end Alembic commands ###
pass
36 changes: 36 additions & 0 deletions app/db/migrations/versions/~2024_08_21_1123-ed84bb152ee3_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""empty message
Revision ID: ed84bb152ee3
Revises: 4ec629449867
Create Date: 2024-08-21 11:23:11.330243
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "ed84bb152ee3"
down_revision = "4ec629449867"
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("default_next_page_id", sa.UUID(), nullable=True))
batch_op.create_foreign_key(
batch_op.f("fk_page_default_next_page_id_page"), "page", ["default_next_page_id"], ["page_id"]
)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("page", schema=None) as batch_op:
batch_op.drop_constraint(batch_op.f("fk_page_default_next_page_id_page"), type_="foreignkey")
batch_op.drop_column("default_next_page_id")

# ### end Alembic commands ###
32 changes: 32 additions & 0 deletions app/db/migrations/versions/~2024_08_21_1528-117417bed885_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""empty message
Revision ID: 117417bed885
Revises: ed84bb152ee3
Create Date: 2024-08-21 15:28:58.583369
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "117417bed885"
down_revision = "ed84bb152ee3"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("lizt", schema=None) as batch_op:
batch_op.add_column(sa.Column("title", sa.String(), nullable=True))

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("lizt", schema=None) as batch_op:
batch_op.drop_column("title")

# ### end Alembic commands ###
7 changes: 7 additions & 0 deletions app/db/models/application_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ class ComponentType(Enum):
HTML = "Html"
YES_NO_FIELD = "YesNoField"
RADIOS_FIELD = "RadiosField"
PARA = "Para"
DATE_PARTS_FIELD = "DatePartsField"
CHECKBOXES_FIELD = "CheckboxesField"
CLIENT_SIDE_FILE_UPLOAD_FIELD = "ClientSideFileUploadField"
WEBSITE_FIELD = "WebsiteField"


@dataclass
Expand Down Expand Up @@ -122,6 +127,7 @@ class Page(BaseModel):
audit_info = Column(JSON(none_as_null=True))
form_index = Column(Integer())
display_path = Column(String())
default_next_page_id = Column(UUID(as_uuid=True), ForeignKey("page.page_id"), nullable=True)
components: Mapped[List["Component"]] = relationship(
"Component",
order_by="Component.page_index",
Expand Down Expand Up @@ -154,6 +160,7 @@ class Lizt(BaseModel):
default=uuid.uuid4,
)
name = Column(String())
title = Column(String())
type = Column(String())
items = Column(JSON())
is_template = Column(Boolean, default=False, nullable=False)
Expand Down
83 changes: 53 additions & 30 deletions app/export/config_generator/generate_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"title": None,
"components": [],
"next": [],
"options": {},
}


Expand Down Expand Up @@ -90,7 +89,9 @@ def build_component(component: Component) -> dict:
"hint": component.hint_text or "",
"schema": {},
"name": component.runner_component_name,
"metadata": {"fund_builder_id": str(component.component_id)},
"metadata": {
# "fund_builder_id": str(component.component_id) TODO why do we need this?
},
}
# add a reference to the relevant list if this component use a list
if component.lizt:
Expand Down Expand Up @@ -133,22 +134,23 @@ def build_page(page: Page = None, page_display_path: str = None) -> dict:

# Goes through the set of pages and updates the conditions and next properties to account for branching
def build_navigation(partial_form_json: dict, input_pages: list[Page]) -> dict:
# TODO order by index not order in list
# Think this is sorted now that the collection is sorted by index, but needs testing
for i in range(0, len(input_pages)):
if i < len(input_pages) - 1:
next_path = input_pages[i + 1].display_path
elif i == len(input_pages) - 1:
next_path = "summary"
for page in input_pages:
if page.controller and page.controller.endswith("summary.js"):
continue
next_page_id = page.default_next_page_id
if next_page_id:
find_next_page = lambda id: next(p for p in input_pages if p.page_id == id) # noqa:E731
next_page = find_next_page(next_page_id)
next_path = next_page.display_path
else:
# all page paths are conditionals which will be processed later
next_path = None

this_page = input_pages[i]
this_page_in_results = next(p for p in partial_form_json["pages"] if p["path"] == f"/{this_page.display_path}")
# find page in prepared output results
this_page_in_results = next(p for p in partial_form_json["pages"] if p["path"] == f"/{page.display_path}")

has_conditions = False

for component in this_page.components:
for component in page.components:
if not component.conditions:
continue
form_json_conditions = build_conditions(component)
Expand All @@ -159,18 +161,18 @@ def build_navigation(partial_form_json: dict, input_pages: list[Page]) -> dict:
if condition["destination_page_path"] == "CONTINUE":
destination_path = f"/{next_path}"
else:
destination_path = f"/{condition['destination_page_path']}"

destination_path = f"/{condition['destination_page_path'].lstrip('/')}"
# TODO No longer needed since db schema change?
# If this points to a pre-built page flow, add that in now (it won't be in the input)
if (
destination_path not in [page["path"] for page in partial_form_json["pages"]]
and not destination_path == "/summary"
):
sub_page = build_page(page_display_path=destination_path[1:])
if not sub_page.get("next", None):
sub_page["next"] = [{"path": f"/{next_path}"}]
# if (
# destination_path not in [page["path"] for page in partial_form_json["pages"]]
# and not destination_path == "/summary"
# ):
# sub_page = build_page(page_display_path=destination_path[1:])
# if not sub_page.get("next", None):
# sub_page["next"] = [{"path": f"/{next_path}"}]

partial_form_json["pages"].append(sub_page)
# partial_form_json["pages"].append(sub_page)

this_page_in_results["next"].append(
{
Expand All @@ -180,8 +182,10 @@ def build_navigation(partial_form_json: dict, input_pages: list[Page]) -> dict:
)

# If there were no conditions we just continue to the next page
if not has_conditions:
if not has_conditions and next_path:
this_page_in_results["next"].append({"path": f"/{next_path}"})
if not has_conditions and not next_path:
this_page_in_results["next"].append({"path": "/summary"})

return partial_form_json

Expand All @@ -193,12 +197,24 @@ def build_lists(pages: list[dict]) -> list:
for component in page["components"]:
if component.get("list"):
list_from_db = get_list_by_id(component["metadata"]["fund_builder_list_id"])
list = {"type": list_from_db.type, "items": list_from_db.items, "name": list_from_db.name}
list = {
"type": list_from_db.type,
"items": list_from_db.items,
"name": list_from_db.name,
"title": list_from_db.title,
}
lists.append(list)
# Remove the metadata key from built_component (no longer needed)
component.pop("metadata", None) # The second argument prevents KeyError if 'metadata' is not found

return lists


def _find_page_by_controller(pages, controller_name) -> dict:

return next((p for p in pages if p.controller and p.controller.endswith(controller_name)), None)


def build_start_page(content: str, form: Form) -> dict:
"""
Builds the start page which contains just an html component comprising a bullet
Expand Down Expand Up @@ -252,10 +268,15 @@ def build_form_json(form: Form) -> dict:
for page in form.pages:
results["pages"].append(build_page(page=page))

# Create the start page
start_page = build_start_page(content=None, form=form)
results["pages"].append(start_page)
results["startPage"] = start_page["path"]
# start page is the page with the controller ending start.js
start_page = _find_page_by_controller(form.pages, "start.js")
if start_page:
results["startPage"] = f"/{start_page.display_path}"
else:
# Create the start page
start_page = build_start_page(content=None, form=form)
results["pages"].append(start_page)
results["startPage"] = start_page["path"]

# Build navigation and add any pages from branching logic
results = build_navigation(results, form.pages)
Expand All @@ -264,6 +285,8 @@ def build_form_json(form: Form) -> dict:
results["lists"] = build_lists(results["pages"])

# Add on the summary page
results["pages"].append(SUMMARY_PAGE)
summary_page = _find_page_by_controller(form.pages, "summary.js")
if not summary_page:
results["pages"].append(SUMMARY_PAGE)

return results
Loading

0 comments on commit a0a3685

Please sign in to comment.