Add route to update TO officer information

This commit is contained in:
Patrick Smith 2019-01-29 09:54:11 -05:00
parent 19169e76ed
commit 5cc1c18378
4 changed files with 219 additions and 64 deletions

View File

@ -1,11 +1,13 @@
from collections import defaultdict from collections import defaultdict
from operator import itemgetter 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 . import portfolios_bp
from atst.database import db
from atst.domain.task_orders import TaskOrders from atst.domain.task_orders import TaskOrders
from atst.domain.portfolios import Portfolios from atst.domain.portfolios import Portfolios
from atst.forms.officers import EditTaskOrderOfficersForm
from atst.models.task_order import Status as TaskOrderStatus 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): def task_order_invitations(portfolio_id, task_order_id):
portfolio = Portfolios.get(g.current_user, portfolio_id) portfolio = Portfolios.get(g.current_user, portfolio_id)
task_order = TaskOrders.get(g.current_user, task_order_id) task_order = TaskOrders.get(g.current_user, task_order_id)
form = EditTaskOrderOfficersForm(obj=task_order)
return render_template( return render_template(
"portfolios/task_orders/invitations.html", "portfolios/task_orders/invitations.html",
portfolio=portfolio, portfolio=portfolio,
task_order=task_order, task_order=task_order,
form=form,
) )
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/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,
)

View File

@ -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 { .officer__actions {
margin-left: -2 * $gap; margin-left: -2 * $gap;

View File

@ -1,76 +1,125 @@
{% extends "portfolios/base.html" %} {% extends "portfolios/base.html" %}
{% from "components/checkbox_input.html" import CheckboxInput %}
{% from "components/icon.html" import Icon %} {% 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='') %}
<a href="{{ url }}" class="icon-link {{ classes }}"> <a href="{{ url }}" {% if onClick %}v-on:click="{{ onClick }}"{% endif %} class="icon-link {{ classes }}">
{{ Icon(icon_name) }} {{ Icon(icon_name) }}
<span>{{ text }}</span> <span>{{ text }}</span>
</a> </a>
{% endmacro %} {% endmacro %}
{% macro OfficerInfo(task_order, officer_type) %} {% macro EditOfficerInfo(form, officer_type) -%}
<div class="panel__content officer"> <div class='officer__form'>
<template v-if="editing">
<div class='alert'>
<div class='alert__content'>
<div class='form-row'>
<div class='form-col form-col--half'>
{{ TextInput(form.first_name) }}
</div>
<div class='form-col form-col--half'>
{{ TextInput(form.last_name) }}
</div>
</div>
<div class='form-row'>
<div class='form-col form-col--half'>
{{ TextInput(form.email, placeholder='name@mail.mil') }}
</div>
<div class='form-col form-col--half'>
{{ TextInput(form.phone_number, placeholder='(123) 456-7890', validation='usPhone') }}
</div>
</div>
{% if form.dod_id.data %}
{{ TextInput(form.dod_id, validation='dodId', disabled=True)}}
{% endif %}
<div class='alert__actions officer__form--actions'>
<a href="#{{ officer_type }}" v-on:click="cancel" class="icon-link">
{{ Icon("x") }}
<span>Cancel</span>
</a>
<input type='submit' class='usa-button usa-button-primary' value='Save Changes' />
</div>
</div>
</div>
</template>
</div>
{% endmacro %}
{% macro OfficerInfo(task_order, officer_type, form) %}
<div class="panel__content officer" id="{{ officer_type }}">
<h2 class="officer__title">{{ ("task_orders.invitations." + officer_type + ".title") | translate }}</h2> <h2 class="officer__title">{{ ("task_orders.invitations." + officer_type + ".title") | translate }}</h2>
<p class="officer__description">{{ ("task_orders.invitations." + officer_type + ".description") | translate }}</p> <p class="officer__description">{{ ("task_orders.invitations." + officer_type + ".description") | translate }}</p>
{% set prefix = { "contracting_officer": "ko", "contracting_officer_representative": "cor", "security_officer": "so" }[officer_type] %} <edit-officer-form v-bind:has-errors='{{ ((form.errors|length) > 0)|tojson }}' inline-template>
{% set first_name = task_order[prefix + "_first_name"] %} <div>
{% 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] %} {% set prefix = { "contracting_officer": "ko", "contracting_officer_representative": "cor", "security_officer": "so" }[officer_type] %}
<div class="officer__info"> {% set first_name = task_order[prefix + "_first_name"] %}
<div class="row"> {% set last_name = task_order[prefix + "_last_name"] %}
<div class="officer__info--name">{{ first_name }} {{ last_name }}</div> {% set email = task_order[prefix + "_email"] %}
<div class="officer__info--status invited"> {% set phone_number = task_order[prefix + "_phone_number"] %}
<span>{{ Icon("ok", classes="invited") }}</span> {% set dod_id = task_order[prefix + "_dod_id"] %}
<span>Invited</span>
{% if task_order[officer_type] %}
<div class="officer__info">
<div class="row">
<div class="officer__info--name">{{ first_name }} {{ last_name }}</div>
<div class="officer__info--status invited">
<span>{{ Icon("ok", classes="invited") }}</span>
<span>Invited</span>
</div>
</div> </div>
<p class="officer__info--email">{{ email }}</p>
<p class="officer__info--phone">{{ phone_number | usPhone }}</p>
<p class="officer__info--dod_id">{{ "task_orders.invitations.dod_id_label" | translate}}: {{ dod_id }}</p>
</div> </div>
<p class="officer__info--email">{{ email }}</p> <div class="officer__actions">
<p class="officer__info--phone">{{ phone_number | usPhone }}</p> {{ Link("Update", "edit", onClick="edit") }}
<p class="officer__info--dod_id">{{ "task_orders.invitations.dod_id_label" | translate}}: {{ dod_id }}</p> {{ Link("Resend Invitation", "avatar") }}
</div> {{ Link("Remove", "trash", classes="remove") }}
<div class="officer__actions"> </div>
{{ Link("Update", "edit") }} {% elif first_name and last_name %}
{{ Link("Resend Invitation", "avatar") }} <div class="officer__info">
{{ Link("Remove", "trash", classes="remove") }} <div class="row">
</div> <div class="officer__info--name">{{ first_name }} {{ last_name }}</div>
{% elif first_name and last_name %} <div class="officer__info--status uninvited">
<div class="officer__info"> <span>{{ Icon("alert", classes="uninvited") }}</span>
<div class="row"> Not Invited
<div class="officer__info--name">{{ first_name }} {{ last_name }}</div> </div>
</div>
<p class="officer__info--email">{{ email }}</p>
<p class="officer__info--phone">{{ phone_number | usPhone }}</p>
</div>
<div class="officer__actions">
{{ Link("Update", "edit", onClick="edit") }}
{{ Link("Remove", "trash", classes="remove") }}
<button type='button' class='usa-button usa-button-primary'>
{{ ("task_orders.invitations." + officer_type + ".invite_button_text") | translate }}
</button>
</div>
{% else %}
<div class="officer__info">
<div class="officer__info--status uninvited"> <div class="officer__info--status uninvited">
<span>{{ Icon("alert", classes="uninvited") }}</span> <span>{{ Icon("alert", classes="uninvited") }}</span>
Not Invited Not specified
</div> </div>
</div> </div>
<p class="officer__info--email">{{ email }}</p> <div class="officer__actions">
<p class="officer__info--phone">{{ phone_number | usPhone }}</p> <button type='button' class='usa-button usa-button-primary'>
</div> {{ ("task_orders.invitations." + officer_type + ".add_button_text") | translate }}
<div class="officer__actions"> </button>
{{ Link("Update", "edit") }}
{{ Link("Remove", "trash", classes="remove") }}
<button type='button' class='usa-button usa-button-primary'>
{{ ("task_orders.invitations." + officer_type + ".invite_button_text") | translate }}
</button>
</div>
{% else %}
<div class="officer__info">
<div class="officer__info--status uninvited">
<span>{{ Icon("alert", classes="uninvited") }}</span>
Not specified
</div> </div>
{% endif %}
{{ EditOfficerInfo(form, officer_type) }}
</div> </div>
<div class="officer__actions"> </edit-officer-form>
<button type='button' class='usa-button usa-button-primary'>
{{ ("task_orders.invitations." + officer_type + ".add_button_text") | translate }}
</button>
</div>
{% endif %}
</div> </div>
{% endmacro %} {% endmacro %}
@ -78,17 +127,21 @@
<div class="task-order-invitations"> <div class="task-order-invitations">
{% include "fragments/flash.html" %} {% include "fragments/flash.html" %}
<div class="panel"> <form method='POST' action="{{ url_for("portfolios.edit_task_order_invitations", portfolio_id=portfolio.id, task_order_id=task_order.id) }}" autocomplete="off">
<div class="panel__heading"> {{ form.csrf_token }}
<h1 class="task-order-invitations__heading subheading">
<div class="h2">Edit Task Order</div>
Oversight
</h1>
</div>
{% for officer in ["contracting_officer", "contracting_officer_representative", "security_officer"] %} <div class="panel">
{{ OfficerInfo(task_order, officer) }} <div class="panel__heading">
{% endfor %} <h1 class="task-order-invitations__heading subheading">
</div> <div class="h2">Edit Task Order</div>
Oversight
</h1>
</div>
{% for officer in ["contracting_officer", "contracting_officer_representative", "security_officer"] %}
{{ OfficerInfo(task_order, officer, form[officer]) }}
{% endfor %}
</div>
</form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,6 +2,7 @@ from flask import url_for
import pytest import pytest
from atst.domain.roles import Roles from atst.domain.roles import Roles
from atst.domain.task_orders import TaskOrders
from atst.models.portfolio_role import Status as PortfolioStatus from atst.models.portfolio_role import Status as PortfolioStatus
from tests.factories import ( from tests.factories import (
@ -100,3 +101,53 @@ def test_can_view_task_order_invitations(client, user_session):
) )
) )
assert response.status_code == 200 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"