From 5cc1c18378f49fb6613722cd6763663d20fe6384 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 29 Jan 2019 09:54:11 -0500 Subject: [PATCH] Add route to update TO officer information --- atst/routes/portfolios/task_orders.py | 36 +++- styles/sections/_task_order.scss | 17 ++ .../portfolios/task_orders/invitations.html | 179 ++++++++++++------ tests/routes/portfolios/test_task_orders.py | 51 +++++ 4 files changed, 219 insertions(+), 64 deletions(-) diff --git a/atst/routes/portfolios/task_orders.py b/atst/routes/portfolios/task_orders.py index 8bcb4715..61a6c9a7 100644 --- a/atst/routes/portfolios/task_orders.py +++ b/atst/routes/portfolios/task_orders.py @@ -1,11 +1,13 @@ from collections import defaultdict from operator import itemgetter -from flask import g, render_template, url_for +from flask import g, redirect, render_template, url_for, request as http_request from . import portfolios_bp +from atst.database import db from atst.domain.task_orders import TaskOrders from atst.domain.portfolios import Portfolios +from atst.forms.officers import EditTaskOrderOfficersForm from atst.models.task_order import Status as TaskOrderStatus @@ -69,8 +71,40 @@ def view_task_order(portfolio_id, task_order_id): def task_order_invitations(portfolio_id, task_order_id): portfolio = Portfolios.get(g.current_user, portfolio_id) task_order = TaskOrders.get(g.current_user, task_order_id) + form = EditTaskOrderOfficersForm(obj=task_order) return render_template( "portfolios/task_orders/invitations.html", portfolio=portfolio, task_order=task_order, + form=form, ) + + +@portfolios_bp.route( + "/portfolios//task_order//invitations", + methods=["POST"], +) +def edit_task_order_invitations(portfolio_id, task_order_id): + portfolio = Portfolios.get(g.current_user, portfolio_id) + task_order = TaskOrders.get(g.current_user, task_order_id) + form = EditTaskOrderOfficersForm(formdata=http_request.form, obj=task_order) + + if form.validate(): + form.populate_obj(task_order) + db.session.add(task_order) + db.session.commit() + + return redirect( + url_for( + "portfolios.task_order_invitations", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ) + ) + else: + return render_template( + "portfolios/task_orders/invitations.html", + portfolio=portfolio, + task_order=task_order, + form=form, + ) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 9e2956a7..078c09ac 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -343,6 +343,23 @@ } } + .officer__form { + .officer__form--actions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + + .usa-button { + margin-left: 4 * $gap; + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + } + } + } + .officer__actions { margin-left: -2 * $gap; diff --git a/templates/portfolios/task_orders/invitations.html b/templates/portfolios/task_orders/invitations.html index 0ebad204..7fda4f2d 100644 --- a/templates/portfolios/task_orders/invitations.html +++ b/templates/portfolios/task_orders/invitations.html @@ -1,76 +1,125 @@ {% extends "portfolios/base.html" %} +{% from "components/checkbox_input.html" import CheckboxInput %} {% from "components/icon.html" import Icon %} +{% from "components/text_input.html" import TextInput %} -{% macro Link(text, icon_name, url='#', classes='') %} - +{% macro Link(text, icon_name, onClick=None, url='#', classes='') %} + {{ Icon(icon_name) }} {{ text }} {% endmacro %} -{% macro OfficerInfo(task_order, officer_type) %} -
+{% macro EditOfficerInfo(form, officer_type) -%} +
+ +
+{% endmacro %} + +{% macro OfficerInfo(task_order, officer_type, form) %} +

{{ ("task_orders.invitations." + officer_type + ".title") | translate }}

{{ ("task_orders.invitations." + officer_type + ".description") | translate }}

- {% set prefix = { "contracting_officer": "ko", "contracting_officer_representative": "cor", "security_officer": "so" }[officer_type] %} - {% set first_name = task_order[prefix + "_first_name"] %} - {% set last_name = task_order[prefix + "_last_name"] %} - {% set email = task_order[prefix + "_email"] %} - {% set phone_number = task_order[prefix + "_phone_number"] %} - {% set dod_id = task_order[prefix + "_dod_id"] %} + +
- {% if task_order[officer_type] %} -
-
-
{{ first_name }} {{ last_name }}
-
- {{ Icon("ok", classes="invited") }} - Invited + {% set prefix = { "contracting_officer": "ko", "contracting_officer_representative": "cor", "security_officer": "so" }[officer_type] %} + {% set first_name = task_order[prefix + "_first_name"] %} + {% set last_name = task_order[prefix + "_last_name"] %} + {% set email = task_order[prefix + "_email"] %} + {% set phone_number = task_order[prefix + "_phone_number"] %} + {% set dod_id = task_order[prefix + "_dod_id"] %} + + {% if task_order[officer_type] %} +
+
+
{{ first_name }} {{ last_name }}
+
+ {{ Icon("ok", classes="invited") }} + Invited +
+ +

{{ phone_number | usPhone }}

+

{{ "task_orders.invitations.dod_id_label" | translate}}: {{ dod_id }}

- -

{{ phone_number | usPhone }}

-

{{ "task_orders.invitations.dod_id_label" | translate}}: {{ dod_id }}

-
-
- {{ Link("Update", "edit") }} - {{ Link("Resend Invitation", "avatar") }} - {{ Link("Remove", "trash", classes="remove") }} -
- {% elif first_name and last_name %} -
-
-
{{ first_name }} {{ last_name }}
+
+ {{ Link("Update", "edit", onClick="edit") }} + {{ Link("Resend Invitation", "avatar") }} + {{ Link("Remove", "trash", classes="remove") }} +
+ {% elif first_name and last_name %} +
+
+
{{ first_name }} {{ last_name }}
+
+ {{ Icon("alert", classes="uninvited") }} + Not Invited +
+
+ +

{{ phone_number | usPhone }}

+
+
+ {{ Link("Update", "edit", onClick="edit") }} + {{ Link("Remove", "trash", classes="remove") }} + +
+ {% else %} +
{{ Icon("alert", classes="uninvited") }} - Not Invited + Not specified
- -

{{ phone_number | usPhone }}

-
-
- {{ Link("Update", "edit") }} - {{ Link("Remove", "trash", classes="remove") }} - -
- {% else %} -
-
- {{ Icon("alert", classes="uninvited") }} - Not specified +
+
+ {% endif %} + + {{ EditOfficerInfo(form, officer_type) }}
-
- -
- {% endif %} +
{% endmacro %} @@ -78,17 +127,21 @@
{% include "fragments/flash.html" %} -
-
-

-
Edit Task Order
- Oversight -

-
+
+ {{ form.csrf_token }} - {% for officer in ["contracting_officer", "contracting_officer_representative", "security_officer"] %} - {{ OfficerInfo(task_order, officer) }} - {% endfor %} -
+
+
+

+
Edit Task Order
+ Oversight +

+
+ + {% for officer in ["contracting_officer", "contracting_officer_representative", "security_officer"] %} + {{ OfficerInfo(task_order, officer, form[officer]) }} + {% endfor %} +
+
{% endblock %} diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index 6bebd3aa..ed2f1c82 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -2,6 +2,7 @@ from flask import url_for import pytest from atst.domain.roles import Roles +from atst.domain.task_orders import TaskOrders from atst.models.portfolio_role import Status as PortfolioStatus from tests.factories import ( @@ -100,3 +101,53 @@ def test_can_view_task_order_invitations(client, user_session): ) ) assert response.status_code == 200 + + +class TestTaskOrderInvitations: + def setup(self): + self.portfolio = PortfolioFactory.create() + self.task_order = TaskOrderFactory.create(portfolio=self.portfolio) + + def _post(self, client, updates): + return client.post( + url_for( + "portfolios.edit_task_order_invitations", + portfolio_id=self.portfolio.id, + task_order_id=self.task_order.id, + ), + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data=updates, + ) + + def test_editing_with_partial_data(self, user_session, client): + user_session(self.portfolio.owner) + response = self._post( + client, + { + "contracting_officer-first_name": "Luke", + "contracting_officer-last_name": "Skywalker", + "security_officer-first_name": "Boba", + "security_officer-last_name": "Fett", + }, + ) + updated_task_order = TaskOrders.get(self.portfolio.owner, self.task_order.id) + assert updated_task_order.ko_first_name == "Luke" + assert updated_task_order.ko_last_name == "Skywalker" + assert updated_task_order.so_first_name == "Boba" + assert updated_task_order.so_last_name == "Fett" + + def test_editing_with_invalid_data(self, user_session, client): + user_session(self.portfolio.owner) + response = self._post( + client, + { + "contracting_officer-phone_number": "invalid input", + "security_officer-first_name": "Boba", + "security_officer-last_name": "Fett", + }, + ) + + assert "There were some errors" in response.data.decode() + + updated_task_order = TaskOrders.get(self.portfolio.owner, self.task_order.id) + assert updated_task_order.so_first_name != "Boba"