diff --git a/app/blueprints/application/routes.py b/app/blueprints/application/routes.py index ec1ff3a8..9395fb75 100644 --- a/app/blueprints/application/routes.py +++ b/app/blueprints/application/routes.py @@ -16,7 +16,6 @@ from app.all_questions.metadata_utils import generate_print_data_for_sections from app.blueprints.application.forms import SectionForm from app.db.queries.application import ( - clone_single_form, delete_form_from_section, delete_section_from_round, get_all_template_forms, @@ -29,6 +28,7 @@ move_section_up, update_section, ) +from app.db.queries.clone import clone_single_form from app.db.queries.fund import get_fund_by_id from app.db.queries.round import get_round_by_id from app.export_config.generate_all_questions import print_html diff --git a/app/blueprints/round/routes.py b/app/blueprints/round/routes.py index b19c29a9..b0d5d203 100644 --- a/app/blueprints/round/routes.py +++ b/app/blueprints/round/routes.py @@ -14,7 +14,7 @@ from app.blueprints.fund_builder.routes import all_funds_as_govuk_select_items from app.blueprints.round.forms import RoundForm, get_datetime from app.db.models.round import Round -from app.db.queries.application import clone_single_round +from app.db.queries.clone import clone_single_round from app.db.queries.fund import get_all_funds from app.db.queries.round import add_round, get_round_by_id, update_round from app.shared.helpers import error_formatter diff --git a/app/db/queries/application.py b/app/db/queries/application.py index d59f9e75..a8f14f07 100644 --- a/app/db/queries/application.py +++ b/app/db/queries/application.py @@ -48,184 +48,6 @@ def get_list_by_id(list_id: str) -> Lizt: return lizt -def _initiate_cloned_component(to_clone: Component, new_page_id=None, new_theme_id=None): - clone = Component(**to_clone.as_dict()) - - clone.component_id = uuid4() - clone.page_id = new_page_id - clone.theme_id = new_theme_id - clone.is_template = False - clone.source_template_id = to_clone.component_id - clone.template_name = None - return clone - - -def _initiate_cloned_page(to_clone: Page, new_form_id=None): - clone = Page(**to_clone.as_dict()) - clone.page_id = uuid4() - clone.form_id = new_form_id - clone.is_template = False - clone.source_template_id = to_clone.page_id - clone.template_name = None - clone.components = [] - return clone - - -def _initiate_cloned_form(to_clone: Form, new_section_id: str, section_index=0) -> Form: - clone = Form(**to_clone.as_dict()) - clone.form_id = uuid4() - clone.section_id = new_section_id - clone.is_template = False - clone.source_template_id = to_clone.form_id - clone.template_name = None - clone.pages = [] - clone.section_index = section_index - return clone - - -def _initiate_cloned_section(to_clone: Section, new_round_id: str) -> Form: - clone = Section(**to_clone.as_dict()) - clone.round_id = new_round_id - clone.section_id = uuid4() - clone.is_template = False - clone.source_template_id = to_clone.section_id - clone.template_name = None - clone.pages = [] - return clone - - -def clone_single_section(section_id: str, new_round_id=None) -> Section: - section_to_clone: Section = db.session.query(Section).where(Section.section_id == section_id).one_or_none() - clone = _initiate_cloned_section(section_to_clone, new_round_id) - - cloned_forms = [] - cloned_pages = [] - cloned_components = [] - # loop through forms in this section and clone each one - for form_to_clone in section_to_clone.forms: - cloned_form = _initiate_cloned_form(form_to_clone, clone.section_id, section_index=form_to_clone.section_index) - # loop through pages in this section and clone each one - for page_to_clone in form_to_clone.pages: - cloned_page = _initiate_cloned_page(page_to_clone, new_form_id=cloned_form.form_id) - cloned_pages.append(cloned_page) - # clone the components on this page - cloned_components.extend( - _initiate_cloned_components_for_page(page_to_clone.components, cloned_page.page_id) - ) - - cloned_forms.append(cloned_form) - - db.session.add_all([clone, *cloned_forms, *cloned_pages, *cloned_components]) - cloned_pages = _fix_cloned_default_pages(cloned_pages) - db.session.commit() - - return clone - - -def _fix_cloned_default_pages(cloned_pages: list[Page]): - # Go through each page - # Get the page ID of the default next page (this will be a template page) - # Find the cloned page that was created from that template - # Get that cloned page's ID - # Update this default_next_page to point to the cloned page - - for clone in cloned_pages: - if clone.default_next_page_id: - template_id = clone.default_next_page_id - concrete_next_page = next(p for p in cloned_pages if p.source_template_id == template_id) - clone.default_next_page_id = concrete_next_page.page_id - - return cloned_pages - - -def clone_single_form(form_id: str, new_section_id=None, section_index=0) -> Form: - form_to_clone: Form = db.session.query(Form).where(Form.form_id == form_id).one_or_none() - clone = _initiate_cloned_form(form_to_clone, new_section_id, section_index=section_index) - - cloned_pages = [] - cloned_components = [] - for page_to_clone in form_to_clone.pages: - cloned_page = _initiate_cloned_page(page_to_clone, new_form_id=clone.form_id) - cloned_pages.append(cloned_page) - cloned_components.extend(_initiate_cloned_components_for_page(page_to_clone.components, cloned_page.page_id)) - db.session.add_all([clone, *cloned_pages, *cloned_components]) - cloned_pages = _fix_cloned_default_pages(cloned_pages) - db.session.commit() - - return clone - - -def _initiate_cloned_components_for_page( - components_to_clone: list[Component], new_page_id: str = None, new_theme_id: str = None -): - cloned_components = [] - for component_to_clone in components_to_clone: - cloned_component = _initiate_cloned_component( - component_to_clone, new_page_id=new_page_id, new_theme_id=None - ) # TODO how should themes work when cloning? - cloned_components.append(cloned_component) - return cloned_components - - -def clone_single_page(page_id: str, new_form_id=None) -> Page: - page_to_clone: Page = db.session.query(Page).where(Page.page_id == page_id).one_or_none() - clone = _initiate_cloned_page(page_to_clone, new_form_id) - - cloned_components = _initiate_cloned_components_for_page(page_to_clone.components, new_page_id=clone.page_id) - db.session.add_all([clone, *cloned_components]) - db.session.commit() - - return clone - - -def clone_single_component(component_id: str, new_page_id=None, new_theme_id=None) -> Component: - component_to_clone: Component = ( - db.session.query(Component).where(Component.component_id == component_id).one_or_none() - ) - clone = _initiate_cloned_component(component_to_clone, new_page_id, new_theme_id) - - db.session.add(clone) - db.session.commit() - - return clone - - -# TODO do we need this? -def clone_multiple_components(component_ids: list[str], new_page_id=None, new_theme_id=None) -> list[Component]: - components_to_clone: list[Component] = ( - db.session.query(Component).filter(Component.component_id.in_(component_ids)).all() - ) - clones = [ - _initiate_cloned_component(to_clone=to_clone, new_page_id=new_page_id, new_theme_id=new_theme_id) - for to_clone in components_to_clone - ] - db.session.add_all(clones) - db.session.commit() - - return clones - - -def clone_single_round(round_id, new_fund_id, new_short_name) -> Round: - round_to_clone = db.session.query(Round).where(Round.round_id == round_id).one_or_none() - cloned_round = Round(**round_to_clone.as_dict()) - cloned_round.fund_id = new_fund_id - cloned_round.short_name = new_short_name - cloned_round.round_id = uuid4() - cloned_round.is_template = False - cloned_round.source_template_id = round_to_clone.round_id - cloned_round.template_name = None - cloned_round.sections = [] - cloned_round.section_base_path = None - - db.session.add(cloned_round) - db.session.commit() - - for section in round_to_clone.sections: - 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): diff --git a/app/db/queries/clone.py b/app/db/queries/clone.py new file mode 100644 index 00000000..2ec009c7 --- /dev/null +++ b/app/db/queries/clone.py @@ -0,0 +1,182 @@ +from uuid import uuid4 + +from app.db import db +from app.db.models import Component, Form, Page, Round, Section + + +def _initiate_cloned_component(to_clone: Component, new_page_id=None, new_theme_id=None): + clone = Component(**to_clone.as_dict()) + + clone.component_id = uuid4() + clone.page_id = new_page_id + clone.theme_id = new_theme_id + clone.is_template = False + clone.source_template_id = to_clone.component_id + clone.template_name = None + return clone + + +def _initiate_cloned_page(to_clone: Page, new_form_id=None): + clone = Page(**to_clone.as_dict()) + clone.page_id = uuid4() + clone.form_id = new_form_id + clone.is_template = False + clone.source_template_id = to_clone.page_id + clone.template_name = None + clone.components = [] + return clone + + +def _initiate_cloned_form(to_clone: Form, new_section_id: str, section_index=0) -> Form: + clone = Form(**to_clone.as_dict()) + clone.form_id = uuid4() + clone.section_id = new_section_id + clone.is_template = False + clone.source_template_id = to_clone.form_id + clone.template_name = None + clone.pages = [] + clone.section_index = section_index + return clone + + +def _initiate_cloned_section(to_clone: Section, new_round_id: str) -> Form: + clone = Section(**to_clone.as_dict()) + clone.round_id = new_round_id + clone.section_id = uuid4() + clone.is_template = False + clone.source_template_id = to_clone.section_id + clone.template_name = None + clone.pages = [] + return clone + + +def clone_single_section(section_id: str, new_round_id=None) -> Section: + section_to_clone: Section = db.session.query(Section).where(Section.section_id == section_id).one_or_none() + clone = _initiate_cloned_section(section_to_clone, new_round_id) + + cloned_forms = [] + cloned_pages = [] + cloned_components = [] + # loop through forms in this section and clone each one + for form_to_clone in section_to_clone.forms: + cloned_form = _initiate_cloned_form(form_to_clone, clone.section_id, section_index=form_to_clone.section_index) + # loop through pages in this section and clone each one + for page_to_clone in form_to_clone.pages: + cloned_page = _initiate_cloned_page(page_to_clone, new_form_id=cloned_form.form_id) + cloned_pages.append(cloned_page) + # clone the components on this page + cloned_components.extend( + _initiate_cloned_components_for_page(page_to_clone.components, cloned_page.page_id) + ) + + cloned_forms.append(cloned_form) + + db.session.add_all([clone, *cloned_forms, *cloned_pages, *cloned_components]) + cloned_pages = _fix_cloned_default_pages(cloned_pages) + db.session.commit() + + return clone + + +def _fix_cloned_default_pages(cloned_pages: list[Page]): + # Go through each page + # Get the page ID of the default next page (this will be a template page) + # Find the cloned page that was created from that template + # Get that cloned page's ID + # Update this default_next_page to point to the cloned page + + for clone in cloned_pages: + if clone.default_next_page_id: + template_id = clone.default_next_page_id + concrete_next_page = next(p for p in cloned_pages if p.source_template_id == template_id) + clone.default_next_page_id = concrete_next_page.page_id + + return cloned_pages + + +def clone_single_form(form_id: str, new_section_id=None, section_index=0) -> Form: + form_to_clone: Form = db.session.query(Form).where(Form.form_id == form_id).one_or_none() + clone = _initiate_cloned_form(form_to_clone, new_section_id, section_index=section_index) + + cloned_pages = [] + cloned_components = [] + for page_to_clone in form_to_clone.pages: + cloned_page = _initiate_cloned_page(page_to_clone, new_form_id=clone.form_id) + cloned_pages.append(cloned_page) + cloned_components.extend(_initiate_cloned_components_for_page(page_to_clone.components, cloned_page.page_id)) + db.session.add_all([clone, *cloned_pages, *cloned_components]) + cloned_pages = _fix_cloned_default_pages(cloned_pages) + db.session.commit() + + return clone + + +def _initiate_cloned_components_for_page( + components_to_clone: list[Component], new_page_id: str = None, new_theme_id: str = None +): + cloned_components = [] + for component_to_clone in components_to_clone: + cloned_component = _initiate_cloned_component( + component_to_clone, new_page_id=new_page_id, new_theme_id=None + ) # TODO how should themes work when cloning? + cloned_components.append(cloned_component) + return cloned_components + + +def clone_single_page(page_id: str, new_form_id=None) -> Page: + page_to_clone: Page = db.session.query(Page).where(Page.page_id == page_id).one_or_none() + clone = _initiate_cloned_page(page_to_clone, new_form_id) + + cloned_components = _initiate_cloned_components_for_page(page_to_clone.components, new_page_id=clone.page_id) + db.session.add_all([clone, *cloned_components]) + db.session.commit() + + return clone + + +def clone_single_component(component_id: str, new_page_id=None, new_theme_id=None) -> Component: + component_to_clone: Component = ( + db.session.query(Component).where(Component.component_id == component_id).one_or_none() + ) + clone = _initiate_cloned_component(component_to_clone, new_page_id, new_theme_id) + + db.session.add(clone) + db.session.commit() + + return clone + + +# TODO do we need this? +def clone_multiple_components(component_ids: list[str], new_page_id=None, new_theme_id=None) -> list[Component]: + components_to_clone: list[Component] = ( + db.session.query(Component).filter(Component.component_id.in_(component_ids)).all() + ) + clones = [ + _initiate_cloned_component(to_clone=to_clone, new_page_id=new_page_id, new_theme_id=new_theme_id) + for to_clone in components_to_clone + ] + db.session.add_all(clones) + db.session.commit() + + return clones + + +def clone_single_round(round_id, new_fund_id, new_short_name) -> Round: + round_to_clone = db.session.query(Round).where(Round.round_id == round_id).one_or_none() + cloned_round = Round(**round_to_clone.as_dict()) + cloned_round.fund_id = new_fund_id + cloned_round.short_name = new_short_name + cloned_round.round_id = uuid4() + cloned_round.is_template = False + cloned_round.source_template_id = round_to_clone.round_id + cloned_round.template_name = None + cloned_round.sections = [] + cloned_round.section_base_path = None + + db.session.add(cloned_round) + db.session.commit() + + for section in round_to_clone.sections: + clone_single_section(section.section_id, cloned_round.round_id) + + return cloned_round diff --git a/tests/test_clone.py b/tests/test_clone.py index 740d73e6..ec54c5da 100644 --- a/tests/test_clone.py +++ b/tests/test_clone.py @@ -2,11 +2,10 @@ import pytest -from app.blueprints.round.routes import clone_single_round from app.db.models import Component, ComponentType, Page from app.db.models.application_config import Form, Section from app.db.models.round import Round -from app.db.queries.application import ( +from app.db.queries.clone import ( _fix_cloned_default_pages, _initiate_cloned_component, _initiate_cloned_form, @@ -16,6 +15,7 @@ clone_single_component, clone_single_form, clone_single_page, + clone_single_round, clone_single_section, ) @@ -23,7 +23,7 @@ @pytest.fixture def mock_new_uuid(mocker): new_id = uuid4() - mocker.patch("app.db.queries.application.uuid4", return_value=new_id) + mocker.patch("app.db.queries.clone.uuid4", return_value=new_id) yield new_id