Update atst to atat

This commit is contained in:
leigh-mil
2020-02-28 16:01:45 -05:00
parent 6eb48239cf
commit c2814416fb
215 changed files with 735 additions and 746 deletions

48
atat/forms/application.py Normal file
View File

@@ -0,0 +1,48 @@
from .forms import BaseForm, remove_empty_string
from wtforms.fields import StringField, TextAreaField, FieldList
from wtforms.validators import Required, Optional, Length
from atat.forms.validators import ListItemRequired, ListItemsUnique, Name, AlphaNumeric
from atat.utils.localization import translate
class EditEnvironmentForm(BaseForm):
name = StringField(
label=translate("forms.environments.name_label"),
validators=[Required(), Name(), Length(max=100)],
filters=[remove_empty_string],
)
class NameAndDescriptionForm(BaseForm):
name = StringField(
label=translate("forms.application.name_label"),
validators=[Required(), Name(), Length(max=100)],
filters=[remove_empty_string],
)
description = TextAreaField(
label=translate("forms.application.description_label"),
validators=[Optional(), Length(max=1_000)],
filters=[remove_empty_string],
)
class EnvironmentsForm(BaseForm):
environment_names = FieldList(
StringField(
label=translate("forms.application.environment_names_label"),
filters=[remove_empty_string],
validators=[AlphaNumeric(), Length(max=100)],
),
validators=[
ListItemRequired(
message=translate(
"forms.application.environment_names_required_validation_message"
)
),
ListItemsUnique(
message=translate(
"forms.application.environment_names_unique_validation_message"
)
),
],
)

View File

@@ -0,0 +1,72 @@
from flask_wtf import FlaskForm
from wtforms.fields import FormField, FieldList, HiddenField, BooleanField
from wtforms.validators import UUID
from wtforms import Form
from .member import NewForm as BaseNewMemberForm
from .data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS
from atat.forms.fields import SelectField
from atat.domain.permission_sets import PermissionSets
from atat.utils.localization import translate
from atat.forms.validators import AlphaNumeric
from wtforms.validators import Length
class EnvironmentForm(Form):
environment_id = HiddenField(validators=[UUID()])
environment_name = HiddenField(validators=[AlphaNumeric(), Length(max=100)])
role = SelectField(
environment_name,
choices=ENV_ROLES,
default=NO_ACCESS,
filters=[lambda x: NO_ACCESS if x == "None" else x],
)
disabled = BooleanField("Revoke Access", default=False)
@property
def data(self):
_data = super().data
if "role" in _data and _data["role"] == NO_ACCESS:
_data["role"] = None
return _data
class PermissionsForm(FlaskForm):
perms_env_mgmt = BooleanField(
translate("portfolios.applications.members.form.env_mgmt.label"),
default=False,
description=translate(
"portfolios.applications.members.form.env_mgmt.description"
),
)
perms_team_mgmt = BooleanField(
translate("portfolios.applications.members.form.team_mgmt.label"),
default=False,
description=translate(
"portfolios.applications.members.form.team_mgmt.description"
),
)
@property
def data(self):
_data = super().data
_data.pop("csrf_token", None)
perm_sets = []
if _data["perms_env_mgmt"]:
perm_sets.append(PermissionSets.EDIT_APPLICATION_ENVIRONMENTS)
if _data["perms_team_mgmt"]:
perm_sets.append(PermissionSets.EDIT_APPLICATION_TEAM)
_data["permission_sets"] = perm_sets
return _data
class NewForm(PermissionsForm):
user_data = FormField(BaseNewMemberForm)
environment_roles = FieldList(FormField(EnvironmentForm))
class UpdateMemberForm(PermissionsForm):
environment_roles = FieldList(FormField(EnvironmentForm))

13
atat/forms/ccpo_user.py Normal file
View File

@@ -0,0 +1,13 @@
from flask_wtf import FlaskForm
from wtforms.validators import Required, Length
from wtforms.fields import StringField
from atat.forms.validators import Number
from atat.utils.localization import translate
class CCPOUserForm(FlaskForm):
dod_id = StringField(
translate("forms.new_member.dod_id_label"),
validators=[Required(), Length(min=10, max=10), Number()],
)

30
atat/forms/data.py Normal file
View File

@@ -0,0 +1,30 @@
from atat.models import CSPRole
from atat.utils.localization import translate
SERVICE_BRANCHES = [
("air_force", translate("forms.portfolio.defense_component.choices.air_force")),
("army", translate("forms.portfolio.defense_component.choices.army")),
(
"marine_corps",
translate("forms.portfolio.defense_component.choices.marine_corps"),
),
("navy", translate("forms.portfolio.defense_component.choices.navy")),
("space_force", translate("forms.portfolio.defense_component.choices.space_force")),
("ccmd_js", translate("forms.portfolio.defense_component.choices.ccmd_js")),
("dafa", translate("forms.portfolio.defense_component.choices.dafa")),
("osd_psas", translate("forms.portfolio.defense_component.choices.osd_psas")),
("other", translate("forms.portfolio.defense_component.choices.other")),
]
ENV_ROLE_NO_ACCESS = "No Access"
ENV_ROLES = [(role.name, role.value) for role in CSPRole] + [
(ENV_ROLE_NO_ACCESS, ENV_ROLE_NO_ACCESS)
]
JEDI_CLIN_TYPES = [
("JEDI_CLIN_1", translate("JEDICLINType.JEDI_CLIN_1")),
("JEDI_CLIN_2", translate("JEDICLINType.JEDI_CLIN_2")),
("JEDI_CLIN_3", translate("JEDICLINType.JEDI_CLIN_3")),
("JEDI_CLIN_4", translate("JEDICLINType.JEDI_CLIN_4")),
]

93
atat/forms/edit_user.py Normal file
View File

@@ -0,0 +1,93 @@
import pendulum
from copy import deepcopy
from wtforms.fields.html5 import DateField, EmailField, TelField
from wtforms.fields import RadioField, StringField
from wtforms.validators import Email, DataRequired, Optional
from .fields import SelectField
from .forms import BaseForm
from .data import SERVICE_BRANCHES
from atat.models.user import User
from atat.utils.localization import translate
from wtforms.validators import Length
from atat.forms.validators import Number
from .validators import Name, DateRange, PhoneNumber
USER_FIELDS = {
"first_name": StringField(
translate("forms.edit_user.first_name_label"),
validators=[Name(), Length(max=100)],
),
"last_name": StringField(
translate("forms.edit_user.last_name_label"),
validators=[Name(), Length(max=100)],
),
"email": EmailField(translate("forms.edit_user.email_label"), validators=[Email()]),
"phone_number": TelField(
translate("forms.edit_user.phone_number_label"), validators=[PhoneNumber()]
),
"phone_ext": StringField("Extension", validators=[Number(), Length(max=10)]),
"service_branch": SelectField(
translate("forms.edit_user.service_branch_label"), choices=SERVICE_BRANCHES
),
"citizenship": RadioField(
choices=[
("United States", "United States"),
("Foreign National", "Foreign National"),
("Other", "Other"),
]
),
"designation": RadioField(
translate("forms.edit_user.designation_label"),
choices=[
("military", "Military"),
("civilian", "Civilian"),
("contractor", "Contractor"),
],
),
"date_latest_training": DateField(
translate("forms.edit_user.date_latest_training_label"),
description=translate("forms.edit_user.date_latest_training_description"),
validators=[
DateRange(
lower_bound=pendulum.duration(years=1),
upper_bound=pendulum.duration(days=0),
message="Must be a date within the last year.",
)
],
format="%m/%d/%Y",
),
}
def inherit_field(unbound_field, required=True):
kwargs = deepcopy(unbound_field.kwargs)
if not "validators" in kwargs:
kwargs["validators"] = []
if required:
kwargs["validators"].append(DataRequired())
else:
kwargs["validators"].append(Optional())
return unbound_field.field_class(*unbound_field.args, **kwargs)
def inherit_user_field(field_name):
required = field_name in User.REQUIRED_FIELDS
return inherit_field(USER_FIELDS[field_name], required=required)
class EditUserForm(BaseForm):
first_name = inherit_user_field("first_name")
last_name = inherit_user_field("last_name")
email = inherit_user_field("email")
phone_number = inherit_user_field("phone_number")
phone_ext = inherit_user_field("phone_ext")
service_branch = inherit_user_field("service_branch")
citizenship = inherit_user_field("citizenship")
designation = inherit_user_field("designation")
date_latest_training = inherit_user_field("date_latest_training")

8
atat/forms/fields.py Normal file
View File

@@ -0,0 +1,8 @@
from wtforms.fields import SelectField as SelectField_
class SelectField(SelectField_):
def __init__(self, *args, **kwargs):
render_kw = kwargs.get("render_kw", {})
kwargs["render_kw"] = {**render_kw, "required": False}
super().__init__(*args, **kwargs)

46
atat/forms/forms.py Normal file
View File

@@ -0,0 +1,46 @@
from flask_wtf import FlaskForm
from flask import current_app, request as http_request
import re
from atat.utils.flash import formatted_flash as flash
EMPTY_LIST_FIELD = ["", None]
def remove_empty_string(value):
# only return strings that contain non whitespace characters
if value and re.search(r"\S", value):
return value.strip()
else:
return None
class BaseForm(FlaskForm):
def __init__(self, formdata=None, **kwargs):
# initialize the form with data from the cache
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):
# remove 'csrf_token' key/value pair
# remove empty strings and None from list fields
# prevent values that are not an option in a RadioField from being saved to the DB
_data = super().data
_data.pop("csrf_token", None)
for field in _data:
if _data[field].__class__.__name__ == "list":
_data[field] = [el for el in _data[field] if el not in EMPTY_LIST_FIELD]
if self[field].__class__.__name__ == "RadioField":
choices = [el[0] for el in self[field].choices]
if _data[field] not in choices:
_data[field] = None
return _data
def validate(self, *args, flash_invalid=True, **kwargs):
valid = super().validate(*args, **kwargs)
if not valid and flash_invalid:
flash("form_errors")
return valid

30
atat/forms/member.py Normal file
View File

@@ -0,0 +1,30 @@
from flask_wtf import FlaskForm
from wtforms.fields.html5 import EmailField, TelField
from wtforms.validators import Required, Email, Length, Optional
from wtforms.fields import StringField
from atat.forms.validators import Number, PhoneNumber, Name
from atat.utils.localization import translate
class NewForm(FlaskForm):
first_name = StringField(
label=translate("forms.new_member.first_name_label"),
validators=[Required(), Name(), Length(max=100)],
)
last_name = StringField(
label=translate("forms.new_member.last_name_label"),
validators=[Required(), Name(), Length(max=100)],
)
email = EmailField(
translate("forms.new_member.email_label"), validators=[Required(), Email()]
)
phone_number = TelField(
translate("forms.new_member.phone_number_label"),
validators=[Optional(), PhoneNumber()],
)
phone_ext = StringField("Extension", validators=[Number(), Length(max=10)])
dod_id = StringField(
translate("forms.new_member.dod_id_label"),
validators=[Required(), Length(min=10), Number()],
)

47
atat/forms/portfolio.py Normal file
View File

@@ -0,0 +1,47 @@
from wtforms.fields import (
SelectMultipleField,
StringField,
TextAreaField,
)
from wtforms.validators import Length, InputRequired
from atat.forms.validators import Name
from wtforms.widgets import ListWidget, CheckboxInput
from .forms import BaseForm
from atat.utils.localization import translate
from .data import SERVICE_BRANCHES
class PortfolioForm(BaseForm):
name = StringField(
translate("forms.portfolio.name.label"),
validators=[
Length(
min=4,
max=100,
message=translate("forms.portfolio.name.length_validation_message"),
),
Name(),
],
)
description = TextAreaField(
translate("forms.portfolio.description.label"), validators=[Length(max=1_000)]
)
class PortfolioCreationForm(PortfolioForm):
defense_component = SelectMultipleField(
translate("forms.portfolio.defense_component.title"),
description=translate("forms.portfolio.defense_component.help_text"),
choices=SERVICE_BRANCHES,
widget=ListWidget(prefix_label=False),
option_widget=CheckboxInput(),
validators=[
InputRequired(
message=translate(
"forms.portfolio.defense_component.validation_message"
)
)
],
)

View File

@@ -0,0 +1,64 @@
from wtforms.validators import Required
from wtforms.fields import BooleanField, FormField
from .forms import BaseForm
from .member import NewForm as BaseNewMemberForm
from atat.domain.permission_sets import PermissionSets
from atat.forms.fields import SelectField
from atat.utils.localization import translate
class PermissionsForm(BaseForm):
perms_app_mgmt = BooleanField(
translate("forms.new_member.app_mgmt.label"),
default=False,
description=translate("forms.new_member.app_mgmt.description"),
)
perms_funding = BooleanField(
translate("forms.new_member.funding.label"),
default=False,
description=translate("forms.new_member.funding.description"),
)
perms_reporting = BooleanField(
translate("forms.new_member.reporting.label"),
default=False,
description=translate("forms.new_member.reporting.description"),
)
perms_portfolio_mgmt = BooleanField(
translate("forms.new_member.portfolio_mgmt.label"),
default=False,
description=translate("forms.new_member.portfolio_mgmt.description"),
)
@property
def data(self):
_data = super().data
_data.pop("csrf_token", None)
perm_sets = []
if _data["perms_app_mgmt"]:
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT)
if _data["perms_funding"]:
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_FUNDING)
if _data["perms_reporting"]:
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_REPORTS)
if _data["perms_portfolio_mgmt"]:
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_ADMIN)
_data["permission_sets"] = perm_sets
return _data
class NewForm(PermissionsForm):
user_data = FormField(BaseNewMemberForm)
class AssignPPOCForm(PermissionsForm):
role_id = SelectField(
label=translate("forms.assign_ppoc.dod_id"),
validators=[Required()],
choices=[("", "- Select -")],
)

176
atat/forms/task_order.py Normal file
View File

@@ -0,0 +1,176 @@
from wtforms.fields import (
BooleanField,
DecimalField,
FieldList,
FormField,
StringField,
HiddenField,
)
from wtforms.fields.html5 import DateField
from wtforms.validators import (
Required,
Length,
Optional,
NumberRange,
ValidationError,
)
from flask_wtf import FlaskForm
import numbers
from atat.forms.validators import Number, AlphaNumeric
from .data import JEDI_CLIN_TYPES
from .fields import SelectField
from .forms import BaseForm, remove_empty_string
from atat.utils.localization import translate
from flask import current_app as app
MAX_CLIN_AMOUNT = 1_000_000_000
def coerce_enum(enum_inst):
if getattr(enum_inst, "value", None):
return enum_inst.value
else:
return enum_inst
def validate_funding(form, field):
if (
isinstance(form.total_amount.data, numbers.Number)
and isinstance(field.data, numbers.Number)
and form.total_amount.data < field.data
):
raise ValidationError(
translate("forms.task_order.clin_funding_errors.obligated_amount_error")
)
def validate_date_in_range(form, field):
contract_start = app.config.get("CONTRACT_START_DATE")
contract_end = app.config.get("CONTRACT_END_DATE")
if field.data and (field.data < contract_start or field.data > contract_end):
raise ValidationError(
translate(
"forms.task_order.pop_errors.range",
{
"start": contract_start.strftime("%b %d, %Y"),
"end": contract_end.strftime("%b %d, %Y"),
},
)
)
def remove_dashes(value):
return value.replace("-", "") if value else None
def coerce_upper(value):
return value.upper() if value else None
class CLINForm(FlaskForm):
jedi_clin_type = SelectField(
translate("task_orders.form.clin_type_label"),
choices=JEDI_CLIN_TYPES,
coerce=coerce_enum,
)
number = StringField(
label=translate("task_orders.form.clin_number_label"),
validators=[Number(), Length(max=4)],
)
start_date = DateField(
translate("task_orders.form.pop_start"),
description=translate("task_orders.form.pop_example"),
format="%m/%d/%Y",
validators=[validate_date_in_range],
)
end_date = DateField(
translate("task_orders.form.pop_end"),
description=translate("task_orders.form.pop_example"),
format="%m/%d/%Y",
validators=[validate_date_in_range],
)
total_amount = DecimalField(
label=translate("task_orders.form.total_funds_label"),
validators=[
NumberRange(
0,
MAX_CLIN_AMOUNT,
translate("forms.task_order.clin_funding_errors.funding_range_error"),
)
],
)
obligated_amount = DecimalField(
label=translate("task_orders.form.obligated_funds_label"),
validators=[
validate_funding,
NumberRange(
0,
MAX_CLIN_AMOUNT,
translate("forms.task_order.clin_funding_errors.funding_range_error"),
),
],
)
def validate(self, *args, **kwargs):
valid = super().validate(*args, **kwargs)
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.pop_errors.date_order")
)
valid = False
return valid
class AttachmentForm(BaseForm):
filename = HiddenField(
id="attachment_filename",
validators=[
Length(
max=100, message=translate("forms.attachment.filename.length_error")
),
AlphaNumeric(),
],
)
object_name = HiddenField(
id="attachment_object_name",
validators=[
Length(
max=40, message=translate("forms.attachment.object_name.length_error")
),
AlphaNumeric(),
],
)
accept = ".pdf,application/pdf"
def validate(self, *args, **kwargs):
return super().validate(*args, **{**kwargs, "flash_invalid": False})
class TaskOrderForm(BaseForm):
number = StringField(
label=translate("forms.task_order.number_description"),
filters=[remove_empty_string, remove_dashes, coerce_upper],
validators=[AlphaNumeric(), Length(min=13, max=17), Optional()],
)
pdf = FormField(AttachmentForm)
clins = FieldList(FormField(CLINForm))
class SignatureForm(BaseForm):
signature = BooleanField(
translate("task_orders.sign.digital_signature_description"),
validators=[Required()],
)
confirm = BooleanField(
translate("task_orders.sign.confirmation_description"), validators=[Required()],
)

104
atat/forms/validators.py Normal file
View File

@@ -0,0 +1,104 @@
from datetime import datetime
import re
from werkzeug.datastructures import FileStorage
from wtforms.validators import ValidationError, Regexp
import pendulum
from atat.utils.localization import translate
def DateRange(lower_bound=None, upper_bound=None, message=None):
def _date_range(form, field):
if field.data is None:
return
now = pendulum.now().date()
if isinstance(field.data, str):
date = datetime.strptime(field.data, field.format)
else:
date = field.data
if lower_bound is not None:
if (now - lower_bound) > date:
raise ValidationError(message)
if upper_bound is not None:
if (now + upper_bound) < date:
raise ValidationError(message)
return _date_range
def Number(message=translate("forms.validators.is_number_message")):
def _is_number(form, field):
if field.data:
try:
int(field.data)
except (ValueError, TypeError):
raise ValidationError(message)
return _is_number
def PhoneNumber(message=translate("forms.validators.phone_number_message")):
def _is_phone_number(form, field):
digits = re.sub(r"\D", "", field.data)
if len(digits) not in [5, 10]:
raise ValidationError(message)
match = re.match(r"[\d\-\(\) ]+", field.data)
if not match or match.group() != field.data:
raise ValidationError(message)
return _is_phone_number
def Name(message=translate("forms.validators.name_message")):
def _name(form, field):
match = re.match(r"[\w \,\.\'\-]+", field.data)
if not match or match.group() != field.data:
raise ValidationError(message)
return _name
def ListItemRequired(
message=translate("forms.validators.list_item_required_message"),
empty_values=[None],
):
def _list_item_required(form, field):
non_empty_values = [
v for v in field.data if (v not in empty_values and re.search(r"\S", v))
]
if len(non_empty_values) == 0:
raise ValidationError(message)
return _list_item_required
def ListItemsUnique(message=translate("forms.validators.list_items_unique_message")):
def _list_items_unique(form, field):
if len(field.data) > len(set(field.data)):
raise ValidationError(message)
return _list_items_unique
def FileLength(max_length=50000000, message=None):
def _file_length(_form, field):
if field.data is None or not isinstance(field.data, FileStorage):
return True
content = field.data.read()
if len(content) > max_length:
raise ValidationError(message)
else:
field.data.seek(0)
return _file_length
def AlphaNumeric(message=translate("forms.validators.alpha_numeric_message")):
return Regexp(regex=r"^[A-Za-z0-9\-_ \.]*$", message=message)