Merge pull request #584 from dod-ccpo/edit-to-officer
Edit Task Order Officer Info
This commit is contained in:
commit
2246e0a5de
60
atst/forms/officers.py
Normal file
60
atst/forms/officers.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms.fields import FormField, StringField
|
||||||
|
from wtforms.fields.html5 import TelField
|
||||||
|
from wtforms.validators import Length, Optional
|
||||||
|
|
||||||
|
from atst.forms.validators import IsNumber, PhoneNumber
|
||||||
|
|
||||||
|
from .forms import CacheableForm
|
||||||
|
|
||||||
|
|
||||||
|
class OfficerForm(FlaskForm):
|
||||||
|
first_name = StringField("First Name")
|
||||||
|
last_name = StringField("Last Name")
|
||||||
|
email = StringField("Email")
|
||||||
|
phone_number = TelField("Phone Number", validators=[PhoneNumber()])
|
||||||
|
dod_id = StringField("DoD ID", validators=[Optional(), Length(min=10), IsNumber()])
|
||||||
|
|
||||||
|
|
||||||
|
class EditTaskOrderOfficersForm(CacheableForm):
|
||||||
|
|
||||||
|
contracting_officer = FormField(OfficerForm)
|
||||||
|
contracting_officer_representative = FormField(OfficerForm)
|
||||||
|
security_officer = FormField(OfficerForm)
|
||||||
|
|
||||||
|
OFFICER_PREFIXES = {
|
||||||
|
"contracting_officer": "ko",
|
||||||
|
"contracting_officer_representative": "cor",
|
||||||
|
"security_officer": "so",
|
||||||
|
}
|
||||||
|
OFFICER_INFO_FIELD_NAMES = [
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"email",
|
||||||
|
"phone_number",
|
||||||
|
"dod_id",
|
||||||
|
]
|
||||||
|
|
||||||
|
def process(self, formdata=None, obj=None, data=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
for name, field in self._fields.items():
|
||||||
|
if name in self.OFFICER_PREFIXES:
|
||||||
|
prefix = self.OFFICER_PREFIXES[name]
|
||||||
|
officer_data = {
|
||||||
|
field_name: getattr(obj, prefix + "_" + field_name)
|
||||||
|
for field_name in self.OFFICER_INFO_FIELD_NAMES
|
||||||
|
}
|
||||||
|
field.process(formdata=formdata, data=officer_data)
|
||||||
|
else:
|
||||||
|
field.process(formdata)
|
||||||
|
else:
|
||||||
|
super(EditTaskOrderOfficersForm, self).process(
|
||||||
|
formdata=formdata, obj=obj, data=data, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def populate_obj(self, obj):
|
||||||
|
for name, field in self._fields.items():
|
||||||
|
if name in self.OFFICER_PREFIXES:
|
||||||
|
prefix = self.OFFICER_PREFIXES[name]
|
||||||
|
for field_name in self.OFFICER_INFO_FIELD_NAMES:
|
||||||
|
setattr(obj, prefix + "_" + field_name, field[field_name].data)
|
@ -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,
|
||||||
|
)
|
||||||
|
36
js/components/forms/__tests__/edit_officer_form.test.js
Normal file
36
js/components/forms/__tests__/edit_officer_form.test.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
|
||||||
|
import EditOfficerForm from '../edit_officer_form'
|
||||||
|
|
||||||
|
describe('EditOfficerForm', () => {
|
||||||
|
it('defaults to not editing', () => {
|
||||||
|
const wrapper = shallowMount(EditOfficerForm)
|
||||||
|
expect(wrapper.vm.$data.editing).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not start in editing mode when no errors', () => {
|
||||||
|
const wrapper = shallowMount(EditOfficerForm, {
|
||||||
|
propsData: { hasErrors: false },
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.$data.editing).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does start in editing mode when the form has errors', () => {
|
||||||
|
const wrapper = shallowMount(EditOfficerForm, {
|
||||||
|
propsData: { hasErrors: true },
|
||||||
|
})
|
||||||
|
expect(wrapper.vm.$data.editing).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('starts editing when edit method called', () => {
|
||||||
|
const wrapper = shallowMount(EditOfficerForm)
|
||||||
|
wrapper.vm.edit({ preventDefault: () => null })
|
||||||
|
expect(wrapper.vm.$data.editing).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stops editing when cancel method called', () => {
|
||||||
|
const wrapper = shallowMount(EditOfficerForm)
|
||||||
|
wrapper.vm.cancel()
|
||||||
|
expect(wrapper.vm.$data.editing).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
40
js/components/forms/edit_officer_form.js
Normal file
40
js/components/forms/edit_officer_form.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import FormMixin from '../../mixins/form'
|
||||||
|
import checkboxinput from '../checkbox_input'
|
||||||
|
import textinput from '../text_input'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'edit-officer-form',
|
||||||
|
|
||||||
|
mixins: [FormMixin],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
checkboxinput,
|
||||||
|
textinput,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
hasErrors: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
editing: this.hasErrors,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
edit: function(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.editing = true
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel: function(event) {
|
||||||
|
this.editing = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
template: '<div></div>',
|
||||||
|
}
|
@ -11,6 +11,7 @@ import multicheckboxinput from './components/multi_checkbox_input'
|
|||||||
import textinput from './components/text_input'
|
import textinput from './components/text_input'
|
||||||
import checkboxinput from './components/checkbox_input'
|
import checkboxinput from './components/checkbox_input'
|
||||||
import DetailsOfUse from './components/forms/details_of_use'
|
import DetailsOfUse from './components/forms/details_of_use'
|
||||||
|
import EditOfficerForm from './components/forms/edit_officer_form'
|
||||||
import poc from './components/forms/poc'
|
import poc from './components/forms/poc'
|
||||||
import oversight from './components/forms/oversight'
|
import oversight from './components/forms/oversight'
|
||||||
import financial from './components/forms/financial'
|
import financial from './components/forms/financial'
|
||||||
@ -64,6 +65,7 @@ const app = new Vue({
|
|||||||
ConfirmationPopover,
|
ConfirmationPopover,
|
||||||
funding,
|
funding,
|
||||||
DateSelector,
|
DateSelector,
|
||||||
|
EditOfficerForm,
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 %}
|
||||||
|
56
tests/forms/test_officers.py
Normal file
56
tests/forms/test_officers.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from werkzeug.datastructures import ImmutableMultiDict
|
||||||
|
|
||||||
|
from atst.forms.officers import EditTaskOrderOfficersForm
|
||||||
|
from tests.factories import TaskOrderFactory, UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
class TestEditTaskOrderOfficersForm:
|
||||||
|
def _assert_officer_info_matches(self, form, task_order, officer):
|
||||||
|
prefix = form.OFFICER_PREFIXES[officer]
|
||||||
|
|
||||||
|
for field in form.OFFICER_INFO_FIELD_NAMES:
|
||||||
|
assert form[officer][field].data == getattr(
|
||||||
|
task_order, "{}_{}".format(prefix, field)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_processing_with_existing_task_order(self):
|
||||||
|
task_order = TaskOrderFactory.create()
|
||||||
|
form = EditTaskOrderOfficersForm(obj=task_order)
|
||||||
|
for officer in form.OFFICER_PREFIXES.keys():
|
||||||
|
self._assert_officer_info_matches(form, task_order, officer)
|
||||||
|
|
||||||
|
def test_processing_form_with_formdata(self):
|
||||||
|
data = {
|
||||||
|
"contracting_officer-first_name": "Han",
|
||||||
|
"contracting_officer-last_name": "Solo",
|
||||||
|
}
|
||||||
|
formdata = ImmutableMultiDict(data)
|
||||||
|
task_order = TaskOrderFactory.create()
|
||||||
|
form = EditTaskOrderOfficersForm(formdata=formdata, obj=task_order)
|
||||||
|
|
||||||
|
for officer in ["contracting_officer_representative", "security_officer"]:
|
||||||
|
self._assert_officer_info_matches(form, task_order, officer)
|
||||||
|
|
||||||
|
prefix = "ko"
|
||||||
|
officer = "contracting_officer"
|
||||||
|
for field in form.OFFICER_INFO_FIELD_NAMES:
|
||||||
|
data_field = "{}-{}".format(officer, field)
|
||||||
|
if data_field in formdata:
|
||||||
|
assert form[officer][field].data == formdata[data_field]
|
||||||
|
else:
|
||||||
|
assert form[officer][field].data == getattr(
|
||||||
|
task_order, "{}_{}".format(prefix, field)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_populate_obj(self):
|
||||||
|
data = {
|
||||||
|
"security_officer-first_name": "Luke",
|
||||||
|
"security_officer-last_name": "Skywalker",
|
||||||
|
}
|
||||||
|
formdata = ImmutableMultiDict(data)
|
||||||
|
task_order = TaskOrderFactory.create()
|
||||||
|
form = EditTaskOrderOfficersForm(formdata=formdata, obj=task_order)
|
||||||
|
|
||||||
|
form.populate_obj(task_order)
|
||||||
|
assert task_order.so_first_name == data["security_officer-first_name"]
|
||||||
|
assert task_order.so_last_name == data["security_officer-last_name"]
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user