Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate new form data for each page load #829

Merged
merged 8 commits into from
Mar 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Go
[here](https://github.com/OpenSourcePolicyCenter/PolicyBrain/pulls?q=is%3Apr+is%3Aclosed)
for a complete commit history.

Release 1.4.2 on 2018-02-28
Release 1.4.2 on 2018-03-01
----------------------------
**Major Changes**
- None
Expand All @@ -14,6 +14,7 @@ Release 1.4.2 on 2018-02-28

**Bug Fixes**
- [#827](https://github.com/OpenSourcePolicyCenter/PolicyBrain/pull/827) - Remove errors for un-displayed parameters and save input data for warning/error message page - Hank Doupe
- [#829](https://github.com/OpenSourcePolicyCenter/PolicyBrain/pull/829) - Generate new form data for each page load - Hank Doupe


Release 1.4.1 on 2018-02-27
Expand Down
63 changes: 28 additions & 35 deletions webapp/apps/dynamic/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _

from ..constants import START_YEAR
from .models import (DynamicSaveInputs, DynamicBehaviorSaveInputs,
DynamicElasticitySaveInputs)
from ..taxbrain.helpers import (TaxCalcField, TaxCalcParam,
Expand All @@ -15,9 +16,9 @@ def bool_like(x):
b = True if x == 'True' or x == True else False
return b

OGUSA_DEFAULT_PARAMS = default_parameters(2015)
BEHAVIOR_DEFAULT_PARAMS = default_behavior_parameters(2015)
ELASTICITY_DEFAULT_PARAMS = default_elasticity_parameters(2015)
OGUSA_DEFAULT_PARAMS = default_parameters(int(START_YEAR))
BEHAVIOR_DEFAULT_PARAMS = default_behavior_parameters(int(START_YEAR))
ELASTICITY_DEFAULT_PARAMS = default_elasticity_parameters(int(START_YEAR))

class DynamicElasticityInputsModelForm(ModelForm):

Expand Down Expand Up @@ -217,25 +218,28 @@ class Meta:
class DynamicBehavioralInputsModelForm(PolicyBrainForm, ModelForm):

def __init__(self, first_year, *args, **kwargs):
if first_year is None:
first_year = START_YEAR
self._first_year = int(first_year)
# reset form data; form data from the `Meta` class is not updated each
# time a new `TaxBrainForm` instance is created
self.set_form_data(self._first_year)
# move parameter fields into `raw_fields` JSON object
args = self.add_fields(args)
# this seems to update the saved data in the appropriate way
# Override `initial` with `instance`. The only relevant field
# in `instance` is `raw_input_fields` which contains all of the user
# input data from the stored run. By overriding the `initial` kw
# argument we are making all of the user input from the previous run
# as stored in the `raw_input_fields` field of `instance` available
# to the fields attribute in django forms. This front-end data is
# derived from this fields attribute.
# Take a look at the source code for more info:
# https://github.com/django/django/blob/1.9/django/forms/models.py#L284-L285
if "instance" in kwargs:
kwargs["initial"] = kwargs["instance"].raw_input_fields

self._first_year = int(first_year)
self._default_params = default_behavior_parameters(self._first_year)
# Defaults are set in the Meta, but we need to swap
# those outs here in the init because the user may
# have chosen a different start year
all_defaults = []
for param in self._default_params.values():
for field in param.col_fields:
all_defaults.append((field.id, field.default_value))

for _id, default in all_defaults:
self._meta.widgets[_id].attrs['placeholder'] = default

super(DynamicBehavioralInputsModelForm, self).__init__(*args, **kwargs)

# update fields in a similar way as
# https://www.pydanny.com/overloading-form-fields.html
self.fields.update(self.Meta.update_fields)
Expand All @@ -254,30 +258,19 @@ def clean(self):
self.do_taxcalc_validations()
self.add_errors_on_extra_inputs()

def set_form_data(self, start_year):
defaults = default_behavior_parameters(start_year)
(self.widgets, self.labels,
self.update_fields) = PolicyBrainForm.set_form(defaults)


class Meta:
model = DynamicBehaviorSaveInputs
# we are only updating the "first_year", "raw_fields", and "fields"
# fields
fields = ['first_year', 'raw_input_fields', 'input_fields']
widgets = {}
labels = {}
update_fields = {}
for param in BEHAVIOR_DEFAULT_PARAMS.values():
for field in param.col_fields:
attrs = {
'class': 'form-control',
'placeholder': field.default_value,
}

widgets[field.id] = forms.TextInput(attrs=attrs)
update_fields[field.id] = forms.BooleanField(
label='',
widget=widgets[field.id],
required=False
)

labels[field.id] = field.label
(widgets, labels,
update_fields) = PolicyBrainForm.set_form(BEHAVIOR_DEFAULT_PARAMS)


class DynamicInputsModelForm(ModelForm):
Expand Down
165 changes: 91 additions & 74 deletions webapp/apps/taxbrain/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def expand_unless_empty(param_values, param_name, param_column_name, form, new_l
return param_values



class PolicyBrainForm:

def add_fields(self, args):
Expand Down Expand Up @@ -127,17 +128,79 @@ def do_taxcalc_validations(self):
("Operator '<' can only be used "
"at the beginning")
)

else:
assert isinstance(value, bool) or len(value) == 0

@staticmethod
def set_form(defaults):
"""
Setup all of the form fields and widgets with the the 2016 default data
"""
widgets = {}
labels = {}
update_fields = {}
boolean_fields = []

TAXCALC_DEFAULTS_2016 = default_policy(2016)
for param in defaults.values():
for field in param.col_fields:
attrs = {
'class': 'form-control',
'placeholder': field.default_value,
}

if param.coming_soon:
attrs['disabled'] = True

if param.tc_id in boolean_fields:
checkbox = forms.CheckboxInput(attrs=attrs, check_test=bool_like)
widgets[field.id] = checkbox
update_fields[field.id] = forms.BooleanField(
label='',
widget=widgets[field.id],
required=False
)
else:
widgets[field.id] = forms.TextInput(attrs=attrs)
update_fields[field.id] = forms.fields.CharField(
label='',
widget=widgets[field.id],
required=False
)

labels[field.id] = field.label

if getattr(param, "inflatable", False):
field = param.cpi_field
attrs = {
'class': 'form-control sr-only',
'placeholder': bool(field.default_value),
}

if param.coming_soon:
attrs['disabled'] = True

widgets[field.id] = forms.NullBooleanSelect(attrs=attrs)
update_fields[field.id] = forms.NullBooleanField(
label='',
widget=widgets[field.id],
required=False
)
return widgets, labels, update_fields


TAXCALC_DEFAULTS = {int(START_YEAR): default_policy(int(START_YEAR))}


class TaxBrainForm(PolicyBrainForm, ModelForm):

def __init__(self, first_year, *args, **kwargs):
if first_year is None:
first_year = START_YEAR
self._first_year = int(first_year)

# reset form data; form data from the `Meta` class is not updated each
# time a new `TaxBrainForm` instance is created
self.set_form_data(self._first_year)
# move parameter fields into `raw_fields` JSON object
args = self.add_fields(args)
# Override `initial` with `instance`. The only relevant field
Expand All @@ -151,6 +214,12 @@ def __init__(self, first_year, *args, **kwargs):
# https://github.com/django/django/blob/1.9/django/forms/models.py#L284-L285
if "instance" in kwargs:
kwargs["initial"] = kwargs["instance"].raw_input_fields

# Update CPI flags if either
# 1. initial is specified in `kwargs` (reform has warning/error msgs)
# 2. if `instance` is specified and `initial` is added above
# (edit parameters page)
if kwargs.get("initial", False):
for k, v in kwargs["initial"].iteritems():
if k.endswith("cpi") and v:
# raw data is stored as choices 1, 2, 3 with the following
Expand All @@ -160,36 +229,20 @@ def __init__(self, first_year, *args, **kwargs):
# '3': False
# value_from_datadict unpacks this data:
# https://github.com/django/django/blob/1.9/django/forms/widgets.py#L582-L589
django_val = self._meta.widgets[k].value_from_datadict(
if v == '1':
continue
django_val = self.widgets[k].value_from_datadict(
kwargs["initial"],
None,
k
)
self._meta.widgets[k].attrs["placeholder"] = django_val
if first_year is None:
first_year = START_YEAR
self._first_year = int(first_year)
self._default_params = default_policy(self._first_year)

# Defaults are set in the Meta, but we need to swap
# those out here in the init because the user may
# have chosen a different start year
all_defaults = []
for param in self._default_params.values():
for field in param.col_fields:
all_defaults.append((field.id, field.default_value))

for _id, default in all_defaults:
self._meta.widgets[_id].attrs['placeholder'] = default
self.widgets[k].attrs["placeholder"] = django_val

super(TaxBrainForm, self).__init__(*args, **kwargs)

# update fields in a similar way as
# https://www.pydanny.com/overloading-form-fields.html
self.fields.update(self.Meta.update_fields)

print('SS_Earnings_c_cpi field', self.fields['SS_Earnings_c_cpi'].__dict__)
print('SS_Earnings_c_cpi widget', self.fields['SS_Earnings_c_cpi'].widget.__dict__)
print('SS_Earnings_c_cpi meta widget', self._meta.widgets['SS_Earnings_c_cpi'].__dict__)
self.fields.update(self.update_fields.copy())

def clean(self):
"""
Expand All @@ -215,61 +268,25 @@ def add_error(self, field, error):
self.cleaned_data = {}
ModelForm.add_error(self, field, error)

class Meta:
def set_form_data(self, start_year):
if start_year not in TAXCALC_DEFAULTS:
TAXCALC_DEFAULTS[start_year] = default_policy(start_year)
defaults = TAXCALC_DEFAULTS[start_year]
(self.widgets, self.labels,
self.update_fields) = PolicyBrainForm.set_form(defaults)

class Meta:
model = TaxSaveInputs
# we are only updating the "first_year", "raw_fields", and "fields"
# fields
fields = ['first_year', 'raw_input_fields', 'input_fields']
widgets = {}
labels = {}
update_fields = {}
boolean_fields = []

for param in TAXCALC_DEFAULTS_2016.values():
for field in param.col_fields:
attrs = {
'class': 'form-control',
'placeholder': field.default_value,
}

if param.coming_soon:
attrs['disabled'] = True

if param.tc_id in boolean_fields:
checkbox = forms.CheckboxInput(attrs=attrs, check_test=bool_like)
widgets[field.id] = checkbox
update_fields[field.id] = forms.BooleanField(
label='',
widget=widgets[field.id],
required=False
)
else:
widgets[field.id] = forms.TextInput(attrs=attrs)
update_fields[field.id] = forms.fields.CharField(
label='',
widget=widgets[field.id],
required=False
)

labels[field.id] = field.label

if param.inflatable:
field = param.cpi_field
attrs = {
'class': 'form-control sr-only',
'placeholder': bool(field.default_value),
}

if param.coming_soon:
attrs['disabled'] = True

widgets[field.id] = forms.NullBooleanSelect(attrs=attrs)
update_fields[field.id] = forms.NullBooleanField(
label='',
widget=widgets[field.id],
required=False
)
start_year = int(START_YEAR)
if start_year not in TAXCALC_DEFAULTS:
TAXCALC_DEFAULTS[start_year] = default_policy(start_year)
(widgets, labels,
update_fields) = PolicyBrainForm.set_form(
TAXCALC_DEFAULTS[start_year]
)

def has_field_errors(form, include_parse_errors=False):
"""
Expand Down