From f8a6d04d64d4fae533f7f31e568767921661e3aa Mon Sep 17 00:00:00 2001 From: Montana Date: Mon, 25 Feb 2019 15:47:24 -0500 Subject: [PATCH 1/5] Move defense_component to portfolios model --- ...91_move_defense_component_to_portfolio_.py | 52 +++++++++++++++++++ atst/models/portfolio.py | 2 + 2 files changed, 54 insertions(+) create mode 100644 alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py diff --git a/alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py b/alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py new file mode 100644 index 00000000..8b84ea53 --- /dev/null +++ b/alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py @@ -0,0 +1,52 @@ +"""Move defense component to Portfolio level + +Revision ID: ec1ba2363191 +Revises: fa3ba4049218 +Create Date: 2019-02-22 14:43:49.408446 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import text + + +# revision identifiers, used by Alembic. +revision = 'ec1ba2363191' +down_revision = 'fa3ba4049218' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('portfolios', sa.Column('defense_component', sa.String(), nullable=True)) + conn = op.get_bind() + conn.execute( + text( + """ + UPDATE portfolios + SET defense_component = task_orders.defense_component + FROM task_orders + WHERE task_orders.portfolio_id = portfolios.id; + """ + ) + ) + op.drop_column('task_orders', 'defense_component') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task_orders', sa.Column('defense_component', sa.VARCHAR(), autoincrement=False, nullable=True)) + conn = op.get_bind() + conn.execute( + text( + """ + UPDATE task_orders + SET defense_component = portfolios.defense_component + FROM portfolios; + """ + ) + ) + op.drop_column('portfolios', 'defense_component') + # ### end Alembic commands ### diff --git a/atst/models/portfolio.py b/atst/models/portfolio.py index 83043bcd..01e9e299 100644 --- a/atst/models/portfolio.py +++ b/atst/models/portfolio.py @@ -13,6 +13,8 @@ class Portfolio(Base, mixins.TimestampsMixin, mixins.AuditableMixin): id = types.Id() name = Column(String) + defense_component = Column(String) # Department of Defense Component + applications = relationship("Application", back_populates="portfolio") roles = relationship("PortfolioRole") From 7ee8858cc7d84d9d997a4232223952b58661336b Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 26 Feb 2019 10:26:23 -0500 Subject: [PATCH 2/5] Add defense_component to Portfolio when new task_order is created without an existing portfolio --- atst/domain/portfolios/portfolios.py | 6 ++++-- atst/models/task_order.py | 5 ++++- atst/routes/task_orders/new.py | 8 +++++++- templates/fragments/task_order_review/app_info.html | 2 +- tests/domain/test_task_orders.py | 2 +- tests/factories.py | 2 +- tests/routes/task_orders/test_new_task_order.py | 9 ++++++++- 7 files changed, 26 insertions(+), 8 deletions(-) diff --git a/atst/domain/portfolios/portfolios.py b/atst/domain/portfolios/portfolios.py index fbb40bad..75ddf28d 100644 --- a/atst/domain/portfolios/portfolios.py +++ b/atst/domain/portfolios/portfolios.py @@ -16,8 +16,10 @@ class PortfolioError(Exception): class Portfolios(object): @classmethod - def create(cls, user, name): - portfolio = PortfoliosQuery.create(name=name) + def create(cls, user, name, defense_component): + portfolio = PortfoliosQuery.create( + name=name, defense_component=defense_component + ) Portfolios._create_portfolio_role( user, portfolio, "owner", status=PortfolioRoleStatus.ACTIVE ) diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 6e30cff3..b8ff5db5 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -53,7 +53,6 @@ class TaskOrder(Base, mixins.TimestampsMixin): dd_254 = relationship("DD254") scope = Column(String) # Cloud Project Scope - defense_component = Column(String) # Department of Defense Component app_migration = Column(String) # App Migration native_apps = Column(String) # Native Apps complexity = Column(ARRAY(String)) # Application Complexity @@ -172,6 +171,10 @@ class TaskOrder(Base, mixins.TimestampsMixin): def portfolio_name(self): return self.portfolio.name + @property + def defense_component(self): + return self.portfolio.defense_component + @property def is_pending(self): return self.status == Status.PENDING diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 8ad10f27..77cb0c63 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -149,6 +149,8 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow): to_data = self.form.data.copy() if "portfolio_name" in to_data: to_data.pop("portfolio_name") + if "defense_component" in to_data: + to_data.pop("defense_component") # don't save other text in DB unless "other" is checked if "complexity" in to_data and "other" not in to_data["complexity"]: @@ -184,7 +186,11 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow): if self.portfolio_id: pf = Portfolios.get(self.user, self.portfolio_id) else: - pf = Portfolios.create(self.user, self.form.portfolio_name.data) + pf = Portfolios.create( + self.user, + self.form.portfolio_name.data, + self.form.defense_component.data, + ) self._task_order = TaskOrders.create(portfolio=pf, creator=self.user) TaskOrders.update(self.user, self.task_order, **self.task_order_form_data) diff --git a/templates/fragments/task_order_review/app_info.html b/templates/fragments/task_order_review/app_info.html index 93db4525..d124da38 100644 --- a/templates/fragments/task_order_review/app_info.html +++ b/templates/fragments/task_order_review/app_info.html @@ -2,7 +2,7 @@
{{ ReviewField(("task_orders.new.review.portfolio" | translate), task_order.portfolio_name) }} - {{ ReviewField(("task_orders.new.review.dod" | translate), task_order.defense_component, filter="normalizeOrder") }} + {{ ReviewField(("task_orders.new.review.dod" | translate), task_order.portfolio.defense_component, filter="normalizeOrder") }}
{{ ReviewField(("task_orders.new.review.scope" | translate), task_order.scope) }} diff --git a/tests/domain/test_task_orders.py b/tests/domain/test_task_orders.py index 401d2db9..7c2b8695 100644 --- a/tests/domain/test_task_orders.py +++ b/tests/domain/test_task_orders.py @@ -18,12 +18,12 @@ def test_section_completion_status(): section = dict_keys[0] attrs = TaskOrders.SECTIONS[section].copy() attrs.remove("portfolio_name") + attrs.remove("defense_component") task_order = TaskOrderFactory.create(**{k: None for k in attrs}) leftover = attrs.pop() for attr in attrs: setattr(task_order, attr, "str12345") - assert TaskOrders.section_completion_status(task_order, section) == "draft" setattr(task_order, leftover, "str12345") diff --git a/tests/factories.py b/tests/factories.py index 1226d79a..d2c0618f 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -104,6 +104,7 @@ class PortfolioFactory(Base): model = Portfolio name = factory.Faker("name") + defense_component = factory.LazyFunction(random_service_branch) @classmethod def _create(cls, model_class, *args, **kwargs): @@ -227,7 +228,6 @@ class TaskOrderFactory(Base): clin_02 = factory.LazyFunction(lambda *args: random.randrange(100, 100_000)) clin_04 = factory.LazyFunction(lambda *args: random.randrange(100, 100_000)) - defense_component = factory.LazyFunction(random_service_branch) app_migration = random_choice(data.APP_MIGRATION) native_apps = random.choice(["yes", "no", "not_sure"]) complexity = [random_choice(data.APPLICATION_COMPLEXITY)] diff --git a/tests/routes/task_orders/test_new_task_order.py b/tests/routes/task_orders/test_new_task_order.py index cfeff1c9..d1e9f433 100644 --- a/tests/routes/task_orders/test_new_task_order.py +++ b/tests/routes/task_orders/test_new_task_order.py @@ -51,7 +51,9 @@ def test_create_new_task_order(client, user_session, pdf_upload): task_order_data = TaskOrderFactory.dictionary() app_info_data = slice_data_for_section(task_order_data, "app_info") portfolio_name = "Mos Eisley" + defense_component = "Defense Health Agency" app_info_data["portfolio_name"] = portfolio_name + app_info_data["defense_component"] = defense_component response = client.post( url_for("task_orders.update", screen=1), @@ -64,6 +66,8 @@ def test_create_new_task_order(client, user_session, pdf_upload): created_task_order = TaskOrders.get(creator, created_task_order_id) assert created_task_order.portfolio is not None assert created_task_order.portfolio.name == portfolio_name + assert created_task_order.portfolio is not None + assert created_task_order.portfolio.defense_component == defense_component funding_data = slice_data_for_section(task_order_data, "funding") funding_data = serialize_dates(funding_data) @@ -89,6 +93,8 @@ def test_create_new_task_order_for_portfolio(client, user_session): app_info_data = slice_data_for_section(task_order_data, "app_info") portfolio_name = "This is ignored for now" app_info_data["portfolio_name"] = portfolio_name + defense_component = "Defense Health Agency" # this is also ignored + app_info_data["defense_component"] = defense_component response = client.post( url_for("task_orders.update", screen=1, portfolio_id=portfolio.id), @@ -203,7 +209,8 @@ def test_show_task_order_form(task_order): task_order.creator, task_order_id=task_order.id ) assert ( - another_workflow.form.data["defense_component"] == task_order.defense_component + another_workflow.form.data["defense_component"] + == task_order.portfolio.defense_component ) From 91db12140eac54bd3ea690592e14df8f499a9ff3 Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 26 Feb 2019 11:19:37 -0500 Subject: [PATCH 3/5] Show defense component on portfolio admin screen --- styles/components/_portfolio_layout.scss | 18 ++++++++++++++++++ templates/portfolios/admin.html | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 13c4a4cb..1c57ad0f 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -446,6 +446,8 @@ .form-col { .usa-input--validation--portfolioName { + margin-bottom: 0; + input { max-width: 30em; } @@ -455,4 +457,20 @@ } } } + + .defense-row { + .admin-title { + padding: 0 0 0.4rem 0; + margin-top: 0; + font-size: 1.7rem; + font-weight: 700; + max-width: 45em; + position: relative; + clear: both; + } + + .admin-content { + margin-bottom: 2rem; + } + } } diff --git a/templates/portfolios/admin.html b/templates/portfolios/admin.html index a39c3194..0bf8770a 100644 --- a/templates/portfolios/admin.html +++ b/templates/portfolios/admin.html @@ -24,6 +24,12 @@
+
+
+
{{ "forms.task_order.defense_component_label" | translate }}
+
{{ portfolio.defense_component }}
+
+
{% include "fragments/audit_events_log.html" %} From f739d2d20ff23d9e01950951bf6462ece7a8789d Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 26 Feb 2019 11:34:53 -0500 Subject: [PATCH 4/5] Update migration pointer to account for rebase --- .../ec1ba2363191_move_defense_component_to_portfolio_.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py b/alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py index 8b84ea53..83710195 100644 --- a/alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py +++ b/alembic/versions/ec1ba2363191_move_defense_component_to_portfolio_.py @@ -1,7 +1,7 @@ """Move defense component to Portfolio level Revision ID: ec1ba2363191 -Revises: fa3ba4049218 +Revises: fb22e47972a3 Create Date: 2019-02-22 14:43:49.408446 """ @@ -12,7 +12,7 @@ from sqlalchemy.sql import text # revision identifiers, used by Alembic. revision = 'ec1ba2363191' -down_revision = 'fa3ba4049218' +down_revision = 'fb22e47972a3' branch_labels = None depends_on = None From 8621f2054bc3c1297ea887ff7f35d447bb7ca97b Mon Sep 17 00:00:00 2001 From: Montana Date: Wed, 27 Feb 2019 09:53:06 -0500 Subject: [PATCH 5/5] Use defense_component property on TO model when appropriate --- templates/fragments/task_order_review/app_info.html | 2 +- tests/routes/task_orders/test_new_task_order.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/templates/fragments/task_order_review/app_info.html b/templates/fragments/task_order_review/app_info.html index d124da38..93db4525 100644 --- a/templates/fragments/task_order_review/app_info.html +++ b/templates/fragments/task_order_review/app_info.html @@ -2,7 +2,7 @@
{{ ReviewField(("task_orders.new.review.portfolio" | translate), task_order.portfolio_name) }} - {{ ReviewField(("task_orders.new.review.dod" | translate), task_order.portfolio.defense_component, filter="normalizeOrder") }} + {{ ReviewField(("task_orders.new.review.dod" | translate), task_order.defense_component, filter="normalizeOrder") }}
{{ ReviewField(("task_orders.new.review.scope" | translate), task_order.scope) }} diff --git a/tests/routes/task_orders/test_new_task_order.py b/tests/routes/task_orders/test_new_task_order.py index d1e9f433..a6908a21 100644 --- a/tests/routes/task_orders/test_new_task_order.py +++ b/tests/routes/task_orders/test_new_task_order.py @@ -209,8 +209,7 @@ def test_show_task_order_form(task_order): task_order.creator, task_order_id=task_order.id ) assert ( - another_workflow.form.data["defense_component"] - == task_order.portfolio.defense_component + another_workflow.form.data["defense_component"] == task_order.defense_component )