From 91e41199b7e7be12030535b20ea127297abb28a6 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Sat, 1 Jun 2019 17:02:18 -0400 Subject: [PATCH] Update TO form based on new model --- atst/domain/task_orders.py | 9 +- atst/forms/task_order.py | 242 +----------- atst/routes/task_orders/new.py | 360 ++---------------- templates/portfolios/task_orders/show.html | 52 --- .../{_new.html => _new-delete-later.html} | 0 templates/task_orders/edit.html | 0 templates/task_orders/new.html | 0 tests/domain/test_invitations.py | 16 +- tests/routes/task_orders/test_new.py | 239 ++---------- tests/routes/test_auth.py | 6 +- tests/test_access.py | 38 +- 11 files changed, 92 insertions(+), 870 deletions(-) rename templates/task_orders/{_new.html => _new-delete-later.html} (100%) create mode 100644 templates/task_orders/edit.html create mode 100644 templates/task_orders/new.html diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 37f8fc74..0a2caeda 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -18,8 +18,10 @@ class TaskOrders(BaseDomainClass): UNCLASSIFIED_FUNDING = [] @classmethod - def create(cls, creator, portfolio): - task_order = TaskOrder(portfolio=portfolio, creator=creator) + def create(cls, creator, portfolio_id, **kwargs): + task_order = TaskOrder(portfolio_id=portfolio_id, creator=creator) + for key, value in kwargs.items(): + setattr(task_order, key, value) db.session.add(task_order) db.session.commit() @@ -27,7 +29,8 @@ class TaskOrders(BaseDomainClass): return task_order @classmethod - def update(cls, task_order, **kwargs): + def update(cls, task_order_id, **kwargs): + task_order = TaskOrders.get(task_order_id) for key, value in kwargs.items(): setattr(task_order, key, value) diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 997d71ff..d6a8406e 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -1,131 +1,25 @@ from wtforms.fields import ( BooleanField, - DecimalField, - RadioField, - SelectField, - SelectMultipleField, StringField, - TextAreaField, - FileField, ) -from wtforms.fields.html5 import DateField, TelField -from wtforms.widgets import ListWidget, CheckboxInput -from wtforms.validators import Email, Length, Required, Optional -from flask_wtf.file import FileAllowed - -from atst.forms.validators import IsNumber, PhoneNumber, RequiredIf +from wtforms.fields.html5 import DateField +from wtforms.validators import Required, Optional from .forms import BaseForm -from .data import ( - SERVICE_BRANCHES, - APP_MIGRATION, - APPLICATION_COMPLEXITY, - DEV_TEAM, - TEAM_EXPERIENCE, - PERIOD_OF_PERFORMANCE_LENGTH, -) from atst.utils.localization import translate -class AppInfoWithExistingPortfolioForm(BaseForm): - scope = TextAreaField( - translate("forms.task_order.scope_label"), - description=translate("forms.task_order.scope_description"), - ) - app_migration = RadioField( - translate("forms.task_order.app_migration.label"), - description=translate("forms.task_order.app_migration.description"), - choices=APP_MIGRATION, - default="", - validators=[Optional()], - ) - native_apps = RadioField( - translate("forms.task_order.native_apps.label"), - description=translate("forms.task_order.native_apps.description"), - choices=[("yes", "Yes"), ("no", "No"), ("not_sure", "Not Sure")], - default="", - validators=[Optional()], - ) - complexity = SelectMultipleField( - translate("forms.task_order.complexity.label"), - description=translate("forms.task_order.complexity.description"), - choices=APPLICATION_COMPLEXITY, - default=None, - filters=[BaseForm.remove_empty_string], - widget=ListWidget(prefix_label=False), - option_widget=CheckboxInput(), - ) - complexity_other = StringField( - translate("forms.task_order.complexity_other_label"), - default=None, - filters=[BaseForm.remove_empty_string], - ) - dev_team = SelectMultipleField( - translate("forms.task_order.dev_team.label"), - description=translate("forms.task_order.dev_team.description"), - choices=DEV_TEAM, - default=None, - filters=[BaseForm.remove_empty_string], - widget=ListWidget(prefix_label=False), - option_widget=CheckboxInput(), - ) - dev_team_other = StringField( - translate("forms.task_order.dev_team_other_label"), - default=None, - filters=[BaseForm.remove_empty_string], - ) - team_experience = RadioField( - translate("forms.task_order.team_experience.label"), - description=translate("forms.task_order.team_experience.description"), - choices=TEAM_EXPERIENCE, - default="", - validators=[Optional()], - ) - - -class AppInfoForm(AppInfoWithExistingPortfolioForm): - portfolio_name = StringField( - translate("forms.task_order.portfolio_name_label"), - description=translate("forms.task_order.portfolio_name_description"), - filters=[BaseForm.remove_empty_string], - 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, - default="", - filters=[BaseForm.remove_empty_string], - ) +class TaskOrderForm(BaseForm): + number = StringField(validators=[Required()]) class FundingForm(BaseForm): - performance_length = SelectField( - translate("forms.task_order.performance_length.label"), - choices=PERIOD_OF_PERFORMANCE_LENGTH, - ) start_date = DateField( translate("forms.task_order.start_date_label"), format="%m/%d/%Y" ) end_date = DateField( translate("forms.task_order.end_date_label"), format="%m/%d/%Y" ) - csp_estimate = FileField( - translate("forms.task_order.csp_estimate_label"), - description=translate("forms.task_order.csp_estimate_description"), - validators=[ - FileAllowed( - ["pdf", "png"], translate("forms.task_order.file_format_not_allowed") - ) - ], - render_kw={"accept": ".pdf,.png,application/pdf,image/png"}, - ) clin_01 = DecimalField( translate("forms.task_order.clin_01_label"), validators=[Optional()] ) @@ -151,135 +45,7 @@ class UnclassifiedFundingForm(FundingForm): ) -class OversightForm(BaseForm): - ko_first_name = StringField( - translate("forms.task_order.oversight_first_name_label"), - filters=[BaseForm.remove_empty_string], - ) - ko_last_name = StringField( - translate("forms.task_order.oversight_last_name_label"), - filters=[BaseForm.remove_empty_string], - ) - ko_email = StringField( - translate("forms.task_order.oversight_email_label"), - validators=[Optional(), Email()], - filters=[BaseForm.remove_empty_string], - ) - ko_phone_number = TelField( - translate("forms.task_order.oversight_phone_label"), - validators=[Optional(), PhoneNumber()], - filters=[BaseForm.remove_empty_string], - ) - ko_dod_id = StringField( - translate("forms.task_order.oversight_dod_id_label"), - filters=[BaseForm.remove_empty_string], - validators=[ - RequiredIf(lambda form: form._fields.get("ko_invite").data), - Length(min=10), - IsNumber(), - ], - ) - - am_cor = BooleanField(translate("forms.task_order.oversight_am_cor_label")) - cor_first_name = StringField( - translate("forms.task_order.oversight_first_name_label"), - filters=[BaseForm.remove_empty_string], - ) - cor_last_name = StringField( - translate("forms.task_order.oversight_last_name_label"), - filters=[BaseForm.remove_empty_string], - ) - cor_email = StringField( - translate("forms.task_order.oversight_email_label"), - filters=[BaseForm.remove_empty_string], - validators=[Optional(), Email()], - ) - cor_phone_number = TelField( - translate("forms.task_order.oversight_phone_label"), - filters=[BaseForm.remove_empty_string], - validators=[ - RequiredIf(lambda form: not form._fields.get("am_cor").data), - Optional(), - PhoneNumber(), - ], - ) - cor_dod_id = StringField( - translate("forms.task_order.oversight_dod_id_label"), - filters=[BaseForm.remove_empty_string], - validators=[ - RequiredIf( - lambda form: not form._fields.get("am_cor").data - and form._fields.get("cor_invite").data - ), - Length(min=10), - IsNumber(), - ], - ) - - so_first_name = StringField( - translate("forms.task_order.oversight_first_name_label"), - filters=[BaseForm.remove_empty_string], - ) - so_last_name = StringField( - translate("forms.task_order.oversight_last_name_label"), - filters=[BaseForm.remove_empty_string], - ) - so_email = StringField( - translate("forms.task_order.oversight_email_label"), - filters=[BaseForm.remove_empty_string], - validators=[Optional(), Email()], - ) - so_phone_number = TelField( - translate("forms.task_order.oversight_phone_label"), - filters=[BaseForm.remove_empty_string], - validators=[Optional(), PhoneNumber()], - ) - so_dod_id = StringField( - translate("forms.task_order.oversight_dod_id_label"), - filters=[BaseForm.remove_empty_string], - validators=[ - RequiredIf(lambda form: form._fields.get("so_invite").data), - Length(min=10), - IsNumber(), - ], - ) - - ko_invite = BooleanField( - translate("forms.task_order.ko_invite_label"), - description=translate("forms.task_order.skip_invite_description"), - ) - cor_invite = BooleanField( - translate("forms.task_order.cor_invite_label"), - description=translate("forms.task_order.skip_invite_description"), - ) - so_invite = BooleanField( - translate("forms.task_order.so_invite_label"), - description=translate("forms.task_order.skip_invite_description"), - ) - - -class ReviewForm(BaseForm): - pass - - class SignatureForm(BaseForm): - level_of_warrant = DecimalField( - translate("task_orders.sign.level_of_warrant_label"), - description=translate("task_orders.sign.level_of_warrant_description"), - validators=[ - RequiredIf( - lambda form: ( - form._fields.get("unlimited_level_of_warrant").data is not True - ) - ) - ], - ) - - unlimited_level_of_warrant = BooleanField( - translate("task_orders.sign.unlimited_level_of_warrant_description"), - validators=[Optional()], - ) - signature = BooleanField( translate("task_orders.sign.digital_signature_label"), description=translate("task_orders.sign.digital_signature_description"), diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index ad9b2826..4ca8cdc0 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -1,355 +1,59 @@ from copy import deepcopy from flask import ( - request as http_request, - render_template, g, redirect, + render_template, + request as http_request, url_for, - current_app as app, ) from . import task_orders_bp -from atst.domain.task_orders import TaskOrders -from atst.domain.portfolios import Portfolios -from atst.utils.flash import formatted_flash as flash -import atst.forms.task_order as task_order_form from atst.domain.authz.decorator import user_can_access_decorator as user_can +from atst.domain.task_orders import TaskOrders +from atst.forms.task_order import TaskOrderForm from atst.models.permissions import Permissions +from atst.utils.flash import formatted_flash as flash from atst.utils.localization import translate -TASK_ORDER_SECTIONS = [ - { - "section": "app_info", - "title": translate("forms.task_order.first_step_title"), - "template": "task_orders/new/app_info.html", - "form": task_order_form.AppInfoForm, - }, - { - "section": "funding", - "title": "Funding", - "template": "task_orders/new/funding.html", - "form": task_order_form.FundingForm, - "unclassified_form": task_order_form.UnclassifiedFundingForm, - }, - { - "section": "oversight", - "title": "Oversight", - "template": "task_orders/new/oversight.html", - "form": task_order_form.OversightForm, - }, - { - "section": "review", - "title": "Review", - "template": "task_orders/new/review.html", - "form": task_order_form.ReviewForm, - }, -] - - -class ShowTaskOrderWorkflow: - 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._task_order = None - self.portfolio_id = portfolio_id - self._portfolio = None - self._section = TASK_ORDER_SECTIONS[screen - 1] - self._form = None - - @property - def task_order(self): - if not self._task_order and self.task_order_id: - if self.portfolio_id: - self._task_order = TaskOrders.get( - self.task_order_id, portfolio_id=self.portfolio_id - ) - else: - self._task_order = TaskOrders.get(self.task_order_id) - - 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 = ( - "unclassified_form" - if "unclassified_form" in self._section and not app.config.get("CLASSIFIED") - else "form" - ) - - if self._form: - pass - elif self.task_order: - if self.pf_attributes_read_only and self.screen == 1: - self._form = task_order_form.AppInfoWithExistingPortfolioForm( - obj=self.task_order - ) - else: - self._form = self._section[form_type](obj=self.task_order) - # manually set SelectMultipleFields - if self._section["section"] == "app_info": - self._form.complexity.data = self.task_order.complexity - self._form.dev_team.data = self.task_order.dev_team - elif self._section["section"] == "oversight": - if self.user.dod_id == self.task_order.cor_dod_id: - self._form.am_cor.data = True - if self.task_order.contracting_officer or self.task_order.ko_invite: - self._form.ko_invite.data = True - if ( - self.task_order.contracting_officer_representative - or self.task_order.cor_invite - ): - self._form.cor_invite.data = True - if self.task_order.security_officer or self.task_order.so_invite: - self._form.so_invite.data = True - - else: - self._form = self._section[form_type]() - return self._form - - @property - def template(self): - return self._section["template"] - - @property - def display_screens(self): - screen_info = deepcopy(TASK_ORDER_SECTIONS) - - if self.task_order: - for section in screen_info: - section["completion"] = TaskOrders.section_completion_status( - self.task_order, section["section"] - ) - - return screen_info - - @property - def is_complete(self): - 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 - - -class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow): - def __init__( - self, user, form_data, screen=1, task_order_id=None, portfolio_id=None - ): - self.user = user - self.form_data = form_data - self.screen = screen - self.task_order_id = task_order_id - self.portfolio_id = portfolio_id - self._task_order = None - self._section = TASK_ORDER_SECTIONS[screen - 1] - 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 - def portfolio(self): - if self.task_order: - return self.task_order.portfolio - - @property - def task_order_form_data(self): - 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 bool(to_data["complexity"]) - and "other" not in to_data["complexity"] - ): - to_data["complexity_other"] = None - if ( - "dev_team" in to_data - and bool(to_data["dev_team"]) - and "other" not in to_data["dev_team"] - ): - to_data["dev_team_other"] = None - - if self.form_data.get("am_cor"): - cor_data = { - "cor_first_name": self.user.first_name, - "cor_last_name": self.user.last_name, - "cor_email": self.user.email, - "cor_phone_number": self.user.phone_number, - "cor_dod_id": self.user.dod_id, - "cor_id": self.user.id, - } - to_data = {**to_data, **cor_data} - - return to_data - - def validate(self): - return self.form.validate() - - def update(self): - if self.task_order: - if "portfolio_name" in self.form.data: - new_name = self.form.data["portfolio_name"] - old_name = self.task_order.portfolio_name - if not new_name == old_name: - Portfolios.update(self.task_order.portfolio, {"name": new_name}) - TaskOrders.update(self.task_order, **self.task_order_form_data) - else: - if self.portfolio_id: - pf = Portfolios.get(self.user, self.portfolio_id) - else: - pf = Portfolios.create( - user=self.user, - portfolio_attrs={ - "name": self.form.portfolio_name.data, - "defense_component": self.form.defense_component.data, - }, - ) - self._task_order = TaskOrders.create(portfolio=pf, creator=self.user) - TaskOrders.update(self.task_order, **self.task_order_form_data) - - return self.task_order - - @task_orders_bp.route("/task_orders/new/get_started") +# TODO: see if this route still exists in new design def get_started(): return render_template("task_orders/new/get_started.html") # pragma: no cover -def is_new_task_order(*_args, **kwargs): - return ( - "screen" in kwargs - and kwargs["screen"] == 1 - and "task_order_id" not in kwargs - and "portfolio_id" not in kwargs - ) +@task_orders_bp.route("/portfolios//task_orders/new") +@user_can(Permissions.CREATE_TASK_ORDER, message="view new task order form") +def new(portfolio_id): + return render_template("task_orders/new", form=TaskOrderForm()) -# TODO: /task_orders/new// should not exist -@task_orders_bp.route("/task_orders/new/") -@task_orders_bp.route("/task_orders/new//") -@task_orders_bp.route("/portfolios//task_orders/new/") -@user_can( - Permissions.CREATE_TASK_ORDER, - override=is_new_task_order, - message="view new task order form", -) -def new(screen, task_order_id=None, portfolio_id=None): - workflow = ShowTaskOrderWorkflow( - g.current_user, screen, task_order_id, portfolio_id - ) - template_args = { - "current": screen, - "task_order_id": task_order_id, - "screens": workflow.display_screens, - "form": workflow.form, - "complete": workflow.is_complete, - } +@task_orders_bp.route("/portfolios//task_orders/new", methods=["POST"]) +@user_can(Permissions.CREATE_TASK_ORDER, message="create new task order") +def create(portfolio_id): + form_data = http_request.form + form = TaskOrderForm(form_data) - 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 - else: - url_args["portfolio_id"] = portfolio_id - - if workflow.task_order: - template_args["task_order"] = workflow.task_order - if http_request.args.get("ko_edit"): - template_args["ko_edit"] = True - template_args["next"] = url_for( - "task_orders.ko_review", task_order_id=task_order_id - ) - url_args["next"] = template_args["next"] - - template_args["action_url"] = url_for("task_orders.update", **url_args) - - return render_template(workflow.template, **template_args) + if form.validate(): + TaskOrders.create(g.current_user, portfolio_id, **form.data) + # TODO: ask UX where do you go after save + + +@task_orders_bp.route("/portfolios//task_orders//edit") +@user_can(Permissions.CREATE_TASK_ORDER, message="update task order") +def edit(portfolio_id, taks_order_id): + return render_template("task_orders/edit", form=TaskOrderForm()) -# TODO: /task_orders/new// should not exist -@task_orders_bp.route("/task_orders/new/", methods=["POST"]) -@task_orders_bp.route("/task_orders/new//", methods=["POST"]) @task_orders_bp.route( - "/portfolios//task_orders/new/", methods=["POST"] + "/portfolios//task_orders/", methods=["POST"] ) -@user_can( - Permissions.CREATE_TASK_ORDER, - override=is_new_task_order, - message="update task order", -) -def update(screen, task_order_id=None, portfolio_id=None): - form_data = {**http_request.form, **http_request.files} - workflow = UpdateTaskOrderWorkflow( - g.current_user, form_data, screen, task_order_id, portfolio_id - ) +@user_can(Permissions.CREATE_TASK_ORDER, message="update task order") +def update(portfolio_id, task_order_id=None): + form_data = http_request.form + form = TaskOrderForm(form_data) - if workflow.validate(): - workflow.update() - if http_request.args.get("next"): - redirect_url = http_request.args.get("next") - else: - redirect_url = url_for( - "task_orders.new", - screen=screen + 1, - task_order_id=workflow.task_order.id, - ) - return redirect(redirect_url) - else: - return render_template( - workflow.template, - current=screen, - task_order_id=task_order_id, - portfolio_id=portfolio_id, - screens=workflow.display_screens, - form=workflow.form, - ) + if form.validate(): + TaskOrders.update(task_order_id, **form.data) + # TODO: ask UX where do you go after save diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index 48afe940..7ac320a1 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -6,48 +6,6 @@ {% block portfolio_content %} -{% macro officer_name(officer) -%} - {%- if not officer -%} - not yet invited - {%- elif officer == g.current_user -%} - you - {%- else -%} - {{ officer.full_name }} - {%- endif -%} -{%- endmacro -%} - -{% macro Step(description="", complete=True, button_text=None, button_url=None) %} -
-
-
- {% if complete %} - Completed - {% else %} - Not started - {% endif %} -
-
-
- {{ description }} -
-
-
- {% if not task_order.is_active and button_text and button_url %} - - {{ button_text }} - - {% endif %} -
-
- - {% if caller %} - {{ caller() }} - {% endif %} -
-{% endmacro %} - {% macro DocumentLink(title="", link_url="", description="") %} {% set disabled = not link_url %}