Merge pull request #677 from dod-ccpo/move-component-to-portfolio
Move defense_component to portfolios model
This commit is contained in:
		| @@ -0,0 +1,52 @@ | ||||
| """Move defense component to Portfolio level | ||||
|  | ||||
| Revision ID: ec1ba2363191 | ||||
| Revises: fb22e47972a3 | ||||
| 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 = 'fb22e47972a3' | ||||
| 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 ### | ||||
| @@ -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 | ||||
|         ) | ||||
|   | ||||
| @@ -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") | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,12 @@ | ||||
|           <button type="submit" class="usa-button usa-button-big usa-button-primary" tabindex="0">Save</button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class='defense-row'> | ||||
|         <div> | ||||
|           <div class='admin-title'>{{ "forms.task_order.defense_component_label" | translate }}</div> | ||||
|           <div class='admin-content'>{{ portfolio.defense_component }}</div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|  | ||||
|     {% include "fragments/audit_events_log.html" %} | ||||
|   | ||||
| @@ -29,12 +29,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") | ||||
|   | ||||
| @@ -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)] | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user