From 070ccec23f08ddae903bb5cb11045628b4d00271 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 15 Feb 2019 14:42:29 -0500 Subject: [PATCH 01/24] Add migration for LOA array type --- .../db161adbafdf_update_loa_to_array_type.py | 28 +++++++++++++++++++ atst/models/task_order.py | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 alembic/versions/db161adbafdf_update_loa_to_array_type.py diff --git a/alembic/versions/db161adbafdf_update_loa_to_array_type.py b/alembic/versions/db161adbafdf_update_loa_to_array_type.py new file mode 100644 index 00000000..afe3ae6d --- /dev/null +++ b/alembic/versions/db161adbafdf_update_loa_to_array_type.py @@ -0,0 +1,28 @@ +"""Update LOA to Array Type + +Revision ID: db161adbafdf +Revises: b3a1a07cf30b +Create Date: 2019-02-15 14:28:33.181136 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'db161adbafdf' +down_revision = 'b3a1a07cf30b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.execute("ALTER TABLE task_orders ALTER COLUMN loa TYPE varchar[] USING array[loa]") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.execute("ALTER TABLE task_orders ALTER COLUMN loa TYPE varchar USING loa[1]") + # ### end Alembic commands ### diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 73e090f9..c265cbe0 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -90,7 +90,7 @@ class TaskOrder(Base, mixins.TimestampsMixin): pdf_attachment_id = Column(ForeignKey("attachments.id")) _pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id]) number = Column(String, unique=True) # Task Order Number - loa = Column(String) # Line of Accounting (LOA) + loa = Column(ARRAY(String)) # Line of Accounting (LOA) custom_clauses = Column(String) # Custom Clauses signer_dod_id = Column(String) signed_at = Column(DateTime) From 3325e4c2198e354febe7db8248bc0c479f7af2b8 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Mon, 18 Feb 2019 16:52:18 -0500 Subject: [PATCH 02/24] Create KoReview component to add multiple LOA items --- atst/forms/ko_review.py | 17 +++- js/components/forms/ko_review.js | 40 ++++++++ js/index.js | 2 + styles/sections/_task_order.scss | 31 +++++++ templates/portfolios/task_orders/review.html | 98 +++++++++++++------- tests/routes/portfolios/test_task_orders.py | 37 +++++++- 6 files changed, 188 insertions(+), 37 deletions(-) create mode 100644 js/components/forms/ko_review.js diff --git a/atst/forms/ko_review.py b/atst/forms/ko_review.py index aec28e7f..2ac0b38d 100644 --- a/atst/forms/ko_review.py +++ b/atst/forms/ko_review.py @@ -1,7 +1,8 @@ from flask_wtf.file import FileAllowed +from flask_wtf import FlaskForm from wtforms.fields.html5 import DateField -from wtforms.fields import StringField, TextAreaField, FileField +from wtforms.fields import StringField, TextAreaField, FileField, FieldList from wtforms.validators import Optional, Length from .forms import CacheableForm @@ -11,6 +12,8 @@ from atst.utils.localization import translate class KOReviewForm(CacheableForm): + EMPTY_LOA = ["", None] + start_date = DateField( translate("forms.ko_review.start_date_label"), format="%m/%d/%Y" ) @@ -26,11 +29,19 @@ class KOReviewForm(CacheableForm): number = StringField( translate("forms.ko_review.to_number"), validators=[Length(min=10)] ) - loa = StringField( - translate("forms.ko_review.loa"), validators=[Length(min=10), IsNumber()] + loa = FieldList( + StringField( + translate("forms.ko_review.loa"), validators=[Length(min=10), IsNumber()] + ) ) custom_clauses = TextAreaField( translate("forms.ko_review.custom_clauses_label"), description=translate("forms.ko_review.custom_clauses_description"), validators=[Optional()], ) + + @property + def data(self): + _data = super(FlaskForm, self).data + _data["loa"] = [n for n in _data["loa"] if n not in self.EMPTY_LOA] + return _data diff --git a/js/components/forms/ko_review.js b/js/components/forms/ko_review.js new file mode 100644 index 00000000..3963431b --- /dev/null +++ b/js/components/forms/ko_review.js @@ -0,0 +1,40 @@ +import textinput from '../text_input' +import DateSelector from '../date_selector' +import uploadinput from '../upload_input' +import inputValidations from '../../lib/input_validations' + +const createLOA = number => ({ number }) + +export default { + name: 'ko-review', + + components: { + textinput, + DateSelector, + uploadinput, + }, + + props: { + initialData: { + type: Object, + default: () => ({}), + }, + modalName: String, + }, + + data: function() { + const { loa } = this.initialData + const loas = + typeof loa === 'array' && loa.length > 0 ? this.initialValue : [''] + + return { + loas, + } + }, + + methods: { + addLOA: function(event) { + this.loas.push(createLOA('')) + }, + }, +} diff --git a/js/index.js b/js/index.js index dbc72654..654be95c 100644 --- a/js/index.js +++ b/js/index.js @@ -31,6 +31,7 @@ import ConfirmationPopover from './components/confirmation_popover' import { isNotInVerticalViewport } from './lib/viewport' import DateSelector from './components/date_selector' import SidenavToggler from './components/sidenav_toggler' +import KoReview from './components/forms/ko_review' Vue.config.productionTip = false @@ -64,6 +65,7 @@ const app = new Vue({ DateSelector, EditOfficerForm, SidenavToggler, + KoReview, }, mounted: function() { diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index b7727a77..464f6832 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -323,6 +323,37 @@ margin: 0; } } + + .task-order__loa-list { + ul { + padding-left: 0; + } + + + + .task-order__loa-add-item { + display: flex; + flex-direction: row-reverse; + justify-content: space-between; + max-width: 30em; + + .icon-link { + &:first-child { + margin-right: -$gap; + } + } + } + } + + .task-order__loa-list-item { + display: flex; + flex-direction: row; + align-items: flex-end; + + .usa-input { + flex-grow: 1; + } + } } .task-order-invitations { diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index 92176252..bf1453d4 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -11,37 +11,29 @@ {% from "components/review_field.html" import ReviewField %} {% from "components/upload_input.html" import UploadInput %} + {% block content %} + +
-
+ {% include "fragments/flash.html" %} - {% include "fragments/flash.html" %} + {% block form_action %} +
+ {% endblock %} - {% block form_action %} - - {% endblock %} + {{ form.csrf_token }} - {{ form.csrf_token }} + {% block form %} - {% block form %} - -
-

- {{ "task_orders.ko_review.title" | translate }} -

- {% include "fragments/ko_review_message.html" %} -
- -
- -
-

-
{{ "task_orders.ko_review.review_title" | translate }}
- {{ "task_orders.new.review.section_title"| translate }} +
+

+ {{ "task_orders.ko_review.title" | translate }}

+ {% include "fragments/ko_review_message.html" %}
-
+
{{ "task_orders.new.review.app_info"| translate }}
@@ -74,24 +66,64 @@ {% include "fragments/task_order_review/oversight.html" %}
-
{{ "task_orders.ko_review.task_order_information"| translate }}
+
+
{{ "task_orders.new.review.app_info"| translate }}
+ {% include "fragments/task_order_review/app_info.html" %} +
+ +
{{ "task_orders.new.review.reporting"| translate }}
+ {% include "fragments/task_order_review/reporting.html" %} +
+ +
{{ "task_orders.new.review.funding"| translate }}
+ {% include "fragments/task_order_review/funding.html" %} + +
+ {{ DatePicker(form.start_date) }} + {{ DatePicker(form.end_date) }} +
+
+ +
{{ "task_orders.new.review.oversight"| translate }}
+ {% include "fragments/task_order_review/oversight.html" %} +
+ +
{{ "task_orders.ko_review.task_order_information"| translate }}
+ +
+ {{ UploadInput(form.pdf, show_label=True) }} + {{ TextInput(form.number, placeholder='1234567890') }} + +
+
    +
  • +
    + + + +
    +
  • +
+
+ +
+
+ + {{ TextInput(form.custom_clauses, paragraph=True) }} +
-
- {{ UploadInput(form.pdf, show_label=True) }} - {{ TextInput(form.number, placeholder='1234567890') }} - {{ TextInput(form.loa, placeholder='1234567890') }} - {{ TextInput(form.custom_clauses, paragraph=True) }}
-
-
- {% endblock %} + {% endblock %}
- + -
+

+ {% endblock %} diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index f11727a8..34c263e2 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -358,7 +358,42 @@ def test_submit_completed_ko_review_page_as_cor(client, user_session, pdf_upload "start_date": "02/10/2019", "end_date": "03/10/2019", "number": "1938745981", - "loa": "0813458013405", + "loa": ["0813458013405"], + "custom_clauses": "hi im a custom clause", + "pdf": pdf_upload, + } + + response = client.post( + url_for( + "portfolios.ko_review", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ), + data=form_data, + ) + + assert task_order.pdf + assert response.headers["Location"] == url_for( + "task_orders.signature_requested", task_order_id=task_order.id, _external=True + ) + + +def test_submit_to_with_multiple_loas(client, user_session, pdf_upload): + portfolio = PortfolioFactory.create() + ko = UserFactory.create() + PortfolioRoleFactory.create( + role=Roles.get("officer"), + portfolio=portfolio, + user=ko, + status=PortfolioStatus.ACTIVE, + ) + task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko) + user_session(ko) + form_data = { + "start_date": "02/10/2019", + "end_date": "03/10/2019", + "number": "1938745981", + "loa": ["0813458013405", "1234567890", "5678901234"], "custom_clauses": "hi im a custom clause", "pdf": pdf_upload, } From 8dd21b49f2ef2eeb32f9e78b683afa1e85b09cac Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 19 Feb 2019 20:11:04 -0500 Subject: [PATCH 03/24] Update format of vue component --- js/components/date_selector.js | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/js/components/date_selector.js b/js/components/date_selector.js index 1d7f49dd..4cf144e3 100644 --- a/js/components/date_selector.js +++ b/js/components/date_selector.js @@ -9,10 +9,18 @@ var paddedNumber = function(number) { } } -export default Vue.component('date-selector', { - props: ['initialday', 'initialmonth', 'initialyear', 'mindate', 'maxdate'], +export default { + name: 'date-selector', - data() { + props: { + initialday: { type: String }, + initialmonth: { type: String }, + initialyear: { type: String }, + mindate: { type: String }, + maxdate: { type: String }, + }, + + data: function() { return { day: this.initialday, month: this.initialmonth, @@ -41,7 +49,7 @@ export default Vue.component('date-selector', { }, computed: { - formattedDate() { + formattedDate: function() { let day = paddedNumber(this.day) let month = paddedNumber(this.month) @@ -52,23 +60,23 @@ export default Vue.component('date-selector', { return `${month}/${day}/${this.year}` }, - isMonthValid() { + isMonthValid: function() { var _month = parseInt(this.month) return _month >= 0 && _month <= 12 }, - isDayValid() { + isDayValid: function() { var _day = parseInt(this.day) return _day >= 0 && _day <= this.daysMaxCalculation }, - isYearValid() { + isYearValid: function() { return parseInt(this.year) >= 1 }, - isWithinDateRange() { + isWithinDateRange: function() { let _mindate = this.mindate ? Date.parse(this.mindate) : null let _maxdate = this.maxdate ? Date.parse(this.maxdate) : null let _dateTimestamp = Date.UTC(this.year, this.month - 1, this.day) @@ -84,7 +92,7 @@ export default Vue.component('date-selector', { return true }, - isDateValid() { + isDateValid: function() { return ( this.day && this.month && @@ -96,7 +104,7 @@ export default Vue.component('date-selector', { ) }, - daysMaxCalculation() { + daysMaxCalculation: function() { switch (parseInt(this.month)) { case 2: // February if (this.year) { @@ -119,8 +127,4 @@ export default Vue.component('date-selector', { } }, }, - - render(createElement) { - return createElement('p', 'Please implement inline-template') - }, -}) +} From f9b379c2f11c57f2678af825c9713a3c84f69998 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Wed, 20 Feb 2019 18:18:18 -0500 Subject: [PATCH 04/24] Fix mistake in template --- templates/portfolios/task_orders/review.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index bf1453d4..a1c5d5fa 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -102,7 +102,7 @@
Line of Accounting (LOA) #
- +
From 68ab32d1d1f38130587138363afbc80314469673 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 21 Feb 2019 17:05:09 -0500 Subject: [PATCH 05/24] Fix template and component so LOA values are posted through the form --- js/components/forms/ko_review.js | 12 +++++++++--- templates/portfolios/task_orders/review.html | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/js/components/forms/ko_review.js b/js/components/forms/ko_review.js index 3963431b..5efdb3cf 100644 --- a/js/components/forms/ko_review.js +++ b/js/components/forms/ko_review.js @@ -2,12 +2,15 @@ import textinput from '../text_input' import DateSelector from '../date_selector' import uploadinput from '../upload_input' import inputValidations from '../../lib/input_validations' +import FormMixin from '../../mixins/form' const createLOA = number => ({ number }) export default { name: 'ko-review', + mixins: [FormMixin], + components: { textinput, DateSelector, @@ -23,15 +26,18 @@ export default { }, data: function() { - const { loa } = this.initialData - const loas = - typeof loa === 'array' && loa.length > 0 ? this.initialValue : [''] + const loa_list = this.initialData['loa'] + const loas = (loa_list.length > 0 ? loa_list : ['']).map(createLOA) return { loas, } }, + mounted: function() { + this.$root.$on('onLOAAdded', this.addLOA) + }, + methods: { addLOA: function(event) { this.loas.push(createLOA('')) diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index a1c5d5fa..6336ade1 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -101,8 +101,8 @@ - - + +
From f1b9bf19a7d4f094f76a1e553c08172dce2a8bef Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 22 Feb 2019 12:01:17 -0500 Subject: [PATCH 06/24] Update migration order and change loa to loas --- alembic/versions/db161adbafdf_update_loa_to_array_type.py | 6 ++++-- atst/forms/ko_review.py | 4 ++-- atst/models/task_order.py | 2 +- js/components/forms/ko_review.js | 2 +- templates/portfolios/task_orders/review.html | 6 +++--- tests/routes/portfolios/test_task_orders.py | 4 ++-- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/alembic/versions/db161adbafdf_update_loa_to_array_type.py b/alembic/versions/db161adbafdf_update_loa_to_array_type.py index afe3ae6d..72ce461c 100644 --- a/alembic/versions/db161adbafdf_update_loa_to_array_type.py +++ b/alembic/versions/db161adbafdf_update_loa_to_array_type.py @@ -1,7 +1,7 @@ """Update LOA to Array Type Revision ID: db161adbafdf -Revises: b3a1a07cf30b +Revises: fa3ba4049218 Create Date: 2019-02-15 14:28:33.181136 """ @@ -11,7 +11,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'db161adbafdf' -down_revision = 'b3a1a07cf30b' +down_revision = 'fa3ba4049218' branch_labels = None depends_on = None @@ -19,10 +19,12 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.execute("ALTER TABLE task_orders ALTER COLUMN loa TYPE varchar[] USING array[loa]") + op.alter_column('task_orders', 'loa', new_column_name='loas') # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.execute("ALTER TABLE task_orders ALTER COLUMN loa TYPE varchar USING loa[1]") + op.alter_column('task_orders', 'loas', new_column_name='loa') # ### end Alembic commands ### diff --git a/atst/forms/ko_review.py b/atst/forms/ko_review.py index 2ac0b38d..c92561ab 100644 --- a/atst/forms/ko_review.py +++ b/atst/forms/ko_review.py @@ -29,7 +29,7 @@ class KOReviewForm(CacheableForm): number = StringField( translate("forms.ko_review.to_number"), validators=[Length(min=10)] ) - loa = FieldList( + loas = FieldList( StringField( translate("forms.ko_review.loa"), validators=[Length(min=10), IsNumber()] ) @@ -43,5 +43,5 @@ class KOReviewForm(CacheableForm): @property def data(self): _data = super(FlaskForm, self).data - _data["loa"] = [n for n in _data["loa"] if n not in self.EMPTY_LOA] + _data["loas"] = [n for n in _data["loas"] if n not in self.EMPTY_LOA] return _data diff --git a/atst/models/task_order.py b/atst/models/task_order.py index c265cbe0..b09eca73 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -90,7 +90,7 @@ class TaskOrder(Base, mixins.TimestampsMixin): pdf_attachment_id = Column(ForeignKey("attachments.id")) _pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id]) number = Column(String, unique=True) # Task Order Number - loa = Column(ARRAY(String)) # Line of Accounting (LOA) + loas = Column(ARRAY(String)) # Line of Accounting (LOA) custom_clauses = Column(String) # Custom Clauses signer_dod_id = Column(String) signed_at = Column(DateTime) diff --git a/js/components/forms/ko_review.js b/js/components/forms/ko_review.js index 5efdb3cf..3b86390e 100644 --- a/js/components/forms/ko_review.js +++ b/js/components/forms/ko_review.js @@ -26,7 +26,7 @@ export default { }, data: function() { - const loa_list = this.initialData['loa'] + const loa_list = this.initialData['loas'] const loas = (loa_list.length > 0 ? loa_list : ['']).map(createLOA) return { diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index 6336ade1..6f93c044 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -98,11 +98,11 @@
  • -
diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index 34c263e2..09d208a6 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -358,7 +358,7 @@ def test_submit_completed_ko_review_page_as_cor(client, user_session, pdf_upload "start_date": "02/10/2019", "end_date": "03/10/2019", "number": "1938745981", - "loa": ["0813458013405"], + "loas": ["0813458013405"], "custom_clauses": "hi im a custom clause", "pdf": pdf_upload, } @@ -393,7 +393,7 @@ def test_submit_to_with_multiple_loas(client, user_session, pdf_upload): "start_date": "02/10/2019", "end_date": "03/10/2019", "number": "1938745981", - "loa": ["0813458013405", "1234567890", "5678901234"], + "loas": ["0813458013405", "1234567890", "5678901234"], "custom_clauses": "hi im a custom clause", "pdf": pdf_upload, } From d1643d31637b8f86c65a8aee6401424b85b2ae66 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 22 Feb 2019 12:30:42 -0500 Subject: [PATCH 07/24] Fix ko review template after rebase --- templates/portfolios/task_orders/review.html | 52 ++++++-------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index 6f93c044..4a9b0425 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -34,48 +34,25 @@
-
- {{ "task_orders.new.review.app_info"| translate }} -
- {% include "fragments/task_order_review/app_info.html" %} -
- -
- {{ "task_orders.new.review.reporting"| translate }} - {{ EditLink(url_for("task_orders.new", screen=1, task_order_id=task_order.id, _anchor="reporting", ko_edit=True)) }} -
- {% include "fragments/task_order_review/reporting.html" %} -
- -
- {{ "task_orders.new.review.funding"| translate }} - {{ EditLink(url_for("task_orders.new", screen=2, task_order_id=task_order.id, _anchor="reporting", ko_edit=True)) }} -
- {% include "fragments/task_order_review/funding.html" %} - -
- {{ DatePicker(form.start_date) }} - {{ DatePicker(form.end_date) }} -
-
- -
- {{ "task_orders.new.review.oversight"| translate }} - {{ EditLink(url_for("task_orders.new", screen=3, task_order_id=task_order.id, _anchor="reporting", ko_edit=True)) }} -
- {% include "fragments/task_order_review/oversight.html" %} -
-
-
{{ "task_orders.new.review.app_info"| translate }}
+ +
+ {{ "task_orders.new.review.app_info"| translate }} +
{% include "fragments/task_order_review/app_info.html" %}
-
{{ "task_orders.new.review.reporting"| translate }}
+
+ {{ "task_orders.new.review.reporting"| translate }} + {{ EditLink(url_for("task_orders.new", screen=1, task_order_id=task_order.id, _anchor="reporting", ko_edit=True)) }} +
{% include "fragments/task_order_review/reporting.html" %}
-
{{ "task_orders.new.review.funding"| translate }}
+
+ {{ "task_orders.new.review.funding"| translate }} + {{ EditLink(url_for("task_orders.new", screen=2, task_order_id=task_order.id, _anchor="reporting", ko_edit=True)) }} +
{% include "fragments/task_order_review/funding.html" %}
@@ -84,7 +61,10 @@

-
{{ "task_orders.new.review.oversight"| translate }}
+
+ {{ "task_orders.new.review.oversight"| translate }} + {{ EditLink(url_for("task_orders.new", screen=3, task_order_id=task_order.id, _anchor="reporting", ko_edit=True)) }} +
{% include "fragments/task_order_review/oversight.html" %}
From 232416cede2f364e31c8682e4b1451a7e0c01266 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 22 Feb 2019 12:40:22 -0500 Subject: [PATCH 08/24] Update test to check loa object type --- tests/routes/portfolios/test_task_orders.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index 09d208a6..8cc589bb 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -358,7 +358,7 @@ def test_submit_completed_ko_review_page_as_cor(client, user_session, pdf_upload "start_date": "02/10/2019", "end_date": "03/10/2019", "number": "1938745981", - "loas": ["0813458013405"], + "loas-0": "0813458013405", "custom_clauses": "hi im a custom clause", "pdf": pdf_upload, } @@ -388,12 +388,15 @@ def test_submit_to_with_multiple_loas(client, user_session, pdf_upload): status=PortfolioStatus.ACTIVE, ) task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko) + loa_list = ["0813458013405", "1234567890", "5678901234"] user_session(ko) form_data = { "start_date": "02/10/2019", "end_date": "03/10/2019", "number": "1938745981", - "loas": ["0813458013405", "1234567890", "5678901234"], + "loas-0": loa_list[0], + "loas-1": loa_list[1], + "loas-2": loa_list[2], "custom_clauses": "hi im a custom clause", "pdf": pdf_upload, } @@ -454,6 +457,7 @@ def test_submit_completed_ko_review_page_as_ko(client, user_session, pdf_upload) assert response.headers["Location"] == url_for( "task_orders.signature_requested", task_order_id=task_order.id, _external=True ) + assert task_order.loas == loa_list def test_so_review_page(app, client, user_session): From bc05bbced0aaad37ad3ec21f30c80a74bbc2bb24 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 22 Feb 2019 13:16:37 -0500 Subject: [PATCH 09/24] Make LOA inputs optional --- atst/forms/ko_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atst/forms/ko_review.py b/atst/forms/ko_review.py index c92561ab..c50deed7 100644 --- a/atst/forms/ko_review.py +++ b/atst/forms/ko_review.py @@ -31,7 +31,8 @@ class KOReviewForm(CacheableForm): ) loas = FieldList( StringField( - translate("forms.ko_review.loa"), validators=[Length(min=10), IsNumber()] + translate("forms.ko_review.loa"), + validators=[Length(min=10), IsNumber(), Optional()], ) ) custom_clauses = TextAreaField( From 96b9dbef87314958efc7780a3d29f5a4ae899537 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 22 Feb 2019 13:17:50 -0500 Subject: [PATCH 10/24] Add render to date-selector so tests will run --- js/components/date_selector.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/components/date_selector.js b/js/components/date_selector.js index 4cf144e3..e5e48128 100644 --- a/js/components/date_selector.js +++ b/js/components/date_selector.js @@ -127,4 +127,8 @@ export default { } }, }, + + render: function(createElement) { + return createElement('p', 'Please implement inline-template') + }, } From 2d2f8d8d938643204b61734f137382de83977c71 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 22 Feb 2019 13:18:06 -0500 Subject: [PATCH 11/24] Update labels for LOA fields --- templates/portfolios/task_orders/review.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index 4a9b0425..39fdc45d 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -79,7 +79,7 @@
  • From 9a7bcd6f29ab7cc887e45caebb9d98a5d6d0da9a Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 22 Feb 2019 15:21:33 -0500 Subject: [PATCH 12/24] Add remove LOA button --- js/components/forms/ko_review.js | 6 ++++++ templates/portfolios/task_orders/review.html | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/js/components/forms/ko_review.js b/js/components/forms/ko_review.js index 3b86390e..c48ecd8f 100644 --- a/js/components/forms/ko_review.js +++ b/js/components/forms/ko_review.js @@ -42,5 +42,11 @@ export default { addLOA: function(event) { this.loas.push(createLOA('')) }, + + removeLOA: function(index) { + if (this.loas.length > 1) { + this.loas.splice(index, 1) + } + }, }, } diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index 39fdc45d..a87d06aa 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -84,6 +84,10 @@
    +
  • From 870e0a299ee2e2404c5d39ea2ffcdae38a49949c Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 26 Feb 2019 14:59:01 -0500 Subject: [PATCH 13/24] Make LOA fields optional --- atst/forms/ko_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atst/forms/ko_review.py b/atst/forms/ko_review.py index c50deed7..624202e8 100644 --- a/atst/forms/ko_review.py +++ b/atst/forms/ko_review.py @@ -32,7 +32,7 @@ class KOReviewForm(CacheableForm): loas = FieldList( StringField( translate("forms.ko_review.loa"), - validators=[Length(min=10), IsNumber(), Optional()], + validators=[Optional()], ) ) custom_clauses = TextAreaField( From f3a2ac1f49b2d9570ed16d97e919b9002df629cb Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 26 Feb 2019 15:33:36 -0500 Subject: [PATCH 14/24] Style remove buttons --- styles/sections/_task_order.scss | 17 ++++++++++++++++- templates/portfolios/task_orders/review.html | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 464f6832..350a2a4e 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -348,11 +348,26 @@ .task-order__loa-list-item { display: flex; flex-direction: row; - align-items: flex-end; + align-items: flex-start; .usa-input { flex-grow: 1; } + + .loa-list-item__remover { + @include icon-link; + @include icon-link-vertical; + @include icon-link-color($color-red, $color-red-lightest); + + margin-bottom: 0; + margin-right: -$gap; + + position: absolute; + margin-top: 3 * $gap; + margin-left: $gap; + left: 45em; + max-width: 30em; + } } } diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index a87d06aa..de3d3b7a 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -84,7 +84,7 @@
    - From b07ab6d1b417225d01d437ac1c9e590513149507 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 26 Feb 2019 15:38:27 -0500 Subject: [PATCH 15/24] Remove unused import and formatting --- atst/forms/ko_review.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/atst/forms/ko_review.py b/atst/forms/ko_review.py index 624202e8..d0f723eb 100644 --- a/atst/forms/ko_review.py +++ b/atst/forms/ko_review.py @@ -6,7 +6,6 @@ from wtforms.fields import StringField, TextAreaField, FileField, FieldList from wtforms.validators import Optional, Length from .forms import CacheableForm -from .validators import IsNumber from atst.utils.localization import translate @@ -30,10 +29,7 @@ class KOReviewForm(CacheableForm): translate("forms.ko_review.to_number"), validators=[Length(min=10)] ) loas = FieldList( - StringField( - translate("forms.ko_review.loa"), - validators=[Optional()], - ) + StringField(translate("forms.ko_review.loa"), validators=[Optional()]) ) custom_clauses = TextAreaField( translate("forms.ko_review.custom_clauses_label"), From 113cc5c0a407f43da2dcc4330a11f781c5f5e528 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 26 Feb 2019 15:44:59 -0500 Subject: [PATCH 16/24] Update migrations after rebase --- alembic/versions/db161adbafdf_update_loa_to_array_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/alembic/versions/db161adbafdf_update_loa_to_array_type.py b/alembic/versions/db161adbafdf_update_loa_to_array_type.py index 72ce461c..96934431 100644 --- a/alembic/versions/db161adbafdf_update_loa_to_array_type.py +++ b/alembic/versions/db161adbafdf_update_loa_to_array_type.py @@ -1,7 +1,7 @@ """Update LOA to Array Type Revision ID: db161adbafdf -Revises: fa3ba4049218 +Revises: fb22e47972a3 Create Date: 2019-02-15 14:28:33.181136 """ @@ -11,7 +11,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'db161adbafdf' -down_revision = 'fa3ba4049218' +down_revision = 'fb22e47972a3' branch_labels = None depends_on = None From 88e2b3c79e6c29a258bd42d4e1072c2be0228aa5 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Wed, 27 Feb 2019 10:57:53 -0500 Subject: [PATCH 17/24] Fix position of remove buttons for FF --- styles/sections/_task_order.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 350a2a4e..7a982261 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -349,6 +349,7 @@ display: flex; flex-direction: row; align-items: flex-start; + position: relative; .usa-input { flex-grow: 1; @@ -365,7 +366,7 @@ position: absolute; margin-top: 3 * $gap; margin-left: $gap; - left: 45em; + left: 35em; max-width: 30em; } } From 7b41b8e94cacbc0811520be148b44b457f59f6ad Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Wed, 27 Feb 2019 11:03:31 -0500 Subject: [PATCH 18/24] Remove redundant test --- tests/routes/portfolios/test_task_orders.py | 47 ++------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index 8cc589bb..d16fc580 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -378,49 +378,6 @@ def test_submit_completed_ko_review_page_as_cor(client, user_session, pdf_upload ) -def test_submit_to_with_multiple_loas(client, user_session, pdf_upload): - portfolio = PortfolioFactory.create() - ko = UserFactory.create() - PortfolioRoleFactory.create( - role=Roles.get("officer"), - portfolio=portfolio, - user=ko, - status=PortfolioStatus.ACTIVE, - ) - task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko) - loa_list = ["0813458013405", "1234567890", "5678901234"] - user_session(ko) - form_data = { - "start_date": "02/10/2019", - "end_date": "03/10/2019", - "number": "1938745981", - "loas-0": loa_list[0], - "loas-1": loa_list[1], - "loas-2": loa_list[2], - "custom_clauses": "hi im a custom clause", - "pdf": pdf_upload, - } - - user_session(cor) - - response = client.post( - url_for( - "portfolios.ko_review", - portfolio_id=portfolio.id, - task_order_id=task_order.id, - ), - data=form_data, - ) - - assert task_order.pdf - assert response.headers["Location"] == url_for( - "portfolios.view_task_order", - task_order_id=task_order.id, - portfolio_id=portfolio.id, - _external=True, - ) - - def test_submit_completed_ko_review_page_as_ko(client, user_session, pdf_upload): portfolio = PortfolioFactory.create() @@ -440,7 +397,9 @@ def test_submit_completed_ko_review_page_as_ko(client, user_session, pdf_upload) "start_date": "02/10/2019", "end_date": "03/10/2019", "number": "1938745981", - "loa": "0813458013405", + "loas-0": loa_list[0], + "loas-1": loa_list[1], + "loas-2": loa_list[2], "custom_clauses": "hi im a custom clause", "pdf": pdf_upload, } From e00e24f0832a24361ef74c7ac25d6e8afea0c7c7 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Wed, 27 Feb 2019 11:37:39 -0500 Subject: [PATCH 19/24] Move removing empty items in a list field to CacheableForm --- atst/forms/application.py | 12 ------------ atst/forms/forms.py | 10 ++++++++++ atst/forms/ko_review.py | 8 -------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/atst/forms/application.py b/atst/forms/application.py index d917f72d..80bb0858 100644 --- a/atst/forms/application.py +++ b/atst/forms/application.py @@ -15,8 +15,6 @@ class ApplicationForm(FlaskForm): class NewApplicationForm(ApplicationForm): - EMPTY_ENVIRONMENT_NAMES = ["", None] - environment_names = FieldList( StringField(label=translate("forms.application.environment_names_label")), validators=[ @@ -32,13 +30,3 @@ class NewApplicationForm(ApplicationForm): ), ], ) - - @property - def data(self): - _data = super(FlaskForm, self).data - _data["environment_names"] = [ - n - for n in _data["environment_names"] - if n not in self.EMPTY_ENVIRONMENT_NAMES - ] - return _data diff --git a/atst/forms/forms.py b/atst/forms/forms.py index 34c861f2..52586ba2 100644 --- a/atst/forms/forms.py +++ b/atst/forms/forms.py @@ -24,8 +24,18 @@ class ValidatedForm(FlaskForm): class CacheableForm(ValidatedForm): + EMPTY_LIST_FIELD = ["", None] + def __init__(self, formdata=None, **kwargs): formdata = formdata or {} cached_data = current_app.form_cache.from_request(http_request) cached_data.update(formdata) super().__init__(cached_data, **kwargs) + + @property + def data(self): + _data = super(FlaskForm, self).data + for field in _data: + if _data[field].__class__.__name__ == "list": + _data[field] = [el for el in _data[field] if el not in self.EMPTY_LIST_FIELD] + return _data diff --git a/atst/forms/ko_review.py b/atst/forms/ko_review.py index d0f723eb..7c02ecc8 100644 --- a/atst/forms/ko_review.py +++ b/atst/forms/ko_review.py @@ -11,8 +11,6 @@ from atst.utils.localization import translate class KOReviewForm(CacheableForm): - EMPTY_LOA = ["", None] - start_date = DateField( translate("forms.ko_review.start_date_label"), format="%m/%d/%Y" ) @@ -36,9 +34,3 @@ class KOReviewForm(CacheableForm): description=translate("forms.ko_review.custom_clauses_description"), validators=[Optional()], ) - - @property - def data(self): - _data = super(FlaskForm, self).data - _data["loas"] = [n for n in _data["loas"] if n not in self.EMPTY_LOA] - return _data From c5e012e5211b87aa879b9cd9313ebc9c53cf8c8c Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Wed, 27 Feb 2019 11:40:56 -0500 Subject: [PATCH 20/24] Fix migration so it will downgrade --- alembic/versions/db161adbafdf_update_loa_to_array_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alembic/versions/db161adbafdf_update_loa_to_array_type.py b/alembic/versions/db161adbafdf_update_loa_to_array_type.py index 96934431..7540383e 100644 --- a/alembic/versions/db161adbafdf_update_loa_to_array_type.py +++ b/alembic/versions/db161adbafdf_update_loa_to_array_type.py @@ -25,6 +25,6 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.execute("ALTER TABLE task_orders ALTER COLUMN loa TYPE varchar USING loa[1]") + op.execute("ALTER TABLE task_orders ALTER COLUMN loas TYPE varchar USING loas[1]") op.alter_column('task_orders', 'loas', new_column_name='loa') # ### end Alembic commands ### From a6296e78df9e73f546d6d3224af114b89a8e6f3d Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Wed, 27 Feb 2019 11:41:26 -0500 Subject: [PATCH 21/24] Formatting --- atst/forms/forms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atst/forms/forms.py b/atst/forms/forms.py index 52586ba2..052d98d3 100644 --- a/atst/forms/forms.py +++ b/atst/forms/forms.py @@ -37,5 +37,7 @@ class CacheableForm(ValidatedForm): _data = super(FlaskForm, self).data for field in _data: if _data[field].__class__.__name__ == "list": - _data[field] = [el for el in _data[field] if el not in self.EMPTY_LIST_FIELD] + _data[field] = [ + el for el in _data[field] if el not in self.EMPTY_LIST_FIELD + ] return _data From 3b5b809947890476da4380fa1052ea854b272e32 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Wed, 27 Feb 2019 13:32:37 -0500 Subject: [PATCH 22/24] Combine data properties and remove unused import --- atst/forms/forms.py | 19 +++++++------------ atst/forms/ko_review.py | 1 - 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/atst/forms/forms.py b/atst/forms/forms.py index 052d98d3..5d6d37c4 100644 --- a/atst/forms/forms.py +++ b/atst/forms/forms.py @@ -5,6 +5,8 @@ from atst.utils.flash import formatted_flash as flash class ValidatedForm(FlaskForm): + EMPTY_LIST_FIELD = ["", None] + def perform_extra_validation(self, *args, **kwargs): """Performs any applicable extra validation. Must return True if the form is valid or False otherwise.""" @@ -13,6 +15,11 @@ class ValidatedForm(FlaskForm): @property def data(self): _data = super().data + for field in _data: + if _data[field].__class__.__name__ == "list": + _data[field] = [ + el for el in _data[field] if el not in self.EMPTY_LIST_FIELD + ] _data.pop("csrf_token", None) return _data @@ -24,20 +31,8 @@ class ValidatedForm(FlaskForm): class CacheableForm(ValidatedForm): - EMPTY_LIST_FIELD = ["", None] - def __init__(self, formdata=None, **kwargs): formdata = formdata or {} cached_data = current_app.form_cache.from_request(http_request) cached_data.update(formdata) super().__init__(cached_data, **kwargs) - - @property - def data(self): - _data = super(FlaskForm, self).data - for field in _data: - if _data[field].__class__.__name__ == "list": - _data[field] = [ - el for el in _data[field] if el not in self.EMPTY_LIST_FIELD - ] - return _data diff --git a/atst/forms/ko_review.py b/atst/forms/ko_review.py index 7c02ecc8..672cad2e 100644 --- a/atst/forms/ko_review.py +++ b/atst/forms/ko_review.py @@ -1,5 +1,4 @@ from flask_wtf.file import FileAllowed -from flask_wtf import FlaskForm from wtforms.fields.html5 import DateField from wtforms.fields import StringField, TextAreaField, FileField, FieldList From 9f47f54751dfd667e08c3e9832ba5947060692b2 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 28 Feb 2019 11:18:00 -0500 Subject: [PATCH 23/24] Fix migration and tests after rebase --- alembic/versions/db161adbafdf_update_loa_to_array_type.py | 4 ++-- tests/routes/portfolios/test_task_orders.py | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/alembic/versions/db161adbafdf_update_loa_to_array_type.py b/alembic/versions/db161adbafdf_update_loa_to_array_type.py index 7540383e..fec51867 100644 --- a/alembic/versions/db161adbafdf_update_loa_to_array_type.py +++ b/alembic/versions/db161adbafdf_update_loa_to_array_type.py @@ -1,7 +1,7 @@ """Update LOA to Array Type Revision ID: db161adbafdf -Revises: fb22e47972a3 +Revises: ec1ba2363191 Create Date: 2019-02-15 14:28:33.181136 """ @@ -11,7 +11,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'db161adbafdf' -down_revision = 'fb22e47972a3' +down_revision = 'ec1ba2363191' branch_labels = None depends_on = None diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index d16fc580..0636df2e 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -363,6 +363,8 @@ def test_submit_completed_ko_review_page_as_cor(client, user_session, pdf_upload "pdf": pdf_upload, } + user_session(cor) + response = client.post( url_for( "portfolios.ko_review", @@ -374,7 +376,10 @@ def test_submit_completed_ko_review_page_as_cor(client, user_session, pdf_upload assert task_order.pdf assert response.headers["Location"] == url_for( - "task_orders.signature_requested", task_order_id=task_order.id, _external=True + "portfolios.view_task_order", + task_order_id=task_order.id, + portfolio_id=portfolio.id, + _external=True, ) @@ -392,6 +397,7 @@ def test_submit_completed_ko_review_page_as_ko(client, user_session, pdf_upload) task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko) user_session(ko) + loa_list = ["123123123", "456456456", "789789789"] form_data = { "start_date": "02/10/2019", From 8b0a11b00514dc8dce8798c980a75ab69e40b9cd Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Fri, 1 Mar 2019 11:05:37 -0500 Subject: [PATCH 24/24] Fix migration chain after rebase --- alembic/versions/db161adbafdf_update_loa_to_array_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/alembic/versions/db161adbafdf_update_loa_to_array_type.py b/alembic/versions/db161adbafdf_update_loa_to_array_type.py index fec51867..acb4f9fd 100644 --- a/alembic/versions/db161adbafdf_update_loa_to_array_type.py +++ b/alembic/versions/db161adbafdf_update_loa_to_array_type.py @@ -1,7 +1,7 @@ """Update LOA to Array Type Revision ID: db161adbafdf -Revises: ec1ba2363191 +Revises: 6512aa8d4641 Create Date: 2019-02-15 14:28:33.181136 """ @@ -11,7 +11,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'db161adbafdf' -down_revision = 'ec1ba2363191' +down_revision = '6512aa8d4641' branch_labels = None depends_on = None