diff --git a/atst/forms/forms.py b/atst/forms/forms.py index e3f2081f..31e62476 100644 --- a/atst/forms/forms.py +++ b/atst/forms/forms.py @@ -18,11 +18,16 @@ class BaseForm(FlaskForm): 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, **kwargs): diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index cc24c14e..b5041d67 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -27,6 +27,10 @@ from .data import ( from atst.utils.localization import translate +def remove_empty_string(value): + return value or None + + class AppInfoWithExistingPortfolioForm(BaseForm): scope = TextAreaField( translate("forms.task_order.scope_label"), @@ -50,20 +54,30 @@ class AppInfoWithExistingPortfolioForm(BaseForm): translate("forms.task_order.complexity.label"), description=translate("forms.task_order.complexity.description"), choices=APPLICATION_COMPLEXITY, - default="", + default=None, + filters=[remove_empty_string], widget=ListWidget(prefix_label=False), option_widget=CheckboxInput(), ) - complexity_other = StringField(translate("forms.task_order.complexity_other_label")) + complexity_other = StringField( + translate("forms.task_order.complexity_other_label"), + default=None, + filters=[remove_empty_string], + ) dev_team = SelectMultipleField( translate("forms.task_order.dev_team.label"), description=translate("forms.task_order.dev_team.description"), choices=DEV_TEAM, - default="", + default=None, + filters=[remove_empty_string], widget=ListWidget(prefix_label=False), option_widget=CheckboxInput(), ) - dev_team_other = StringField(translate("forms.task_order.dev_team_other_label")) + dev_team_other = StringField( + translate("forms.task_order.dev_team_other_label"), + default=None, + filters=[remove_empty_string], + ) team_experience = RadioField( translate("forms.task_order.team_experience.label"), description=translate("forms.task_order.team_experience.description"), @@ -77,6 +91,7 @@ class AppInfoForm(AppInfoWithExistingPortfolioForm): portfolio_name = StringField( translate("forms.task_order.portfolio_name_label"), description=translate("forms.task_order.portfolio_name_description"), + filters=[remove_empty_string], validators=[ Required(), Length( @@ -87,7 +102,10 @@ class AppInfoForm(AppInfoWithExistingPortfolioForm): ], ) defense_component = SelectField( - translate("forms.task_order.defense_component_label"), choices=SERVICE_BRANCHES + translate("forms.task_order.defense_component_label"), + choices=SERVICE_BRANCHES, + default="", + filters=[remove_empty_string], ) @@ -129,29 +147,36 @@ class FundingForm(BaseForm): class UnclassifiedFundingForm(FundingForm): clin_02 = StringField( translate("forms.task_order.unclassified_clin_02_label"), - filters=[lambda x: x or None], + filters=[remove_empty_string], ) clin_04 = StringField( translate("forms.task_order.unclassified_clin_04_label"), - filters=[lambda x: x or None], + filters=[remove_empty_string], ) class OversightForm(BaseForm): ko_first_name = StringField( - translate("forms.task_order.oversight_first_name_label") + translate("forms.task_order.oversight_first_name_label"), + filters=[remove_empty_string], + ) + ko_last_name = StringField( + translate("forms.task_order.oversight_last_name_label"), + filters=[remove_empty_string], ) - ko_last_name = StringField(translate("forms.task_order.oversight_last_name_label")) ko_email = StringField( translate("forms.task_order.oversight_email_label"), validators=[Optional(), Email()], + filters=[remove_empty_string], ) ko_phone_number = TelField( translate("forms.task_order.oversight_phone_label"), validators=[Optional(), PhoneNumber()], + filters=[remove_empty_string], ) ko_dod_id = StringField( translate("forms.task_order.oversight_dod_id_label"), + filters=[remove_empty_string], validators=[ RequiredIf(lambda form: form._fields.get("ko_invite").data), Length(min=10), @@ -161,15 +186,21 @@ class OversightForm(BaseForm): am_cor = BooleanField(translate("forms.task_order.oversight_am_cor_label")) cor_first_name = StringField( - translate("forms.task_order.oversight_first_name_label") + translate("forms.task_order.oversight_first_name_label"), + filters=[remove_empty_string], + ) + cor_last_name = StringField( + translate("forms.task_order.oversight_last_name_label"), + filters=[remove_empty_string], ) - cor_last_name = StringField(translate("forms.task_order.oversight_last_name_label")) cor_email = StringField( translate("forms.task_order.oversight_email_label"), + filters=[remove_empty_string], validators=[Optional(), Email()], ) cor_phone_number = TelField( translate("forms.task_order.oversight_phone_label"), + filters=[remove_empty_string], validators=[ RequiredIf(lambda form: not form._fields.get("am_cor").data), Optional(), @@ -178,6 +209,7 @@ class OversightForm(BaseForm): ) cor_dod_id = StringField( translate("forms.task_order.oversight_dod_id_label"), + filters=[remove_empty_string], validators=[ RequiredIf( lambda form: not form._fields.get("am_cor").data @@ -189,19 +221,26 @@ class OversightForm(BaseForm): ) so_first_name = StringField( - translate("forms.task_order.oversight_first_name_label") + translate("forms.task_order.oversight_first_name_label"), + filters=[remove_empty_string], + ) + so_last_name = StringField( + translate("forms.task_order.oversight_last_name_label"), + filters=[remove_empty_string], ) - so_last_name = StringField(translate("forms.task_order.oversight_last_name_label")) so_email = StringField( translate("forms.task_order.oversight_email_label"), + filters=[remove_empty_string], validators=[Optional(), Email()], ) so_phone_number = TelField( translate("forms.task_order.oversight_phone_label"), + filters=[remove_empty_string], validators=[Optional(), PhoneNumber()], ) so_dod_id = StringField( translate("forms.task_order.oversight_dod_id_label"), + filters=[remove_empty_string], validators=[ RequiredIf(lambda form: form._fields.get("so_invite").data), Length(min=10), diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 799fd024..f7fe1b37 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -190,9 +190,17 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow): to_data.pop("defense_component") # don't save other text in DB unless "other" is checked - if "complexity" in to_data and "other" not in to_data["complexity"]: + if ( + "complexity" in to_data + and bool(to_data["complexity"]) + and "other" not in to_data["complexity"] + ): to_data["complexity_other"] = None - if "dev_team" in to_data and "other" not in to_data["dev_team"]: + if ( + "dev_team" in to_data + and bool(to_data["dev_team"]) + and "other" not in to_data["dev_team"] + ): to_data["dev_team_other"] = None if self.form_data.get("am_cor"): diff --git a/tests/forms/test_base_form.py b/tests/forms/test_base_form.py new file mode 100644 index 00000000..fe6456e7 --- /dev/null +++ b/tests/forms/test_base_form.py @@ -0,0 +1,36 @@ +import pytest +from wtforms.fields import RadioField +from werkzeug.datastructures import ImmutableMultiDict + +from atst.forms.forms import BaseForm + + +class FormWithChoices(BaseForm): + force_side = RadioField( + "Choose your side", + choices=[ + ("light", "Light Side"), + ("dark", "Dark Side"), + ("neutral", "Chaotic Neutral"), + ], + ) + + +class TestBaseForm: + class Foo: + person = {"force_side": None} + + obj = Foo() + + def test_radio_field_saves_only_as_choice(self): + form_data_1 = ImmutableMultiDict({"force_side": "None"}) + form_1 = FormWithChoices(form_data_1, obj=self.obj) + assert form_1.data["force_side"] is None + + form_data_2 = ImmutableMultiDict({"force_side": "a fake choice"}) + form_2 = FormWithChoices(form_data_2, obj=self.obj) + assert form_2.data["force_side"] is None + + form_data_3 = ImmutableMultiDict({"force_side": "dark"}) + form_3 = FormWithChoices(form_data_3, obj=self.obj) + assert form_3.data["force_side"] is "dark"