Merge pull request #546 from dod-ccpo/am-cor-checkbox

am COR checkbox
This commit is contained in:
montana-mil 2019-01-17 11:39:06 -05:00 committed by GitHub
commit d0857a4a74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 149 additions and 20 deletions

View File

@ -12,7 +12,7 @@ from wtforms.fields.html5 import DateField, TelField
from wtforms.widgets import ListWidget, CheckboxInput
from wtforms.validators import Required, Length
from atst.forms.validators import IsNumber, PhoneNumber
from atst.forms.validators import IsNumber, PhoneNumber, RequiredIfNot
from .forms import CacheableForm
from .data import (
@ -120,17 +120,19 @@ class OversightForm(CacheableForm):
validators=[Required(), Length(min=10), IsNumber()],
)
am_cor = BooleanField(translate("forms.task_order.oversight_am_cor_label"))
cor_first_name = StringField(
translate("forms.task_order.oversight_first_name_label")
)
cor_last_name = StringField(translate("forms.task_order.oversight_last_name_label"))
cor_email = StringField(translate("forms.task_order.oversight_email_label"))
cor_phone_number = TelField(
translate("forms.task_order.oversight_phone_label"), validators=[PhoneNumber()]
translate("forms.task_order.oversight_phone_label"),
validators=[RequiredIfNot("am_cor"), PhoneNumber()],
)
cor_dod_id = StringField(
translate("forms.task_order.oversight_dod_id_label"),
validators=[Required(), Length(min=10), IsNumber()],
validators=[RequiredIfNot("am_cor"), Length(min=10), IsNumber()],
)
so_first_name = StringField(

View File

@ -1,5 +1,5 @@
import re
from wtforms.validators import ValidationError
from wtforms.validators import ValidationError, StopValidation
import pendulum
from datetime import datetime
from atst.utils.localization import translate
@ -78,3 +78,28 @@ def ListItemsUnique(message=translate("forms.validators.list_items_unique_messag
raise ValidationError(message)
return _list_items_unique
def RequiredIfNot(other_field_name, message=translate("forms.validators.is_required")):
""" A validator which makes a field required only if another field
has a falsy value
Args:
other_field_name (str): the name of the field we check before
determining if this field is required; if this other field is falsy,
the field will be required
message (str): an optional message to display if the field is
required but hasNone value
"""
def _required_if_not(form, field):
other_field = form._fields.get(other_field_name)
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
if not bool(other_field.data):
if field.data is None:
raise ValidationError(message)
else:
raise StopValidation()
return _required_if_not

View File

@ -78,6 +78,10 @@ class ShowTaskOrderWorkflow:
if self._section["section"] == "app_info":
self._form.complexity.data = self.task_order.complexity
self._form.dev_team.data = self.task_order.dev_team
elif self._section["section"] == "oversight":
if self.user.dod_id == self.task_order.cor_dod_id:
self._form.am_cor.data = True
else:
self._form = self._section[form_type]()
@ -133,6 +137,16 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow):
if "dev_team" in to_data and "other" not in to_data["dev_team"]:
to_data["dev_team_other"] = None
if self.form_data.get("am_cor"):
cor_data = {
"cor_first_name": self.user.first_name,
"cor_last_name": self.user.last_name,
"cor_email": self.user.email,
"cor_phone_number": self.user.phone_number,
"cor_dod_id": self.user.dod_id,
}
to_data = {**to_data, **cor_data}
return to_data
def validate(self):

View File

@ -0,0 +1,31 @@
import FormMixin from '../../mixins/form'
import textinput from '../text_input'
import checkboxinput from '../checkbox_input'
export default {
name: 'cor',
mixins: [FormMixin],
components: {
textinput,
checkboxinput,
},
props: {
initialData: {
type: Object,
default: () => ({})
}
},
data: function () {
const {
am_cor = false
} = this.initialData
return {
am_cor
}
}
}

View File

@ -11,6 +11,7 @@ import textinput from './components/text_input'
import checkboxinput from './components/checkbox_input'
import DetailsOfUse from './components/forms/details_of_use'
import poc from './components/forms/poc'
import cor from './components/forms/cor'
import financial from './components/forms/financial'
import toggler from './components/toggler'
import NewApplication from './components/forms/new_application'
@ -44,6 +45,7 @@ const app = new Vue({
checkboxinput,
DetailsOfUse,
poc,
cor,
financial,
NewApplication,
selector,

View File

@ -21,17 +21,16 @@
<h3 class="subheading">{{ "task_orders.new.oversight.cor_info_title" | translate }}</h3>
<p>{{ "task_orders.new.oversight.cor_info_paragraph" | translate }}</p>
<cor inline-template v-bind:initial-data='{{ form.data|tojson }}'>
<div class='usa-input'>
<fieldset class="usa-input__choices">
<legend>
<input type="checkbox" name="am_cor" id="am_cor" value="y" />
<label for="am_cor">{{ "task_orders.new.oversight.am_cor_label" | translate }}</label>
</legend>
</fieldset>
</div>
{{ CheckboxInput(form.am_cor) }}
<template v-if="!am_cor">
{{ UserInfo(form.cor_first_name, form.cor_last_name, form.cor_email, form.cor_phone_number) }}
{{ CheckboxInput(form.cor_invite) }}
{{ TextInput(form.cor_dod_id, placeholder="1234567890", tooltip="Why", tooltip_title='Why', validation='dodId')}}
</template>
</div>
</cor>
<hr />

View File

@ -6,6 +6,7 @@ import alembic.command
from logging.config import dictConfig
from werkzeug.datastructures import FileStorage
from tempfile import TemporaryDirectory
from collections import OrderedDict
from atst.app import make_app, make_config
from atst.database import db as _db
@ -84,14 +85,17 @@ def session(db, request):
class DummyForm(dict):
pass
def __init__(self, data=OrderedDict(), errors=(), raw_data=None):
self._fields = data
self.errors = list(errors)
class DummyField(object):
def __init__(self, data=None, errors=(), raw_data=None):
def __init__(self, data=None, errors=(), raw_data=None, name=None):
self.data = data
self.errors = list(errors)
self.raw_data = raw_data
self.name = name
@pytest.fixture
@ -99,6 +103,15 @@ def dummy_form():
return DummyForm()
@pytest.fixture
def dummy_form_with_field():
def set_field(name, value):
data = DummyField(data=value, name=name)
return DummyForm(data=OrderedDict({name: data}))
return set_field
@pytest.fixture
def dummy_field():
return DummyField()

View File

@ -1,7 +1,13 @@
from wtforms.validators import ValidationError
from wtforms.validators import ValidationError, StopValidation
import pytest
from atst.forms.validators import Name, IsNumber, PhoneNumber, ListItemsUnique
from atst.forms.validators import (
Name,
IsNumber,
PhoneNumber,
ListItemsUnique,
RequiredIfNot,
)
class TestIsNumber:
@ -73,3 +79,32 @@ class TestListItemsUnique:
dummy_field.data = invalid
with pytest.raises(ValidationError):
validator(dummy_form, dummy_field)
class TestRequiredIfNot:
def test_RequiredIfNot_requires_field_if_arg_is_falsy(
self, dummy_form_with_field, dummy_field
):
form = dummy_form_with_field("arg", False)
validator = RequiredIfNot("arg")
dummy_field.data = None
with pytest.raises(ValidationError):
validator(form, dummy_field)
def test_RequiredIfNot_does_not_require_field_if_arg_is_truthy(
self, dummy_form_with_field, dummy_field
):
form = dummy_form_with_field("arg", True)
validator = RequiredIfNot("arg")
dummy_field.data = None
with pytest.raises(StopValidation):
validator(form, dummy_field)
def test_RequiredIfNot_arg_is_None_raises_error(self, dummy_form, dummy_field):
validator = RequiredIfNot("arg")
dummy_field.data = "some data"
with pytest.raises(Exception):
validator(dummy_form, dummy_field)

View File

@ -203,6 +203,13 @@ def test_other_text_not_saved_if_other_not_checked(task_order):
assert not workflow.task_order.complexity_other
def test_cor_data_set_to_user_data_if_am_cor_is_checked(task_order):
to_data = {**task_order.to_dictionary(), "am_cor": True}
workflow = UpdateTaskOrderWorkflow(task_order.creator, to_data, 3, task_order.id)
workflow.update()
assert task_order.cor_dod_id == task_order.creator.dod_id
def test_invite_officers_to_task_order(task_order, queue):
to_data = {
**TaskOrderFactory.dictionary(),

View File

@ -223,6 +223,7 @@ forms:
oversight_email_label: Email
oversight_phone_label: Phone Number
oversight_dod_id_label: DoD ID
oversight_am_cor_label: I am the Contracting Officer Representative (COR) for this Task Order
ko_invite_label: Invite KO to Task Order Builder
cor_invite_label: Invite COR to Task Order Builder
so_invite_label: Invite Security Officer to Task Order Builder
@ -234,6 +235,7 @@ forms:
list_items_unique_message: Items must be unique
name_message: 'This field accepts letters, numbers, commas, apostrophes, hyphens, and periods.'
phone_number_message: Please enter a valid 5 or 10 digit phone number.
is_required: This field is required.
portfolio:
name_label: Portfolio Name
name_length_validation_message: Portfolio names must be at least 4 and not more than 50 characters
@ -403,7 +405,6 @@ task_orders:
skip_ko_label: "Skip for now (We'll remind you to enter one later)"
cor_info_title: Contracting Officer Representative (COR) Information
cor_info_paragraph: Your COR may assist in submitting the Task Order documents within thier official system of record. They may also be invited to log in an manage the Task Order entry within the JEDI Cloud portal.
am_cor_label: I am the Contracting Officer Representative (COR) for this Task Order
so_info_title: Security Officer Information
so_info_paragraph: Your Security Officer will need to answer some security configuration questions in order to generate a DD-254 document, then electronically sign.
review: