diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1f849c0..456369a 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,15 +10,31 @@
"request": "launch",
"module": "flask",
"env": {
- "FLASK_APP": "app.app.py",
- "FLASK_DEBUG": "1"
+ "FLASK_APP": "app.app.py",
+ "FLASK_DEBUG": "1"
},
- "args": [
- "run",
- "--debug"
- ],
+ "args": ["run", "--debug"],
"jinja": true,
"autoStartBrowser": false
- }
+ },
+ {
+ "name": "Docker Runner FAB",
+ "type": "python",
+ "request": "attach",
+ "connect": {
+ "host": "localhost",
+ "port": 5686
+ },
+ "pathMappings": [
+ {
+ "localRoot": "${workspaceFolder:funding-service-design-fund-application-builder}",
+ "remoteRoot": "."
+ }
+ ],
+ "justMyCode": true
+ }
+ ]
+ }
+
]
}
diff --git a/app/blueprints/self_serve/data/data_access.py b/app/blueprints/self_serve/data/data_access.py
index 853aaff..499aebe 100644
--- a/app/blueprints/self_serve/data/data_access.py
+++ b/app/blueprints/self_serve/data/data_access.py
@@ -3,6 +3,7 @@
from app.blueprints.self_serve.data.not_a_db import LISTS
from app.blueprints.self_serve.data.not_a_db import PAGES
from app.blueprints.self_serve.data.not_a_db import SECTIONS
+from app.db.queries.application import insert_new_section
saved_responses = []
saved_sections = {}
@@ -66,20 +67,134 @@ def get_list_by_id(id: str) -> dict:
return LISTS.get(id, None)
-def save_question(question: dict):
+# TODO Implement front end journey that can use the section/form/page/component CRUD operations
+# from app.db.queries.application import insert_new_section
+# from app.db.queries.application import insert_new_form
+# from app.db.queries.application import insert_new_page
+# from app.db.queries.application import insert_new_component
+
+
+def save_template_component(component: dict):
+ """
+ TODO:
+ Save a template component to the database
+ Parameters:
+ component: dict The component to save to the database as a template
+ Returns:
+ dict The saved component
+
+ component_config = {
+ "page_id": component.get("page_id"),
+ "theme_id": component.get("theme_id"),
+ "title": component.get("title"),
+ "hint_text": component.get("hint"),
+ "options": component.get("options"),
+ "type": component.get("question_type"),
+ "template_name": component.get("template_name"),
+ "is_template": True,
+ "audit_info": component.get("audit_info"),
+ "page_index": component.get("page_index"),
+ "theme_index": component.get("theme_index"),
+ "conditions": component.get("conditions"),
+ "runner_component_name": component.get("runner_component_name"),
+ "list_id": component.get("list_id"),
+ }
+
+ return insert_new_component(component_config)
+ """
+
+ # temp in memory solution
COMPONENTS.append(
{
"json_snippet": {
"options": {},
- "type": question["question_type"],
- "title": question["title"],
- "hint": question["hint"],
+ "type": component["question_type"],
+ "title": component["title"],
+ "hint": component["hint"],
},
- "id": question["id"],
- "builder_display_name": question["builder_display_name"],
+ "id": component["id"],
+ "builder_display_name": component["builder_display_name"],
}
)
-def save_section(section: dict):
+def save_template_page(page: dict):
+ """
+ TODO:
+ Save a template page to the database
+ Parameters:
+ page: dict The page to save to the database as a template
+ Returns:
+ dict The saved page
+
+ page_config = {
+ "form_id": page.get("form_id"),
+ "name_in_apply_json": {
+ "en": page.get("form_display_name"),
+ },
+ "template_name": page.get("builder_display_name"),
+ "is_template": True,
+ "audit_info": page.get("audit_info"),
+ "form_index": page.get("form_index"),
+ "display_path": page.get("display_path"),
+ "controller": page.get("controller"),
+ }
+
+ return insert_new_page(page_config)
+ """
+
+ # Temp in memory solution
+ PAGES.append(page)
+
+
+def save_template_form(form: dict):
+ """
+ TODO:
+ Save a template form to the database
+ Parameters:
+ form: dict The form to save to the database as a template
+ Returns:
+ dict The saved form
+ form_config = {
+ "name_in_apply_json": {
+ "en": form.get("form_title"),
+ },
+ "is_template": True,
+ "template_name": form.get("builder_display_name"),
+ "audit_info": form.get("audit_info"),
+ "section_id": form.get("section_id"),
+ "section_index": form.get("section_index"),
+ "runner_publish_name": None # This is a template
+ }
+
+ insert_new_form(form_config)
+ """
+
+ # Temp in memory solution
+ FORMS.append(form)
+
+
+def save_template_section(section: dict):
+ """
+ TODO:
+ Save a template section to the database
+ Parameters:
+ section: dict The section to save to the database as a template
+ Returns:
+ dict The saved section
+
+ section_config = {
+ "name_in_apply_json": {
+ "en": section.get("section_display_name"),
+ },
+ "is_template": True, # Assuming this remains a constant value
+ "template_name": section.get("builder_display_name"),
+ "description": section.get("description"),
+ "audit_info": section.get("audit_info"),
+ }
+
+ return insert_new_section(section_config)
+ """
+
+ # Temp in memory solution
SECTIONS.append(section)
diff --git a/app/blueprints/self_serve/routes.py b/app/blueprints/self_serve/routes.py
index 96afaaa..8fb5125 100644
--- a/app/blueprints/self_serve/routes.py
+++ b/app/blueprints/self_serve/routes.py
@@ -15,10 +15,10 @@
from app.blueprints.self_serve.data.data_access import get_component_by_name
from app.blueprints.self_serve.data.data_access import get_pages_to_display_in_builder
from app.blueprints.self_serve.data.data_access import get_saved_forms
-from app.blueprints.self_serve.data.data_access import save_form
-from app.blueprints.self_serve.data.data_access import save_page
-from app.blueprints.self_serve.data.data_access import save_question
-from app.blueprints.self_serve.data.data_access import save_section
+from app.blueprints.self_serve.data.data_access import save_template_component
+from app.blueprints.self_serve.data.data_access import save_template_form
+from app.blueprints.self_serve.data.data_access import save_template_page
+from app.blueprints.self_serve.data.data_access import save_template_section
from app.blueprints.self_serve.forms.form_form import FormForm
from app.blueprints.self_serve.forms.page_form import PageForm
from app.blueprints.self_serve.forms.question_form import QuestionForm
@@ -43,38 +43,6 @@ def index():
return render_template("index.html")
-@self_serve_bp.route("/build_form", methods=["GET", "POST"])
-def build_form():
- form = FormForm()
- if form.validate_on_submit():
- new_form = {
- "builder_display_name": form.builder_display_name.data,
- "start_page_guidance": form.start_page_guidance.data,
- "form_display_name": form.form_title.data,
- "id": human_to_kebab_case(form.form_title.data),
- "pages": form.selected_pages.data,
- }
- save_form(new_form)
- flash(message=f'Form {new_form["form_display_name"]} was saved')
- return redirect(url_for("self_serve_bp.index"))
-
- available_pages = []
- pages = get_pages_to_display_in_builder()
- for page in pages:
- questions = [
- x["json_snippet"]["title"] if (x := get_component_by_name(comp_name)) else comp_name
- for comp_name in page["component_names"]
- ]
- available_pages.append(
- {
- "id": page["id"],
- "display_name": page["builder_display_name"],
- "hover_info": {"title": page["form_display_name"], "questions": questions},
- }
- )
- return render_template("build_form.html", available_pages=available_pages, form=form)
-
-
@self_serve_bp.route("/download_json", methods=["POST"])
def generate_json():
form_json = generate_form_config_from_request()["form_json"]
@@ -129,11 +97,40 @@ def view_form_questions():
return render_template("view_questions.html", section_name=form_config["title"], question_html=html)
-@self_serve_bp.route("build_section", methods=["GET", "POST"])
-def build_section():
+@self_serve_bp.route("/section_questions", methods=["POST"])
+def view_section_questions():
+ # form_config = generate_form_config_from_request()
+ # print_data = generate_print_data_for_sections(
+ # sections=[
+ # {
+ # "section_title": form_config["title"],
+ # "forms": [{"name": form_config["form_id"], "form_data": form_config["form_json"]}],
+ # }
+ # ],
+ # lang="en",
+ # )
+ # html = print_html(print_data)
+ # return render_template("view_questions.html", section_name=form_config["title"], question_html=html)
+ pass
+
+
+# CRUD routes
+
+
+# Create routes
+@self_serve_bp.route("section", methods=["GET", "POST", "PUT", "DELETE"])
+def section():
+ # TODO: Create frontend routes and connect to middleware
+ if request.method == "GET":
+ pass
+ if request.method == "PUT":
+ pass
+ if request.method == "DELETE":
+ pass
+
form = SectionForm()
if form.validate_on_submit():
- save_section(form.as_dict())
+ save_template_section(form.as_dict())
flash(message=f"Section '{form['builder_display_name'].data}' was saved")
return redirect(url_for("self_serve_bp.index"))
@@ -147,22 +144,60 @@ def build_section():
"hover_info": {"title": f["builder_display_name"], "pages": f["pages"]},
}
)
- return render_template("build_section.html", available_forms=available_forms, form=form)
+ # save to db here
+ return render_template("create_section.html", available_forms=available_forms, form=form)
-@self_serve_bp.route("/add_question", methods=["GET", "POST"])
-def add_question():
- form = QuestionForm()
- question = form.as_dict()
+@self_serve_bp.route("/form", methods=["GET", "POST", "PUT", "DELETE"])
+def form():
+ # TODO: Create frontend routes and connect to middleware
+ if request.method == "GET":
+ pass
+ if request.method == "PUT":
+ pass
+ if request.method == "DELETE":
+ pass
+
+ form = FormForm()
if form.validate_on_submit():
- save_question(question)
- flash(message=f"Question '{question['title']}' was saved")
+ new_form = {
+ "builder_display_name": form.builder_display_name.data,
+ "start_page_guidance": form.start_page_guidance.data,
+ "form_display_name": form.form_title.data,
+ "id": human_to_kebab_case(form.form_title.data),
+ "pages": form.selected_pages.data,
+ }
+ save_template_form(new_form)
+ flash(message=f'Form {new_form["form_display_name"]} was saved')
return redirect(url_for("self_serve_bp.index"))
- return render_template("add_question.html", form=form)
+ available_pages = []
+ pages = get_pages_to_display_in_builder()
+ for page in pages:
+ questions = [
+ x["json_snippet"]["title"] if (x := get_component_by_name(comp_name)) else comp_name
+ for comp_name in page["component_names"]
+ ]
+ available_pages.append(
+ {
+ "id": page["id"],
+ "display_name": page["builder_display_name"],
+ "hover_info": {"title": page["form_display_name"], "questions": questions},
+ }
+ )
+ return render_template("create_form.html", available_pages=available_pages, form=form)
+
+
+@self_serve_bp.route("/page", methods=["GET", "POST", "PUT", "DELETE"])
+def page():
+ # TODO: Create frontend routes and connect to middleware
+ if request.method == "GET":
+ pass
+ if request.method == "PUT":
+ pass
+ if request.method == "DELETE":
+ pass
-@self_serve_bp.route("/build_page", methods=["GET", "POST"])
-def build_page():
form = PageForm()
if form.validate_on_submit():
new_page = {
@@ -172,7 +207,7 @@ def build_page():
"component_names": form.selected_components.data,
"show_in_builder": True,
}
- save_page(new_page)
+ save_template_page(new_page)
flash(message=f"Page '{form.builder_display_name.data}' was saved")
return redirect(url_for("self_serve_bp.index"))
components = get_all_components()
@@ -184,21 +219,23 @@ def build_page():
}
for c in components
]
- return render_template("build_page.html", form=form, available_questions=available_questions)
+ return render_template("create_page.html", form=form, available_questions=available_questions)
-@self_serve_bp.route("/section_questions", methods=["POST"])
-def view_section_questions():
- # form_config = generate_form_config_from_request()
- # print_data = generate_print_data_for_sections(
- # sections=[
- # {
- # "section_title": form_config["title"],
- # "forms": [{"name": form_config["form_id"], "form_data": form_config["form_json"]}],
- # }
- # ],
- # lang="en",
- # )
- # html = print_html(print_data)
- # return render_template("view_questions.html", section_name=form_config["title"], question_html=html)
- pass
+@self_serve_bp.route("/question", methods=["GET", "PUT", "POST", "DELETE"])
+def question():
+ # TODO: Create frontend routes and connect to middleware
+ if request.method == "GET":
+ pass
+ if request.method == "PUT":
+ pass
+ if request.method == "DELETE":
+ pass
+
+ form = QuestionForm()
+ question = form.as_dict()
+ if form.validate_on_submit():
+ save_template_component(question)
+ flash(message=f"Question '{question['title']}' was saved")
+ return redirect(url_for("self_serve_bp.index"))
+ return render_template("create_question.html", form=form)
diff --git a/app/blueprints/self_serve/templates/build_form.html b/app/blueprints/self_serve/templates/create_form.html
similarity index 99%
rename from app/blueprints/self_serve/templates/build_form.html
rename to app/blueprints/self_serve/templates/create_form.html
index 12ba792..aaa2ce3 100644
--- a/app/blueprints/self_serve/templates/build_form.html
+++ b/app/blueprints/self_serve/templates/create_form.html
@@ -208,7 +208,7 @@
}
function saveForm() {
section_form = document.getElementById("form_form")
- section_form.setAttribute("action", "{{url_for('self_serve_bp.build_form')}}")
+ section_form.setAttribute("action", "{{url_for('self_serve_bp.create_form')}}")
section_form.submit()
}
diff --git a/app/blueprints/self_serve/templates/build_page.html b/app/blueprints/self_serve/templates/create_page.html
similarity index 99%
rename from app/blueprints/self_serve/templates/build_page.html
rename to app/blueprints/self_serve/templates/create_page.html
index ff95f02..6b73cb5 100644
--- a/app/blueprints/self_serve/templates/build_page.html
+++ b/app/blueprints/self_serve/templates/create_page.html
@@ -211,7 +211,7 @@
}
function savePage() {
section_form = document.getElementById("page_form")
- section_form.setAttribute("action", "{{url_for('self_serve_bp.build_page')}}")
+ section_form.setAttribute("action", "{{url_for('self_serve_bp.create_page')}}")
section_form.submit()
}
diff --git a/app/blueprints/self_serve/templates/add_question.html b/app/blueprints/self_serve/templates/create_question.html
similarity index 100%
rename from app/blueprints/self_serve/templates/add_question.html
rename to app/blueprints/self_serve/templates/create_question.html
diff --git a/app/blueprints/self_serve/templates/build_section.html b/app/blueprints/self_serve/templates/create_section.html
similarity index 99%
rename from app/blueprints/self_serve/templates/build_section.html
rename to app/blueprints/self_serve/templates/create_section.html
index e385e05..d79fd61 100644
--- a/app/blueprints/self_serve/templates/build_section.html
+++ b/app/blueprints/self_serve/templates/create_section.html
@@ -207,7 +207,7 @@
}
function saveSection() {
section_form = document.getElementById("section_form")
- section_form.setAttribute("action", "{{url_for('self_serve_bp.build_section')}}")
+ section_form.setAttribute("action", "{{url_for('self_serve_bp.section')}}")
section_form.submit()
}
diff --git a/app/blueprints/self_serve/templates/index.html b/app/blueprints/self_serve/templates/index.html
index 212a18f..1c831d4 100644
--- a/app/blueprints/self_serve/templates/index.html
+++ b/app/blueprints/self_serve/templates/index.html
@@ -19,18 +19,24 @@
What do you want to do?
-
Application Setup
+
Template Setup
Fund Metadata
diff --git a/app/db/models/application_config.py b/app/db/models/application_config.py
index ad59dde..8bb6001 100644
--- a/app/db/models/application_config.py
+++ b/app/db/models/application_config.py
@@ -54,7 +54,7 @@ class Section(BaseModel):
is_template = Column(Boolean, default=False, nullable=False)
audit_info = Column(JSON(none_as_null=True))
forms: Mapped[List["Form"]] = relationship(
- "Form", order_by="Form.section_index", collection_class=ordering_list("section_index")
+ "Form", order_by="Form.section_index", collection_class=ordering_list("section_index"), passive_deletes="all"
)
index = Column(Integer())
source_template_id = Column(UUID(as_uuid=True), nullable=True)
@@ -85,7 +85,7 @@ class Form(BaseModel):
audit_info = Column(JSON(none_as_null=True))
section_index = Column(Integer())
pages: Mapped[List["Page"]] = relationship(
- "Page", order_by="Page.form_index", collection_class=ordering_list("form_index")
+ "Page", order_by="Page.form_index", collection_class=ordering_list("form_index"), passive_deletes="all"
)
runner_publish_name = Column(db.String())
source_template_id = Column(UUID(as_uuid=True), nullable=True)
@@ -117,7 +117,10 @@ class Page(BaseModel):
form_index = Column(Integer())
display_path = Column(String())
components: Mapped[List["Component"]] = relationship(
- "Component", order_by="Component.page_index", collection_class=ordering_list("page_index")
+ "Component",
+ order_by="Component.page_index",
+ collection_class=ordering_list("page_index"),
+ passive_deletes="all",
)
source_template_id = Column(UUID(as_uuid=True), nullable=True)
controller = Column(String(), nullable=True)
diff --git a/app/db/queries/application.py b/app/db/queries/application.py
index 727c651..c8ce85f 100644
--- a/app/db/queries/application.py
+++ b/app/db/queries/application.py
@@ -199,3 +199,285 @@ def clone_single_round(round_id, new_fund_id, new_short_name) -> Round:
clone_single_section(section.section_id, cloned_round.round_id)
return cloned_round
+
+
+# CRUD operations for Section, Form, Page, and Component
+# CRUD SECTION
+def insert_new_section(new_section_config):
+ """
+ Inserts a section object based on the provided configuration.
+
+ Parameters:
+ new_section_config (dict): A dictionary containing the configuration for the new section.
+ new_section_config keys:
+ - round_id (str): The ID of the round to which the section belongs.
+ - name_in_apply_json (dict): The name of the section as it will be in the Application JSON (support multiple languages/keys).
+ - template_name (str): The name of the template.
+ - is_template (bool): A flag indicating whether the section is a template.
+ - source_template_id (str): The ID of the source template.
+ - audit_info (dict): Audit information for the section.
+ - index (int): The index of the section.
+ Returns:
+ Section: The newly created section object.
+ """
+ section = Section(
+ section_id=uuid4(),
+ round_id=new_section_config.get("round_id", None),
+ name_in_apply_json=new_section_config.get("name_in_apply_json"),
+ template_name=new_section_config.get("template_name", None),
+ is_template=new_section_config.get("is_template", False),
+ source_template_id=new_section_config.get("source_template_id", None),
+ audit_info=new_section_config.get("audit_info", {}),
+ index=new_section_config.get("index"),
+ )
+ db.session.add(section)
+ db.session.commit()
+ return section
+
+
+def update_section(section_id, new_section_config):
+ section = db.session.query(Section).where(Section.section_id == section_id).one_or_none()
+ if section:
+ # Define a list of allowed keys to update
+ allowed_keys = ["round_id", "name_in_apply_json", "template_name", "is_template", "audit_info", "index"]
+
+ for key, value in new_section_config.items():
+ # Update the section if the key is allowed
+ if key in allowed_keys:
+ setattr(section, key, value)
+
+ db.session.commit()
+ return section
+
+
+def delete_section(section_id):
+ section = db.session.query(Section).where(Section.section_id == section_id).one_or_none()
+ db.session.delete(section)
+ db.session.commit()
+ return section
+
+
+# CRUD FORM
+def insert_new_form(new_form_config):
+ """
+ Inserts a form object based on the provided configuration.
+
+ Parameters:
+ new_form_config (dict): A dictionary containing the configuration for the new form.
+ new_form_config keys:
+ - section_id (str): The ID of the section to which the form belongs.
+ - name_in_apply_json (dict): The name of the form as it will be in the Application JSON (support multiple languages/keys).
+ - is_template (bool): A flag indicating whether the form is a template.
+ - template_name (str): The name of the template.
+ - source_template_id (str): The ID of the source template.
+ - audit_info (dict): Audit information for the form.
+ - section_index (int): The index of the form within the section.
+ - runner_publish_name (bool): The path of the form in the form runner (kebab case).
+ Returns:
+ Form: The newly created form object.
+ """
+
+ form = Form(
+ form_id=uuid4(),
+ section_id=new_form_config.get("section_id", None),
+ name_in_apply_json=new_form_config.get("name_in_apply_json"),
+ is_template=new_form_config.get("is_template", False),
+ template_name=new_form_config.get("template_name", None),
+ source_template_id=new_form_config.get("source_template_id", None),
+ audit_info=new_form_config.get("audit_info", {}),
+ section_index=new_form_config.get("section_index"),
+ runner_publish_name=new_form_config.get("runner_publish_name", None),
+ )
+ db.session.add(form)
+ db.session.commit()
+ return form
+
+
+def update_form(form_id, new_form_config):
+ form = db.session.query(Form).where(Form.form_id == form_id).one_or_none()
+ if form:
+ # Define a list of allowed keys to update
+ allowed_keys = [
+ "section_id",
+ "name_in_apply_json",
+ "template_name",
+ "is_template",
+ "audit_info",
+ "section_index",
+ "runner_publish_name",
+ ]
+
+ # Iterate over the new_form_config dictionary
+ for key, value in new_form_config.items():
+ # Update the form if the key is allowed
+ if key in allowed_keys:
+ setattr(form, key, value)
+
+ db.session.commit()
+ return form
+
+
+def delete_form(form_id):
+ form = db.session.query(Form).where(Form.form_id == form_id).one_or_none()
+ db.session.delete(form)
+ db.session.commit()
+ return form
+
+
+# CRUD PAGE
+def insert_new_page(new_page_config):
+ """
+ Inserts a page object based on the provided configuration.
+
+ Parameters:
+ new_page_config (dict): A dictionary containing the configuration for the new page.
+ new_page_config keys:
+ - form_id (str): The ID of the form to which the page belongs.
+ - name_in_apply_json (str): The name of the page as it will be in the Application JSON.
+ - template_name (str): The name of the template.
+ - is_template (bool): A flag indicating whether the page is a template.
+ - source_template_id (str): The ID of the source template.
+ - audit_info (dict): Audit information for the page.
+ - form_index (int): The index of the page within the form.
+ - display_path (str): The form runner display path of the page (kebab case).
+ - controller (str): The form runner controller path for the page (e.g. './pages/summary.js').
+ Returns:
+ Page: The newly created page object.
+ """
+ page = Page(
+ page_id=uuid4(),
+ form_id=new_page_config.get("form_id", None),
+ name_in_apply_json=new_page_config.get("name_in_apply_json"),
+ template_name=new_page_config.get("template_name", None),
+ is_template=new_page_config.get("is_template", False),
+ source_template_id=new_page_config.get("source_template_id", None),
+ audit_info=new_page_config.get("audit_info", {}),
+ form_index=new_page_config.get("form_index"),
+ display_path=new_page_config.get("display_path"),
+ controller=new_page_config.get("controller", None),
+ )
+ db.session.add(page)
+ db.session.commit()
+ return page
+
+
+def update_page(page_id, new_page_config):
+ page = db.session.query(Page).where(Page.page_id == page_id).one_or_none()
+ if page:
+ # Define a list of allowed keys to update
+ allowed_keys = [
+ "form_id",
+ "name_in_apply_json",
+ "template_name",
+ "is_template",
+ "audit_info",
+ "form_index",
+ "display_path",
+ "controller",
+ ]
+
+ for key, value in new_page_config.items():
+ # Update the page if the key is allowed
+ if key in allowed_keys:
+ setattr(page, key, value)
+
+ db.session.commit()
+ return page
+
+
+def delete_page(page_id):
+ page = db.session.query(Page).where(Page.page_id == page_id).one_or_none()
+ db.session.delete(page)
+ db.session.commit()
+ return page
+
+
+# CRUD COMPONENT
+def insert_new_component(new_component_config: dict):
+ """
+ Inserts a component object based on the provided configuration.
+
+ Parameters:
+ new_component_config (dict): A dictionary containing the configuration for the new component.
+ new_component_config keys:
+ - page_id (str): The ID of the page to which the component belongs.
+ - theme_id (str): The ID of the theme to which the component belongs.
+ - title (str): The title of the component.
+ - hint_text (str): The hint text for the component.
+ - options (dict): The options such as classes, prefix etc
+ - type (str): The type of the component.
+ - template_name (str): The name of the template.
+ - is_template (bool): A flag indicating whether the component is a template.
+ - source_template_id (str): The ID of the source template.
+ - audit_info (dict): Audit information for the component.
+ - page_index (int): The index of the component within the page.
+ - theme_index (int): The index of the component within the theme.
+ - conditions (dict): The conditions such as potential routes based on the components value (can specify page path).
+ - runner_component_name (str): The name of the runner component.
+ - list_id (str): The ID of the list to which the component belongs.
+ Returns:
+ Component: The newly created component object.
+ """
+ # Instantiate the Component object with the provided and default values
+ component = Component(
+ component_id=uuid4(),
+ page_id=new_component_config.get("page_id", None),
+ theme_id=new_component_config.get("theme_id", None),
+ title=new_component_config.get("title"),
+ hint_text=new_component_config.get("hint_text"),
+ options=new_component_config.get("options", {}),
+ type=new_component_config.get("type"),
+ is_template=new_component_config.get("is_template", False),
+ template_name=new_component_config.get("template_name", None),
+ source_template_id=new_component_config.get("source_template_id", None),
+ audit_info=new_component_config.get("audit_info", {}),
+ page_index=new_component_config.get("page_index"),
+ theme_index=new_component_config.get("theme_index"),
+ conditions=new_component_config.get("conditions", []),
+ runner_component_name=new_component_config.get("runner_component_name"),
+ list_id=new_component_config.get("list_id", None),
+ )
+
+ # Add the component to the session and commit
+ db.session.add(component)
+ db.session.commit()
+
+ # Return the created component object or its ID based on your requirements
+ return component
+
+
+def update_component(component_id, new_component_config):
+ component = db.session.query(Component).where(Component.component_id == component_id).one_or_none()
+ if component:
+ # Define a list of allowed keys to update to prevent updating unintended fields
+ allowed_keys = [
+ "page_id",
+ "theme_id",
+ "title",
+ "hint_text",
+ "options",
+ "type",
+ "template_name",
+ "is_template",
+ "audit_info",
+ "page_index",
+ "theme_index",
+ "conditions",
+ "runner_component_name",
+ "list_id",
+ ]
+
+ for key, value in new_component_config.items():
+ # Update the component if the key is allowed
+ if key in allowed_keys:
+ setattr(component, key, value)
+
+ db.session.commit()
+ return component
+
+
+def delete_component(component_id):
+ component = db.session.query(Component).where(Component.component_id == component_id).one_or_none()
+ db.session.delete(component)
+ db.session.commit()
+ return component
diff --git a/build.py b/build.py
new file mode 100644
index 0000000..17d4ece
--- /dev/null
+++ b/build.py
@@ -0,0 +1,94 @@
+import glob
+import os
+import shutil
+import urllib.request
+import zipfile
+
+import static_assets
+
+
+def build_govuk_assets(static_dist_root="app/static/dist"):
+ DIST_ROOT = "./" + static_dist_root
+ GOVUK_DIR = "/govuk-frontend"
+ GOVUK_URL = "https://github.com/alphagov/govuk-frontend/releases/download/v5.4.0/release-v5.4.0.zip"
+ ZIP_FILE = "./govuk_frontend.zip"
+ DIST_PATH = DIST_ROOT + GOVUK_DIR
+ ASSETS_DIR = "/assets"
+ ASSETS_PATH = DIST_PATH + ASSETS_DIR
+
+ # Checks if GovUK Frontend Assets already built
+ if os.path.exists(DIST_PATH):
+ print("GovUK Frontend assets already built.If you require a rebuild manually run build.build_govuk_assets")
+ return True
+
+ # Download zips from GOVUK_URL
+ # There is a known problem on Mac where one must manually
+ # run the script "Install Certificates.command" found
+ # in the python application folder for this to work.
+
+ print("Downloading static file zip.")
+ urllib.request.urlretrieve(GOVUK_URL, ZIP_FILE) # nosec
+
+ # Attempts to delete the old files, states if
+ # one doesn't exist.
+
+ print("Deleting old " + DIST_PATH)
+ try:
+ shutil.rmtree(DIST_PATH)
+ except FileNotFoundError:
+ print("No old " + DIST_PATH + " to remove.")
+
+ # Extract the previously downloaded zip to DIST_PATH
+
+ print("Unzipping file to " + DIST_PATH + "...")
+ with zipfile.ZipFile(ZIP_FILE, "r") as zip_ref:
+ zip_ref.extractall(DIST_PATH)
+
+ # Move files from ASSETS_PATH to DIST_PATH
+
+ print("Moving files from " + ASSETS_PATH + " to " + DIST_PATH)
+ for file_to_move in os.listdir(ASSETS_PATH):
+ shutil.move("/".join([ASSETS_PATH, file_to_move]), DIST_PATH)
+
+ # Update relative paths
+
+ print("Updating relative paths in css files to " + GOVUK_DIR)
+ cwd = os.getcwd()
+ os.chdir(DIST_PATH)
+ for css_file in glob.glob("*.css"):
+
+ # Read in the file
+ with open(css_file, "r") as file:
+ filedata = file.read()
+
+ # Replace the target string
+ filedata = filedata.replace(ASSETS_DIR, ASSETS_DIR + GOVUK_DIR)
+
+ # Write the file out again
+ with open(css_file, "w") as file:
+ file.write(filedata)
+ os.chdir(cwd)
+
+ # Copy css
+ os.makedirs("./app/static/dist/styles")
+
+ # Copy over JS source
+ os.makedirs("./app/static/dist/js")
+
+ # Delete temp files
+ print("Deleting " + ASSETS_PATH)
+ shutil.rmtree(ASSETS_PATH)
+ os.remove(ZIP_FILE)
+
+
+def build_all(static_dist_root="app/static/dist", remove_existing=False):
+ if remove_existing:
+ relative_dist_root = "./" + static_dist_root
+ if os.path.exists(relative_dist_root):
+ shutil.rmtree(relative_dist_root)
+ build_govuk_assets(static_dist_root=static_dist_root)
+ static_assets.build_bundles(static_folder=static_dist_root)
+
+
+if __name__ == "__main__":
+ build_all(remove_existing=True)
diff --git a/tests/test_clone.py b/tests/test_clone.py
index 30ef412..e44ef9d 100644
--- a/tests/test_clone.py
+++ b/tests/test_clone.py
@@ -169,6 +169,8 @@ def test_clone_single_component(flask_test_client, _db):
assert result
new_id = result.component_id
+ assert old_id != new_id
+
# check can retrieve new component
assert _db.session.get(Component, new_id)
@@ -286,6 +288,8 @@ def test_clone_page_no_components(seed_dynamic_data, _db):
assert result
new_id = result.page_id
+ assert old_id != new_id
+
# check new page exists
new_page_from_db = _db.session.query(Page).where(Page.page_id == new_id).one_or_none()
assert new_page_from_db
@@ -363,6 +367,8 @@ def test_clone_page_with_components(seed_dynamic_data, _db):
assert result
new_id = result.page_id
+ assert old_page_id != new_id
+
# check new page exists
new_page_from_db = _db.session.query(Page).where(Page.page_id == new_id).one_or_none()
assert new_page_from_db
diff --git a/tests/test_db_template_CRUD.py b/tests/test_db_template_CRUD.py
new file mode 100644
index 0000000..960d418
--- /dev/null
+++ b/tests/test_db_template_CRUD.py
@@ -0,0 +1,521 @@
+import uuid
+from copy import deepcopy
+
+from app.db.models import ComponentType
+from app.db.models.application_config import Component
+from app.db.models.application_config import Form
+from app.db.models.application_config import Page
+from app.db.models.application_config import Section
+from app.db.queries.application import delete_component
+from app.db.queries.application import delete_form
+from app.db.queries.application import delete_page
+from app.db.queries.application import delete_section
+from app.db.queries.application import insert_new_component
+from app.db.queries.application import insert_new_form
+from app.db.queries.application import insert_new_page
+from app.db.queries.application import insert_new_section
+from app.db.queries.application import update_component
+from app.db.queries.application import update_form
+from app.db.queries.application import update_page
+from app.db.queries.application import update_section
+
+new_template_section_config = {
+ "round_id": uuid.uuid4(),
+ "name_in_apply_json": {"en": "Section Name"},
+ "template_name": "Template Name",
+ "is_template": True,
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "index": 1,
+}
+
+new_section_config = {
+ "round_id": uuid.uuid4(),
+ "name_in_apply_json": {"en": "Template Section Name"},
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "index": 1,
+}
+
+
+def test_insert_new_section(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ # Access actual round_id from seed_dynamic_data (could also be None)
+ round_id = seed_dynamic_data["rounds"][0].round_id
+
+ # Update the configs with the round_id
+ new_template_section_config["round_id"] = round_id
+ new_section_config["round_id"] = round_id
+
+ new_section = insert_new_section(new_section_config)
+ template_section = insert_new_section(new_template_section_config)
+
+ assert isinstance(template_section, Section)
+ assert template_section.round_id == new_template_section_config["round_id"]
+ assert template_section.name_in_apply_json == new_template_section_config["name_in_apply_json"]
+ assert template_section.template_name == new_template_section_config["template_name"]
+ assert template_section.is_template == True
+ assert new_section.source_template_id == None
+ assert template_section.audit_info == new_template_section_config["audit_info"]
+ assert template_section.index == new_template_section_config["index"]
+
+ assert isinstance(new_section, Section)
+ assert new_section.round_id == new_section_config["round_id"]
+ assert new_section.name_in_apply_json == new_section_config["name_in_apply_json"]
+ assert new_section.template_name == None
+ assert new_section.is_template == False
+ assert new_section.source_template_id == None
+ assert new_section.audit_info == new_section_config["audit_info"]
+ assert new_section.index == new_section_config["index"]
+
+
+def test_update_section(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ round_id = seed_dynamic_data["rounds"][0].round_id
+ new_section_config["round_id"] = round_id
+ new_section = insert_new_section(new_section_config)
+
+ assert new_section.round_id == new_section_config["round_id"]
+ assert new_section.name_in_apply_json == new_section_config["name_in_apply_json"]
+ assert new_section.template_name == None
+ assert new_section.is_template == False
+ assert new_section.source_template_id == None
+ assert new_section.audit_info == new_section_config["audit_info"]
+ assert new_section.index == new_section_config["index"]
+
+ # Update new_section_config
+ updated_section_config = deepcopy(new_section_config)
+ updated_section_config["name_in_apply_json"] = {"en": "Updated Section Name"}
+ updated_section_config["audit_info"] = {"created_by": "Jonny Doe", "created_at": "2024-01-02"}
+
+ updated_section = update_section(new_section.section_id, updated_section_config)
+ # write assertions for updated_section
+ assert isinstance(updated_section, Section)
+ assert updated_section.round_id == updated_section_config["round_id"]
+ assert updated_section.name_in_apply_json == updated_section_config["name_in_apply_json"]
+ assert updated_section.audit_info == updated_section_config["audit_info"]
+
+
+def test_delete_section(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ round_id = seed_dynamic_data["rounds"][0].round_id
+ new_section_config["round_id"] = round_id
+ new_section = insert_new_section(new_section_config)
+
+ assert isinstance(new_section, Section)
+ assert new_section.audit_info == new_section_config["audit_info"]
+
+ delete_section(new_section.section_id)
+ assert _db.session.query(Section).filter(Section.section_id == new_section.section_id).one_or_none() == None
+
+
+from sqlalchemy.exc import IntegrityError
+
+
+def test_failed_delete_section_with_fk_to_forms(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ new_section_config["round_id"] = None
+ section = insert_new_section(new_section_config)
+ # CREATE FK link to Form
+ new_form_config["section_id"] = section.section_id
+ form = insert_new_form(new_form_config)
+ # check inserted form has same section_id
+ assert form.section_id == section.section_id
+ assert isinstance(section, Section)
+ assert section.audit_info == new_section_config["audit_info"]
+
+ try:
+ delete_section(form.section_id)
+ assert False, "Expected IntegrityError was not raised"
+ except IntegrityError:
+ _db.session.rollback() # Rollback the failed transaction to maintain DB integrity
+ assert True # Explicitly pass the test to indicate the expected error was caught
+
+ existing_section = _db.session.query(Section).filter(Section.section_id == section.section_id).one_or_none()
+ assert existing_section is not None, "Section was unexpectedly deleted"
+
+
+new_form_config = {
+ "section_id": uuid.uuid4(),
+ "name_in_apply_json": {"en": "Form Name"},
+ "is_template": False,
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "section_index": 1,
+ "runner_publish_name": "test-form",
+}
+
+new_template_form_config = {
+ "section_id": uuid.uuid4(),
+ "name_in_apply_json": {"en": "Template Form Name"},
+ "template_name": "Form Template Name",
+ "is_template": True,
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "section_index": 1,
+ "runner_publish_name": None, # This is a template
+}
+
+
+def test_insert_new_form(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ round_id = seed_dynamic_data["rounds"][0].round_id
+ new_section_config["round_id"] = round_id
+ new_section = insert_new_section(new_section_config)
+ # Point to a section that exists in the db
+ new_form_config["section_id"] = new_section.section_id # *Does not need to belong to a section
+ new_template_form_config["section_id"] = new_section.section_id # *Does not need to belong to a section
+
+ new_template_form = insert_new_form(new_template_form_config)
+ assert isinstance(new_template_form, Form)
+ assert new_template_form.section_id == new_template_form_config["section_id"]
+ assert new_template_form.name_in_apply_json == new_template_form_config["name_in_apply_json"]
+ assert new_template_form.template_name == new_template_form_config["template_name"]
+ assert new_template_form.is_template == True
+ assert new_template_form.source_template_id == None
+ assert new_template_form.audit_info == new_template_form_config["audit_info"]
+ assert new_template_form.section_index == new_template_form_config["section_index"]
+ assert new_template_form.runner_publish_name == None
+
+ new_form = insert_new_form(new_form_config)
+ assert isinstance(new_form, Form)
+ assert new_form.section_id == new_form_config["section_id"]
+ assert new_form.name_in_apply_json == new_form_config["name_in_apply_json"]
+ assert new_form.template_name == None
+ assert new_form.source_template_id == None # not cloned, its a new non-template form
+ assert new_form.is_template == False
+ assert new_form.audit_info == new_form_config["audit_info"]
+ assert new_form.section_index == new_form_config["section_index"]
+ assert new_form.runner_publish_name == new_form_config["runner_publish_name"]
+
+ new_form_config["section_index"] = 2
+ new_form = insert_new_form(new_form_config)
+ assert new_form.section_index == new_form_config["section_index"]
+
+
+def test_update_form(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ round_id = seed_dynamic_data["rounds"][0].round_id
+ new_section_config["round_id"] = round_id
+ new_section = insert_new_section(new_section_config)
+ new_form_config["section_id"] = new_section.section_id
+ new_form = insert_new_form(new_form_config)
+
+ assert new_form.section_id == new_form_config["section_id"]
+ assert new_form.name_in_apply_json == new_form_config["name_in_apply_json"]
+ assert new_form.template_name == None
+ assert new_form.is_template == False
+ assert new_form.source_template_id == None
+ assert new_form.audit_info == new_form_config["audit_info"]
+ assert new_form.section_index == new_form_config["section_index"]
+ assert new_form.runner_publish_name == new_form_config["runner_publish_name"]
+
+ # Update new_form_config
+ updated_form_config = deepcopy(new_form_config)
+ updated_form_config["name_in_apply_json"] = {"en": "Updated Form Name"}
+ updated_form_config["audit_info"] = {"created_by": "Jonny Doe", "created_at": "2024-01-02"}
+
+ updated_form = update_form(new_form.form_id, updated_form_config)
+
+ assert isinstance(updated_form, Form)
+ assert updated_form.section_id == updated_form_config["section_id"]
+ assert updated_form.name_in_apply_json == updated_form_config["name_in_apply_json"]
+ assert updated_form.audit_info == updated_form_config["audit_info"]
+
+
+def test_delete_form(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ round_id = seed_dynamic_data["rounds"][0].round_id
+ new_section_config["round_id"] = round_id
+ new_section = insert_new_section(new_section_config)
+ new_form_config["section_id"] = new_section.section_id
+ new_form = insert_new_form(new_form_config)
+
+ assert isinstance(new_form, Form)
+ assert new_form.audit_info == new_form_config["audit_info"]
+
+ delete_form(new_form.form_id)
+ assert _db.session.query(Form).filter(Form.form_id == new_form.form_id).one_or_none() == None
+
+
+def test_failed_delete_form_with_fk_to_page(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ new_form_config["section_id"] = None
+ form = insert_new_form(new_form_config)
+ # CREATE FK link to Form
+ new_page_config["form_id"] = form.form_id
+ page = insert_new_page(new_page_config)
+
+ try:
+ delete_form(page.form_id)
+ assert False, "Expected IntegrityError was not raised"
+ except IntegrityError:
+ _db.session.rollback() # Rollback the failed transaction to maintain DB integrity
+ assert True # Explicitly pass the test to indicate the expected error was caught
+
+ existing_form = _db.session.query(Form).filter(Form.form_id == form.form_id).one_or_none()
+ assert existing_form is not None, "Form was unexpectedly deleted"
+
+
+new_page_config = {
+ "form_id": uuid.uuid4(),
+ "name_in_apply_json": {"en": "Page Name"},
+ "is_template": False,
+ "template_name": None,
+ "source_template_id": None,
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "form_index": 1,
+ "display_path": "test-page",
+ "controller": "./test-controller",
+}
+
+new_template_page_config = {
+ "form_id": uuid.uuid4(),
+ "name_in_apply_json": {"en": "Template Page Name"},
+ "is_template": True,
+ "template_name": "Page Template Name",
+ "source_template_id": None,
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "form_index": 1,
+ "display_path": "test-page",
+ "controller": None,
+}
+
+
+def test_insert_new_page(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+
+ new_form_config["section_id"] = None
+ new_form = insert_new_form(new_form_config)
+
+ new_page_config["form_id"] = new_form.form_id # *Does not need to belong to a form
+ new_template_page_config["form_id"] = None # *Does not need to belong to a form
+
+ new_template_page = insert_new_page(new_template_page_config)
+ assert isinstance(new_template_page, Page)
+ assert new_template_page.form_id is None
+ assert new_template_page.name_in_apply_json == new_template_page_config["name_in_apply_json"]
+ assert new_template_page.template_name == new_template_page_config["template_name"]
+ assert new_template_page.is_template == True
+ assert new_template_page.source_template_id == None
+ assert new_template_page.audit_info == new_template_page_config["audit_info"]
+ assert new_template_page.form_index == new_template_page_config["form_index"]
+ assert new_template_page.display_path == new_page_config["display_path"]
+ assert new_template_page.controller == new_template_page_config["controller"]
+
+ new_page = insert_new_page(new_page_config)
+ assert isinstance(new_page, Page)
+ assert new_page.form_id == new_page_config["form_id"]
+ assert new_page.name_in_apply_json == new_page_config["name_in_apply_json"]
+ assert new_page.template_name == None
+ assert new_page.is_template == False
+ assert new_page.source_template_id == None
+ assert new_page.audit_info == new_page_config["audit_info"]
+ assert new_page.form_index == new_page_config["form_index"]
+ assert new_page.display_path == new_page_config["display_path"]
+ assert new_page.controller == new_page_config["controller"]
+
+
+def test_update_page(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ new_page_config["form_id"] = None
+ new_page = insert_new_page(new_page_config)
+
+ assert new_page.form_id is None
+ assert new_page.name_in_apply_json == new_page_config["name_in_apply_json"]
+ assert new_page.template_name == None
+ assert new_page.is_template == False
+ assert new_page.source_template_id == None
+ assert new_page.audit_info == new_page_config["audit_info"]
+ assert new_page.form_index == new_page_config["form_index"]
+ assert new_page.display_path == new_page_config["display_path"]
+ assert new_page.controller == new_page_config["controller"]
+
+ # Update new_page_config
+ updated_page_config = deepcopy(new_page_config)
+ updated_page_config["name_in_apply_json"] = {"en": "Updated Page Name"}
+ updated_page_config["audit_info"] = {"created_by": "Jonny Doe", "created_at": "2024-01-02"}
+
+ updated_page = update_page(new_page.page_id, updated_page_config)
+
+ assert isinstance(updated_page, Page)
+ assert updated_page.form_id == updated_page_config["form_id"]
+ assert updated_page.name_in_apply_json == updated_page_config["name_in_apply_json"]
+ assert updated_page.audit_info == updated_page_config["audit_info"]
+
+
+def test_delete_page(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ new_page_config["form_id"] = None
+ new_page = insert_new_page(new_page_config)
+
+ assert isinstance(new_page, Page)
+ assert new_page.audit_info == new_page_config["audit_info"]
+
+ delete_page(new_page.page_id)
+ assert _db.session.query(Page).filter(Page.page_id == new_page.page_id).one_or_none() == None
+
+
+def test_failed_delete_page_with_fk_to_component(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ new_page_config["form_id"] = None
+ new_page = insert_new_page(new_page_config)
+ # CREATE FK link to Component
+ new_component_config["page_id"] = new_page.page_id
+ new_component_config["list_id"] = None
+ new_component_config["theme_id"] = None
+ component = insert_new_component(new_component_config)
+ # check inserted component has same page_id
+ assert component.page_id == new_page.page_id
+ assert isinstance(new_page, Page)
+ assert new_page.audit_info == new_page_config["audit_info"]
+
+ try:
+ delete_page(component.page_id)
+ assert False, "Expected IntegrityError was not raised"
+ except IntegrityError:
+ _db.session.rollback() # Rollback the failed transaction to maintain DB integrity
+ assert True # Explicitly pass the test to indicate the expected error was caught
+
+ existing_page = _db.session.query(Page).filter(Page.page_id == new_page.page_id).one_or_none()
+ assert existing_page is not None, "Page was unexpectedly deleted"
+
+
+new_component_config = {
+ "page_id": uuid.uuid4(),
+ "theme_id": uuid.uuid4(),
+ "title": "Component Title",
+ "hint_text": "Component Hint Text",
+ "options": {"hideTitle": False, "classes": "test-class"},
+ "type": ComponentType.TEXT_FIELD,
+ "is_template": False,
+ "template_name": None,
+ "source_template_id": None,
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "page_index": 1,
+ "theme_index": 1,
+ "conditions": [
+ {
+ "name": "organisation_other_names_no",
+ "value": "false", # this must be lowercaes or the navigation doesn't work
+ "operator": "is",
+ "destination_page_path": "CONTINUE",
+ },
+ {
+ "name": "organisation_other_names_yes",
+ "value": "true", # this must be lowercaes or the navigation doesn't work
+ "operator": "is",
+ "destination_page_path": "organisation-alternative-names",
+ },
+ ],
+ "runner_component_name": "test-component",
+ "list_id": uuid.uuid4(),
+}
+
+
+new_template_component_config = {
+ "page_id": uuid.uuid4(),
+ "theme_id": uuid.uuid4(),
+ "title": "Template Component Title",
+ "hint_text": "Template Component Hint Text",
+ "options": {"hideTitle": False, "classes": "test-class"},
+ "type": ComponentType.TEXT_FIELD,
+ "is_template": True,
+ "template_name": "Component Template Name",
+ "source_template_id": None,
+ "audit_info": {"created_by": "John Doe", "created_at": "2022-01-01"},
+ "page_index": 1,
+ "theme_index": 2,
+ "conditions": [
+ {
+ "name": "path_start_no",
+ "value": "false", # this must be lowercaes or the navigation doesn't work
+ "operator": "is",
+ "destination_page_path": "path-1",
+ },
+ {
+ "name": "path_start_yes",
+ "value": "true", # this must be lowercaes or the navigation doesn't work
+ "operator": "is",
+ "destination_page_path": "path-2",
+ },
+ ],
+ "runner_component_name": "test-component",
+ "list_id": uuid.uuid4(),
+}
+
+
+def test_insert_new_component(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ page_id = seed_dynamic_data["pages"][0].page_id
+ list_id = seed_dynamic_data["lists"][0].list_id
+ theme_id = seed_dynamic_data["themes"][0].theme_id
+ new_component_config["page_id"] = page_id
+ new_template_component_config["page_id"] = None
+ new_component_config["list_id"] = list_id
+ new_template_component_config["list_id"] = list_id
+ new_component_config["theme_id"] = theme_id
+ new_template_component_config["theme_id"] = theme_id
+
+ component = insert_new_component(new_component_config)
+ assert isinstance(component, Component)
+ assert component.page_id == new_component_config["page_id"]
+ assert component.theme_id == new_component_config["theme_id"]
+ assert component.title == new_component_config["title"]
+ assert component.hint_text == new_component_config["hint_text"]
+ assert component.options == new_component_config["options"]
+ assert component.type == new_component_config["type"]
+ assert component.is_template == False
+ assert component.template_name == None
+ assert component.source_template_id == None
+ assert component.audit_info == new_component_config["audit_info"]
+ assert component.page_index == new_component_config["page_index"]
+ assert component.theme_index == new_component_config["theme_index"]
+ assert component.conditions == new_component_config["conditions"]
+ assert component.runner_component_name == new_component_config["runner_component_name"]
+ assert component.list_id == new_component_config["list_id"]
+
+ template_component = insert_new_component(new_template_component_config)
+ assert isinstance(template_component, Component)
+ assert template_component.page_id is None
+ assert template_component.theme_id == new_template_component_config["theme_id"]
+ assert template_component.title == new_template_component_config["title"]
+ assert template_component.hint_text == new_template_component_config["hint_text"]
+ assert template_component.options == new_template_component_config["options"]
+ assert template_component.type == new_template_component_config["type"]
+ assert template_component.is_template == True
+ assert template_component.template_name == new_template_component_config["template_name"]
+ assert template_component.source_template_id == None
+ assert template_component.audit_info == new_template_component_config["audit_info"]
+ assert template_component.page_index == new_template_component_config["page_index"]
+ assert template_component.theme_index == new_template_component_config["theme_index"]
+ assert template_component.conditions == new_template_component_config["conditions"]
+ assert template_component.runner_component_name == new_template_component_config["runner_component_name"]
+ assert template_component.list_id == new_template_component_config["list_id"]
+
+
+def test_update_component(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ page_id = seed_dynamic_data["pages"][0].page_id
+ list_id = seed_dynamic_data["lists"][0].list_id
+ theme_id = seed_dynamic_data["themes"][0].theme_id
+ new_component_config["page_id"] = page_id
+ new_component_config["list_id"] = list_id
+ new_component_config["theme_id"] = theme_id
+
+ component = insert_new_component(new_component_config)
+
+ assert component.title == new_component_config["title"]
+ assert component.audit_info == new_component_config["audit_info"]
+ assert component.is_template == False
+
+ # Update new_component_config
+ updated_component_config = deepcopy(new_component_config)
+ updated_component_config["title"] = "Updated Component Title"
+ updated_component_config["audit_info"] = {"created_by": "Adam Doe", "created_at": "2024-01-02"}
+
+ updated_component = update_component(component.component_id, updated_component_config)
+
+ assert isinstance(updated_component, Component)
+ assert updated_component.title == updated_component_config["title"]
+ assert updated_component.audit_info == updated_component_config["audit_info"]
+ assert updated_component.is_template == False
+
+
+def test_delete_component(flask_test_client, _db, clear_test_data, seed_dynamic_data):
+ page_id = seed_dynamic_data["pages"][0].page_id
+ list_id = seed_dynamic_data["lists"][0].list_id
+ theme_id = seed_dynamic_data["themes"][0].theme_id
+ new_component_config["page_id"] = page_id
+ new_component_config["list_id"] = list_id
+ new_component_config["theme_id"] = theme_id
+
+ component = insert_new_component(new_component_config)
+
+ assert isinstance(component, Component)
+ assert component.audit_info == new_component_config["audit_info"]
+
+ delete_component(component.component_id)
+ assert _db.session.query(Component).filter(Component.component_id == component.component_id).one_or_none() == None