From 470df0a5723b73757647c09feaa05742afd7ee8b Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 29 Jul 2019 15:48:30 -0400 Subject: [PATCH 01/11] New route for cancelling TO form --- atst/routes/task_orders/new.py | 24 ++++++++++++++++++++++++ tests/routes/task_orders/test_new.py | 14 ++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 43736e1a..9d55ed02 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -115,6 +115,30 @@ def submit_form_step_one_add_pdf(portfolio_id=None, task_order_id=None): ) +@task_orders_bp.route("/task_orders//form/cancel", methods=["POST"]) +@user_can(Permissions.CREATE_TASK_ORDER, message="view task order form") +def cancel_edit(task_order_id): + save = http_request.args.get("save", False) + if save: + form_data = {**http_request.form} + form = None + if task_order_id: + task_order = TaskOrders.get(task_order_id) + form = TaskOrderForm(form_data, obj=task_order) + else: + form = TaskOrderForm(form_data) + + if form.validate(): + task_order = None + if task_order_id: + task_order = TaskOrders.update(task_order_id, **form.data) + portfolio_id = task_order.portfolio_id + else: + task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data) + + return redirect(url_for("task_orders.portfolio_funding", portfolio_id=portfolio_id)) + + @task_orders_bp.route("/task_orders//form/step_2") @user_can(Permissions.CREATE_TASK_ORDER, message="view task order form") def form_step_two_add_number(task_order_id): diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index b88bbf7b..1ed515bd 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -2,16 +2,13 @@ import pytest from flask import url_for from datetime import timedelta, date -from atst.domain.permission_sets import PermissionSets from atst.domain.task_orders import TaskOrders from atst.models.task_order import Status as TaskOrderStatus -from atst.models import Attachment, TaskOrder -from atst.utils.localization import translate +from atst.models import TaskOrder from tests.factories import ( CLINFactory, PortfolioFactory, - PortfolioRoleFactory, TaskOrderFactory, UserFactory, ) @@ -326,6 +323,15 @@ def test_task_orders_edit_redirects_to_latest_incomplete_step( assert expected_step in response.location +def test_can_cancel_edit_and_save_task_order(client, user_session, task_order, session): + user_session(task_order.portfolio.owner) + response = client.post(url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), data={"number": "0123456789012"}) + assert response.status_code == 302 + + updated_task_order = session.query(TaskOrder).get(task_order.id) + assert updated_task_order.number == "0123456789012" + + @pytest.mark.skip(reason="Reevaluate how form handles invalid data") def test_task_orders_update_invalid_data(client, user_session, portfolio): user_session(portfolio.owner) From 59545aaf0e01bc09c8b18b3bfc482d70fb2d1506 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 09:51:27 -0400 Subject: [PATCH 02/11] Add TO cancel modal --- templates/task_orders/builder_base.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/templates/task_orders/builder_base.html b/templates/task_orders/builder_base.html index 3b571459..01e63d4a 100644 --- a/templates/task_orders/builder_base.html +++ b/templates/task_orders/builder_base.html @@ -1,6 +1,7 @@ {% extends "portfolios/base.html" %} {% from "components/sticky_cta.html" import StickyCTA %} +{% from "components/modal.html" import Modal %} {% block portfolio_content %} @@ -26,13 +27,21 @@ {% endif %} {{ "common.cancel" | translate }} {% endcall %} + {% call Modal(name='cancel', dismissable=True) %} +
+

Do you want to save this draft?

+ No, delete it + Yes, save for later +
+ {% endcall %} + {% include "fragments/flash.html" %}
From 88853e352c933a4dc95b539b976c4b5040e20154 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 09:53:50 -0400 Subject: [PATCH 03/11] Formatting --- atst/routes/task_orders/new.py | 4 +++- tests/routes/task_orders/test_new.py | 12 +++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 9d55ed02..fc4535b8 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -134,7 +134,9 @@ def cancel_edit(task_order_id): task_order = TaskOrders.update(task_order_id, **form.data) portfolio_id = task_order.portfolio_id else: - task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data) + task_order = TaskOrders.create( + g.current_user, portfolio_id, **form.data + ) return redirect(url_for("task_orders.portfolio_funding", portfolio_id=portfolio_id)) diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 1ed515bd..90e34209 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -6,12 +6,7 @@ from atst.domain.task_orders import TaskOrders from atst.models.task_order import Status as TaskOrderStatus from atst.models import TaskOrder -from tests.factories import ( - CLINFactory, - PortfolioFactory, - TaskOrderFactory, - UserFactory, -) +from tests.factories import CLINFactory, PortfolioFactory, TaskOrderFactory, UserFactory @pytest.fixture @@ -325,7 +320,10 @@ def test_task_orders_edit_redirects_to_latest_incomplete_step( def test_can_cancel_edit_and_save_task_order(client, user_session, task_order, session): user_session(task_order.portfolio.owner) - response = client.post(url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), data={"number": "0123456789012"}) + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), + data={"number": "0123456789012"}, + ) assert response.status_code == 302 updated_task_order = session.query(TaskOrder).get(task_order.id) From dd93dd5aeaddfc2879af38bd5395fb55c390e334 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 10:30:06 -0400 Subject: [PATCH 04/11] Allow user to cancel without saving --- atst/routes/task_orders/new.py | 12 ++++++---- templates/task_orders/builder_base.html | 4 ++-- tests/routes/task_orders/test_new.py | 32 +++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index fc4535b8..7f888536 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -21,7 +21,10 @@ def render_task_orders_edit(template, portfolio_id=None, task_order_id=None, for else: render_args["form"] = form or TaskOrderForm() - render_args["cancel_url"] = url_for( + render_args["cancel_save_url"] = url_for( + "task_orders.portfolio_funding", portfolio_id=portfolio_id, save=True + ) + render_args["cancel_discard_url"] = url_for( "task_orders.portfolio_funding", portfolio_id=portfolio_id ) @@ -116,7 +119,7 @@ def submit_form_step_one_add_pdf(portfolio_id=None, task_order_id=None): @task_orders_bp.route("/task_orders//form/cancel", methods=["POST"]) -@user_can(Permissions.CREATE_TASK_ORDER, message="view task order form") +@user_can(Permissions.CREATE_TASK_ORDER, message="cancel task order form") def cancel_edit(task_order_id): save = http_request.args.get("save", False) if save: @@ -132,13 +135,14 @@ def cancel_edit(task_order_id): task_order = None if task_order_id: task_order = TaskOrders.update(task_order_id, **form.data) - portfolio_id = task_order.portfolio_id else: task_order = TaskOrders.create( g.current_user, portfolio_id, **form.data ) - return redirect(url_for("task_orders.portfolio_funding", portfolio_id=portfolio_id)) + return redirect( + url_for("task_orders.portfolio_funding", portfolio_id=g.portfolio.id) + ) @task_orders_bp.route("/task_orders//form/step_2") diff --git a/templates/task_orders/builder_base.html b/templates/task_orders/builder_base.html index 01e63d4a..c1931693 100644 --- a/templates/task_orders/builder_base.html +++ b/templates/task_orders/builder_base.html @@ -37,8 +37,8 @@ {% call Modal(name='cancel', dismissable=True) %}

Do you want to save this draft?

- No, delete it - Yes, save for later + No, delete it + Yes, save for later
{% endcall %} diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 90e34209..cc22094d 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -322,12 +322,40 @@ def test_can_cancel_edit_and_save_task_order(client, user_session, task_order, s user_session(task_order.portfolio.owner) response = client.post( url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), - data={"number": "0123456789012"}, + data={"number": "7896564324567"}, ) assert response.status_code == 302 updated_task_order = session.query(TaskOrder).get(task_order.id) - assert updated_task_order.number == "0123456789012" + assert updated_task_order.number == "7896564324567" + + +def test_cancel_edit_does_not_save_invalid_form_input(client, user_session, session): + task_order = TaskOrderFactory.create() + user_session(task_order.portfolio.owner) + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), + data={"clins": "not really clins"}, + ) + assert response.status_code == 302 + + # CLINs should be unchanged + updated_task_order = session.query(TaskOrder).get(task_order.id) + assert updated_task_order.clins == task_order.clins + + +def test_cancel_edit_without_saving(client, user_session, session): + task_order = TaskOrderFactory.create(number=None) + user_session(task_order.portfolio.owner) + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id), + data={"number": "7643906432984"}, + ) + assert response.status_code == 302 + + # TO number should be unchanged + updated_task_order = session.query(TaskOrder).get(task_order.id) + assert updated_task_order.number is None @pytest.mark.skip(reason="Reevaluate how form handles invalid data") From 0216faf1e6d905bcc05d780c7c3e1f398977e7ef Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 10:40:56 -0400 Subject: [PATCH 05/11] Fix cancel urls --- atst/routes/task_orders/new.py | 4 ++-- templates/task_orders/builder_base.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 7f888536..4496a21a 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -22,10 +22,10 @@ def render_task_orders_edit(template, portfolio_id=None, task_order_id=None, for render_args["form"] = form or TaskOrderForm() render_args["cancel_save_url"] = url_for( - "task_orders.portfolio_funding", portfolio_id=portfolio_id, save=True + "task_orders.cancel_edit", task_order_id=task_order_id, portfolio_id=portfolio_id, save=True ) render_args["cancel_discard_url"] = url_for( - "task_orders.portfolio_funding", portfolio_id=portfolio_id + "task_orders.cancel_edit", task_order_id=task_order_id, portfolio_id=portfolio_id ) return render_template(template, **render_args) diff --git a/templates/task_orders/builder_base.html b/templates/task_orders/builder_base.html index c1931693..4319e06b 100644 --- a/templates/task_orders/builder_base.html +++ b/templates/task_orders/builder_base.html @@ -37,8 +37,8 @@ {% call Modal(name='cancel', dismissable=True) %}

Do you want to save this draft?

- No, delete it - Yes, save for later + +
{% endcall %} From 3c56f96fa25d065721b2d73ba8b611fd6925b7cb Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 10:44:01 -0400 Subject: [PATCH 06/11] Not using previous_button_link for step 1 --- templates/task_orders/step_1.html | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/task_orders/step_1.html b/templates/task_orders/step_1.html index 21b38aac..e81ad47c 100644 --- a/templates/task_orders/step_1.html +++ b/templates/task_orders/step_1.html @@ -12,7 +12,6 @@ {% endif %} {% set next_button_text = "Next: Add TO Number" %} -{% set previous_button_link = cancel_url %} {% set step = "1" %} From ec6494c6c352f7a5ceec6b9ce9dfd5d4d6d1eb3e Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 11:06:59 -0400 Subject: [PATCH 07/11] Add another route for new TOs --- atst/routes/task_orders/new.py | 3 ++- tests/routes/task_orders/test_new.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 4496a21a..c3259902 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -118,9 +118,10 @@ def submit_form_step_one_add_pdf(portfolio_id=None, task_order_id=None): ) +@task_orders_bp.route("/portfolios//task_orders/form/cancel", methods=["POST"]) @task_orders_bp.route("/task_orders//form/cancel", methods=["POST"]) @user_can(Permissions.CREATE_TASK_ORDER, message="cancel task order form") -def cancel_edit(task_order_id): +def cancel_edit(task_order_id=None, portfolio_id=None): save = http_request.args.get("save", False) if save: form_data = {**http_request.form} diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index cc22094d..cee4c26d 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -329,6 +329,14 @@ def test_can_cancel_edit_and_save_task_order(client, user_session, task_order, s updated_task_order = session.query(TaskOrder).get(task_order.id) assert updated_task_order.number == "7896564324567" +def test_cancel_can_create_new_to(client, user_session, portfolio): + user_session(portfolio.owner) + response = client.post( + url_for("task_orders.cancel_edit", portfolio_id=portfolio.id), + data={"number": "7643906432984"}, + ) + assert response.status_code == 302 + def test_cancel_edit_does_not_save_invalid_form_input(client, user_session, session): task_order = TaskOrderFactory.create() From c99e05ed8642ddc8b4bc2b4ddaf3baf2095435a8 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 11:07:16 -0400 Subject: [PATCH 08/11] Some styling for cancel modal --- atst/routes/task_orders/new.py | 9 +++++++-- styles/sections/_task_order.scss | 9 +++++++++ templates/task_orders/builder_base.html | 6 ++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index c3259902..a8b63257 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -22,10 +22,15 @@ def render_task_orders_edit(template, portfolio_id=None, task_order_id=None, for render_args["form"] = form or TaskOrderForm() render_args["cancel_save_url"] = url_for( - "task_orders.cancel_edit", task_order_id=task_order_id, portfolio_id=portfolio_id, save=True + "task_orders.cancel_edit", + task_order_id=task_order_id, + portfolio_id=portfolio_id, + save=True, ) render_args["cancel_discard_url"] = url_for( - "task_orders.cancel_edit", task_order_id=task_order_id, portfolio_id=portfolio_id + "task_orders.cancel_edit", + task_order_id=task_order_id, + portfolio_id=portfolio_id, ) return render_template(template, **render_args) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index f9604e17..eb801826 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -249,3 +249,12 @@ } } } + +.task-order__modal-cancel { + text-align: center; +} + +.task-order__modal-cancel_buttons { + display: flex; + justify-content: center; +} diff --git a/templates/task_orders/builder_base.html b/templates/task_orders/builder_base.html index 4319e06b..0f4848f2 100644 --- a/templates/task_orders/builder_base.html +++ b/templates/task_orders/builder_base.html @@ -37,8 +37,10 @@ {% call Modal(name='cancel', dismissable=True) %}

Do you want to save this draft?

- - +
+ + +
{% endcall %} From 24049c4c818a66eae79556c0dff6f67c84490cc0 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 30 Jul 2019 11:13:53 -0400 Subject: [PATCH 09/11] Formatting --- atst/routes/task_orders/new.py | 4 +++- tests/routes/task_orders/test_new.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index a8b63257..6f00e1ad 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -123,7 +123,9 @@ def submit_form_step_one_add_pdf(portfolio_id=None, task_order_id=None): ) -@task_orders_bp.route("/portfolios//task_orders/form/cancel", methods=["POST"]) +@task_orders_bp.route( + "/portfolios//task_orders/form/cancel", methods=["POST"] +) @task_orders_bp.route("/task_orders//form/cancel", methods=["POST"]) @user_can(Permissions.CREATE_TASK_ORDER, message="cancel task order form") def cancel_edit(task_order_id=None, portfolio_id=None): diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index cee4c26d..83a42006 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -329,6 +329,7 @@ def test_can_cancel_edit_and_save_task_order(client, user_session, task_order, s updated_task_order = session.query(TaskOrder).get(task_order.id) assert updated_task_order.number == "7896564324567" + def test_cancel_can_create_new_to(client, user_session, portfolio): user_session(portfolio.owner) response = client.post( From e034269a0239b495387d6a1d7d8faab2fa269140 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Fri, 2 Aug 2019 16:07:32 -0400 Subject: [PATCH 10/11] Don't flash when cancelling TO form with invalid data --- atst/forms/forms.py | 4 ++-- atst/routes/task_orders/new.py | 2 +- tests/routes/task_orders/test_new.py | 21 +++++++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/atst/forms/forms.py b/atst/forms/forms.py index bb0d086f..4287c81c 100644 --- a/atst/forms/forms.py +++ b/atst/forms/forms.py @@ -30,9 +30,9 @@ class BaseForm(FlaskForm): _data[field] = None return _data - def validate(self, *args, **kwargs): + def validate(self, *args, flash_invalid=True, **kwargs): valid = super().validate(*args, **kwargs) - if not valid: + if not valid and flash_invalid: flash("form_errors") return valid diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 6f00e1ad..9e88f8d4 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -139,7 +139,7 @@ def cancel_edit(task_order_id=None, portfolio_id=None): else: form = TaskOrderForm(form_data) - if form.validate(): + if form.validate(flash_invalid=False): task_order = None if task_order_id: task_order = TaskOrders.update(task_order_id, **form.data) diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 83a42006..0ab8a2bc 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -1,5 +1,5 @@ import pytest -from flask import url_for +from flask import url_for, get_flashed_messages from datetime import timedelta, date from atst.domain.task_orders import TaskOrders @@ -342,9 +342,10 @@ def test_cancel_can_create_new_to(client, user_session, portfolio): def test_cancel_edit_does_not_save_invalid_form_input(client, user_session, session): task_order = TaskOrderFactory.create() user_session(task_order.portfolio.owner) + bad_data = {"clins-0-jedi_clin_type": "foo"} response = client.post( url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), - data={"clins": "not really clins"}, + data=bad_data, ) assert response.status_code == 302 @@ -353,6 +354,22 @@ def test_cancel_edit_does_not_save_invalid_form_input(client, user_session, sess assert updated_task_order.clins == task_order.clins +def test_cancel_edit_on_invalid_input_does_not_flash( + app, client, user_session, session +): + task_order = TaskOrderFactory.create() + user_session(task_order.portfolio.owner) + + bad_data = {"clins-0-jedi_clin_type": "foo"} + + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), + data=bad_data, + ) + + assert len(get_flashed_messages()) == 0 + + def test_cancel_edit_without_saving(client, user_session, session): task_order = TaskOrderFactory.create(number=None) user_session(task_order.portfolio.owner) From 1cd35445eab0cc7c60bd036dd2c92da264141dc0 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 5 Aug 2019 14:02:06 -0400 Subject: [PATCH 11/11] Fix CLINForm.validate() --- atst/forms/task_order.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index fca7bcfd..80a31085 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -53,7 +53,11 @@ class CLINForm(FlaskForm): def validate(self, *args, **kwargs): valid = super().validate(*args, **kwargs) - if self.start_date.data > self.end_date.data: + if ( + self.start_date.data + and self.end_date.data + and self.start_date.data > self.end_date.data + ): self.start_date.errors.append( translate("forms.task_order.start_date_error") )