Merge pull request #685 from dod-ccpo/use-pf-name-and-dc-for-to
Portfolio Name and Defense Component read only
This commit is contained in:
commit
a2754d0646
@ -27,26 +27,11 @@ from .data import (
|
||||
from atst.utils.localization import translate
|
||||
|
||||
|
||||
class AppInfoForm(BaseForm):
|
||||
portfolio_name = StringField(
|
||||
translate("forms.task_order.portfolio_name_label"),
|
||||
description=translate("forms.task_order.portfolio_name_description"),
|
||||
validators=[
|
||||
Required(),
|
||||
Length(
|
||||
min=4,
|
||||
max=100,
|
||||
message=translate("forms.portfolio.name_length_validation_message"),
|
||||
),
|
||||
],
|
||||
)
|
||||
class AppInfoWithExistingPortfolioForm(BaseForm):
|
||||
scope = TextAreaField(
|
||||
translate("forms.task_order.scope_label"),
|
||||
description=translate("forms.task_order.scope_description"),
|
||||
)
|
||||
defense_component = SelectField(
|
||||
translate("forms.task_order.defense_component_label"), choices=SERVICE_BRANCHES
|
||||
)
|
||||
app_migration = RadioField(
|
||||
translate("forms.task_order.app_migration.label"),
|
||||
description=translate("forms.task_order.app_migration.description"),
|
||||
@ -88,6 +73,24 @@ class AppInfoForm(BaseForm):
|
||||
)
|
||||
|
||||
|
||||
class AppInfoForm(AppInfoWithExistingPortfolioForm):
|
||||
portfolio_name = StringField(
|
||||
translate("forms.task_order.portfolio_name_label"),
|
||||
description=translate("forms.task_order.portfolio_name_description"),
|
||||
validators=[
|
||||
Required(),
|
||||
Length(
|
||||
min=4,
|
||||
max=100,
|
||||
message=translate("forms.portfolio.name_length_validation_message"),
|
||||
),
|
||||
],
|
||||
)
|
||||
defense_component = SelectField(
|
||||
translate("forms.task_order.defense_component_label"), choices=SERVICE_BRANCHES
|
||||
)
|
||||
|
||||
|
||||
class FundingForm(BaseForm):
|
||||
performance_length = SelectField(
|
||||
translate("forms.task_order.performance_length.label"),
|
||||
|
@ -36,6 +36,10 @@ class Portfolio(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
||||
def user_count(self):
|
||||
return len(self.members)
|
||||
|
||||
@property
|
||||
def num_task_orders(self):
|
||||
return len(self.task_orders)
|
||||
|
||||
@property
|
||||
def members(self):
|
||||
return (
|
||||
|
@ -46,12 +46,14 @@ TASK_ORDER_SECTIONS = [
|
||||
|
||||
|
||||
class ShowTaskOrderWorkflow:
|
||||
def __init__(self, user, screen=1, task_order_id=None):
|
||||
def __init__(self, user, screen=1, task_order_id=None, portfolio_id=None):
|
||||
self.user = user
|
||||
self.screen = screen
|
||||
self.task_order_id = task_order_id
|
||||
self._section = TASK_ORDER_SECTIONS[screen - 1]
|
||||
self._task_order = None
|
||||
self.portfolio_id = portfolio_id
|
||||
self._portfolio = None
|
||||
self._section = TASK_ORDER_SECTIONS[screen - 1]
|
||||
self._form = None
|
||||
|
||||
@property
|
||||
@ -61,6 +63,16 @@ class ShowTaskOrderWorkflow:
|
||||
|
||||
return self._task_order
|
||||
|
||||
@property
|
||||
def portfolio(self):
|
||||
if not self._portfolio:
|
||||
if self.task_order:
|
||||
self._portfolio = self.task_order.portfolio
|
||||
elif self.portfolio_id:
|
||||
self._portfolio = Portfolios.get(self.user, self.portfolio_id)
|
||||
|
||||
return self._portfolio
|
||||
|
||||
@property
|
||||
def form(self):
|
||||
form_type = (
|
||||
@ -90,6 +102,11 @@ class ShowTaskOrderWorkflow:
|
||||
else:
|
||||
self._form = self._section[form_type]()
|
||||
|
||||
if self.pf_attributes_read_only and self.screen == 1:
|
||||
self._form = task_order_form.AppInfoWithExistingPortfolioForm(
|
||||
obj=self.task_order
|
||||
)
|
||||
|
||||
return self._form
|
||||
|
||||
@property
|
||||
@ -110,9 +127,17 @@ class ShowTaskOrderWorkflow:
|
||||
|
||||
@property
|
||||
def is_complete(self):
|
||||
if self.task_order:
|
||||
if TaskOrders.all_sections_complete(self.task_order):
|
||||
return True
|
||||
if self.task_order and TaskOrders.all_sections_complete(self.task_order):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def pf_attributes_read_only(self):
|
||||
if self.task_order and self.portfolio.num_task_orders > 1:
|
||||
return True
|
||||
elif self.portfolio_id:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@ -128,15 +153,27 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow):
|
||||
self.portfolio_id = portfolio_id
|
||||
self._task_order = None
|
||||
self._section = TASK_ORDER_SECTIONS[screen - 1]
|
||||
form_type = (
|
||||
"unclassified_form"
|
||||
if "unclassified_form" in self._section and not app.config.get("CLASSIFIED")
|
||||
else "form"
|
||||
)
|
||||
self._form = self._section[form_type](self.form_data, obj=self.task_order)
|
||||
self._form = None
|
||||
|
||||
@property
|
||||
def form(self):
|
||||
if not self._form:
|
||||
form_type = (
|
||||
"unclassified_form"
|
||||
if "unclassified_form" in self._section
|
||||
and not app.config.get("CLASSIFIED")
|
||||
else "form"
|
||||
)
|
||||
|
||||
if self.pf_attributes_read_only and self.screen == 1:
|
||||
self._form = task_order_form.AppInfoWithExistingPortfolioForm(
|
||||
self.form_data
|
||||
)
|
||||
else:
|
||||
self._form = self._section[form_type](
|
||||
self.form_data, obj=self.task_order
|
||||
)
|
||||
|
||||
return self._form
|
||||
|
||||
@property
|
||||
@ -206,21 +243,24 @@ def get_started():
|
||||
@task_orders_bp.route("/task_orders/new/<int:screen>/<task_order_id>")
|
||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new/<int:screen>")
|
||||
def new(screen, task_order_id=None, portfolio_id=None):
|
||||
if task_order_id and screen is 4:
|
||||
task_order = TaskOrders.get(g.current_user, task_order_id)
|
||||
if not TaskOrders.all_sections_complete(task_order):
|
||||
flash("task_order_draft")
|
||||
|
||||
workflow = ShowTaskOrderWorkflow(g.current_user, screen, task_order_id)
|
||||
workflow = ShowTaskOrderWorkflow(
|
||||
g.current_user, screen, task_order_id, portfolio_id
|
||||
)
|
||||
template_args = {
|
||||
"current": screen,
|
||||
"task_order_id": task_order_id,
|
||||
"portfolio_id": portfolio_id,
|
||||
"screens": workflow.display_screens,
|
||||
"form": workflow.form,
|
||||
"complete": workflow.is_complete,
|
||||
}
|
||||
|
||||
if task_order_id and screen is 4:
|
||||
if not TaskOrders.all_sections_complete(workflow.task_order):
|
||||
flash("task_order_draft")
|
||||
|
||||
if workflow.pf_attributes_read_only:
|
||||
template_args["portfolio"] = workflow.portfolio
|
||||
|
||||
url_args = {"screen": screen}
|
||||
if task_order_id:
|
||||
url_args["task_order_id"] = task_order_id
|
||||
|
@ -4,6 +4,7 @@
|
||||
{% from "components/options_input.html" import OptionsInput %}
|
||||
{% from "components/date_input.html" import DateInput %}
|
||||
{% from "components/multi_checkbox_input.html" import MultiCheckboxInput %}
|
||||
{% from "components/review_field.html" import ReviewField %}
|
||||
|
||||
{% block heading %}
|
||||
{{ "task_orders.new.app_info.section_title"| translate }}
|
||||
@ -14,11 +15,21 @@
|
||||
|
||||
<!-- App Info Section -->
|
||||
<h3 class="task-order-form__heading subheading">{{ "task_orders.new.app_info.basic_info_title"| translate }}</h3>
|
||||
{{ TextInput(form.portfolio_name, placeholder="The name of your office or organization", validation="portfolioName") }}
|
||||
|
||||
{% if portfolio %}
|
||||
{{ ReviewField(heading="forms.portfolio.name_label" | translate, field=portfolio.name) }}
|
||||
{% else %}
|
||||
{{ TextInput(form.portfolio_name, placeholder="The name of your office or organization", validation="portfolioName") }}
|
||||
{% endif %}
|
||||
{{ TextInput(form.scope, paragraph=True) }}
|
||||
<p><i>{{ "task_orders.new.app_info.sample_scope" | translate | safe }}</i></p>
|
||||
|
||||
<div class="subheading--black">
|
||||
{{ OptionsInput(form.defense_component) }}
|
||||
{% if portfolio %}
|
||||
{{ ReviewField(heading="forms.task_order.defense_component_label" | translate, field=portfolio.defense_component) }}
|
||||
{% else %}
|
||||
{{ OptionsInput(form.defense_component) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
@ -9,6 +9,26 @@ from atst.utils.localization import translate
|
||||
from tests.factories import UserFactory, TaskOrderFactory, PortfolioFactory
|
||||
|
||||
|
||||
class TestShowTaskOrderWorkflow:
|
||||
def test_portfolio_when_task_order_exists(self):
|
||||
portfolio = PortfolioFactory.create()
|
||||
task_order = TaskOrderFactory(portfolio=portfolio)
|
||||
assert portfolio.num_task_orders > 0
|
||||
|
||||
workflow = ShowTaskOrderWorkflow(
|
||||
user=task_order.creator, task_order_id=task_order.id
|
||||
)
|
||||
assert portfolio == workflow.portfolio
|
||||
|
||||
def test_portfolio_with_portfolio_id(self):
|
||||
user = UserFactory.create()
|
||||
portfolio = PortfolioFactory.create(owner=user)
|
||||
workflow = ShowTaskOrderWorkflow(
|
||||
user=portfolio.owner, portfolio_id=portfolio.id
|
||||
)
|
||||
assert portfolio == workflow.portfolio
|
||||
|
||||
|
||||
def test_new_task_order(client, user_session):
|
||||
creator = UserFactory.create()
|
||||
user_session()
|
||||
@ -42,6 +62,37 @@ def serialize_dates(data):
|
||||
return data
|
||||
|
||||
|
||||
def test_new_to_can_edit_pf_attributes_screen_1():
|
||||
portfolio = PortfolioFactory.create()
|
||||
workflow = ShowTaskOrderWorkflow(user=portfolio.owner)
|
||||
assert not workflow.pf_attributes_read_only
|
||||
|
||||
|
||||
def test_new_pf_can_edit_pf_attributes_on_back_navigation():
|
||||
portfolio = PortfolioFactory.create()
|
||||
pf_task_order = TaskOrderFactory(portfolio=portfolio)
|
||||
pf_workflow = ShowTaskOrderWorkflow(
|
||||
user=pf_task_order.creator, task_order_id=pf_task_order.id
|
||||
)
|
||||
assert not pf_workflow.pf_attributes_read_only
|
||||
|
||||
|
||||
def test_to_on_pf_cannot_edit_pf_attributes():
|
||||
portfolio = PortfolioFactory.create()
|
||||
pf_task_order = TaskOrderFactory(portfolio=portfolio)
|
||||
|
||||
workflow = ShowTaskOrderWorkflow(user=portfolio.owner, portfolio_id=portfolio.id)
|
||||
assert portfolio.num_task_orders == 1
|
||||
assert workflow.pf_attributes_read_only
|
||||
|
||||
second_task_order = TaskOrderFactory(portfolio=portfolio)
|
||||
second_workflow = ShowTaskOrderWorkflow(
|
||||
user=portfolio.owner, task_order_id=second_task_order.id
|
||||
)
|
||||
assert portfolio.num_task_orders > 1
|
||||
assert second_workflow.pf_attributes_read_only
|
||||
|
||||
|
||||
# TODO: this test will need to be more complicated when we add validation to
|
||||
# the forms
|
||||
def test_create_new_task_order(client, user_session, pdf_upload):
|
||||
@ -66,7 +117,6 @@ 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")
|
||||
@ -91,10 +141,8 @@ def test_create_new_task_order_for_portfolio(client, user_session):
|
||||
|
||||
task_order_data = TaskOrderFactory.dictionary()
|
||||
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
|
||||
app_info_data["portfolio_name"] = portfolio.name
|
||||
app_info_data["defense_component"] = portfolio.defense_component
|
||||
|
||||
response = client.post(
|
||||
url_for("task_orders.update", screen=1, portfolio_id=portfolio.id),
|
||||
@ -105,6 +153,8 @@ def test_create_new_task_order_for_portfolio(client, user_session):
|
||||
|
||||
created_task_order_id = response.headers["Location"].split("/")[-1]
|
||||
created_task_order = TaskOrders.get(creator, created_task_order_id)
|
||||
assert created_task_order.portfolio_name == portfolio.name
|
||||
assert created_task_order.defense_component == portfolio.defense_component
|
||||
assert created_task_order.portfolio == portfolio
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user